From ec0b405a6ffb42fc72e3a20b908e616650a426a7 Mon Sep 17 00:00:00 2001 From: vrubezhny Date: Thu, 12 Sep 2013 22:33:35 +0400 Subject: Bug 415980 - HTML5 attribute validator marks ng-app AngularJS attributes as undefined A new preference is created for the pattern of attribute names to be ignored by HTMLAttributeValidator Web->HTML Files->Validation preference page is modified due to allow the ignored attribute names to be shown/edited by users. New Quick-fix is added due to allow the users to add attribute names and attribute name patterns to the HTML Attribute Validator ignore list. Signed-off-by: Victor V Rubezhny --- .../preferences/HTMLCorePreferenceInitializer.java | 6 +- .../preferences/HTMLCorePreferenceNames.java | 19 +- .../internal/validate/HTMLAttributeValidator.java | 87 ++++++- .../html/core/internal/validate/StringMatcher.java | 230 ++++++++++++++++ .../icons/full/etool16/donotvalidate.png | Bin 0 -> 643 bytes bundles/org.eclipse.wst.html.ui/plugin.xml | 4 + .../wst/html/ui/internal/HTMLUIMessages.java | 11 +- .../ui/internal/HTMLUIPluginResources.properties | 10 +- .../ui/internal/editor/HTMLEditorPluginImages.java | 5 +- .../ui/HTMLValidationPreferencePage.java | 289 ++++++++++++++++++++- .../HTMLAttributeValidationQuickFixProcessor.java | 228 ++++++++++++++++ .../IgnoreAttributeNameCompletionProposal.java | 187 +++++++++++++ 12 files changed, 1062 insertions(+), 14 deletions(-) create mode 100644 bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/validate/StringMatcher.java create mode 100644 bundles/org.eclipse.wst.html.ui/icons/full/etool16/donotvalidate.png create mode 100644 bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/text/correction/HTMLAttributeValidationQuickFixProcessor.java create mode 100644 bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/text/correction/IgnoreAttributeNameCompletionProposal.java diff --git a/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/preferences/HTMLCorePreferenceInitializer.java b/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/preferences/HTMLCorePreferenceInitializer.java index 6eb852a6b1..f3a4b142a7 100644 --- a/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/preferences/HTMLCorePreferenceInitializer.java +++ b/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/preferences/HTMLCorePreferenceInitializer.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2005, 2012 IBM Corporation and others. + * Copyright (c) 2005, 2013 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 @@ -72,6 +72,10 @@ public class HTMLCorePreferenceInitializer extends AbstractPreferenceInitializer * @param node the Eclipse preference node */ private void initializeValidationPreferences(IEclipsePreferences node) { + // Ignored Attribute names + node.putBoolean(HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES, HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES_DEFAULT); + node.put(HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE, HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE_DEFAULT); + // Attributes node.putInt(HTMLCorePreferenceNames.ATTRIBUTE_UNDEFINED_NAME, ValidationMessage.WARNING); node.putInt(HTMLCorePreferenceNames.ATTRIBUTE_UNDEFINED_VALUE, ValidationMessage.WARNING); diff --git a/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/preferences/HTMLCorePreferenceNames.java b/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/preferences/HTMLCorePreferenceNames.java index fe7fb60010..2b9e70ef9f 100644 --- a/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/preferences/HTMLCorePreferenceNames.java +++ b/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/preferences/HTMLCorePreferenceNames.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2005, 2011 IBM Corporation and others. + * Copyright (c) 2005, 2013 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 @@ -238,7 +238,23 @@ public class HTMLCorePreferenceNames { */ public static final int UPPER = 2; + /** + * Default value for the preference #IGNORE_ATTRIBUTE_NAMES + * + * @see #IGNORE_ATTRIBUTE_NAMES + */ + public static final boolean IGNORE_ATTRIBUTE_NAMES_DEFAULT = false; + + /** + * Default value for the preference #ATTRIBUTE_NAMES_TO_IGNORE + * + * @see #ATTRIBUTE_NAMES_TO_IGNORE + */ + public static final String ATTRIBUTE_NAMES_TO_IGNORE_DEFAULT = ""; //$NON-NLS-1$ + public static final String USE_PROJECT_SETTINGS = "use-project-settings";//$NON-NLS-1$ + public static final String IGNORE_ATTRIBUTE_NAMES = "ignoreAttrNames";//$NON-NLS-1$ + public static final String ATTRIBUTE_NAMES_TO_IGNORE = "attrNamesToIgnore";//$NON-NLS-1$ public static final String ATTRIBUTE_UNDEFINED_NAME = "attrUndefName";//$NON-NLS-1$ public static final String ATTRIBUTE_UNDEFINED_VALUE = "attrUndefValue";//$NON-NLS-1$ @@ -252,7 +268,6 @@ public class HTMLCorePreferenceNames { public static final String ATTRIBUTE_OBSOLETE_NAME = "attrObsoleteName";//$NON-NLS-1$ public static final String ATTRIBUTE_VALUE_EQUALS_MISSING = "attrValueEqualsMissing";//$NON-NLS-1$ - public static final String ELEM_UNKNOWN_NAME = "elemUnknownName";//$NON-NLS-1$ public static final String ELEM_INVALID_NAME = "elemInvalidName";//$NON-NLS-1$ public static final String ELEM_START_INVALID_CASE = "elemStartInvalidCase";//$NON-NLS-1$ diff --git a/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/validate/HTMLAttributeValidator.java b/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/validate/HTMLAttributeValidator.java index c4d794fb81..bf68297173 100644 --- a/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/validate/HTMLAttributeValidator.java +++ b/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/validate/HTMLAttributeValidator.java @@ -11,14 +11,29 @@ package org.eclipse.wst.html.core.internal.validate; import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.Set; +import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ProjectScope; import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; +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.wst.html.core.internal.HTMLCorePlugin; import org.eclipse.wst.html.core.internal.document.HTMLDocumentTypeConstants; +import org.eclipse.wst.html.core.internal.preferences.HTMLCorePreferenceNames; import org.eclipse.wst.sse.core.internal.provisional.INodeNotifier; import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion; @@ -49,6 +64,9 @@ public class HTMLAttributeValidator extends PrimeValidator { private static final char SINGLE_QUOTE = '\''; private static final char DOUBLE_QUOTE = '\"'; + private IPreferencesService fPreferenceService; + private static Map fIgnorePatterns = new HashMap(); // A storage for ignore patterns (instances of StringMatcher) + // HTML(5) data attributes private static final String ATTR_NAME_DATA = "data-"; //$NON-NLS-1$ private static final int ATTR_NAME_DATA_LENGTH = ATTR_NAME_DATA.length(); @@ -63,6 +81,7 @@ public class HTMLAttributeValidator extends PrimeValidator { */ public HTMLAttributeValidator() { super(); + fPreferenceService = Platform.getPreferencesService(); } /** @@ -153,11 +172,15 @@ public class HTMLAttributeValidator extends PrimeValidator { } if (adec == null) { - if ((attrName.startsWith(ATTR_NAME_DATA) && attrName.length() > ATTR_NAME_DATA_LENGTH) || (attrName.startsWith(ATTR_NAME_USER_AGENT_FEATURE) && attrName.length() > ATTR_NAME_USER_AGENT_FEATURE_LENGTH)) { - DocumentTypeAdapter documentTypeAdapter = (DocumentTypeAdapter) ((INodeNotifier) target.getOwnerDocument()).getAdapterFor(DocumentTypeAdapter.class); - if (documentTypeAdapter != null && documentTypeAdapter.hasFeature(HTMLDocumentTypeConstants.HTML5)) + if ((attrName.startsWith(ATTR_NAME_DATA) && attrName.length() > ATTR_NAME_DATA_LENGTH) || + (attrName.startsWith(ATTR_NAME_USER_AGENT_FEATURE) && attrName.length() > ATTR_NAME_USER_AGENT_FEATURE_LENGTH)) { + if (isHTML5(target)) continue; - } + } + // Check for user-defined exclusions for HTML5 attribute names + if (!shouldValidateAttributeName(target, attrName)) + continue; + // No attr declaration was found. That is, the attr name is // undefined. // but not regard it as undefined name if it includes nested @@ -333,4 +356,60 @@ public class HTMLAttributeValidator extends PrimeValidator { return (c == SINGLE_QUOTE) || (c == DOUBLE_QUOTE); } // D210422 + + private boolean isHTML5(Element target) { + DocumentTypeAdapter documentTypeAdapter = (DocumentTypeAdapter) ((INodeNotifier) target.getOwnerDocument()).getAdapterFor(DocumentTypeAdapter.class); + return (documentTypeAdapter != null && + documentTypeAdapter.hasFeature(HTMLDocumentTypeConstants.HTML5)); + } + + private boolean shouldValidateAttributeName(Element target, String attrName) { + if (!isHTML5(target)) return true; + + Object adapter = (target instanceof IAdaptable ? ((IAdaptable)target).getAdapter(IResource.class) : null); + IProject project = (adapter instanceof IResource ? ((IResource)adapter).getProject() : null); + + Iterator excludedAttributes = getExcludedAttributeNames(project).iterator(); + while (excludedAttributes.hasNext()) { + String excluded = (String)excludedAttributes.next(); + StringMatcher strMatcher = (StringMatcher)fIgnorePatterns.get(excluded); + if (strMatcher == null) { + strMatcher = new StringMatcher(excluded); + fIgnorePatterns.put(excluded, strMatcher); + } + if (strMatcher.match(attrName)) + return false; + } + + return true; + } + + private Set getExcludedAttributeNames(IProject project) { + IScopeContext[] fLookupOrder = new IScopeContext[] {new InstanceScope(), new DefaultScope()}; + if (project != null) { + ProjectScope projectScope = new ProjectScope(project); + if(projectScope.getNode(HTMLCorePlugin.getDefault().getBundle().getSymbolicName()).getBoolean(HTMLCorePreferenceNames.USE_PROJECT_SETTINGS, false)) + fLookupOrder = new IScopeContext[] {projectScope, new InstanceScope(), new DefaultScope()}; + } + + Set result = new HashSet(); + if (fPreferenceService.getBoolean(HTMLCorePlugin.getDefault().getBundle().getSymbolicName(), + HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES, HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES_DEFAULT, + fLookupOrder)) { + String ignoreList = fPreferenceService.getString(HTMLCorePlugin.getDefault().getBundle().getSymbolicName(), + HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE, HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE_DEFAULT, + fLookupOrder); + + if (ignoreList.trim().isEmpty()) + return result; + + 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.isEmpty()) + result.add(name.toLowerCase()); + } + } + return result; + } } diff --git a/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/validate/StringMatcher.java b/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/validate/StringMatcher.java new file mode 100644 index 0000000000..6871163e20 --- /dev/null +++ b/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/validate/StringMatcher.java @@ -0,0 +1,230 @@ +/******************************************************************************* + * Copyright (c) 2000, 2013 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.core.internal.validate; + +import java.util.ArrayList; + +/** + * A string pattern matcher, supporting "*" and "?" wild cards. + * + * @since 3.2 + */ +public class StringMatcher { + private static final char SINGLE_WILD_CARD = '\u0000'; + + /** + * Boundary value beyond which we don't need to search in the text + */ + private int bound = 0; + + private boolean hasLeadingStar; + + private boolean hasTrailingStar; + + private final String pattern; + + private final int patternLength; + + /** + * The pattern split into segments separated by * + */ + private String segments[]; + + /** + * StringMatcher constructor takes in a String object that is a simple + * pattern which may contain '*' for 0 and many characters and + * '?' for exactly one character. + * + * Literal '*' and '?' characters must be escaped in the pattern + * e.g., "\*" means literal "*", etc. + * + * Escaping any other character (including the escape character itself), + * just results in that character in the pattern. + * e.g., "\a" means "a" and "\\" means "\" + * + * If invoking the StringMatcher with string literals in Java, don't forget + * escape characters are represented by "\\". + * + * @param pattern the pattern to match text against + */ + public StringMatcher(String pattern) { + if (pattern == null) + throw new IllegalArgumentException(); + this.pattern = pattern; + patternLength = pattern.length(); + parseWildCards(); + } + + /** + * @param text a simple regular expression that may only contain '?'(s) + * @param start the starting index in the text for search, inclusive + * @param end the stopping point of search, exclusive + * @param p a simple regular expression that may contain '?' + * @return the starting index in the text of the pattern , or -1 if not found + */ + private int findPosition(String text, int start, int end, String p) { + boolean hasWildCard = p.indexOf(SINGLE_WILD_CARD) >= 0; + int plen = p.length(); + for (int i = start, max = end - plen; i <= max; ++i) { + if (hasWildCard) { + if (regExpRegionMatches(text, i, p, 0, plen)) + return i; + } else { + if (text.regionMatches(true, i, p, 0, plen)) + return i; + } + } + return -1; + } + + /** + * Given the starting (inclusive) and the ending (exclusive) positions in the + * text, determine if the given substring matches with aPattern + * @return true if the specified portion of the text matches the pattern + * @param text a String object that contains the substring to match + */ + public boolean match(String text) { + if (text == null) + return false; + final int end = text.length(); + final int segmentCount = segments.length; + if (segmentCount == 0 && (hasLeadingStar || hasTrailingStar)) // pattern contains only '*'(s) + return true; + if (end == 0) + return patternLength == 0; + if (patternLength == 0) + return false; + int currentTextPosition = 0; + if ((end - bound) < 0) + return false; + int segmentIndex = 0; + String current = segments[segmentIndex]; + + /* process first segment */ + if (!hasLeadingStar) { + int currentLength = current.length(); + if (!regExpRegionMatches(text, 0, current, 0, currentLength)) + return false; + segmentIndex++; + currentTextPosition = currentTextPosition + currentLength; + } + if ((segmentCount == 1) && (!hasLeadingStar) && (!hasTrailingStar)) { + // only one segment to match, no wild cards specified + return currentTextPosition == end; + } + /* process middle segments */ + while (segmentIndex < segmentCount) { + current = segments[segmentIndex]; + int currentMatch = findPosition(text, currentTextPosition, end, current); + if (currentMatch < 0) + return false; + currentTextPosition = currentMatch + current.length(); + segmentIndex++; + } + + /* process final segment */ + if (!hasTrailingStar && currentTextPosition != end) { + int currentLength = current.length(); + return regExpRegionMatches(text, end - currentLength, current, 0, currentLength); + } + return segmentIndex == segmentCount; + } + + /** + * Parses the pattern into segments separated by wildcard '*' characters. + */ + private void parseWildCards() { + if (pattern.startsWith("*"))//$NON-NLS-1$ + hasLeadingStar = true; + if (pattern.endsWith("*")) {//$NON-NLS-1$ + /* make sure it's not an escaped wildcard */ + if (patternLength > 1 && pattern.charAt(patternLength - 2) != '\\') { + hasTrailingStar = true; + } + } + + ArrayList temp = new ArrayList(); + + int pos = 0; + StringBuffer buf = new StringBuffer(); + while (pos < patternLength) { + char c = pattern.charAt(pos++); + switch (c) { + case '\\' : + if (pos >= patternLength) { + buf.append(c); + } else { + char next = pattern.charAt(pos++); + /* if it's an escape sequence */ + if (next == '*' || next == '?' || next == '\\') { + buf.append(next); + } else { + /* not an escape sequence, just insert literally */ + buf.append(c); + buf.append(next); + } + } + break; + case '*' : + if (buf.length() > 0) { + /* new segment */ + temp.add(buf.toString()); + bound += buf.length(); + buf.setLength(0); + } + break; + case '?' : + /* append special character representing single match wildcard */ + buf.append(SINGLE_WILD_CARD); + break; + default : + buf.append(c); + } + } + + /* add last buffer to segment list */ + if (buf.length() > 0) { + temp.add(buf.toString()); + bound += buf.length(); + } + segments = (String[])temp.toArray(new String[temp.size()]); + } + + /** + * + * @return boolean + * @param text a String to match + * @param tStart the starting index of match, inclusive + * @param p a simple regular expression that may contain '?' + * @param pStart The start position in the pattern + * @param plen The length of the pattern + */ + private boolean regExpRegionMatches(String text, int tStart, String p, int pStart, int plen) { + while (plen-- > 0) { + char tchar = text.charAt(tStart++); + char pchar = p.charAt(pStart++); + + // process wild cards, skipping single wild cards + if (pchar == SINGLE_WILD_CARD) + continue; + if (pchar == tchar) + continue; + if (Character.toUpperCase(tchar) == Character.toUpperCase(pchar)) + continue; + // comparing after converting to upper case doesn't handle all cases; + // also compare after converting to lower case + if (Character.toLowerCase(tchar) == Character.toLowerCase(pchar)) + continue; + return false; + } + return true; + } +} \ No newline at end of file diff --git a/bundles/org.eclipse.wst.html.ui/icons/full/etool16/donotvalidate.png b/bundles/org.eclipse.wst.html.ui/icons/full/etool16/donotvalidate.png new file mode 100644 index 0000000000..ecb20f587f Binary files /dev/null and b/bundles/org.eclipse.wst.html.ui/icons/full/etool16/donotvalidate.png differ diff --git a/bundles/org.eclipse.wst.html.ui/plugin.xml b/bundles/org.eclipse.wst.html.ui/plugin.xml index 146107863c..8bf535d3c2 100644 --- a/bundles/org.eclipse.wst.html.ui/plugin.xml +++ b/bundles/org.eclipse.wst.html.ui/plugin.xml @@ -49,6 +49,10 @@ type="org.eclipse.jface.text.quickassist.IQuickAssistProcessor" class="org.eclipse.wst.xml.ui.internal.correction.XMLQuickAssistProcessor" target="org.eclipse.wst.html.HTML_DEFAULT" /> + You may edit the list of ignored attributes at Web→HTML Files→Validation Preference Page +DoNotValidateAllAttributesAddInfo=Adds the pattern based on the attribute name to the list of ignored attributes for HTML Attribute Validator
You may edit the list of ignored attributes at Web→HTML Files→Validation Preference Page \ No newline at end of file diff --git a/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/editor/HTMLEditorPluginImages.java b/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/editor/HTMLEditorPluginImages.java index 1fa0f242e7..be2c050e0b 100644 --- a/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/editor/HTMLEditorPluginImages.java +++ b/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/editor/HTMLEditorPluginImages.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2004, 2006 IBM Corporation and others. + * Copyright (c) 2004, 2013 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 @@ -29,4 +29,5 @@ public class HTMLEditorPluginImages { public static final String IMG_OBJ_TAG_TITLE = "icons/full/obj16/tag-title.gif"; //$NON-NLS-1$ public static final String IMG_OBJ_TAG = "icons/full/obj16/tag.gif"; //$NON-NLS-1$ public static final String IMG_WIZBAN_NEWHTMLFILE = "icons/full/wizban/newhfile_wiz.png"; //$NON-NLS-1$ -} + public static final String IMG_DTOOL_DO_NOT_VALIDATE = "icons/full/etool16/donotvalidate.png"; //$NON-NLS-1$ +} \ No newline at end of file diff --git a/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/preferences/ui/HTMLValidationPreferencePage.java b/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/preferences/ui/HTMLValidationPreferencePage.java index e3db43c07c..3e14f55f76 100644 --- a/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/preferences/ui/HTMLValidationPreferencePage.java +++ b/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/preferences/ui/HTMLValidationPreferencePage.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008, 2012 IBM Corporation and others. + * Copyright (c) 2008, 2013 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 @@ -10,35 +10,152 @@ *******************************************************************************/ package org.eclipse.wst.html.ui.internal.preferences.ui; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.preferences.DefaultScope; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.preferences.IPreferencesService; +import org.eclipse.core.runtime.preferences.IScopeContext; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.layout.PixelConverter; +import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.Widget; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.forms.widgets.ExpandableComposite; import org.eclipse.wst.html.core.internal.HTMLCorePlugin; import org.eclipse.wst.html.core.internal.preferences.HTMLCorePreferenceNames; import org.eclipse.wst.html.ui.internal.HTMLUIMessages; import org.eclipse.wst.html.ui.internal.HTMLUIPlugin; +import org.eclipse.wst.html.ui.internal.Logger; import org.eclipse.wst.sse.core.internal.validate.ValidationMessage; import org.eclipse.wst.sse.ui.internal.preferences.ui.AbstractValidationSettingsPage; import org.eclipse.wst.sse.ui.internal.preferences.ui.ScrolledPageContent; +import org.osgi.service.prefs.BackingStoreException; public class HTMLValidationPreferencePage extends AbstractValidationSettingsPage { + public static final String PROPERTY_PAGE_ID = "org.eclipse.wst.html.ui.propertyPage.project.validation"; + + public static final String PREFERENCE_PAGE_ID = "org.eclipse.wst.html.ui.preferences.validation"; + private static final int[] SEVERITIES = {ValidationMessage.ERROR, ValidationMessage.WARNING, ValidationMessage.IGNORE}; private static final String SETTINGS_SECTION_NAME = "HTMLValidationSeverities";//$NON-NLS-1$ + + private class BooleanData { + private String fKey; + private boolean fValue; + boolean originalValue = false; + + public BooleanData(String key) { + fKey = key; + } + + public String getKey() { + return fKey; + } + + /** + * Sets enablement for the attribute names ignorance + * + * @param severity the severity level + */ + public void setValue(boolean value) { + fValue = value; + } + + /** + * Returns the value for the attribute names ignorance + * + * @return + */ + public boolean getValue() { + return fValue; + } + + boolean isChanged() { + return (originalValue != fValue); + } + } + + private class TextData { + private String fKey; + private String fValue; + String originalValue = ""; //$NON-NLS-1$ + + public TextData(String key) { + fKey = key; + } + + public String getKey() { + return fKey; + } + + /** + * Sets the ignored attribute names pattern + * + * @param severity the severity level + */ + public void setValue(String value) { + fValue = value; + } + + /** + * Returns non-null value for the ignored attribute names pattern + * + * @return + */ + public String getValue() { + return fValue != null ? fValue : ""; //$NON-NLS-1$ + } + + boolean isChanged() { + return !originalValue.equalsIgnoreCase(fValue); + } + } public HTMLValidationPreferencePage() { super(); + fPreferencesService = Platform.getPreferencesService(); } private PixelConverter fPixelConverter; + private Button fIgnoreAttributeNames; + private Label fIgnoredAttributeNamesLabel; + private Text fIgnoredAttributeNames; + private IPreferencesService fPreferencesService = null; + + private boolean fUseOriginOverrides = false; + private boolean fIgnoreAttributeNamesOriginOverride = HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES_DEFAULT; + private String fIgnoredAttributeNamesOriginOverride = HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE_DEFAULT; + + public void overrideOriginValues(boolean enableIgnore, String attributeNames) { + fIgnoreAttributeNamesOriginOverride = enableIgnore; + fIgnoredAttributeNamesOriginOverride = attributeNames; + fUseOriginOverrides = true; + + if (fIgnoreAttributeNames != null) { + BooleanData data = (BooleanData)fIgnoreAttributeNames.getData(); + if (data != null) + data.originalValue = fIgnoreAttributeNamesOriginOverride; + } + if (fIgnoredAttributeNames != null) { + TextData data = (TextData)fIgnoredAttributeNames.getData(); + if (data != null) + data.originalValue = fIgnoredAttributeNamesOriginOverride; + } + } protected Control createCommonContents(Composite parent) { final Composite page = new Composite(parent, SWT.NULL); @@ -71,6 +188,59 @@ public class HTMLValidationPreferencePage extends AbstractValidationSettingsPage layout.marginWidth= 0; composite.setLayout(layout); + // Ignored Attribute Names Pattern + BooleanData ignoreData = new BooleanData(HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES); + fIgnoreAttributeNames = new Button(composite, SWT.CHECK); + fIgnoreAttributeNames.setData(ignoreData); + fIgnoreAttributeNames.setFont(page.getFont()); + fIgnoreAttributeNames.setText(HTMLUIMessages.IgnoreAttributeNames); + fIgnoreAttributeNames.setEnabled(true); + + boolean ignoreAttributeNamesIsSelected = fPreferencesService.getBoolean(getPreferenceNodeQualifier(), + ignoreData.getKey(), HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES_DEFAULT, createPreferenceScopes()); + ignoreData.setValue(ignoreAttributeNamesIsSelected); + ignoreData.originalValue = fUseOriginOverrides ? fIgnoreAttributeNamesOriginOverride : ignoreAttributeNamesIsSelected; + + fIgnoreAttributeNames.setSelection(ignoreData.getValue()); + fIgnoreAttributeNames.addSelectionListener(new SelectionListener() { + public void widgetDefaultSelected(SelectionEvent e) { + controlChanged(e.widget); + } + public void widgetSelected(SelectionEvent e) { + controlChanged(e.widget); + } + }); + fIgnoreAttributeNames.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, true, false, 3, 1)); + + fIgnoredAttributeNamesLabel = new Label(composite, SWT.LEFT | SWT.WRAP); + fIgnoredAttributeNamesLabel.setFont(composite.getFont()); + fIgnoredAttributeNamesLabel.setEnabled(ignoreData.getValue()); + fIgnoredAttributeNamesLabel.setText(HTMLUIMessages.IgnoreAttributeNamesPattern); + fIgnoredAttributeNamesLabel.setLayoutData(new GridData(SWT.FILL, SWT.END, true, false, 3, 1)); + setHorizontalIndent(fIgnoredAttributeNamesLabel, 20); + + TextData data = new TextData(HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE); + fIgnoredAttributeNames = new Text(composite, SWT.SINGLE | SWT.BORDER); + fIgnoredAttributeNames.setData(data); + fIgnoredAttributeNames.setTextLimit(500); + fIgnoredAttributeNames.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, 3, 1)); + setHorizontalIndent(fIgnoredAttributeNames, 20); + setWidthHint(fIgnoredAttributeNames, convertWidthInCharsToPixels(65)); + String ignoredAttributeNames = fPreferencesService.getString(getPreferenceNodeQualifier(), data.getKey(), HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE_DEFAULT, createPreferenceScopes()); + data.setValue(ignoredAttributeNames); + data.originalValue = fUseOriginOverrides ? fIgnoredAttributeNamesOriginOverride : ignoredAttributeNames; + fIgnoredAttributeNames.setText(data.getValue()); + + fIgnoredAttributeNames.addModifyListener(new ModifyListener() { + + public void modifyText(ModifyEvent e) { + if (verifyIgnoredAttributeNames()) { + controlChanged(e.widget); + } + } + }); + controlChanged(fIgnoreAttributeNames); + Label description = new Label(composite, SWT.NONE); description.setText(HTMLUIMessages.Validation_description); description.setFont(page.getFont()); @@ -285,16 +455,129 @@ public class HTMLValidationPreferencePage extends AbstractValidationSettingsPage return spContent; } + + private void setHorizontalIndent(Control control, int indent) { + Object ld= control.getLayoutData(); + if (ld instanceof GridData) { + ((GridData) ld).horizontalIndent= indent; + } + } + + private void setWidthHint(Control control, int widthHint) { + Object ld= control.getLayoutData(); + if (ld instanceof GridData) { + ((GridData)ld).widthHint= widthHint; + } + } + + private boolean verifyIgnoredAttributeNames() { + final String text = fIgnoredAttributeNames.getText().trim(); + if (text.length() == 0) + return true; + + boolean valid = true; + for (int i = 0; valid && i < text.length(); i++) { + if (!Character.isJavaIdentifierPart(text.charAt(i)) && + '-' != text.charAt(i) && '_' != text.charAt(i) && + '*' != text.charAt(i) && '?' != text.charAt(i) && + ',' != text.charAt(i)) + valid = false; + } + + if (!valid) { + setErrorMessage(NLS.bind(HTMLUIMessages.BadIgnoreAttributeNamesPattern, text)); + setValid(false); + } + else { + setErrorMessage(null); + setValid(true); + } + return valid; + } + + protected void controlChanged(Widget widget) { + if (widget instanceof Text) { + TextData data= (TextData) widget.getData(); + data.setValue(((Text)widget).getText()); + } else if (widget instanceof Button) { + BooleanData data = (BooleanData) widget.getData(); + if (data != null) { + data.setValue(((Button)widget).getSelection()); + fIgnoredAttributeNamesLabel.setEnabled(data.getValue()); + fIgnoredAttributeNames.setEnabled(data.getValue()); + if (data.getValue()) { + fIgnoredAttributeNames.setFocus(); + } + } + } + } + + /** + * Returns true in case of the Attribute Names to ignore preferences is changed + * causing the full validation to be requested. + */ + protected boolean shouldRevalidateOnSettingsChange() { + TextData data = (TextData)fIgnoredAttributeNames.getData(); + if (data.isChanged()) + return true; + + BooleanData ignoreData = (BooleanData)fIgnoreAttributeNames.getData(); + if (ignoreData.isChanged()) + return true; + + return super.shouldRevalidateOnSettingsChange(); + } + + /* + * (non-Javadoc) + * @see org.eclipse.wst.sse.ui.internal.preferences.ui.AbstractSettingsPage#storeValues() + */ + protected void storeValues() { + IScopeContext[] contexts = createPreferenceScopes(); + + BooleanData ignoreData = (BooleanData)fIgnoreAttributeNames.getData(); + contexts[0].getNode(getPreferenceNodeQualifier()).putBoolean(ignoreData.getKey(), ignoreData.getValue()); + + TextData data = (TextData)fIgnoredAttributeNames.getData(); + contexts[0].getNode(getPreferenceNodeQualifier()).put(data.getKey(), data.getValue()); + + + for(int i = 0; i < contexts.length; i++) { + try { + contexts[i].getNode(getPreferenceNodeQualifier()).flush(); + } catch (BackingStoreException e) { + Logger.logException(e); + } + } + super.storeValues(); + } /* * (non-Javadoc) * @see org.eclipse.jface.preference.PreferencePage#performDefaults() */ protected void performDefaults() { + resetIgnoreAttributeNamesPattern(); resetSeverities(); super.performDefaults(); } + protected void resetIgnoreAttributeNamesPattern() { + IEclipsePreferences defaultContext = new DefaultScope().getNode(getPreferenceNodeQualifier()); + BooleanData ignoreData = (BooleanData)fIgnoreAttributeNames.getData(); + boolean ignoreAttributeNames = defaultContext.getBoolean(ignoreData.getKey(), HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES_DEFAULT); + ignoreData.setValue(ignoreAttributeNames); + fIgnoreAttributeNames.setSelection(ignoreData.getValue()); + + TextData data = (TextData)fIgnoredAttributeNames.getData(); + String ignoredAttributeNames = defaultContext.get(data.getKey(), HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE_DEFAULT); + data.setValue(ignoredAttributeNames); + fIgnoredAttributeNames.setText(data.getValue()); + + controlChanged(fIgnoreAttributeNames); + } + + protected IDialogSettings getDialogSettings() { return HTMLUIPlugin.getDefault().getDialogSettings(); } @@ -317,7 +600,7 @@ public class HTMLValidationPreferencePage extends AbstractValidationSettingsPage } protected String getPreferencePageID() { - return "org.eclipse.wst.html.ui.preferences.validation";//$NON-NLS-1$ + return PREFERENCE_PAGE_ID; } protected String getProjectSettingsKey() { @@ -325,7 +608,7 @@ public class HTMLValidationPreferencePage extends AbstractValidationSettingsPage } protected String getPropertyPageID() { - return "org.eclipse.wst.html.ui.propertyPage.project.validation";//$NON-NLS-1$ + return PROPERTY_PAGE_ID; } public void init(IWorkbench workbench) { diff --git a/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/text/correction/HTMLAttributeValidationQuickFixProcessor.java b/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/text/correction/HTMLAttributeValidationQuickFixProcessor.java new file mode 100644 index 0000000000..5e3f7cd3ef --- /dev/null +++ b/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/text/correction/HTMLAttributeValidationQuickFixProcessor.java @@ -0,0 +1,228 @@ +/******************************************************************************* + * Copyright (c) 2013 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.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); + + 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()}; + } + + String ignoreList = fPreferenceService.getString( + getPreferenceNodeQualifier(), HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE, + HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE_DEFAULT, fLookupOrder); + + Set result = new HashSet(); + if (!ignoreList.trim().isEmpty()) { + 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.isEmpty()) + result.add(name.toLowerCase()); + } + } + + String name = getAttributeName(node, offset); + if (name == null) continue; + + if (!result.contains(name.toLowerCase())) { + IgnoreAttributeNameCompletionProposal p = new IgnoreAttributeNameCompletionProposal( + name, 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 (result.contains(namePattern.toString().toLowerCase())) + break; + + if (!result.contains(namePattern.toString().toLowerCase())) { + IgnoreAttributeNameCompletionProposal p = new IgnoreAttributeNameCompletionProposal( + namePattern.toString(), 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 String getPreferenceNodeQualifier() { + return HTMLCorePlugin.getDefault().getBundle().getSymbolicName(); + } + + private String getProjectSettingsKey() { + return HTMLCorePreferenceNames.USE_PROJECT_SETTINGS; + } +} diff --git a/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/text/correction/IgnoreAttributeNameCompletionProposal.java b/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/text/correction/IgnoreAttributeNameCompletionProposal.java new file mode 100644 index 0000000000..14c9f80917 --- /dev/null +++ b/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/text/correction/IgnoreAttributeNameCompletionProposal.java @@ -0,0 +1,187 @@ +/******************************************************************************* + * Copyright (c) 2013 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 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.preference.PreferenceDialog; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.eclipse.jface.text.contentassist.IContextInformation; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.dialogs.PreferencesUtil; +import org.eclipse.wst.html.core.internal.HTMLCorePlugin; +import org.eclipse.wst.html.core.internal.preferences.HTMLCorePreferenceNames; +import org.eclipse.wst.html.ui.internal.HTMLUIPlugin; +import org.eclipse.wst.html.ui.internal.Logger; +import org.eclipse.wst.html.ui.internal.editor.HTMLEditorPluginImageHelper; +import org.eclipse.wst.html.ui.internal.editor.HTMLEditorPluginImages; +import org.eclipse.wst.html.ui.internal.preferences.ui.HTMLValidationPreferencePage; +import org.osgi.service.prefs.BackingStoreException; +import org.w3c.dom.Node; + +public class IgnoreAttributeNameCompletionProposal implements ICompletionProposal { + /** The string to be added to the Ignored HTML Attributes list. */ + private String fPattern; + /** The target node */ + private Node fTarget; + /** The string to be displayed in the completion proposal popup. */ + private String fDisplayString; + /** The replacement offset. */ + private int fReplacementOffset; + /** The context information of this proposal. */ + private IContextInformation fContextInformation; + /** The additional info of this proposal. */ + private String fAdditionalProposalInfo; + + private IPreferencesService fPreferenceService; + + public IgnoreAttributeNameCompletionProposal(String pattern, int offset, String displayString, String additionalProposalInfo, Node target) { + fReplacementOffset= offset; + fPattern = pattern; + fDisplayString= displayString; + fAdditionalProposalInfo= additionalProposalInfo; + fTarget = target; + fPreferenceService = Platform.getPreferencesService(); + } + + /* + * @see ICompletionProposal#apply(IDocument) + */ + public void apply(IDocument document) { + Object adapter = (fTarget instanceof IAdaptable ? ((IAdaptable)fTarget).getAdapter(IResource.class) : null); + IProject project = (adapter instanceof IResource ? ((IResource)adapter).getProject() : null); + + IScopeContext[] fLookupOrder = new IScopeContext[] {new InstanceScope(), new DefaultScope()}; + boolean hasProjectSettings = false; + if (project != null) { + ProjectScope projectScope = new ProjectScope(project); + if(projectScope.getNode(getPreferenceNodeQualifier()) + .getBoolean(getProjectSettingsKey(), false)) { + hasProjectSettings = true; + fLookupOrder = new IScopeContext[] {projectScope, new InstanceScope(), new DefaultScope()}; + } + } + + boolean originalEnableIgnore = fPreferenceService.getBoolean( + getPreferenceNodeQualifier(), HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES, + HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES_DEFAULT, fLookupOrder); + + String originalAttributeNames = fPreferenceService.getString( + getPreferenceNodeQualifier(), HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE, + HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE_DEFAULT, fLookupOrder); + + StringBuffer ignoreList = new StringBuffer(originalAttributeNames); + + if (ignoreList.length() > 0) + ignoreList.append(','); + + ignoreList.append(fPattern); + + fLookupOrder[0].getNode(getPreferenceNodeQualifier()) + .putBoolean(HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES, true); + + fLookupOrder[0].getNode(getPreferenceNodeQualifier()) + .put(HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE, ignoreList.toString()); + + for(int i = 0; i < fLookupOrder.length; i++) { + try { + fLookupOrder[i].getNode(getPreferenceNodeQualifier()).flush(); + } catch (BackingStoreException e) { + Logger.logException(e); + } + } + + PreferenceDialog dialog = hasProjectSettings ? + PreferencesUtil.createPropertyDialogOn(getShell(), project, HTMLValidationPreferencePage.PROPERTY_PAGE_ID, null, null) : + PreferencesUtil.createPreferenceDialogOn(getShell(), HTMLValidationPreferencePage.PREFERENCE_PAGE_ID, null, null); + if (dialog != null) { + Object page = dialog.getSelectedPage(); + if (page instanceof HTMLValidationPreferencePage) { + ((HTMLValidationPreferencePage)page).overrideOriginValues(originalEnableIgnore, originalAttributeNames); + } + dialog.open(); + } + } + + /* + * @see ICompletionProposal#getDisplayString() + */ + public String getDisplayString() { + if (fDisplayString != null) + return fDisplayString; + return ""; //$NON-NLS-1$ + } + + /* + * @see ICompletionProposal#getAdditionalProposalInfo() + */ + public String getAdditionalProposalInfo() { + return fAdditionalProposalInfo; + } + + /* + * @see ICompletionProposal#getImage() + */ + public Image getImage() { + return HTMLEditorPluginImageHelper.getInstance().getImage(HTMLEditorPluginImages.IMG_DTOOL_DO_NOT_VALIDATE); + } + + /* + * @see ICompletionProposal#getSelection(IDocument) + */ + public Point getSelection(IDocument document) { + return new Point(fReplacementOffset /*+ fCursorPosition*/, 0); + } + + /* + * @see ICompletionProposal#getContextInformation() + */ + public IContextInformation getContextInformation() { + return fContextInformation; + } + + /* + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (obj instanceof IgnoreAttributeNameCompletionProposal) { + IgnoreAttributeNameCompletionProposal p = (IgnoreAttributeNameCompletionProposal)obj; + return (this.fPattern.equals(p.fPattern) && this.fTarget == p.fTarget && this.fReplacementOffset == p.fReplacementOffset); + } + return false; + } + + private String getPreferenceNodeQualifier() { + return HTMLCorePlugin.getDefault().getBundle().getSymbolicName(); + } + + private String getProjectSettingsKey() { + return HTMLCorePreferenceNames.USE_PROJECT_SETTINGS; + } + + private Shell getShell() { + IWorkbench workBench = HTMLUIPlugin.getDefault().getWorkbench(); + IWorkbenchWindow workBenchWindow = workBench == null ? null : workBench.getActiveWorkbenchWindow(); + return workBenchWindow == null ? null : workBenchWindow.getShell(); + } +} -- cgit v1.2.3