package org.simantics.scl.ui.console; import java.util.ArrayList; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.bindings.keys.KeyStroke; import org.eclipse.jface.bindings.keys.ParseException; import org.eclipse.jface.fieldassist.ContentProposalAdapter; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.widgets.Composite; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.osgi.util.tracker.ServiceTracker; import org.simantics.scl.compiler.commands.CommandSession; import org.simantics.scl.compiler.commands.SCLConsoleListener; import org.simantics.scl.compiler.errors.CompilationError; import org.simantics.scl.compiler.errors.Locations; import org.simantics.scl.osgi.SCLOsgi; import org.simantics.scl.runtime.reporting.AbstractSCLReportingHandler; import org.simantics.scl.runtime.reporting.SCLReportingHandler; import org.simantics.scl.ui.Activator; import org.simantics.scl.ui.assist.SCLContentProposalProvider; import org.simantics.scl.ui.assist.StyledTextContentAdapter; import gnu.trove.set.hash.THashSet; /** * An SCL console with input and output area that can be embedded * into any editor or view. * @author Hannu Niemistö */ public class SCLConsole extends AbstractCommandConsole { public static final String JOB_NAME = "org.simantics.scl.console.job"; public static final long TERMINATE_GRACE_PERIOD = 1000L; private THashSet currentJobs = new THashSet(); private final IdentitySchedulingRule schedulingRule = new IdentitySchedulingRule(); private ArrayList listeners = new ArrayList(2); private boolean consoleIsEmpty = true; SCLReportingHandler handler = new AbstractSCLReportingHandler() { @Override public void print(String text) { appendOutput(text + "\n", null, null); } @Override public void printError(String error) { appendOutput(error + "\n", redColor, null); } @Override public void printCommand(String command) { appendOutput("> " + command.replace("\n", "\n ") + "\n", greenColor, null); } }; CommandSession session = new CommandSession(SCLOsgi.MODULE_REPOSITORY, handler); ContentProposalAdapter contentProposalAdapter; public SCLConsole(Composite parent, int style) { super(parent, style); StyledTextContentAdapter styledTextContentAdapter = new StyledTextContentAdapter(); SCLContentProposalProvider contentProvider = new SCLContentProposalProvider(session); try { contentProposalAdapter = new ContentProposalAdapter( input, styledTextContentAdapter, contentProvider, KeyStroke.getInstance("Ctrl+Space"), null); contentProposalAdapter.setAutoActivationDelay(200); } catch (ParseException e) { // No content assist then. } addContributedListeners(); } @Override protected boolean canExecuteCommand() { return !contentProposalAdapter.isProposalPopupOpen(); } @Override public ErrorAnnotation[] validate(String command) { if(command.isEmpty()) return ErrorAnnotation.EMPTY_ARRAY; CompilationError[] errors = session.validate(command); if(errors.length == 0) return ErrorAnnotation.EMPTY_ARRAY; ErrorAnnotation[] annotations = new ErrorAnnotation[errors.length]; for(int i=0;i 0) --begin; else ++end; } annotations[i] = new ErrorAnnotation(begin, end, error.description); } return annotations; } private String jobNameFromCommand(String command) { return command.split("\n")[0]; } @Override public void execute(final String command) { Job job = new Job(jobNameFromCommand(command)) { @Override protected IStatus run(IProgressMonitor monitor) { try { session.execute(command, handler); } finally { synchronized(currentJobs) { currentJobs.remove(this); if(currentJobs.isEmpty()) for(SCLConsoleListener listener : listeners) listener.finishedExecution(); } } return Status.OK_STATUS; } @Override protected void canceling() { Thread thread = getThread(); if(thread != null) thread.interrupt(); try { Thread.sleep(TERMINATE_GRACE_PERIOD); } catch (InterruptedException e) { e.printStackTrace(); return; } thread = getThread(); if(thread != null) thread.stop(); } }; job.setRule(schedulingRule); synchronized(currentJobs) { boolean firstJob = currentJobs.isEmpty(); currentJobs.add(job); if(firstJob) { synchronized(listeners) { for(SCLConsoleListener listener : listeners) listener.startedExecution(); } } } job.schedule(); } public CommandSession getSession() { return session; } public void interruptCurrentCommands() { synchronized(currentJobs) { for(Job job : currentJobs) job.cancel(); currentJobs.clear(); } } public void addListener(SCLConsoleListener listener) { synchronized (listeners) { listeners.add(listener); } } public void removeListener(SCLConsoleListener listener) { synchronized (listeners) { listeners.remove(listener); } } @Override public void appendOutput(String text, Color foreground, Color background) { super.appendOutput(text, foreground, background); if(consoleIsEmpty) { consoleIsEmpty = false; synchronized (listeners) { for(SCLConsoleListener listener : listeners) listener.consoleIsNotEmptyAnymore(); } } } @Override public void clear() { super.clear(); consoleIsEmpty = true; } private void addContributedListeners() { final BundleContext context = Activator.getInstance().getBundle().getBundleContext(); new ServiceTracker(context, SCLConsoleListener.class, null) { @Override public SCLConsoleListener addingService( ServiceReference reference) { SCLConsoleListener listener = context.getService(reference); addListener(listener); return listener; } @Override public void modifiedService( ServiceReference reference, SCLConsoleListener service) { } @Override public void removedService( ServiceReference reference, SCLConsoleListener service) { removeListener(service); } }.open(); } }