package org.simantics.scl.ui.console; import java.io.IOException; import java.util.ArrayList; import java.util.Deque; 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.core.runtime.preferences.InstanceScope; import org.eclipse.jface.preference.IPersistentPreferenceStore; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.resource.LocalResourceManager; import org.eclipse.jface.window.DefaultToolTip; import org.eclipse.jface.window.ToolTip; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyleRange; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.custom.VerifyKeyListener; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.VerifyEvent; import org.eclipse.swt.events.VerifyListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.FormAttachment; import org.eclipse.swt.layout.FormData; import org.eclipse.swt.layout.FormLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Sash; import org.eclipse.ui.preferences.ScopedPreferenceStore; /** * A console with input and output area that can be embedded * into any editor or view. * @author Hannu Niemistö */ public abstract class AbstractCommandConsole extends Composite { public static final String PLUGIN_ID = "org.simantics.scl.ui"; public static final int COMMAND_HISTORY_SIZE = 50; public static final int SASH_HEIGHT = 3; LocalResourceManager resourceManager; StyledText output; Sash sash; protected StyledText input; int userInputHeight=0; int minInputHeight=0; protected Color greenColor; protected Color redColor; ArrayList commandHistory = new ArrayList(); int commandHistoryPos = 0; boolean outputModiLock = false; /* Shell tip = null; Label label = null; */ public AbstractCommandConsole(Composite parent, int style) { super(parent, style); createControl(); } @Override public boolean setFocus() { return input.setFocus(); } protected boolean canExecuteCommand() { return true; } private void createControl() { resourceManager = new LocalResourceManager(JFaceResources.getResources(), this); greenColor = resourceManager.createColor(new RGB(0, 128, 0)); redColor = resourceManager.createColor(new RGB(172, 0, 0)); setLayout(new FormLayout()); Font textFont = new Font(getDisplay(),"Courier New",12,SWT.NONE); // Sash sash = new Sash(this, /*SWT.BORDER |*/ SWT.HORIZONTAL); sash.addListener(SWT.Selection, new Listener () { public void handleEvent(Event e) { Rectangle bounds = AbstractCommandConsole.this.getBounds(); int max = bounds.y + bounds.height; userInputHeight = max-e.y; int actualInputHeight = Math.max(userInputHeight, minInputHeight); sash.setBounds(e.x, max-actualInputHeight, e.width, e.height); setInputHeight(actualInputHeight); } }); // Upper output = new StyledText(this, SWT.MULTI /*| SWT.READ_ONLY*/ | SWT.V_SCROLL | SWT.H_SCROLL); output.setFont(textFont); { FormData formData = new FormData(); formData.top = new FormAttachment(0); formData.bottom = new FormAttachment(sash); formData.left = new FormAttachment(0); formData.right = new FormAttachment(100); output.setLayoutData(formData); } output.addVerifyListener(new VerifyListener() { @Override public void verifyText(VerifyEvent e) { if(outputModiLock) return; input.append(e.text); input.setFocus(); input.setCaretOffset(input.getText().length()); e.doit = false; } }); // Deco StyledText deco = new StyledText(this, SWT.MULTI | SWT.READ_ONLY); deco.setFont(textFont); deco.setEnabled(false); GC gc = new GC(deco); int inputLeftPos = gc.getFontMetrics().getAverageCharWidth()*2; deco.setText(">"); { FormData formData = new FormData(); formData.top = new FormAttachment(sash); formData.bottom = new FormAttachment(100); formData.left = new FormAttachment(0); formData.right = new FormAttachment(0, inputLeftPos); deco.setLayoutData(formData); } // Input area input = new StyledText(this, SWT.MULTI); input.setFont(textFont); { FormData formData = new FormData(); formData.top = new FormAttachment(sash); formData.bottom = new FormAttachment(100); formData.left = new FormAttachment(0, inputLeftPos); formData.right = new FormAttachment(100); input.setLayoutData(formData); } adjustInputSize(""); input.addVerifyKeyListener(new VerifyKeyListener() { @Override public void verifyKey(VerifyEvent event) { switch(event.keyCode) { case SWT.KEYPAD_CR: case SWT.CR: if((event.stateMask & SWT.CTRL) == 0) { if(canExecuteCommand()) execute(); event.doit = false; } break; case SWT.ARROW_UP: case SWT.ARROW_DOWN: if((event.stateMask & SWT.CTRL) != 0) { int targetHistoryPos = commandHistoryPos; if(event.keyCode == SWT.ARROW_UP) { if(commandHistoryPos <= 0) return; --targetHistoryPos; } else { if(commandHistoryPos >= commandHistory.size()-1) return; ++targetHistoryPos; } setInputText(commandHistory.get(targetHistoryPos)); commandHistoryPos = targetHistoryPos; event.doit = false; } break; // case SWT.ESC: // setInputText(""); // commandHistoryPos = commandHistory.size(); // break; } } }); input.addVerifyListener(new VerifyListener() { @Override public void verifyText(VerifyEvent e) { if(e.text.contains("\n")) { int lineId = input.getLineAtOffset(e.start); int lineOffset = input.getOffsetAtLine(lineId); int indentAmount; for(indentAmount=0; lineOffset+indentAmount < input.getCharCount() && input.getTextRange(lineOffset+indentAmount, 1).equals(" "); ++indentAmount); StringBuilder indent = new StringBuilder(); indent.append('\n'); for(int i=0;i offset) { min = Math.max(min, annotation.start); max = Math.max(min, annotation.end); if(first) first = false; else description.append('\n'); description.append(annotation.description); } } if(min != Integer.MIN_VALUE) { Rectangle bounds = input.getTextBounds(min, max-1); toolTip.setText(description.toString()); toolTip.show(new Point(bounds.x, bounds.y+bounds.height)); toolTipVisible = true; } return; } case SWT.MouseMove: if(toolTipVisible) { int offset = getOffsetInInput(e.x, e.y); if(offset < min || offset >= max) { toolTip.hide(); toolTipVisible = false; return; } } return; case SWT.MouseExit: if(toolTipVisible) { toolTip.hide(); toolTipVisible = false; } return; } } }; input.addListener(SWT.MouseHover, hoverListener); input.addListener(SWT.MouseMove, hoverListener); input.addListener(SWT.MouseExit, hoverListener); readPreferences(); addListener(SWT.Dispose, new Listener() { @Override public void handleEvent(Event event) { try { writePreferences(); } catch (IOException e) { e.printStackTrace(); } } }); } private int getOffsetInInput(int x, int y) { int offset; try { offset = input.getOffsetAtLocation(new Point(x, y)); } catch(IllegalArgumentException e) { return -1; } if(offset == input.getText().length()) --offset; else if(offset > 0) { Rectangle rect = input.getTextBounds(offset, offset); if(!rect.contains(x, y)) --offset; } return offset; } public void setInputText(String text) { input.setText(text); input.setCaretOffset(text.length()); adjustInputSize(text); } String validatedText; Job validationJob = new Job("SCL input validation") { @Override protected IStatus run(IProgressMonitor monitor) { String text = validatedText; asyncSetErrorAnnotations(text, validate(text)); return Status.OK_STATUS; } }; Job preValidationJob = new Job("SCL input validation") { @Override protected IStatus run(IProgressMonitor monitor) { if(!input.isDisposed()) { input.getDisplay().asyncExec(new Runnable() { @Override public void run() { if(!input.isDisposed()) { validatedText = input.getText(); validationJob.setPriority(Job.BUILD); validationJob.schedule(); } } }); } return Status.OK_STATUS; } }; private void asyncValidate() { if(!input.getText().equals(errorAnnotationsForCommand)) { preValidationJob.cancel(); preValidationJob.setPriority(Job.BUILD); preValidationJob.schedule(500); } } private static int rowCount(String text) { int rowCount = 1; for(int i=0;i COMMAND_HISTORY_SIZE*2) commandHistory = new ArrayList( commandHistory.subList(COMMAND_HISTORY_SIZE, COMMAND_HISTORY_SIZE*2)); } commandHistoryPos = commandHistory.size(); // Print it into output area //appendOutput("> " + command.replace("\n", "\n ") + "\n", greenColor, null); input.setText(""); // Execute execute(command); } public static final ErrorAnnotation[] EMPTY_ANNOTATION_ARRAY = new ErrorAnnotation[0]; String errorAnnotationsForCommand; ErrorAnnotation[] errorAnnotations = EMPTY_ANNOTATION_ARRAY; private void syncSetErrorAnnotations(String forCommand, ErrorAnnotation[] annotations) { errorAnnotationsForCommand = forCommand; errorAnnotations = annotations; { StyleRange clearRange = new StyleRange(0, forCommand.length(), null, null); input.setStyleRange(clearRange); } for(int i=0;i recentImportPaths = Preferences.decodePaths(commandHistoryPref); commandHistory = new ArrayList(recentImportPaths); commandHistoryPos = commandHistory.size(); return true; } private void writePreferences() throws IOException { IPersistentPreferenceStore store = new ScopedPreferenceStore(InstanceScope.INSTANCE, PLUGIN_ID); store.putValue(Preferences.COMMAND_HISTORY, Preferences.encodePaths(commandHistory)); if (store.needsSaving()) store.save(); } public abstract void execute(String command); public abstract ErrorAnnotation[] validate(String command); public void clear() { outputModiLock = true; output.setText(""); outputModiLock = false; } public StyledText getOutputWidget() { return output; } }