package org.simantics.scl.ui.editor; import java.util.Collection; import java.util.Iterator; import org.eclipse.jface.resource.ImageRegistry; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.ITextListener; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.TextEvent; import org.eclipse.jface.text.source.Annotation; import org.eclipse.jface.text.source.AnnotationModel; import org.eclipse.jface.text.source.AnnotationPainter; import org.eclipse.jface.text.source.IAnnotationModel; import org.eclipse.jface.text.source.ISharedTextColors; import org.eclipse.jface.text.source.OverviewRuler; import org.eclipse.jface.text.source.SourceViewer; import org.eclipse.jface.text.source.VerticalRuler; import org.eclipse.swt.SWT; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Composite; import org.simantics.scl.compiler.ErrorMessage; import org.simantics.scl.compiler.InvalidInputException; import org.simantics.scl.compiler.SCLCompiler; import org.simantics.scl.compiler.SCLCompilerConfiguration; public class SCLTextEditor extends Composite { private static final int DELAY_BEFORE_COMPILATION = 500 /*ms*/; SCLCompilerConfiguration configuration; SourceViewer viewer; ImageRegistry imageRegistry; SCLAnnotationAccess annotationAccess; ISharedTextColors sharedTextColors; IAnnotationModel annotationModel; public SCLTextEditor(Composite parent, int style, SCLCompilerConfiguration configuration) { super(parent, style); setLayout(new FillLayout()); this.configuration = configuration; imageRegistry = new ImageRegistry(parent.getDisplay()); annotationAccess = new SCLAnnotationAccess(imageRegistry); sharedTextColors = new SharedTextColors(getDisplay()); annotationModel = new AnnotationModel(); VerticalRuler leftRuler = new VerticalRuler(12, annotationAccess); leftRuler.setModel(annotationModel); OverviewRuler rightRuler = new OverviewRuler(annotationAccess, 12, sharedTextColors); rightRuler.setModel(annotationModel); rightRuler.addAnnotationType("error"); rightRuler.setAnnotationTypeLayer("error", 0); rightRuler.setAnnotationTypeColor("error", sharedTextColors.getColor(new RGB(255,0,128))); viewer = new SourceViewer(this, leftRuler, rightRuler, true, SWT.H_SCROLL | SWT.V_SCROLL); Document document = new Document(); viewer.setDocument(document, annotationModel); viewer.setEditable(true); viewer.configure(new SCLSourceViewerConfiguration( getDisplay(), sharedTextColors)); // Annotations to text area AnnotationPainter annotationPainter = new AnnotationPainter(viewer, annotationAccess); annotationPainter.addAnnotationType("error"); annotationPainter.setAnnotationTypeColor("error", sharedTextColors.getColor(new RGB(255,0,128))); viewer.addPainter(annotationPainter); annotationModel.addAnnotationModelListener(annotationPainter); // Undo support (maybe not needed in workbench?) viewer.getTextWidget().addKeyListener(new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { } @Override public void keyPressed(KeyEvent e) { if(e.keyCode=='z'&& e.stateMask == SWT.CTRL) { viewer.getUndoManager().undo(); } else if(e.keyCode=='y'&& e.stateMask == SWT.CTRL) { viewer.getUndoManager().redo(); } } }); // Automatic compilation when text changes viewer.addTextListener(new ITextListener() { @Override public void textChanged(TextEvent event) { scheduleCompilation(); } }); } @Override public void dispose() { super.dispose(); sharedTextColors.dispose(); } @SuppressWarnings("unchecked") private void removeAnnotations() { Iterator it = annotationModel.getAnnotationIterator(); while(it.hasNext()) { Annotation annotation = it.next(); annotationModel.removeAnnotation(annotation); } } private void setAnnotations(Collection messages) { removeAnnotations(); for(ErrorMessage message : messages) { annotationModel.addAnnotation( new Annotation("error", true, message.getMessage()), new Position(message.getStart(), message.getStop()-message.getStart()+1)); } } /** * Tries to compile current */ private void compileSync(String code) { try { SCLCompiler.compileExpression(configuration, code); getDisplay().asyncExec(new Runnable() { @Override public void run() { removeAnnotations(); } }); } catch (final InvalidInputException e) { getDisplay().asyncExec(new Runnable() { @Override public void run() { setAnnotations(e.getErrors()); } }); } catch(Exception e) { e.printStackTrace(); } } Object compilationLock = new Object(); String codeToBeCompiled; private synchronized void scheduleCompilation() { synchronized(compilationLock) { if(codeToBeCompiled == null) { new Thread("SCLTextEditor compilation") { public void run() { while(true) { String code; // Waits until code has remained unmodified for // time specified by DELAY_BEFORE_COMPILATION. synchronized(compilationLock) { do { code = codeToBeCompiled; try { compilationLock.wait(DELAY_BEFORE_COMPILATION); } catch (InterruptedException e) { } } while(!code.equals(codeToBeCompiled)); } // Does the actual compilation and updates // annotations. compileSync(code); // If code was not modified during compilation, // exits the compilation thread and sets // codeToBeCompiled null to signal inactivity. synchronized(compilationLock) { if(code.equals(codeToBeCompiled)) { codeToBeCompiled = null; return; } } } } }.start(); } codeToBeCompiled = viewer.getDocument().get(); compilationLock.notify(); } } public String getContent() { final String[] result = new String[1]; getDisplay().syncExec(new Runnable() { @Override public void run() { result[0] = viewer.getDocument().get(); } }); return result[0]; } public void setContent(final String content) { getDisplay().asyncExec(new Runnable() { @Override public void run() { if (viewer.getTextWidget().isDisposed()) return; viewer.getDocument().set(content); } }); } private Point storedSelectedRange; public void storeSelectedRange() { storedSelectedRange = viewer.getSelectedRange(); } public void restoreSelectedRange() { if (storedSelectedRange != null) { viewer.setSelectedRange(storedSelectedRange.x, storedSelectedRange.y); storedSelectedRange = null; } } }