DslFromSelectionAssistProcessor.java
/*******************************************************************************
* Copyright (c) 2026 Carsten Hammer.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Carsten Hammer
*******************************************************************************/
package org.sandbox.jdt.internal.ui.quickassist;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.ui.text.java.IInvocationContext;
import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
import org.eclipse.jdt.ui.text.java.IProblemLocation;
import org.eclipse.jdt.ui.text.java.IQuickAssistProcessor;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;
import org.sandbox.jdt.internal.ui.RuleInferenceUiFeedback;
import org.sandbox.jdt.triggerpattern.llm.AiRuleInferenceEngine;
import org.sandbox.jdt.triggerpattern.llm.CommitEvaluation;
import org.sandbox.jdt.triggerpattern.mining.llm.EclipseLlmService;
/** AI-assisted authoring Quick Assist for generating TriggerPattern DSL from a Java selection. */
public class DslFromSelectionAssistProcessor implements IQuickAssistProcessor {
private static final ILog LOG = Platform.getLog(DslFromSelectionAssistProcessor.class);
private static final String PROPOSAL_LABEL = "Generate DSL rule from selection (AI)"; //$NON-NLS-1$
@Override
public boolean hasAssists(IInvocationContext context) {
return EclipseLlmService.getInstance().isAvailable()
&& context.getSelectionLength() > 0
&& targetProject(context) != null;
}
@Override
public IJavaCompletionProposal[] getAssists(IInvocationContext context,
IProblemLocation[] locations) {
IProject targetProject = targetProject(context);
if (!hasAssists(context) || targetProject == null) {
return new IJavaCompletionProposal[0];
}
return new IJavaCompletionProposal[] {
new DslRuleProposal(context.getSelectionOffset(), context.getSelectionLength(), targetProject)
};
}
private static IProject targetProject(IInvocationContext context) {
ICompilationUnit unit = context.getCompilationUnit();
return unit != null && unit.getJavaProject() != null
? unit.getJavaProject().getProject()
: null;
}
private static class DslRuleProposal implements IJavaCompletionProposal {
private final int selectionOffset;
private final int selectionLength;
private final IProject targetProject;
DslRuleProposal(int offset, int length, IProject targetProject) {
this.selectionOffset = offset;
this.selectionLength = length;
this.targetProject = targetProject;
}
@Override
public void apply(IDocument document) {
try {
String selectedCode = document.get(selectionOffset, selectionLength);
Job job = new Job("Generating DSL rule from selection") { //$NON-NLS-1$
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
AiRuleInferenceEngine engine = EclipseLlmService.getInstance().getEngine();
String[] lines = selectedCode.split("\n", -1); //$NON-NLS-1$
StringBuilder sb = new StringBuilder();
sb.append("--- a/snippet.java\n+++ b/snippet.java\n"); //$NON-NLS-1$
sb.append("@@ -1,0 +1,").append(lines.length).append(" @@\n"); //$NON-NLS-1$ //$NON-NLS-2$
for (String line : lines) {
sb.append('+').append(line).append('\n');
}
Optional<CommitEvaluation> result = engine.inferRuleFromDiff(sb.toString());
if (result.isPresent() && result.get().dslRule() != null
&& !result.get().dslRule().isBlank()) {
openHintFileOnUi(targetProject, result.get().dslRule());
return Status.OK_STATUS;
}
RuleInferenceUiFeedback.showNoRule("the selected Java code"); //$NON-NLS-1$
return Status.OK_STATUS;
} catch (RuntimeException e) {
LOG.error("Failed to infer a DSL rule from the Java selection", e); //$NON-NLS-1$
RuleInferenceUiFeedback.showFailure("the selected Java code", e); //$NON-NLS-1$
return Status.error("DSL inference from Java selection failed", e); //$NON-NLS-1$
}
}
};
job.setUser(true);
job.schedule();
} catch (Exception e) {
LOG.error("Failed to infer DSL rule from selection", e); //$NON-NLS-1$
RuleInferenceUiFeedback.showFailure("the selected Java code", e); //$NON-NLS-1$
}
}
@Override
public Point getSelection(IDocument document) {
return null;
}
@Override
public String getAdditionalProposalInfo() {
return "Uses the configured LLM to infer a TriggerPattern DSL rule from the selected code snippet."; //$NON-NLS-1$
}
@Override
public String getDisplayString() {
return PROPOSAL_LABEL;
}
@Override
public Image getImage() {
return null;
}
@Override
public IContextInformation getContextInformation() {
return null;
}
@Override
public int getRelevance() {
return 1;
}
private static void openHintFileOnUi(IProject project, String ruleContent) {
Display.getDefault().asyncExec(() -> {
try {
String fileName = "inferred-rule-" + System.currentTimeMillis() + ".sandbox-hint"; //$NON-NLS-1$ //$NON-NLS-2$
IFile file = project.getFile(new Path(fileName));
file.create(new ByteArrayInputStream(ruleContent.getBytes(StandardCharsets.UTF_8)), true, null);
IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
if (page != null) {
IDE.openEditor(page, file);
}
} catch (Exception e) {
LOG.error("Failed to open hint file for inferred rule", e); //$NON-NLS-1$
RuleInferenceUiFeedback.showFailure("the generated hint file", e); //$NON-NLS-1$
}
});
}
}
}