diff --git a/io.cucumber.eclipse.editor/plugin.xml b/io.cucumber.eclipse.editor/plugin.xml index 697f8d8a..99dfdb29 100644 --- a/io.cucumber.eclipse.editor/plugin.xml +++ b/io.cucumber.eclipse.editor/plugin.xml @@ -176,6 +176,16 @@ + + + + + + + + + + + diff --git a/io.cucumber.eclipse.editor/src/io/cucumber/eclipse/editor/marker/MarkerFactory.java b/io.cucumber.eclipse.editor/src/io/cucumber/eclipse/editor/marker/MarkerFactory.java index 5490d01c..1ece350c 100644 --- a/io.cucumber.eclipse.editor/src/io/cucumber/eclipse/editor/marker/MarkerFactory.java +++ b/io.cucumber.eclipse.editor/src/io/cucumber/eclipse/editor/marker/MarkerFactory.java @@ -62,6 +62,10 @@ public class MarkerFactory { public static final String CUCUMBER_NATURE_MISSING_MARKER = CUCUMBER_MARKER + ".project.cucumber_nature_missing"; + public static final String LANGUAGE_SUPPORT_AVAILABLE = CUCUMBER_MARKER + ".language.support_available"; + public static final String LANGUAGE_SUPPORT_BUNDLE_ID_ATTRIBUTE = LANGUAGE_SUPPORT_AVAILABLE + ".bundle_id"; + public static final String LANGUAGE_SUPPORT_LANGUAGE_ATTRIBUTE = LANGUAGE_SUPPORT_AVAILABLE + ".language"; + private MarkerFactory() { } @@ -408,4 +412,35 @@ public static boolean hasMarker(IResource resource, String type, int lineNumber) } + public static void languageSupportAvailable(final IResource resource, final String language, + final String bundleId, boolean persistent) { + mark(resource, new IMarkerBuilder() { + @Override + public void build() throws CoreException { + // Check if marker already exists for this language + String sourceId = language + "_" + bundleId; + Map existingMarker = getExistingMarker(resource, LANGUAGE_SUPPORT_AVAILABLE); + IMarker marker = existingMarker.remove(sourceId); + if (marker == null) { + marker = resource.createMarker(LANGUAGE_SUPPORT_AVAILABLE); + marker.setAttribute(IMarker.SOURCE_ID, sourceId); + } + marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO); + marker.setAttribute(IMarker.MESSAGE, + String.format("Enhanced %s support is available and can be installed", language)); + marker.setAttribute(IMarker.LINE_NUMBER, 1); + marker.setAttribute(IMarker.TRANSIENT, persistent); + marker.setAttribute(LANGUAGE_SUPPORT_BUNDLE_ID_ATTRIBUTE, bundleId); + marker.setAttribute(LANGUAGE_SUPPORT_LANGUAGE_ATTRIBUTE, language); + } + }); + } + + public static void deleteLanguageSupportMarkers(final IResource resource) throws CoreException { + IMarker[] markers = resource.findMarkers(LANGUAGE_SUPPORT_AVAILABLE, true, IResource.DEPTH_ZERO); + for (IMarker marker : markers) { + marker.delete(); + } + } + } diff --git a/io.cucumber.eclipse.editor/src/io/cucumber/eclipse/editor/quickfix/LanguageSupportMarkerResolutionGenerator.java b/io.cucumber.eclipse.editor/src/io/cucumber/eclipse/editor/quickfix/LanguageSupportMarkerResolutionGenerator.java new file mode 100644 index 00000000..bd7edd86 --- /dev/null +++ b/io.cucumber.eclipse.editor/src/io/cucumber/eclipse/editor/quickfix/LanguageSupportMarkerResolutionGenerator.java @@ -0,0 +1,107 @@ +package io.cucumber.eclipse.editor.quickfix; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.dialogs.ProgressMonitorDialog; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.IMarkerResolution; +import org.eclipse.ui.IMarkerResolutionGenerator; +import org.eclipse.ui.IMarkerResolutionGenerator2; +import org.eclipse.ui.PlatformUI; + +import io.cucumber.eclipse.editor.Images; +import io.cucumber.eclipse.editor.marker.MarkerFactory; + +import java.lang.reflect.InvocationTargetException; + +/** + * Provides quick fixes for language support markers. + * When a project has a supported language nature but the support bundle is not installed, + * this generator provides a resolution to install the support. + */ +public class LanguageSupportMarkerResolutionGenerator implements IMarkerResolutionGenerator, IMarkerResolutionGenerator2 { + + @Override + public IMarkerResolution[] getResolutions(IMarker marker) { + try { + if (hasResolutions(marker)) { + String language = marker.getAttribute(MarkerFactory.LANGUAGE_SUPPORT_LANGUAGE_ATTRIBUTE, ""); + String bundleId = marker.getAttribute(MarkerFactory.LANGUAGE_SUPPORT_BUNDLE_ID_ATTRIBUTE, ""); + + if (!language.isEmpty() && !bundleId.isEmpty()) { + return new IMarkerResolution[] { + new InstallLanguageSupportResolution(language, bundleId) + }; + } + } + } catch (CoreException e) { + // Log error but don't fail + } + return new IMarkerResolution[0]; + } + + @Override + public boolean hasResolutions(IMarker marker) { + try { + return MarkerFactory.LANGUAGE_SUPPORT_AVAILABLE.equals(marker.getType()); + } catch (CoreException e) { + return false; + } + } + + /** + * Resolution that installs language support bundle. + */ + private static class InstallLanguageSupportResolution implements IMarkerResolution { + private final String language; + private final String bundleId; + + public InstallLanguageSupportResolution(String language, String bundleId) { + this.language = language; + this.bundleId = bundleId; + } + + @Override + public String getLabel() { + return String.format("Install %s support for Cucumber", language); + } + + @Override + public void run(IMarker marker) { + Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(); + + ProgressMonitorDialog dialog = new ProgressMonitorDialog(shell); + try { + dialog.run(true, false, new IRunnableWithProgress() { + @Override + public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { + monitor.beginTask(String.format("Installing %s support...", language), 100); + + // Simulate installation work - actual implementation will be added later + for (int i = 0; i < 10; i++) { + if (monitor.isCanceled()) { + break; + } + Thread.sleep(1000); + monitor.worked(10); + } + + monitor.done(); + } + }); + + // After successful installation, delete the marker + marker.delete(); + } catch (InvocationTargetException | InterruptedException | CoreException e) { + // Handle error - actual implementation will show proper error dialog + } + } + + public Image getImage() { + return Images.getCukesIcon(); + } + } +} diff --git a/io.cucumber.eclipse.editor/src/io/cucumber/eclipse/editor/quickfix/package-info.java b/io.cucumber.eclipse.editor/src/io/cucumber/eclipse/editor/quickfix/package-info.java new file mode 100644 index 00000000..8e17f4e3 --- /dev/null +++ b/io.cucumber.eclipse.editor/src/io/cucumber/eclipse/editor/quickfix/package-info.java @@ -0,0 +1,19 @@ +/** + * Provides quick fixes (marker resolutions) for various Cucumber editor markers. + * + *

This package contains implementations of {@link org.eclipse.ui.IMarkerResolutionGenerator} + * that offer quick fixes for issues detected in Gherkin feature files.

+ * + *

Language Support Detection

+ *

The {@link io.cucumber.eclipse.editor.quickfix.LanguageSupportMarkerResolutionGenerator} + * provides quick fixes when enhanced language support is available but not installed. + * This is detected by checking project natures: + *

    + *
  • Java projects (org.eclipse.jdt.core.javanature) - suggests io.cucumber.eclipse.java
  • + *
  • Python projects (org.python.pydev.pythonNature) - suggests io.cucumber.eclipse.python
  • + *
+ *

+ * + * @since 3.0.0 + */ +package io.cucumber.eclipse.editor.quickfix; diff --git a/io.cucumber.eclipse.editor/src/io/cucumber/eclipse/editor/validation/GherkinSyntaxValidator.java b/io.cucumber.eclipse.editor/src/io/cucumber/eclipse/editor/validation/GherkinSyntaxValidator.java index 2b7132c4..4a76e435 100644 --- a/io.cucumber.eclipse.editor/src/io/cucumber/eclipse/editor/validation/GherkinSyntaxValidator.java +++ b/io.cucumber.eclipse.editor/src/io/cucumber/eclipse/editor/validation/GherkinSyntaxValidator.java @@ -6,7 +6,9 @@ import java.util.stream.Collectors; import org.eclipse.core.filebuffers.IDocumentSetupParticipant; +import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; @@ -14,7 +16,10 @@ import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentListener; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import io.cucumber.eclipse.editor.Activator; import io.cucumber.eclipse.editor.document.GherkinEditorDocument; import io.cucumber.eclipse.editor.marker.MarkerFactory; import io.cucumber.messages.types.ParseError; @@ -103,12 +108,75 @@ protected IStatus run(IProgressMonitor monitor) { return Status.CANCEL_STATUS; } MarkerFactory.syntaxErrorOnGherkin(resource, list, peristent); + + // Check for language-specific support + checkLanguageSupport(resource); } } jobMap.remove(document, this); return monitor.isCanceled() ? Status.CANCEL_STATUS : Status.OK_STATUS; } + private void checkLanguageSupport(IResource resource) { + try { + IProject project = resource.getProject(); + if (project == null || !project.isAccessible()) { + return; + } + + // Clear existing language support markers + MarkerFactory.deleteLanguageSupportMarkers(resource); + + // Check for Java nature + if (project.hasNature("org.eclipse.jdt.core.javanature")) { + if (!isBundleInstalled("io.cucumber.eclipse.java")) { + MarkerFactory.languageSupportAvailable(resource, "Java", + "io.cucumber.eclipse.java", false); + } + } + + // Check for Python nature + if (project.hasNature("org.python.pydev.pythonNature")) { + if (!isBundleInstalled("io.cucumber.eclipse.python")) { + MarkerFactory.languageSupportAvailable(resource, "Python", + "io.cucumber.eclipse.python", false); + } + } + } catch (CoreException e) { + Activator activator = Activator.getDefault(); + if (activator != null) { + activator.getLog().log(new Status(IStatus.WARNING, Activator.PLUGIN_ID, + "Failed to check language support", e)); + } + } + } + + private boolean isBundleInstalled(String bundleSymbolicName) { + try { + Activator activator = Activator.getDefault(); + if (activator == null) { + return false; + } + + BundleContext context = activator.getBundle().getBundleContext(); + if (context == null) { + return false; + } + + for (Bundle bundle : context.getBundles()) { + if (bundleSymbolicName.equals(bundle.getSymbolicName())) { + int state = bundle.getState(); + return state == Bundle.ACTIVE || state == Bundle.STARTING || + state == Bundle.RESOLVED || state == Bundle.STOPPING; + } + } + } catch (Exception e) { + // If we can't check, assume it's not installed + return false; + } + return false; + } + } }