| author | kwannheden | 2009-08-21 08:09:05 (EDT) |
|---|---|---|
| committer | sefftinge | 2009-08-21 08:09:05 (EDT) |
| commit | a923e59705d156d0709e7f4d06599e6113df7e59 (patch) (side-by-side diff) | |
| tree | 91ecf569fa182431203dbddb20666fd764d55dea | |
| parent | 3a690efefcd739c1fbff5b8562f17c4c4e2d2601 (diff) | |
| download | org.eclipse.xtext-a923e59705d156d0709e7f4d06599e6113df7e59.zip org.eclipse.xtext-a923e59705d156d0709e7f4d06599e6113df7e59.tar.gz org.eclipse.xtext-a923e59705d156d0709e7f4d06599e6113df7e59.tar.bz2 | |
Feature: declarative quickfix support - https://bugs.eclipse.org/bugs/show_bug.cgi?id=263793
20 files changed, 764 insertions, 28 deletions
diff --git a/examples/org.eclipse.xtext.example.domainmodel.ui/META-INF/MANIFEST.MF b/examples/org.eclipse.xtext.example.domainmodel.ui/META-INF/MANIFEST.MF index faf3330..fb9eb40 100644 --- a/examples/org.eclipse.xtext.example.domainmodel.ui/META-INF/MANIFEST.MF +++ b/examples/org.eclipse.xtext.example.domainmodel.ui/META-INF/MANIFEST.MF @@ -14,7 +14,8 @@ Require-Bundle: org.eclipse.xtext.example.domainmodel, org.eclipse.ui, org.eclipse.emf.index;bundle-version="0.7.0", org.eclipse.emf.index.ui;bundle-version="0.7.0", - org.eclipse.xtext.xtext.ui;bundle-version="0.8.0" + org.eclipse.xtext.xtext.ui;bundle-version="0.8.0", + org.eclipse.ui.ide Export-Package: org.eclipse.xtext.example, org.eclipse.xtext.example.contentassist Bundle-Activator: org.eclipse.xtext.example.ui.internal.DomainmodelActivator diff --git a/examples/org.eclipse.xtext.example.domainmodel.ui/plugin.xml b/examples/org.eclipse.xtext.example.domainmodel.ui/plugin.xml index e82ea83..4f91046 100644 --- a/examples/org.eclipse.xtext.example.domainmodel.ui/plugin.xml +++ b/examples/org.eclipse.xtext.example.domainmodel.ui/plugin.xml @@ -112,7 +112,7 @@ </command> </extension> <extension point="org.eclipse.ui.menus"> - <menuContribution + <menuContribution locationURI="popup:#TextEditorContext?after=group.open"> <command commandId="org.eclipse.xtext.example.Domainmodel.validate" @@ -120,12 +120,20 @@ tooltip="Trigger expensive validation"> <visibleWhen checkEnabled="false"> - <reference - definitionId="org.eclipse.xtext.example.Domainmodel.Editor.opened"> - </reference> + <reference + definitionId="org.eclipse.xtext.example.Domainmodel.Editor.opened"> + </reference> </visibleWhen> </command> </menuContribution> - </extension> + </extension> + + <!-- quickfix marker resolution generator --> + <extension + point="org.eclipse.ui.ide.markerResolution"> + <markerResolutionGenerator + class="org.eclipse.xtext.example.DomainmodelExecutableExtensionFactory:org.eclipse.xtext.example.quickfix.DomainmodelQuickfixProvider"> + </markerResolutionGenerator> + </extension> </plugin> diff --git a/examples/org.eclipse.xtext.example.domainmodel.ui/plugin.xml_gen b/examples/org.eclipse.xtext.example.domainmodel.ui/plugin.xml_gen index 3cb7488..1392bbe 100644 --- a/examples/org.eclipse.xtext.example.domainmodel.ui/plugin.xml_gen +++ b/examples/org.eclipse.xtext.example.domainmodel.ui/plugin.xml_gen @@ -112,5 +112,12 @@ </extension> + <!-- quickfix marker resolution generator --> + <extension + point="org.eclipse.ui.ide.markerResolution"> + <markerResolutionGenerator + class="org.eclipse.xtext.example.DomainmodelExecutableExtensionFactory:org.eclipse.xtext.example.quickfix.DomainmodelQuickfixProvider"> + </markerResolutionGenerator> + </extension> </plugin> diff --git a/examples/org.eclipse.xtext.example.domainmodel.ui/src/org/eclipse/xtext/example/quickfix/DomainmodelQuickfixProvider.java b/examples/org.eclipse.xtext.example.domainmodel.ui/src/org/eclipse/xtext/example/quickfix/DomainmodelQuickfixProvider.java new file mode 100644 index 0000000..03e62db --- a/dev/null +++ b/examples/org.eclipse.xtext.example.domainmodel.ui/src/org/eclipse/xtext/example/quickfix/DomainmodelQuickfixProvider.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2009 itemis AG (http://www.itemis.eu) 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 + * + *******************************************************************************/ +package org.eclipse.xtext.example.quickfix; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.xtext.example.domainmodel.Type; +import org.eclipse.xtext.example.validation.DomainmodelJavaValidator; +import org.eclipse.xtext.ui.core.quickfix.AbstractDeclarativeQuickfixProvider; +import org.eclipse.xtext.ui.core.quickfix.Fix; + +public class DomainmodelQuickfixProvider extends AbstractDeclarativeQuickfixProvider { + + @Fix(code = DomainmodelJavaValidator.CAPITAL_TYPE_NAME, label = "Capitalize name", description = "Capitalize name of type") + public void fixName(Type type, IMarker marker) { + type.setName(type.getName().toUpperCase()); + } + +} diff --git a/examples/org.eclipse.xtext.example.domainmodel/src/org/eclipse/xtext/example/GenerateDomainmodelLanguage.mwe b/examples/org.eclipse.xtext.example.domainmodel/src/org/eclipse/xtext/example/GenerateDomainmodelLanguage.mwe index a21b197..13971dc 100644 --- a/examples/org.eclipse.xtext.example.domainmodel/src/org/eclipse/xtext/example/GenerateDomainmodelLanguage.mwe +++ b/examples/org.eclipse.xtext.example.domainmodel/src/org/eclipse/xtext/example/GenerateDomainmodelLanguage.mwe @@ -28,6 +28,7 @@ </fragment> <fragment class="org.eclipse.xtext.generator.scoping.JavaScopingFragment"/> <fragment class="org.eclipse.xtext.generator.validation.JavaValidatorFragment"/> + <fragment class="org.eclipse.xtext.generator.quickfix.QuickfixProviderFragment"/> <fragment class="org.eclipse.xtext.generator.formatting.FormatterFragment"/> <fragment class="org.eclipse.xtext.ui.generator.contentAssist.JavaBasedContentAssistFragment"/> diff --git a/examples/org.eclipse.xtext.example.domainmodel/src/org/eclipse/xtext/example/validation/DomainmodelJavaValidator.java b/examples/org.eclipse.xtext.example.domainmodel/src/org/eclipse/xtext/example/validation/DomainmodelJavaValidator.java index f691ea7..1f92afc 100644 --- a/examples/org.eclipse.xtext.example.domainmodel/src/org/eclipse/xtext/example/validation/DomainmodelJavaValidator.java +++ b/examples/org.eclipse.xtext.example.domainmodel/src/org/eclipse/xtext/example/validation/DomainmodelJavaValidator.java @@ -1,14 +1,18 @@ package org.eclipse.xtext.example.validation; - +import org.eclipse.xtext.example.domainmodel.DomainmodelPackage; +import org.eclipse.xtext.example.domainmodel.Type; +import org.eclipse.xtext.validation.Check; public class DomainmodelJavaValidator extends AbstractDomainmodelJavaValidator { -// @Check -// public void checkTypeNameStartsWithCapital(Type type) { -// if (!Character.isUpperCase(type.getName().charAt(0))) { -// warning("Name should start with a capital", MyDslPackage.TYPE__NAME); -// } -// } + public static final int CAPITAL_TYPE_NAME = 1; + + @Check + public void checkTypeNameStartsWithCapital(Type type) { + if (!Character.isUpperCase(type.getName().charAt(0))) { + warning("Name should start with a capital", DomainmodelPackage.TYPE__NAME, CAPITAL_TYPE_NAME); + } + } } diff --git a/plugins/org.eclipse.xtext.ui.core/META-INF/MANIFEST.MF b/plugins/org.eclipse.xtext.ui.core/META-INF/MANIFEST.MF index 70ad1e3..0399904 100644 --- a/plugins/org.eclipse.xtext.ui.core/META-INF/MANIFEST.MF +++ b/plugins/org.eclipse.xtext.ui.core/META-INF/MANIFEST.MF @@ -34,6 +34,7 @@ Export-Package: org.eclipse.xtext.ui.core, org.eclipse.xtext.ui.core.editor.toggleComments, org.eclipse.xtext.ui.core.editor.utils, org.eclipse.xtext.ui.core.internal, + org.eclipse.xtext.ui.core.quickfix, org.eclipse.xtext.ui.core.util, org.eclipse.xtext.ui.core.wizard Bundle-RequiredExecutionEnvironment: J2SE-1.5 diff --git a/plugins/org.eclipse.xtext.ui.core/plugin.xml b/plugins/org.eclipse.xtext.ui.core/plugin.xml index 52f4f60..cad2157 100644 --- a/plugins/org.eclipse.xtext.ui.core/plugin.xml +++ b/plugins/org.eclipse.xtext.ui.core/plugin.xml @@ -147,6 +147,9 @@ point="org.eclipse.core.resources.markers"> <super type="org.eclipse.emf.ecore.diagnostic"/> <persistent value="true"/> + <attribute + name="code"> + </attribute> </extension> <extension id="org.eclipse.xtext.ui.core.check.fast" diff --git a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/DefaultDiagnosticConverter.java b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/DefaultDiagnosticConverter.java index e67d66a..06368b0 100644 --- a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/DefaultDiagnosticConverter.java +++ b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/DefaultDiagnosticConverter.java @@ -8,14 +8,15 @@ package org.eclipse.xtext.ui.core.editor; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.core.resources.IMarker; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.EValidator; import org.eclipse.emf.ecore.resource.Resource.Diagnostic; +import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.xtext.parsetree.AbstractNode; import org.eclipse.xtext.parsetree.NodeAdapter; import org.eclipse.xtext.parsetree.NodeUtil; @@ -64,12 +65,24 @@ public class DefaultDiagnosticConverter implements IDiagnosticConverter { marker.put(IMarker.CHAR_START, locationData.getSecond()); marker.put(IMarker.CHAR_END, locationData.getThird()); } + final EObject causer = getCauser(diagnostic); + if (causer != null) + marker.put(EValidator.URI_ATTRIBUTE, EcoreUtil.getURI(causer).toString()); + final Integer code = diagnostic.getCode(); + if (code != null) + marker.put(IXtextResourceChecker.CODE_KEY, code); marker.put(IXtextResourceChecker.DIAGNOSTIC_KEY, diagnostic); marker.put(IMarker.MESSAGE, diagnostic.getMessage()); marker.put(IMarker.PRIORITY, Integer.valueOf(IMarker.PRIORITY_LOW)); acceptor.accept(marker); } + protected EObject getCauser(org.eclipse.emf.common.util.Diagnostic diagnostic) { + // causer is the first element see Diagnostician.getData + Object causer = diagnostic.getData().get(0); + return causer instanceof EObject ? (EObject) causer : null; + } + /** * @return the location data for the given diagnostic. * <ol> @@ -79,15 +92,13 @@ public class DefaultDiagnosticConverter implements IDiagnosticConverter { * </ol> */ protected Triple<Integer, Integer, Integer> getLocationData(org.eclipse.emf.common.util.Diagnostic diagnostic) { - Iterator<?> data = diagnostic.getData().iterator(); - // causer is the first element see Diagnostician.getData - Object causer = data.next(); - if (causer instanceof EObject) { - EObject ele = (EObject) causer; + EObject causer = getCauser(diagnostic); + if (causer != null) { // feature is the second element see Diagnostician.getData - Object feature = data.hasNext() ? data.next() : null; - EStructuralFeature structuralFeature = resolveStructuralFeature(ele, feature); - return getLocationData(ele, structuralFeature); + List<?> data = diagnostic.getData(); + Object feature = data.size() > 1 ? data.get(1) : null; + EStructuralFeature structuralFeature = resolveStructuralFeature(causer, feature); + return getLocationData(causer, structuralFeature); } return null; } diff --git a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/IXtextResourceChecker.java b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/IXtextResourceChecker.java index 53d8481..f55ff9d 100644 --- a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/IXtextResourceChecker.java +++ b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/IXtextResourceChecker.java @@ -7,7 +7,8 @@ import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.emf.ecore.resource.Resource; public interface IXtextResourceChecker { - + + static final String CODE_KEY = "code"; static final String DIAGNOSTIC_KEY = "EmfDiagnostic"; List<Map<String, Object>> check(final Resource resource, Map<?, ?> context, IProgressMonitor monitor); diff --git a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/XtextQuickAssistAssistant.java b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/XtextQuickAssistAssistant.java new file mode 100644 index 0000000..a9a93d0 --- a/dev/null +++ b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/XtextQuickAssistAssistant.java @@ -0,0 +1,176 @@ +package org.eclipse.xtext.ui.core.editor; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.jface.text.AbstractReusableInformationControlCreator; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.DefaultInformationControl; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IInformationControl; +import org.eclipse.jface.text.IInformationControlCreator; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.DefaultInformationControl.IInformationPresenter; +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.eclipse.jface.text.contentassist.ICompletionProposalExtension3; +import org.eclipse.jface.text.contentassist.IContextInformation; +import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext; +import org.eclipse.jface.text.quickassist.IQuickAssistProcessor; +import org.eclipse.jface.text.quickassist.QuickAssistAssistant; +import org.eclipse.jface.text.source.Annotation; +import org.eclipse.jface.text.source.IAnnotationModel; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.IMarkerResolution; +import org.eclipse.ui.IMarkerResolution2; +import org.eclipse.ui.ide.IDE; +import org.eclipse.ui.texteditor.MarkerAnnotation; + +/** + * @author Knut Wannheden - Initial contribution and API + */ +public class XtextQuickAssistAssistant extends QuickAssistAssistant { + + private static class XtextCompletionProposal implements ICompletionProposal, ICompletionProposalExtension3 { + + private Position pos; + private IMarker marker; + private IMarkerResolution resolution; + + public XtextCompletionProposal(Position pos, IMarker marker, IMarkerResolution resolution) { + this.pos = pos; + this.marker = marker; + this.resolution = resolution; + } + + public void apply(IDocument document) { + resolution.run(marker); + } + + public Point getSelection(IDocument document) { + return new Point(pos.offset, 0); + } + + public String getAdditionalProposalInfo() { + if (resolution instanceof IMarkerResolution2) + return ((IMarkerResolution2) resolution).getDescription(); + return null; + } + + public String getDisplayString() { + return resolution.getLabel(); + } + + public Image getImage() { + if (resolution instanceof IMarkerResolution2) { + return ((IMarkerResolution2) resolution).getImage(); + } + return null; + } + + public IContextInformation getContextInformation() { + return null; + } + + public IInformationControlCreator getInformationControlCreator() { + return null; + } + + public int getPrefixCompletionStart(IDocument document, int completionOffset) { + return 0; + } + + public CharSequence getPrefixCompletionText(IDocument document, int completionOffset) { + return null; + } + + } + + private static class XtextQuickAssistProcessor implements IQuickAssistProcessor { + + private String errorMessage; + + public String getErrorMessage() { + return errorMessage; + } + + public boolean canFix(Annotation annotation) { + if (annotation.isMarkedDeleted() || !(annotation instanceof MarkerAnnotation)) + return false; + + final MarkerAnnotation markerAnnotation = (MarkerAnnotation) annotation; + if (markerAnnotation.isQuickFixableStateSet()) + return markerAnnotation.isQuickFixable(); + + final IMarker marker = markerAnnotation.getMarker(); + boolean canFix = IDE.getMarkerHelpRegistry().hasResolutions(marker); + + if (canFix) { + final IMarkerResolution[] contributedResolutions = IDE.getMarkerHelpRegistry().getResolutions(marker); + canFix = contributedResolutions.length > 0; + } + + if (!markerAnnotation.isQuickFixableStateSet()) + markerAnnotation.setQuickFixable(canFix); + + return canFix; + } + + public boolean canAssist(IQuickAssistInvocationContext invocationContext) { + return false; + } + + public ICompletionProposal[] computeQuickAssistProposals(IQuickAssistInvocationContext invocationContext) { + final IAnnotationModel amodel = invocationContext.getSourceViewer().getAnnotationModel(); + final IDocument doc = invocationContext.getSourceViewer().getDocument(); + + final int offset = invocationContext.getOffset(); + final List<ICompletionProposal> list = new ArrayList<ICompletionProposal>(); + + for (Iterator<?> it = amodel.getAnnotationIterator(); it.hasNext();) { + Object key = it.next(); + if (!(key instanceof MarkerAnnotation) || !((MarkerAnnotation) key).isQuickFixable()) + continue; + + MarkerAnnotation annotation = (MarkerAnnotation) key; + IMarker marker = annotation.getMarker(); + + IMarkerResolution[] resolutions = IDE.getMarkerHelpRegistry().getResolutions(marker); + if (resolutions != null) { + Position pos = amodel.getPosition(annotation); + try { + int line = doc.getLineOfOffset(pos.getOffset()); + int start = pos.getOffset(); + String delim = doc.getLineDelimiter(line); + int delimLength = delim != null ? delim.length() : 0; + int end = doc.getLineLength(line) + start - delimLength; + if (offset >= start && offset <= end) { + for (int i = 0; i < resolutions.length; i++) { + list.add(new XtextCompletionProposal(pos, marker, resolutions[i])); + } + } + } + catch (BadLocationException e) { + errorMessage = e.getMessage(); + } + + } + } + return list.toArray(new ICompletionProposal[list.size()]); + } + } + + public XtextQuickAssistAssistant() { + setQuickAssistProcessor(new XtextQuickAssistProcessor()); + setInformationControlCreator(new AbstractReusableInformationControlCreator() { + @Override + public IInformationControl doCreateInformationControl(Shell parent) { + return new DefaultInformationControl(parent, (IInformationPresenter) null); + } + }); + } + +}
\ No newline at end of file diff --git a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/XtextSourceViewerConfiguration.java b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/XtextSourceViewerConfiguration.java index 548a99c..125a316 100644 --- a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/XtextSourceViewerConfiguration.java +++ b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/XtextSourceViewerConfiguration.java @@ -18,6 +18,7 @@ import org.eclipse.jface.text.formatter.IContentFormatter; import org.eclipse.jface.text.hyperlink.IHyperlink; import org.eclipse.jface.text.hyperlink.IHyperlinkDetector; import org.eclipse.jface.text.presentation.IPresentationReconciler; +import org.eclipse.jface.text.quickassist.IQuickAssistAssistant; import org.eclipse.jface.text.reconciler.IReconciler; import org.eclipse.jface.text.source.IAnnotationHover; import org.eclipse.jface.text.source.ISourceViewer; @@ -64,6 +65,9 @@ public class XtextSourceViewerConfiguration extends TextSourceViewerConfiguratio return new ProblemHover(sourceViewer); } + @Inject(optional = true) + private IQuickAssistAssistant quickAssistant; + @Override public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) { return contentAssistantFactory.createConfiguredAssistant(this, sourceViewer); @@ -114,6 +118,16 @@ public class XtextSourceViewerConfiguration extends TextSourceViewerConfiguratio return detectors.toArray(new IHyperlinkDetector[detectors.size()]); } + @Override + public IQuickAssistAssistant getQuickAssistAssistant(ISourceViewer sourceViewer) { + if (sourceViewer.isEditable()) { + if (quickAssistant == null) + quickAssistant = new XtextQuickAssistAssistant(); + return quickAssistant; + } + return null; + } + @Override public IContentFormatter getContentFormatter(ISourceViewer sourceViewer) { if (contentFormatterFactory != null) diff --git a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/XtextDocumentUtil.java b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/XtextDocumentUtil.java index 68e9a9a..33b2d43 100644 --- a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/XtextDocumentUtil.java +++ b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/XtextDocumentUtil.java @@ -1,7 +1,12 @@ package org.eclipse.xtext.ui.core.editor.model; +import org.eclipse.core.resources.IFile; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.projection.ProjectionDocument; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.part.FileEditorInput; +import org.eclipse.xtext.ui.core.editor.XtextEditor; public class XtextDocumentUtil { @@ -12,6 +17,12 @@ public class XtextDocumentUtil { return get(((ProjectionDocument) ctx).getMasterDocument()); if (ctx instanceof ITextViewer) return get(((ITextViewer) ctx).getDocument()); + if (ctx instanceof XtextEditor) + return ((XtextEditor) ctx).getDocument(); + if (ctx instanceof IFile) { + IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); + return get(activePage.findEditor(new FileEditorInput((IFile) ctx))); + } return null; } } diff --git a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/quickfix/AbstractDeclarativeQuickfixProvider.java b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/quickfix/AbstractDeclarativeQuickfixProvider.java new file mode 100644 index 0000000..aab2b5a --- a/dev/null +++ b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/quickfix/AbstractDeclarativeQuickfixProvider.java @@ -0,0 +1,198 @@ +/******************************************************************************* + * Copyright (c) 2009 itemis AG (http://www.itemis.eu) 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 + *******************************************************************************/ +package org.eclipse.xtext.ui.core.quickfix; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EValidator; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.IMarkerResolution; +import org.eclipse.ui.IMarkerResolution2; +import org.eclipse.ui.IMarkerResolutionGenerator2; +import org.eclipse.xtext.concurrent.IUnitOfWork; +import org.eclipse.xtext.resource.XtextResource; +import org.eclipse.xtext.ui.core.IImageHelper; +import org.eclipse.xtext.ui.core.editor.IXtextResourceChecker; +import org.eclipse.xtext.ui.core.editor.MarkerUtil; +import org.eclipse.xtext.ui.core.editor.model.IXtextDocument; +import org.eclipse.xtext.ui.core.editor.model.XtextDocumentUtil; +import org.eclipse.xtext.ui.core.editor.model.edit.IDocumentEditor; +import org.eclipse.xtext.util.SimpleCache; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.inject.Inject; + +/** + * @author Knut Wannheden - Initial contribution and API + */ +public class AbstractDeclarativeQuickfixProvider implements IMarkerResolutionGenerator2 { + + @Inject + private IDocumentEditor documentEditor; + + public IDocumentEditor getDocumentEditor() { + return documentEditor; + } + + @Inject + private IImageHelper imageHelper; + + public IImageHelper getImageHelper() { + return imageHelper; + } + + protected IXtextDocument getDocument(IMarker marker) { + return XtextDocumentUtil.get(marker.getResource()); + } + + protected URI getURI(final IMarker marker) { + String uri = marker.getAttribute(EValidator.URI_ATTRIBUTE, null); + return uri == null ? null : URI.createURI(uri); + } + + protected Integer getCode(final IMarker marker) { + Integer code = marker.getAttribute(IXtextResourceChecker.CODE_KEY, -1); + return code; + } + + protected EObject getContext(final IMarker marker) { + final IXtextDocument document = getDocument(marker); + if (document == null) + return null; + + final EObject context = document.readOnly(new IUnitOfWork<EObject, XtextResource>() { + public EObject exec(XtextResource state) throws Exception { + URI uri = getURI(marker); + return uri != null ? state.getEObject(uri.fragment()) : null; + } + }); + return context; + } + + protected Predicate<Method> getFixMethodPredicate(final IMarker marker) { + return new Predicate<Method>() { + public boolean apply(Method input) { + Fix annotation = input.getAnnotation(Fix.class); + return input.getParameterTypes().length == 2 && Void.TYPE == input.getReturnType() + && annotation != null && annotation.code() == getCode(marker); + } + }; + } + + private volatile Set<Method> fixMethods = null; + + private final SimpleCache<Class<?>, List<Method>> methodsForType = new SimpleCache<Class<?>, List<Method>>( + new Function<Class<?>, List<Method>>() { + public List<Method> apply(Class<?> param) { + List<Method> result = Lists.newArrayList(); + for (Method m : fixMethods) { + if (methodMatched(m, param)) + result.add(m); + } + return result; + } + + private boolean methodMatched(Method method, Class<?> param) { + return method.getParameterTypes()[0].isAssignableFrom(param); + } + }); + + protected IMarkerResolution[] getMarkerResolutions(final IMarker marker, List<Method> fixMethods) { + return Lists.transform(fixMethods, new Function<Method, IMarkerResolution>() { + public IMarkerResolution apply(final Method from) { + return new IMarkerResolution2() { + private final Fix annotation = from.getAnnotation(Fix.class); + + public void run(IMarker marker) { + executeFixMethod(from, marker); + } + + public String getLabel() { + return annotation.label(); + } + + public Image getImage() { + String image = annotation.image(); + return image != null ? getImageHelper().getImage(image) : null; + } + + public String getDescription() { + return annotation.description(); + } + }; + } + }).toArray(new IMarkerResolution[fixMethods.size()]); + } + + protected void executeFixMethod(final Method method, final IMarker marker) { + IXtextDocument document = getDocument(marker); + documentEditor.process(new IUnitOfWork<Void, XtextResource>() { + public java.lang.Void exec(XtextResource state) throws Exception { + URI uri = getURI(marker); + EObject context = state.getEObject(uri.fragment()); + method.invoke(AbstractDeclarativeQuickfixProvider.this, new Object[] { context, marker }); + return null; + } + }, document); + } + + private Iterable<Method> collectMethods(Class<? extends AbstractDeclarativeQuickfixProvider> clazz, IMarker marker) { + List<Method> methods = Lists.newArrayList(clazz.getMethods()); + return Iterables.filter(methods, getFixMethodPredicate(marker)); + } + + protected List<Method> getFixMethods(final IMarker marker) { + final EObject context = getContext(marker); + if (context == null) + return Collections.emptyList(); + + if (fixMethods == null) { + synchronized (this) { + if (fixMethods == null) { + Set<Method> methods = Sets.newLinkedHashSet(collectMethods(getClass(), marker)); + this.fixMethods = methods; + } + } + } + + final List<Method> fixMethods = methodsForType.get(context.getClass()); + return fixMethods; + } + + public boolean hasResolutions(final IMarker marker) { + try { + if (!marker.isSubtypeOf(MarkerUtil.CHECK_MARKER_ID)) + return false; + } + catch (CoreException e) { + return false; + } + if (getURI(marker) == null || getCode(marker) == null) + return false; + + List<Method> fixMethods = getFixMethods(marker); + return !fixMethods.isEmpty(); + } + + public IMarkerResolution[] getResolutions(IMarker marker) { + List<Method> fixMethods = getFixMethods(marker); + return getMarkerResolutions(marker, fixMethods); + } + +} diff --git a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/quickfix/Fix.java b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/quickfix/Fix.java new file mode 100644 index 0000000..c522623 --- a/dev/null +++ b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/quickfix/Fix.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2009 itemis AG (http://www.itemis.eu) 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 + *******************************************************************************/ +package org.eclipse.xtext.ui.core.quickfix; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Knut Wannheden - Initial contribution and API + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface Fix { + + int code(); + + String label() default ""; + + String description() default ""; + + String image() default ""; +} diff --git a/tests/org.eclipse.xtext.generator.tests/src/org/eclipse/xtext/validation/AbstractDeclarativeValidatorTest.java b/tests/org.eclipse.xtext.generator.tests/src/org/eclipse/xtext/validation/AbstractDeclarativeValidatorTest.java index 18ca9d7..5101098 100755 --- a/tests/org.eclipse.xtext.generator.tests/src/org/eclipse/xtext/validation/AbstractDeclarativeValidatorTest.java +++ b/tests/org.eclipse.xtext.generator.tests/src/org/eclipse/xtext/validation/AbstractDeclarativeValidatorTest.java @@ -218,6 +218,24 @@ public class AbstractDeclarativeValidatorTest extends TestCase { assertEquals(Diagnostic.ERROR, diag.getSeverity()); } + public void testErrorWithCode() { + AbstractDeclarativeValidator test = new AbstractDeclarativeValidator() { + @Check + @SuppressWarnings("unused") + public void foo(Object x) { + error("Error Message", 1, 42); + } + }; + BasicDiagnostic chain = new BasicDiagnostic(); + test.validate(EcorePackage.eINSTANCE.getEClass(), chain, Collections.emptyMap()); + assertEquals(1, chain.getChildren().size()); + + Diagnostic diag = chain.getChildren().get(0); + assertEquals("Error Message", diag.getMessage()); + assertEquals(42, diag.getCode()); + assertEquals(Diagnostic.ERROR, diag.getSeverity()); + } + public void testWarning() { AbstractDeclarativeValidator test = new AbstractDeclarativeValidator() { @Check @@ -254,6 +272,24 @@ public class AbstractDeclarativeValidatorTest extends TestCase { assertEquals(Diagnostic.WARNING, diag.getSeverity()); } + public void testWarningWithCode() { + AbstractDeclarativeValidator test = new AbstractDeclarativeValidator() { + @Check + @SuppressWarnings("unused") + public void foo(Object x) { + warning("Error Message", 1, 42); + } + }; + BasicDiagnostic chain = new BasicDiagnostic(); + test.validate(EcorePackage.eINSTANCE.getEClass(), chain, Collections.emptyMap()); + assertEquals(1, chain.getChildren().size()); + + Diagnostic diag = chain.getChildren().get(0); + assertEquals("Error Message", diag.getMessage()); + assertEquals(42, diag.getCode()); + assertEquals(Diagnostic.WARNING, diag.getSeverity()); + } + public void testAssertTrue1() { AbstractDeclarativeValidator test = new AbstractDeclarativeValidator() { @Check diff --git a/tests/org.eclipse.xtext.generator.tests/src/org/eclipse/xtext/xtext/XtextInspectorTest.java b/tests/org.eclipse.xtext.generator.tests/src/org/eclipse/xtext/xtext/XtextInspectorTest.java index 7d7fd58..0dca433 100644 --- a/tests/org.eclipse.xtext.generator.tests/src/org/eclipse/xtext/xtext/XtextInspectorTest.java +++ b/tests/org.eclipse.xtext.generator.tests/src/org/eclipse/xtext/xtext/XtextInspectorTest.java @@ -47,14 +47,14 @@ public abstract class XtextInspectorTest extends AbstractXtextTests implements V protected abstract boolean isExpectingWarnings(); - public void acceptError(String message, EObject object, Integer feature) { + public void acceptError(String message, EObject object, Integer feature, Integer code) { if (!isExpectingErrors()) fail("unexpected call to acceptError"); Triple<String,EObject,Integer> error = Tuples.create(message, object, feature); errors.add(error); } - public void acceptWarning(String message, EObject object, Integer feature) { + public void acceptWarning(String message, EObject object, Integer feature, Integer code) { if (!isExpectingWarnings()) fail("unexpected call to acceptWarning"); Triple<String,EObject,Integer> warning = Tuples.create(message, object, feature); diff --git a/tests/org.eclipse.xtext.generator.tests/src/org/eclipse/xtext/xtext/XtextValidationTest.java b/tests/org.eclipse.xtext.generator.tests/src/org/eclipse/xtext/xtext/XtextValidationTest.java index 3ac1db5..822ce8d 100644 --- a/tests/org.eclipse.xtext.generator.tests/src/org/eclipse/xtext/xtext/XtextValidationTest.java +++ b/tests/org.eclipse.xtext.generator.tests/src/org/eclipse/xtext/xtext/XtextValidationTest.java @@ -596,7 +596,7 @@ public class XtextValidationTest extends AbstractGeneratorTest implements Valida assertEquals(diag.getSeverity(), Diagnostic.OK); assertTrue(diag.getChildren().toString(), diag.getChildren().isEmpty()); } - + public void testBug_286683() throws Exception { XtextResource resource = getResourceFromString("grammar org.xtext.example.MyDsl with org.xtext.example.MyDsl\n"+ "generate myDsl 'http://www.xtext.org/example/MyDsl'\n"+ @@ -610,12 +610,12 @@ public class XtextValidationTest extends AbstractGeneratorTest implements Valida assertEquals(diag.getChildren().toString(), 1, diag.getChildren().size()); } - public void acceptError(String message, EObject object, Integer feature) { + public void acceptError(String message, EObject object, Integer feature, Integer code) { assertNull(lastMessage); lastMessage = message; } - public void acceptWarning(String message, EObject object, Integer feature) { + public void acceptWarning(String message, EObject object, Integer feature, Integer code) { fail("Unexpected call to acceptWarning(..)"); } } diff --git a/tests/org.eclipse.xtext.ui.core.tests/src/org/eclipse/xtext/ui/core/quickfix/AbstractDeclarativeQuickfixProviderTest.java b/tests/org.eclipse.xtext.ui.core.tests/src/org/eclipse/xtext/ui/core/quickfix/AbstractDeclarativeQuickfixProviderTest.java new file mode 100644 index 0000000..b9710a1 --- a/dev/null +++ b/tests/org.eclipse.xtext.ui.core.tests/src/org/eclipse/xtext/ui/core/quickfix/AbstractDeclarativeQuickfixProviderTest.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2009 itemis AG (http://www.itemis.eu) 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 + *******************************************************************************/ +package org.eclipse.xtext.ui.core.quickfix; + +import junit.framework.TestCase; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.EcorePackage; +import org.eclipse.ui.IMarkerResolution; + +/** + * @author Knut Wannheden - Initial contribution and API + */ +public class AbstractDeclarativeQuickfixProviderTest extends TestCase { + + private static final int DUMMY_CODE = 42; + + public void testHasResolutions() throws Exception { + AbstractDeclarativeQuickfixProvider generator = new AbstractDeclarativeQuickfixProvider() { + @Override + protected EObject getContext(IMarker marker) { + return ((MockMarker) marker).getContext(); + } + + @Fix(code = DUMMY_CODE) + @SuppressWarnings("unused") + public void fixError(EObject obj, IMarker marker) { + } + }; + IMarker marker = MockMarker.newFastErrorMarker(null, null, null); + assertFalse(generator.hasResolutions(marker)); + marker = MockMarker.newFastErrorMarker(null, EcorePackage.eINSTANCE.getEClass(), DUMMY_CODE); + assertTrue(generator.hasResolutions(marker)); + assertEquals(1, generator.getResolutions(marker).length); + } + + public void testGetResolutions() throws Exception { + AbstractDeclarativeQuickfixProvider generator = new AbstractDeclarativeQuickfixProvider() { + @Override + protected EObject getContext(IMarker marker) { + return ((MockMarker) marker).getContext(); + } + + @Fix(code = DUMMY_CODE, label = "fixError1") + @SuppressWarnings("unused") + public void fixError1(EObject obj, IMarker marker) { + } + + @Fix(code = DUMMY_CODE, label = "fixError2") + @SuppressWarnings("unused") + public void fixError2(EPackage obj, IMarker marker) { + } + + @Fix(code = DUMMY_CODE, label = "fixError3") + @SuppressWarnings("unused") + public void fixError3(EClass obj, IMarker marker) { + } + }; + IMarker marker = MockMarker.newFastErrorMarker(null, null, null); + IMarkerResolution[] resolutions = generator.getResolutions(marker); + assertEquals(0, resolutions.length); + marker = MockMarker.newFastErrorMarker(null, EcorePackage.eINSTANCE.getEClass(), DUMMY_CODE); + resolutions = generator.getResolutions(marker); + assertEquals(2, resolutions.length); + assertEquals("fixError1", resolutions[0].getLabel()); + assertEquals("fixError3", resolutions[1].getLabel()); + } + +} diff --git a/tests/org.eclipse.xtext.ui.core.tests/src/org/eclipse/xtext/ui/core/quickfix/MockMarker.java b/tests/org.eclipse.xtext.ui.core.tests/src/org/eclipse/xtext/ui/core/quickfix/MockMarker.java new file mode 100644 index 0000000..84f8f2c --- a/dev/null +++ b/tests/org.eclipse.xtext.ui.core.tests/src/org/eclipse/xtext/ui/core/quickfix/MockMarker.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * Copyright (c) 2009 itemis AG (http://www.itemis.eu) 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 + *******************************************************************************/ +package org.eclipse.xtext.ui.core.quickfix; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EValidator; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.xtext.ui.core.editor.IXtextResourceChecker; +import org.eclipse.xtext.ui.core.editor.MarkerUtil; + +/** + * @author knut - Initial contribution and API + */ +final class MockMarker implements IMarker { + + private String type; + private IResource resource; + private EObject context; + private Map<String, ?> attributes; + + public static MockMarker newFastErrorMarker(IResource resource, EObject context, Integer code) { + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(IMarker.SEVERITY, IMarker.SEVERITY_ERROR); + if (context != null) + attributes.put(EValidator.URI_ATTRIBUTE, EcoreUtil.getURI(context).toString()); + attributes.put(IXtextResourceChecker.CODE_KEY, code); + return new MockMarker(MarkerUtil.FAST_CHECK_MARKER_ID, resource, context, attributes); + } + + public MockMarker(String type, IResource resource, EObject context, Map<String, ?> attributes) { + this.type = type; + this.resource = resource; + this.context = context; + this.attributes = attributes; + } + + @SuppressWarnings("unchecked") + public Object getAdapter(Class adapter) { + return null; + } + + public void setAttributes(String[] attributeNames, Object[] values) throws CoreException { + throw new UnsupportedOperationException(); + } + + @SuppressWarnings("unchecked") + public void setAttributes(Map attributes) throws CoreException { + throw new UnsupportedOperationException(); + } + + public void setAttribute(String attributeName, boolean value) throws CoreException { + throw new UnsupportedOperationException(); + } + + public void setAttribute(String attributeName, Object value) throws CoreException { + throw new UnsupportedOperationException(); + } + + public void setAttribute(String attributeName, int value) throws CoreException { + throw new UnsupportedOperationException(); + } + + public boolean isSubtypeOf(String superType) throws CoreException { + return true; + } + + public String getType() throws CoreException { + return type; + } + + public IResource getResource() { + return resource; + } + + public EObject getContext() { + return context; + } + + public long getId() { + return 0; + } + + public long getCreationTime() throws CoreException { + return 0; + } + + public Object[] getAttributes(String[] attributeNames) throws CoreException { + return null; + } + + @SuppressWarnings("unchecked") + public Map getAttributes() throws CoreException { + return attributes; + } + + public boolean getAttribute(String attributeName, boolean defaultValue) { + Object result = attributes.get(attributeName); + return result == null ? defaultValue : (Boolean) result; + } + + public String getAttribute(String attributeName, String defaultValue) { + Object result = attributes.get(attributeName); + return result == null ? defaultValue : (String) result; + } + + public int getAttribute(String attributeName, int defaultValue) { + Object result = attributes.get(attributeName); + return result == null ? defaultValue : (Integer) result; + } + + public Object getAttribute(String attributeName) throws CoreException { + return attributes.get(attributeName); + } + + public boolean exists() { + return true; + } + + public void delete() throws CoreException { + throw new UnsupportedOperationException(); + } + +}
\ No newline at end of file |

