/** * Copyright (c) 2009-2011, The HATS Consortium. All rights reserved. * This file is licensed under the terms of the Modified BSD License. */ package org.absmodels.abs.plugin.editor; import static org.absmodels.abs.plugin.util.Constants.*; import static org.absmodels.abs.plugin.util.UtilityFunctions.standardExceptionHandling; import java.io.File; import java.util.ArrayList; import java.util.List; import org.absmodels.abs.plugin.Activator; import org.absmodels.abs.plugin.builder.AbsNature; import org.absmodels.abs.plugin.editor.decoration.ABSDecorationSupport; import org.absmodels.abs.plugin.editor.outline.ABSContentOutlinePage; import org.absmodels.abs.plugin.editor.outline.PackageAbsFileEditorInput; import org.absmodels.abs.plugin.editor.reconciling.ABSReconcilingStrategy; import org.absmodels.abs.plugin.editor.reconciling.AbsModelManager; import org.absmodels.abs.plugin.editor.reconciling.CompilationUnitChangeListener; import org.absmodels.abs.plugin.util.Constants; import org.absmodels.abs.plugin.util.CoreControlUnit; import org.absmodels.abs.plugin.util.InternalASTNode; import org.absmodels.abs.plugin.util.UtilityFunctions; import org.absmodels.abs.plugin.util.CoreControlUnit.ResourceBuildListener; import org.absmodels.abs.plugin.util.CoreControlUnit.ResourceBuiltEvent; import org.absmodels.abs.plugin.util.UtilityFunctions.EditorPosition; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentExtension3; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.text.TextSelection; import org.eclipse.jface.text.source.DefaultCharacterPairMatcher; import org.eclipse.jface.text.source.IAnnotationModel; import org.eclipse.jface.text.source.ICharacterPairMatcher; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.text.source.SourceViewer; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.DecorationOverlayIcon; import org.eclipse.jface.viewers.IDecoration; import org.eclipse.jface.viewers.IPostSelectionProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.swt.SWTException; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.IURIEditorInput; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.contexts.IContextService; import org.eclipse.ui.editors.text.TextEditor; import org.eclipse.ui.texteditor.IDocumentProvider; import org.eclipse.ui.texteditor.ITextEditor; import org.eclipse.ui.texteditor.SourceViewerDecorationSupport; import org.eclipse.ui.views.contentoutline.IContentOutlinePage; import abs.frontend.ast.ASTNode; import abs.frontend.ast.CompilationUnit; /** * The editor for ABS file. Includes syntax highlighting and content assist for ABS files * as well as annotations for ABS errors. * * @author tfischer, cseise, fstrauss, mweber * */ public class ABSEditor extends TextEditor implements CompilationUnitChangeListener { private final class UpdateEditorIcon implements ResourceBuildListener { @Override public void resourceBuilt(ResourceBuiltEvent builtevent) { final IResource editorres = (IResource)ABSEditor.this.getEditorInput().getAdapter(IResource.class); if(builtevent.hasChanged(editorres)){ Display.getDefault().asyncExec(new Runnable() { @Override public void run() { updateEditorIcon(editorres); // workaround for squiggly lines not vanishing after rebuild //refresh(); } }); } } } private ABSContentOutlinePage outlinePage = null; private ResourceBuildListener builtListener; private IPropertyChangeListener propertyChangeListener; private List<CompilationUnitChangeListener> modelChangeListeners = new ArrayList<CompilationUnitChangeListener>(); private CompilationUnit compilationUnit; private ABSReconcilingStrategy reconciler; private volatile int caretPos; private AbsInformationPresenter informationPresenter; public ABSEditor() { super(); setSourceViewerConfiguration(new ABSSourceViewerConfiguration(this)); setDocumentProvider(new ABSDocumentProvider()); builtListener = new UpdateEditorIcon(); CoreControlUnit.addResourceBuildListener(builtListener); // if preferences of syntax highlighting change: Reinstall the PresentationReconciler to make the changes appear. propertyChangeListener = new IPropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent event) { ISourceViewer sourceviewer = ABSEditor.this.getSourceViewer(); getSourceViewerConfiguration().getPresentationReconciler(sourceviewer).install(sourceviewer); } }; Activator.getDefault().getPreferenceStore().addPropertyChangeListener(propertyChangeListener); // this.getSelectionProvider().addSelectionChangedListener(new ISelectionChangedListener() { // // @Override // public void selectionChanged(SelectionChangedEvent event) { // System.out.println("selection = " + event.getSelection()); // // } // }); // this.getSourceViewer().getSelectedRange(); // StyledText styledText = (StyledText) getAdapter(Control.class); // styledText.addCaretListener(new CaretListener() { // // @Override // public void caretMoved(CaretEvent e) { // System.out.println(e.caretOffset); // // } // }); } /** * Reinitializes the editor's source viewer based on the old editor input / document. * */ private void reinitializeSourceViewer() { IEditorInput input = getEditorInput(); IDocumentProvider documentProvider = getDocumentProvider(); IAnnotationModel model = documentProvider.getAnnotationModel(input); IDocument document = documentProvider.getDocument(input); ISourceViewer fSourceViewer = getSourceViewer(); if (document != null) { fSourceViewer.setDocument(document, model); } } /** * highlights the given line as the current instruction point * @param line the line the debugger is currently running in */ public void highlightLine(int line){ IDocument doc = getDocumentProvider().getDocument(getEditorInput()); int lineOffset; try { lineOffset = doc.getLineOffset(line); IResource resource = getResource(); if (resource != null) { // can be null for files inside jars resource.deleteMarkers(Constants.CURRENT_IP_MARKER, false, IResource.DEPTH_ZERO); getSourceViewer().invalidateTextPresentation(); IMarker marker = resource.createMarker(Constants.CURRENT_IP_MARKER); marker.setAttribute(IMarker.LINE_NUMBER, line); marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO); marker.setAttribute(IMarker.MESSAGE, "current instruction pointer"); } if(getSourceViewer() instanceof SourceViewer){ SourceViewer sourceviewer = (SourceViewer)getSourceViewer(); sourceviewer.setSelection(new TextSelection(lineOffset, 0), true); //sourceviewer.refresh(); } } catch (CoreException e) { standardExceptionHandling(e); } catch (BadLocationException e) { standardExceptionHandling(e); } } /** * Highlights the given {@link ASTNode} in the editor.<br/> * Only one ASTNode at a time can be highlighted (This is a restriction of {@link ITextEditor}}).<br/><br/> * * @param edit The target editor * @param node The node that should be highlighted. */ void highlightInEditor(ASTNode<?> node, boolean moveCursor) { EditorPosition pos = UtilityFunctions.getPosition(node); if (pos != null) { IDocument doc = getDocumentProvider().getDocument( getEditorInput()); try { /* Calculate the position on the editor by retrieving the char offset * of the position line and the target column in this line. */ int startOff = doc.getLineOffset(pos.getLinestart()) + pos.getColstart(); int endOff = doc.getLineOffset(pos.getLineend()) + pos.getColend(); if (startOff < 0) { // TODO: This happens sometimes - new parser? Activator.logException(new IllegalArgumentException("FIXME")); return; } assert startOff > -1; assert endOff >= startOff; setHighlightRange(startOff, endOff - startOff, moveCursor); } catch (BadLocationException e) { /* * Should not be thrown, as an ASTNode in a document must have a * specific location. */ } } } /** * Highlights the a given {@link InternalASTNode} in the editor.<br/> * @see #highlightInEditor(ITextEditor, ASTNode) */ public void highlightInEditor(InternalASTNode<?> node, boolean moveCursor) { if (node != null){ highlightInEditor(node.getASTNode(), moveCursor); } } /** * removes the the highlighting set by {@link #highlightLine(int)}. */ public void removeHighlighting(){ IResource resource = getResource(); if (resource == null) { // can be null for files inside jars return; } try { resource.deleteMarkers(Constants.CURRENT_IP_MARKER, false, IResource.DEPTH_INFINITE); getSourceViewer().invalidateTextPresentation(); } catch (CoreException e) { standardExceptionHandling(e); } } @Override protected SourceViewerDecorationSupport getSourceViewerDecorationSupport(ISourceViewer viewer) { if (fSourceViewerDecorationSupport == null) { fSourceViewerDecorationSupport = new ABSDecorationSupport(viewer, getOverviewRuler(), getAnnotationAccess(), getSharedColors()); configureSourceViewerDecorationSupport(fSourceViewerDecorationSupport); } return fSourceViewerDecorationSupport; } @Override public void init(IEditorSite site, IEditorInput input) throws PartInitException { super.init(site, input); // activate abseditorscope when part is active // for example: F3 (JumpToDeclaration) is only active if this editor scope is enabled IContextService cs = (IContextService)getSite().getService(IContextService.class); cs.activateContext(ABSEDITOR_CONTEXT_ID); } @Override public void createPartControl(Composite parent) { super.createPartControl(parent); // listen to changes of the caret position: getSelectionProvider().addPostSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { ISelection selection = event.getSelection(); if (selection instanceof ITextSelection) { ITextSelection ts = (ITextSelection) selection; caretPos = ts.getOffset(); } } }); initCompilationUnit(); } public IPostSelectionProvider getSelectionProvider() { return (IPostSelectionProvider) super.getSelectionProvider(); } private void initCompilationUnit() { AbsNature absNature = UtilityFunctions.getAbsNature(getResource()); if (absNature != null) { AbsModelManager modelManager = absNature.getModelManager(); compilationUnit = modelManager.getCompilationUnit(getAbsoluteFilePath()); } else { // we are looking at abslang.abs or a file inside a jar-package IURIEditorInput uriInput = (IURIEditorInput) getEditorInput().getAdapter(IURIEditorInput.class); if (uriInput != null) { // We're looking e.g. at abslang.abs which only exists in memory. // create an empty model which only contains abslang.abs: absNature = new AbsNature(); absNature.emptyModel(); File f = new File(uriInput.getURI()); String path = f.getAbsolutePath(); compilationUnit = absNature.getCompilationUnit(path); } } } @Override @SuppressWarnings("rawtypes") public Object getAdapter(Class key){ if (IContentOutlinePage.class.equals(key)){ if(outlinePage == null){ outlinePage = new ABSContentOutlinePage(this); } return outlinePage; } return super.getAdapter(key); } @Override protected void configureSourceViewerDecorationSupport (SourceViewerDecorationSupport support) { IPreferenceStore store = getPreferenceStore(); store.setValue(LOCATION_TYPE_NEAR_TEXTSTYLE_KEY, LOCATION_TYPE_NEAR_TEXTSTYLE_VALUE); store.setValue(LOCATION_TYPE_FAR_TEXTSTYLE_KEY, LOCATION_TYPE_FAR_TEXTSTYLE_VALUE); store.setValue(LOCATION_TYPE_SOMEWHERE_TEXTSTYLE_KEY, LOCATION_TYPE_SOMEWHERE_TEXTSTYLE_VALUE); super.configureSourceViewerDecorationSupport(support); char[] matchChars = {'(', ')', '[', ']', '{', '}'}; //which brackets to match ICharacterPairMatcher matcher = new DefaultCharacterPairMatcher(matchChars , IDocumentExtension3.DEFAULT_PARTITIONING); support.setCharacterPairMatcher(matcher); support.setMatchingCharacterPainterPreferenceKeys(EDITOR_MATCHING_BRACKETS, EDITOR_MATCHING_BRACKETS_COLOR); //Enable bracket highlighting in the preference store store.setDefault(EDITOR_MATCHING_BRACKETS, true); store.setDefault(EDITOR_MATCHING_BRACKETS_COLOR, Constants.DEFAULT_MATCHING_BRACKETS_COLOR); } @Override protected void doSetInput(IEditorInput input) throws CoreException { super.doSetInput(input); } public IResource getResource() { return (IResource)getEditorInput().getAdapter(IResource.class); } /** * Throws a {@link SWTException} if the display is disposed * @param editorres the resource of the editor input */ private void updateEditorIcon(IResource editorres) { try { int sev = editorres.findMaxProblemSeverity(MARKER_TYPE, true, IResource.DEPTH_INFINITE); if(sev == IMarker.SEVERITY_INFO){ setTitleImage(getEditorInput().getImageDescriptor().createImage()); return; } ISharedImages simages = PlatformUI.getWorkbench().getSharedImages(); ImageDescriptor overlayIcon = null; switch(sev){ case IMarker.SEVERITY_WARNING: overlayIcon = simages.getImageDescriptor(ISharedImages.IMG_DEC_FIELD_WARNING); break; case IMarker.SEVERITY_ERROR: overlayIcon = simages.getImageDescriptor(ISharedImages.IMG_DEC_FIELD_ERROR); break; } Image resourceImage = getEditorInput().getImageDescriptor().createImage(); final DecorationOverlayIcon icon = new DecorationOverlayIcon(resourceImage, overlayIcon, IDecoration.BOTTOM_LEFT); setTitleImage(icon.createImage()); } catch (CoreException e) { standardExceptionHandling(e); } } @Override public void dispose() { CoreControlUnit.removeResourceBuildListener(builtListener); Activator.getDefault().getPreferenceStore().removePropertyChangeListener(propertyChangeListener); super.dispose(); } public void openInformation(String title, String message) { MessageDialog.openInformation(getSite().getShell(), title, message); } public void openError(String title, String message) { MessageDialog.openError(getSite().getShell(), title, message); } public IProject getProject() { if (getResource() != null) { return getResource().getProject(); } PackageAbsFileEditorInput storageInput = (PackageAbsFileEditorInput) getEditorInput().getAdapter(PackageAbsFileEditorInput.class); if (storageInput != null) { // we are looking at a file inside a jar package return storageInput.getFile().getProject(); } return null; } /** * returns the absolute file path to the file opened by the editor * or "<unknown>" if no such file exists */ public String getAbsoluteFilePath() { File f = getFile(); if (f != null) { return f.getAbsolutePath(); } PackageAbsFileEditorInput storageInput = (PackageAbsFileEditorInput) getEditorInput().getAdapter(PackageAbsFileEditorInput.class); if (storageInput != null) { // we are looking at a file inside a jar package return storageInput.getFile().getAbsoluteFilePath(); } return "<unknown>"; } /** * returns the file shown by the editor * or null if the current resource is not a file */ public File getFile() { if (getResource() instanceof IFile) { IFile iFile = (IFile) getResource(); return iFile.getLocation().toFile(); } return null; } /** * adds a listener which is notified whenever the underlying * compilationUnit of this editor changes */ public void addModelChangeListener(CompilationUnitChangeListener modelChangeListener) { this.modelChangeListeners.add(modelChangeListener); if (compilationUnit != null) { modelChangeListener.onCompilationUnitChange(compilationUnit); } } public void removeModelChangeListener(CompilationUnitChangeListener modelChangeListener) { this.modelChangeListeners.remove(modelChangeListener); } @Override public void onCompilationUnitChange(CompilationUnit newCu) { this.compilationUnit = newCu; for (CompilationUnitChangeListener mcl : modelChangeListeners) { mcl.onCompilationUnitChange(newCu); } } /** * parses the current contents of the editor * and updates the compilationUnit * @param documentOffset */ public void reconcile(boolean withTypechecks) { if (reconciler != null) { reconciler.reconcile(getAbsNature(), withTypechecks); } } public void setReconciler(ABSReconcilingStrategy absReconcilingStrategy) { this.reconciler = absReconcilingStrategy; } /** * returns the current compilationUnit or null * when viewing files outside an ABS project */ public synchronized InternalASTNode<CompilationUnit> getCompilationUnit() { if (getAbsNature() == null || compilationUnit == null) { return null; } return new InternalASTNode<CompilationUnit>(compilationUnit, getAbsNature()); } public AbsNature getAbsNature() { return UtilityFunctions.getAbsNature(getProject()); } public void setCaretPos(int caretPos) { this.caretPos = caretPos; } public int getCaretPos() { return caretPos; } public AbsInformationPresenter getInformationPresenter() { if (informationPresenter == null) { informationPresenter = new AbsInformationPresenter(); informationPresenter.install(getSourceViewer()); } return informationPresenter; } }