blob: f6334b784931d7463581b284547df7bc6c36e3bd [file] [log] [blame]
/*********************************************************************
* 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/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipse.epsilon.flexmi.dt;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
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.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.epsilon.common.dt.util.LogUtil;
import org.eclipse.epsilon.common.dt.util.ThemeChangeListener;
import org.eclipse.epsilon.emc.emf.EmfUtil;
import org.eclipse.epsilon.flexmi.EObjectLocation;
import org.eclipse.epsilon.flexmi.FlexmiParseException;
import org.eclipse.epsilon.flexmi.FlexmiResource;
import org.eclipse.epsilon.flexmi.FlexmiResourceFactory;
import org.eclipse.epsilon.flexmi.dt.xml.XMLConfiguration;
import org.eclipse.epsilon.flexmi.dt.xml.XMLDocumentProvider;
import org.eclipse.epsilon.flexmi.dt.yaml.YamlConfiguration;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension3;
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.SourceViewerConfiguration;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.editors.text.FileDocumentProvider;
import org.eclipse.ui.editors.text.TextEditor;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.MarkerUtilities;
import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
public class FlexmiEditor extends TextEditor {
private FlexmiHighlightingManager highlightingManager;
protected Job parseResourceJob = null;
protected FlexmiContentOutlinePage outlinePage = null;
protected FlexmiResource resource = null;
protected IFile file;
protected FlexmiFlavour flexmiFlavour = FlexmiFlavour.XML;
public FlexmiEditor() {
super();
setEditorContextMenuId("#TextEditorContext");
setRulerContextMenuId("editor.rulerMenu");
highlightingManager = new FlexmiHighlightingManager();
highlightingManager.initialiseDefaultColors();
setSourceViewerConfiguration(createSourceViewerConfiguration(highlightingManager));
setDocumentProvider(createDocumentProvider());
}
public IFile getFile() {
return file;
}
@Override
public void init(IEditorSite site, IEditorInput input)
throws PartInitException {
super.init(site, input);
outlinePage = new FlexmiContentOutlinePage(this);
final int delay = 1000;
parseResourceJob = new Job("Parsing module") {
protected int status = -1;
@Override
protected IStatus run(IProgressMonitor monitor) {
if (!isClosed()) {
int textHashCode = getText().hashCode();
if (status != textHashCode) {
parseResource();
status = textHashCode;
}
this.schedule(delay);
}
return Status.OK_STATUS;
}
};
parseResourceJob.setSystem(true);
parseResourceJob.schedule(delay);
PlatformUI.getWorkbench().getThemeManager().addPropertyChangeListener(new ThemeChangeListener() {
@Override
public void themeChange() {
highlightingManager.initialiseDefaultColors();
refreshText();
}
});
highlightingManager.getPreferenceStore().addPropertyChangeListener(new IPropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent event) {
if (highlightingManager.isColorPreference(event.getProperty())) {
refreshText();
}
}
});
}
public void refreshText() {
ISourceViewer viewer= getSourceViewer();
setSourceViewerConfiguration(createSourceViewerConfiguration(highlightingManager));
viewer.configure(getSourceViewerConfiguration());
}
protected SourceViewerConfiguration createSourceViewerConfiguration(FlexmiHighlightingManager highlightingManager) {
if (flexmiFlavour == FlexmiFlavour.XML) return new XMLConfiguration(highlightingManager);
else return new YamlConfiguration(highlightingManager);
}
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");
}
private IDocumentProvider createDocumentProvider() {
if (flexmiFlavour == FlexmiFlavour.XML) return new XMLDocumentProvider();
else return new FileDocumentProvider();
}
@Override
protected void doSetSelection(ISelection selection) {
super.doSetSelection(selection);
}
/*
@Override
protected boolean isTabsToSpacesConversionEnabled() {
return super.isTabsToSpacesConversionEnabled() || flexmiFlavour == FlexmiFlavour.YAML;
}*/
public void parseResource() {
// 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();
file = fileInputEditor.getFile();
final IDocument doc = this.getDocumentProvider().getDocument(
this.getEditorInput());
// Replace tabs with spaces to match
// column numbers produced by the parser
String code = doc.get();
// Detect the flavour of the Flexmi document and refresh the configuration accordingly
FlexmiFlavour detectedFlavour = FlexmiResource.isXml(new BufferedInputStream(new ByteArrayInputStream(code.getBytes()))) ? FlexmiFlavour.XML : FlexmiFlavour.YAML;
if ((detectedFlavour != flexmiFlavour)) {
flexmiFlavour = detectedFlavour;
Display.getDefault().asyncExec(() -> refreshText());
}
FlexmiParseException parseException = null;
ResourceSet resourceSet = new ResourceSetImpl();
try {
resourceSet.getPackageRegistry().put(EcorePackage.eINSTANCE.getNsURI(), EcorePackage.eINSTANCE);
resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("*", new FlexmiResourceFactory());
resource = (FlexmiResource) resourceSet.createResource(URI.createFileURI(file.getLocation().toOSString()));
resource.load(new ByteArrayInputStream(code.getBytes()), null);
}
catch (Exception ex) {
if (ex instanceof FlexmiParseException) {
parseException = (FlexmiParseException) ex;
}
else {
LogUtil.log(ex);
}
}
final String markerType = "org.eclipse.epsilon.flexmi.dt.problemmarker";
// Update problem markers
try {
List<FlexmiResource> flexmiResources = new ArrayList<FlexmiResource>();
for (Resource resourceSetResource : resource.getResourceSet().getResources()) {
if (resourceSetResource instanceof FlexmiResource) flexmiResources.add((FlexmiResource) resourceSetResource);
}
for (FlexmiResource flexmiResource : flexmiResources) {
// Delete existing markers from all imported Flexmi resources
try {
for (IFile flexmiFile : ResourcesPlugin.getWorkspace().getRoot().findFilesForLocationURI(new java.net.URI(flexmiResource.getURI().toString()))) {
flexmiFile.deleteMarkers(markerType, true, IResource.DEPTH_INFINITE);
}
} catch (URISyntaxException e) {}
// Delete existing markers from all included Flexmi resources
for (URI uri : flexmiResource.getParsedFragmentURIs()) {
try {
for (IFile parsedFragmentFile : ResourcesPlugin.getWorkspace().getRoot().findFilesForLocationURI(new java.net.URI(uri.toString()))) {
parsedFragmentFile.deleteMarkers(markerType, true, IResource.DEPTH_INFINITE);
}
}
catch (Exception ex) {}
}
}
if (parseException != null) {
createMarker(parseException.getMessage(), parseException.getLineNumber(), true, file, markerType);
}
else {
for (FlexmiResource flexmiResource : flexmiResources) {
// Create warnings for e.g. unresolvable references, incompatible attribute types
for (Resource.Diagnostic warning : flexmiResource.getWarnings()) {
try {
for (IFile markerFile : ResourcesPlugin.getWorkspace().getRoot().findFilesForLocationURI(new java.net.URI(warning.getLocation()))) {
createMarker(warning.getMessage(), warning.getLine(), false, markerFile, markerType);
}
} catch (URISyntaxException e) {}
}
// Create markers for metamodel conformance issues
for (Diagnostic diagnostic : EmfUtil.validate(flexmiResource).getChildren()) {
if (diagnostic.getSeverity() == Diagnostic.INFO) continue;
for (Object data : diagnostic.getData()) {
if (data instanceof EObject) {
EObjectLocation location = flexmiResource.getEObjectTraceManager().getLine((EObject) data);
if (location != null) {
try {
for (IFile markerFile : ResourcesPlugin.getWorkspace().getRoot().findFilesForLocationURI(new java.net.URI(location.getUri().toString()))) {
createMarker(diagnostic.getMessage(), location.getLine(), false /*diagnostic.getSeverity() == Diagnostic.ERROR*/, markerFile, markerType);
}
} catch (URISyntaxException e) {}
}
}
}
}
}
outlinePage.setResourceSet(resourceSet);
}
} catch (CoreException e1) {
e1.printStackTrace();
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public Object getAdapter(Class required) {
if (IContentOutlinePage.class.equals(required)) {
return outlinePage;
}
return super.getAdapter(required);
}
protected void createMarker(String message, int lineNumber, boolean error, IFile file, String markerType) throws CoreException {
Map<String, Object> attr = new HashMap<>();
attr.put(IMarker.LINE_NUMBER, lineNumber);
attr.put(IMarker.MESSAGE, message);
int markerSeverity = IMarker.SEVERITY_WARNING;
if (error) markerSeverity = IMarker.SEVERITY_ERROR;
attr.put(IMarker.SEVERITY, markerSeverity);
MarkerUtilities.createMarker(file, attr, markerType);
}
public boolean isClosed() {
return this.getDocumentProvider() == null;
}
public String getText() {
return this.getDocumentProvider().getDocument(
this.getEditorInput()).get();
}
public FlexmiResource getResource() {
return resource;
}
@Override
public void dispose() {
super.dispose();
}
}