/******************************************************************************* | |
* Copyright (c) 2008 The University of York. | |
* This program and the accompanying materials | |
* are made available under the terms of the Eclipse Public License 2.0 | |
* which is available at https://www.eclipse.org/legal/epl-2.0/ | |
* | |
* Contributors: | |
* Dimitrios Kolovos - initial API and implementation | |
******************************************************************************/ | |
package org.eclipse.epsilon.common.dt.editor; | |
import java.io.File; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.Collection; | |
import java.util.HashMap; | |
import java.util.HashSet; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Set; | |
import org.eclipse.core.resources.IFile; | |
import org.eclipse.core.resources.IMarker; | |
import org.eclipse.core.resources.IResource; | |
import org.eclipse.core.runtime.CoreException; | |
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.epsilon.common.dt.EpsilonCommonsPlugin; | |
import org.eclipse.epsilon.common.dt.editor.contentassist.IAbstractModuleEditorTemplateContributor; | |
import org.eclipse.epsilon.common.dt.editor.highlighting.EpsilonHighlightingManager; | |
import org.eclipse.epsilon.common.dt.editor.outline.ModuleContentOutlinePage; | |
import org.eclipse.epsilon.common.dt.editor.outline.ModuleContentProvider; | |
import org.eclipse.epsilon.common.dt.editor.outline.ModuleElementLabelProvider; | |
import org.eclipse.epsilon.common.dt.preferences.EpsilonPreferencePage; | |
import org.eclipse.epsilon.common.dt.util.LogUtil; | |
import org.eclipse.epsilon.common.dt.util.ThemeChangeListener; | |
import org.eclipse.epsilon.common.module.IModule; | |
import org.eclipse.epsilon.common.module.IModuleValidator; | |
import org.eclipse.epsilon.common.module.ModuleElement; | |
import org.eclipse.epsilon.common.module.ModuleMarker; | |
import org.eclipse.epsilon.common.module.ModuleMarker.Severity; | |
import org.eclipse.epsilon.common.parse.Region; | |
import org.eclipse.epsilon.common.parse.problem.ParseProblem; | |
import org.eclipse.epsilon.eol.IEolModule; | |
import org.eclipse.epsilon.eol.compile.context.IEolCompilationContext; | |
import org.eclipse.jface.action.IAction; | |
import org.eclipse.jface.preference.IPreferenceStore; | |
import org.eclipse.jface.text.BadLocationException; | |
import org.eclipse.jface.text.IDocument; | |
import org.eclipse.jface.text.IDocumentExtension3; | |
import org.eclipse.jface.text.TextSelection; | |
import org.eclipse.jface.text.source.AnnotationModel; | |
import org.eclipse.jface.text.source.DefaultCharacterPairMatcher; | |
import org.eclipse.jface.text.source.ICharacterPairMatcher; | |
import org.eclipse.jface.text.source.ISourceViewer; | |
import org.eclipse.jface.text.source.ISourceViewerExtension2; | |
import org.eclipse.jface.text.source.IVerticalRuler; | |
import org.eclipse.jface.text.source.SourceViewerConfiguration; | |
import org.eclipse.jface.text.source.projection.ProjectionSupport; | |
import org.eclipse.jface.text.source.projection.ProjectionViewer; | |
import org.eclipse.jface.text.templates.Template; | |
import org.eclipse.swt.SWT; | |
import org.eclipse.swt.graphics.Color; | |
import org.eclipse.swt.widgets.Composite; | |
import org.eclipse.ui.IEditorInput; | |
import org.eclipse.ui.IEditorSite; | |
import org.eclipse.ui.PartInitException; | |
import org.eclipse.ui.PlatformUI; | |
import org.eclipse.ui.part.FileEditorInput; | |
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditor; | |
import org.eclipse.ui.texteditor.MarkerUtilities; | |
import org.eclipse.ui.texteditor.SourceViewerDecorationSupport; | |
import org.eclipse.ui.texteditor.TextOperationAction; | |
public abstract class AbstractModuleEditor extends AbstractDecoratedTextEditor { | |
protected Color backgroundColor = null; | |
protected Job parseModuleJob = null; | |
protected ArrayList<IModuleParseListener> moduleParsedListeners = new ArrayList<>(); | |
protected ArrayList<IAbstractModuleEditorTemplateContributor> templateContributors = new ArrayList<>(); | |
protected EpsilonHighlightingManager highlightingManager; | |
public static final String PROBLEM_MARKER = "org.eclipse.epsilon.common.dt.problemmarker"; | |
private ModuleContentOutlinePage outlinePage; | |
public AbstractModuleEditor() { | |
super(); | |
setDocumentProvider(new AbstractModuleEditorDocumentProvider()); | |
setEditorContextMenuId("#TextEditorContext"); | |
setRulerContextMenuId("editor.rulerMenu"); | |
highlightingManager = new EpsilonHighlightingManager(); | |
highlightingManager.initialiseDefaultColors(); | |
} | |
public void addModuleParsedListener(IModuleParseListener listener) { | |
this.moduleParsedListeners.add(listener); | |
} | |
public ArrayList<IModuleParseListener> getModuleParsedListeners() { | |
return moduleParsedListeners; | |
} | |
public boolean removeModuleParsedListener(IModuleParseListener listener) { | |
return moduleParsedListeners.remove(listener); | |
} | |
public void addTemplateContributor(IAbstractModuleEditorTemplateContributor templateContributor) { | |
this.templateContributors.add(templateContributor); | |
} | |
public boolean removeTemplateContributor(IAbstractModuleEditorTemplateContributor templateContributor) { | |
return this.templateContributors.remove(templateContributor); | |
} | |
protected void notifyModuleParsedListeners(IModule module) { | |
for (IModuleParseListener listener : moduleParsedListeners) { | |
listener.moduleParsed(this, module); | |
} | |
} | |
public ModuleElement adaptToAST(Object o) { | |
if (o instanceof ModuleElement) { | |
return (ModuleElement) o; | |
} else | |
return null; | |
} | |
public void insertText(String text) { | |
IDocument doc = this.getDocumentProvider().getDocument(this.getEditorInput()); | |
TextSelection selection = (TextSelection) getSelectionProvider().getSelection(); | |
try { | |
doc.replace(selection.getOffset(), 0, text); | |
} catch (BadLocationException e) { | |
e.printStackTrace(); | |
} | |
} | |
public final static String EDITOR_MATCHING_BRACKETS = "matchingBrackets"; | |
public final static String EDITOR_MATCHING_BRACKETS_COLOR = "matchingBracketsColor"; | |
@Override | |
protected void configureSourceViewerDecorationSupport(SourceViewerDecorationSupport support) { | |
super.configureSourceViewerDecorationSupport(support); | |
char[] matchChars = { '(', ')', '[', ']', '{', '}' }; | |
ICharacterPairMatcher matcher = new DefaultCharacterPairMatcher(matchChars, | |
IDocumentExtension3.DEFAULT_PARTITIONING); | |
support.setCharacterPairMatcher(matcher); | |
support.setMatchingCharacterPainterPreferenceKeys(EDITOR_MATCHING_BRACKETS, EDITOR_MATCHING_BRACKETS_COLOR); | |
IPreferenceStore store = getPreferenceStore(); | |
store.setDefault(EDITOR_MATCHING_BRACKETS, true); | |
store.setDefault(EDITOR_MATCHING_BRACKETS_COLOR, "128,128,128"); | |
} | |
@SuppressWarnings("unchecked") | |
@Override | |
public <T> T getAdapter(Class<T> required) { | |
if (required != null && required.isInstance(outlinePage)) { | |
return (T) outlinePage; | |
} | |
return super.getAdapter(required); | |
} | |
// TODO: (fonso) this list seems incomplete with respect to the book list | |
public List<String> getAssertions() { | |
return Arrays.asList("assert", "assertError"); | |
} | |
public Collection<String> getTypes() { | |
// The list returned by Arrays.asList cannot be changed in size, | |
// as it is just a wrapper over the Java array. Therefore, any | |
// calls to add/remove will return an UnsupportedOperationException. | |
return new ArrayList<>(Arrays.asList("String", "Boolean", "Integer", "Real", "Any", "Map", "Collection", "Bag", | |
"Sequence", "Set", "OrderedSet", "Native", "List", "Tuple", "ConcurrentSet", "ConcurrentBag", | |
"ConcurrentMap")); | |
} | |
public abstract List<String> getKeywords(); | |
public abstract List<String> getBuiltinVariables(); | |
public ModuleContentOutlinePage createOutlinePage() { | |
ModuleContentOutlinePage outline = new ModuleContentOutlinePage(this.getDocumentProvider(), this, | |
createModuleElementLabelProvider(), createModuleContentProvider()); | |
addModuleParsedListener(outline); | |
return outline; | |
} | |
public abstract IModule createModule(); | |
public abstract ModuleElementLabelProvider createModuleElementLabelProvider(); | |
protected abstract ModuleContentProvider createModuleContentProvider(); | |
ProjectionSupport projectionSupport; | |
AnnotationModel annotationModel; | |
@Override | |
public void createPartControl(Composite parent) { | |
super.createPartControl(parent); | |
ProjectionViewer viewer = (ProjectionViewer) getSourceViewer(); | |
projectionSupport = new ProjectionSupport(viewer, getAnnotationAccess(), getSharedColors()); | |
projectionSupport.install(); | |
// turn projection mode on | |
viewer.doOperation(ProjectionViewer.TOGGLE); | |
annotationModel = viewer.getProjectionAnnotationModel(); | |
// updateFoldingStructure(); | |
} | |
@Override | |
protected ISourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, int styles) { | |
ISourceViewer viewer = new ProjectionViewer(parent, ruler, getOverviewRuler(), isOverviewRulerVisible(), | |
styles); | |
// IDocument doc = this.getDocumentProvider().getDocument( | |
// this.getEditorInput()); | |
// autoclosingPairManager = new AutoclosingPairManager(doc, | |
// getSourceViewerConfiguration().getUndoManager(viewer)); | |
// viewer.getTextWidget().addVerifyKeyListener(autoclosingPairManager); | |
getSourceViewerDecorationSupport(viewer); | |
return viewer; | |
} | |
// TODO : Improve the folding functionality | |
// boolean useCodeFolding = false; | |
// | |
// public void updateFoldingStructure() { | |
// if (!useCodeFolding) return; | |
// | |
// IModule module = createModule(); | |
// IDocument doc = this.getDocumentProvider().getDocument( | |
// this.getEditorInput()); | |
// | |
// try { | |
// annotationModel.removeAllAnnotations(); | |
// module.parse(doc.get()); | |
// ListIterator li = module.getChildren().listIterator(); | |
// while (li.hasNext()){ | |
// ModuleElement child = (ModuleElement) li.next(); | |
// ProjectionAnnotation annotation = new ProjectionAnnotation(); | |
// Position pos = new Position(0); | |
// | |
// int startOffset = 0; | |
// | |
// if (li.hasPrevious()){ | |
// startOffset = doc.getLineOffset(Math.max(child.getAst().getLine() - 1,0)) + child.getAst().getColumn(); | |
// } | |
// | |
// int endOffset = Math.max(doc.getLength(),0); | |
// if (li.hasNext()) { | |
// ModuleElement nextElement = (ModuleElement) li.next(); | |
// endOffset = doc.getLineOffset(Math.max(nextElement.getAst().getLine() - 2,0)) + nextElement.getAst().getColumn() - 1; | |
// li.previous(); | |
// } | |
// | |
// pos.setOffset(startOffset); | |
// pos.setLength(endOffset-startOffset); | |
// annotationModel.addAnnotation(annotation,pos); | |
// } | |
// | |
// | |
// } catch (Exception e) { | |
// e.printStackTrace(); | |
// } | |
// annotationModel.modifyAnnotationPosition(annotation,new | |
// Position(0,10)); | |
/* | |
* Annotation[] annotations = new Annotation[positions.size()]; | |
* | |
* //this will hold the new annotations along //with their corresponding | |
* positions HashMap newAnnotations = new HashMap(); | |
* | |
* for(int i = 0; i < positions.size();i++) { ProjectionAnnotation annotation = | |
* new ProjectionAnnotation(); | |
* | |
* newAnnotations.put(annotation, positions.get(i)); | |
* | |
* annotations[i] = annotation; } | |
* | |
* annotationModel.mmodifyAnnotation(oldAnnotations, newAnnotations,null); | |
* | |
* oldAnnotations = annotations; | |
*/ | |
// } | |
public SourceViewerConfiguration createSourceViewerConfiguration() { | |
return new AbstractModuleEditorSourceViewerConfiguration(this); | |
} | |
@Override | |
public void init(IEditorSite site, IEditorInput input) { | |
try { | |
super.init(site, input); | |
} catch (PartInitException e) { | |
e.printStackTrace(); | |
} | |
setSourceViewerConfiguration(createSourceViewerConfiguration()); | |
PlatformUI.getWorkbench().getThemeManager().addPropertyChangeListener(new ThemeChangeListener() { | |
@Override | |
public void themeChange() { | |
highlightingManager.initialiseDefaultColors(); | |
refreshText(); | |
} | |
}); | |
highlightingManager.getPreferenceStore().addPropertyChangeListener(event -> { | |
if (highlightingManager.isColorPreference(event.getProperty())) { | |
refreshText(); | |
} | |
}); | |
outlinePage = createOutlinePage(); | |
final long delay = 1000; | |
parseModuleJob = new Job("Parsing module") { | |
protected int status = -1; | |
@Override | |
protected IStatus run(IProgressMonitor monitor) { | |
if (!isClosed()) { | |
int textHashCode = getText().hashCode(); | |
if (status != textHashCode) { | |
parseModule(); | |
status = textHashCode; | |
} | |
this.schedule(delay); | |
} | |
return Status.OK_STATUS; | |
} | |
}; | |
parseModuleJob.setSystem(true); | |
parseModuleJob.schedule(delay); | |
} | |
public boolean isClosed() { | |
return this.getDocumentProvider() == null; | |
} | |
public String getText() { | |
return this.getDocumentProvider().getDocument(this.getEditorInput()).get(); | |
} | |
public void parseModule() { | |
// Return early if the file is opened in an unexpected editor (e.g. in a | |
// Subclipse RemoteFileEditor) | |
if (!(getEditorInput() instanceof FileEditorInput)) | |
return; | |
FileEditorInput fileInputEditor = (FileEditorInput) getEditorInput(); | |
IFile file = fileInputEditor.getFile(); | |
final IModule module = createModule(); | |
final IDocument doc = this.getDocumentProvider().getDocument(this.getEditorInput()); | |
// Replace tabs with spaces to match | |
// column numbers produced by the parser | |
String code = doc.get(); | |
code = code.replaceAll("\t", " "); | |
try { | |
module.parse(code, new File(file.getLocation().toOSString())); | |
} catch (Exception e) { | |
} | |
// Update problem markers | |
// TODO: Update problem markers in all referenced files | |
try { | |
// Delete all the old markers | |
for (String markerType : getMarkerTypes()) { | |
file.deleteMarkers(markerType, true, IResource.DEPTH_INFINITE); | |
} | |
// Create markers for parse problems | |
for (ParseProblem problem : module.getParseProblems()) { | |
Map<String, Object> attr = new HashMap<>(); | |
attr.put(IMarker.LINE_NUMBER, problem.getLine()); | |
attr.put(IMarker.MESSAGE, problem.getReason()); | |
int markerSeverity; | |
if (problem.getSeverity() == ParseProblem.ERROR) { | |
markerSeverity = IMarker.SEVERITY_ERROR; | |
} else { | |
markerSeverity = IMarker.SEVERITY_WARNING; | |
} | |
attr.put(IMarker.SEVERITY, markerSeverity); | |
MarkerUtilities.createMarker(file, attr, AbstractModuleEditor.PROBLEM_MARKER); | |
} | |
// If the module has no parse problems, pass it on to the validators | |
// Use try/catch to protect against unexpected exceptions in the validators | |
if (module.getParseProblems().isEmpty()) { | |
try { | |
if (EpsilonCommonsPlugin.getDefault().getPreferenceStore() | |
.getBoolean(EpsilonPreferencePage.ENABLE_STATIC_ANALYSIS)) { | |
if (module instanceof IEolModule) { | |
IEolCompilationContext compilationContext = ((IEolModule) module).getCompilationContext(); | |
if (compilationContext != null) | |
compilationContext.setModelFactory(new ModelTypeExtensionFactory()); | |
} | |
createMarkers(module.compile(), doc, file, AbstractModuleEditor.PROBLEM_MARKER); | |
} | |
for (IModuleValidator validator : ModuleValidatorExtensionPointManager.getDefault() | |
.getExtensions()) { | |
String markerType = (validator.getMarkerType() == null ? AbstractModuleEditor.PROBLEM_MARKER | |
: validator.getMarkerType()); | |
createMarkers(validator.validate(module), doc, file, markerType); | |
} | |
} catch (Exception ex) { | |
LogUtil.log(ex); | |
} | |
} | |
} catch (CoreException e1) { | |
} | |
if (module != null && module.getParseProblems().size() == 0) { | |
notifyModuleParsedListeners(module); | |
} | |
} | |
private void createMarkers(List<ModuleMarker> moduleMarkers, IDocument doc, IFile file, String markerType) | |
throws BadLocationException, CoreException { | |
if (moduleMarkers == null) | |
return; | |
for (ModuleMarker moduleMarker : moduleMarkers) { | |
Map<String, Object> attr = new HashMap<>(); | |
Region region = moduleMarker.getRegion(); | |
int startOffset = doc.getLineOffset(region.getStart().getLine() - 1) + region.getStart().getColumn(); | |
int endOffset = doc.getLineOffset(region.getEnd().getLine() - 1) + region.getEnd().getColumn(); | |
attr.put(IMarker.CHAR_START, startOffset); | |
attr.put(IMarker.CHAR_END, endOffset); | |
attr.put(IMarker.MESSAGE, moduleMarker.getMessage()); | |
if (moduleMarker.getSeverity() == Severity.Error) { | |
attr.put(IMarker.SEVERITY, IMarker.SEVERITY_ERROR); | |
} else if (moduleMarker.getSeverity() == Severity.Warning) { | |
attr.put(IMarker.SEVERITY, IMarker.SEVERITY_WARNING); | |
} else { | |
attr.put(IMarker.SEVERITY, IMarker.SEVERITY_INFO); | |
} | |
MarkerUtilities.createMarker(file, attr, markerType); | |
} | |
} | |
private Collection<String> getMarkerTypes() { | |
final Set<String> markerTypes = new HashSet<>(); | |
markerTypes.add(AbstractModuleEditor.PROBLEM_MARKER); | |
for (IModuleValidator validator : ModuleValidatorExtensionPointManager.getDefault().getExtensions()) { | |
markerTypes.add(validator.getMarkerType()); | |
} | |
return markerTypes; | |
} | |
@Override | |
public void doSave(IProgressMonitor progressMonitor) { | |
super.doSave(progressMonitor); | |
if (!supportsDirtyTextParsing()) | |
parseModule(); | |
} | |
protected abstract boolean supportsHyperlinks(); | |
protected abstract boolean supportsDirtyTextParsing(); | |
public Color getBackgroundColor() { | |
return backgroundColor; | |
} | |
public void setBackgroundColor(Color backgroundColor) { | |
this.backgroundColor = backgroundColor; | |
} | |
@Override | |
public void close(boolean save) { | |
parseModuleJob.cancel(); | |
super.close(save); | |
} | |
public final List<Template> getTemplates() { | |
List<Template> templates = new ArrayList<>(); | |
for (IAbstractModuleEditorTemplateContributor contributor : templateContributors) { | |
templates.addAll(contributor.getTemplates()); | |
} | |
return templates; | |
} | |
private static final String CONTENTASSIST_PROPOSAL_ID = "org.eclipse.common.dt.editor.AbsractModuleEditor.ContentAssistProposal"; | |
private static final String TOGGLE_COMMENT_ID = "org.eclipse.epsilon.common.dt.editor.AbsractModuleEditor.ToggleComment"; | |
@Override | |
protected void createActions() { | |
super.createActions(); | |
// This action will fire a CONTENTASSIST_PROPOSALS operation | |
// when executed | |
IAction action = new TextOperationAction(new EmptyResourceBundle(), "ContentAssistProposal", this, | |
ISourceViewer.CONTENTASSIST_PROPOSALS); | |
action.setActionDefinitionId(CONTENTASSIST_PROPOSAL_ID); | |
setAction(CONTENTASSIST_PROPOSAL_ID, action); | |
// Tell the editor to execute this action | |
// when Ctrl+Spacebar is pressed | |
setActionActivationCode(CONTENTASSIST_PROPOSAL_ID, ' ', -1, SWT.CTRL); | |
// Set up the toggle comment action | |
ToggleCommentAction toggleCommentAction = new ToggleCommentAction(new EmptyResourceBundle(), "ToggleComment", | |
this); | |
toggleCommentAction.setActionDefinitionId(TOGGLE_COMMENT_ID); | |
toggleCommentAction.configure(getSourceViewer(), getSourceViewerConfiguration()); | |
setAction(TOGGLE_COMMENT_ID, toggleCommentAction); | |
} | |
public EpsilonHighlightingManager getHighlightingManager() { | |
return highlightingManager; | |
} | |
private void refreshText() { | |
ISourceViewer viewer = getSourceViewer(); | |
if (!(viewer instanceof ISourceViewerExtension2)) | |
return; | |
((ISourceViewerExtension2) viewer).unconfigure(); | |
setSourceViewerConfiguration(createSourceViewerConfiguration()); | |
viewer.configure(getSourceViewerConfiguration()); | |
} | |
@Override | |
public boolean isDirty() { | |
// TODO add logic to fix bug 376294 | |
return super.isDirty(); | |
} | |
} |