/**
* Copyright (c) 2015 committers of YAKINDU and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
* Contributors:
* committers of YAKINDU - initial API and implementation
*
*/
package org.yakindu.base.xtext.utils.jface.viewers;
import java.util.List;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.fieldassist.ControlDecoration;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.IDocumentPartitioner;
import org.eclipse.jface.text.contentassist.IContentAssistant;
import org.eclipse.jface.text.source.AnnotationModel;
import org.eclipse.jface.text.source.ICharacterPairMatcher;
import org.eclipse.jface.text.source.ISharedTextColors;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.editors.text.EditorsPlugin;
import org.eclipse.ui.swt.IFocusService;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditor;
import org.eclipse.ui.texteditor.AnnotationPreference;
import org.eclipse.ui.texteditor.DefaultMarkerAnnotationAccess;
import org.eclipse.ui.texteditor.MarkerAnnotationPreferences;
import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.ILeafNode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.parser.IParseResult;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.ui.editor.XtextEditor;
import org.eclipse.xtext.ui.editor.XtextSourceViewer;
import org.eclipse.xtext.ui.editor.XtextSourceViewerConfiguration;
import org.eclipse.xtext.ui.editor.bracketmatching.BracketMatchingPreferencesInitializer;
import org.eclipse.xtext.ui.editor.model.XtextDocument;
import org.eclipse.xtext.ui.editor.preferences.IPreferenceStoreAccess;
import org.eclipse.xtext.ui.editor.quickfix.IssueResolutionProvider;
import org.eclipse.xtext.ui.editor.validation.AnnotationIssueProcessor;
import org.eclipse.xtext.ui.editor.validation.ValidationJob;
import org.eclipse.xtext.util.concurrent.IUnitOfWork;
import org.eclipse.xtext.validation.CheckMode;
import org.eclipse.xtext.validation.IResourceValidator;
import org.eclipse.xtext.validation.Issue;
import org.yakindu.base.xtext.utils.jface.fieldassist.CompletionProposalAdapter;
import org.yakindu.base.xtext.utils.jface.viewers.context.IXtextFakeContextResourcesProvider;
import org.yakindu.base.xtext.utils.jface.viewers.context.XtextFakeResourceContext;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;
/**
*
* @author andreas.muelder@itemis.de
* @author alexander.nyssen@itemis.de
* @author patrick.koenemann@itemis.de
*
*/
@SuppressWarnings("restriction")
public class StyledTextXtextAdapter {
/**
* The sourceViewer, that provides additional functions to the styled text
* widget
*/
protected XtextSourceViewer sourceviewer;
private ValidationJob validationJob;
private IssueResolutionProvider resolutionProvider = new IssueResolutionProvider.NullImpl();
@Inject
private IPreferenceStoreAccess preferenceStoreAccess;
@Inject
private ICharacterPairMatcher characterPairMatcher;
@Inject
private XtextSourceViewerConfiguration configuration;
@Inject
private XtextStyledTextHighlightingHelper xtextStyledTextHighlightingHelper;
@Inject
private IResourceValidator validator;
@Inject
private Provider<IDocumentPartitioner> documentPartitioner;
@Inject
private XtextDocument document;
private XtextFakeResourceContext fakeResourceContext;
private final IXtextFakeContextResourcesProvider contextFakeResourceProvider;
private StyledText styledText;
private ControlDecoration decoration;
public StyledTextXtextAdapter(Injector injector, IXtextFakeContextResourcesProvider contextFakeResourceProvider) {
this.contextFakeResourceProvider = contextFakeResourceProvider;
injector.injectMembers(this);
// create fake resource and containing resource set
createFakeResourceContext(injector);
}
public StyledTextXtextAdapter(Injector injector) {
this(injector, IXtextFakeContextResourcesProvider.NULL_CONTEXT_PROVIDER);
}
public void adapt(StyledText styledText) {
this.styledText = styledText;
// perform initialization of fake resource context
updateFakeResourceContext();
// connect Xtext document to fake resource
initXtextDocument(fakeResourceContext);
// connect xtext document to xtext source viewer
createXtextSourceViewer();
// install semantic highlighting support
installHighlightingHelper();
validationJob = createValidationJob();
document.setValidationJob(validationJob);
styledText.setData(StyledTextXtextAdapter.class.getCanonicalName(), this);
final IContentAssistant contentAssistant = sourceviewer.getContentAssistant();
final CompletionProposalAdapter completionProposalAdapter = new CompletionProposalAdapter(styledText,
contentAssistant, KeyStroke.getInstance(SWT.CTRL, SWT.SPACE), null);
if ((styledText.getStyle() & SWT.SINGLE) != 0) {
// The regular key down event is too late (after popup is closed).
// when using the StyledText.VerifyKey event (3005), we get the
// event early enough!
styledText.addListener(3005, new Listener() {
public void handleEvent(Event event) {
if (event.character == SWT.CR && !completionProposalAdapter.isProposalPopupOpen()) {
Event selectionEvent = new Event();
selectionEvent.type = SWT.DefaultSelection;
selectionEvent.widget = event.widget;
for (Listener l : event.widget.getListeners(SWT.DefaultSelection)) {
l.handleEvent(selectionEvent);
}
}
}
});
}
// Register focus tracker for evaluating the active focus control in
// core expression
IFocusService service = (IFocusService) PlatformUI.getWorkbench().getService(IFocusService.class);
service.addFocusTracker(styledText, StyledText.class.getCanonicalName());
// add JDT Style code completion hint decoration
createContentAssistDecoration(styledText);
initSelectionProvider();
}
protected void initSelectionProvider() {
// Overrides the editors selection provider to provide the text
// selection if opened within an editor context
try {
IWorkbenchPartSite site = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage()
.getActiveEditor().getSite();
XtextStyledTextSelectionProvider xtextStyledTextSelectionProvider = new XtextStyledTextSelectionProvider();
ChangeSelectionProviderOnFocusGain listener = new ChangeSelectionProviderOnFocusGain(site,
xtextStyledTextSelectionProvider);
styledText.addFocusListener(listener);
styledText.addDisposeListener(listener);
} catch (NullPointerException ex) {
//Do nothing, not opened within editor context
}
}
private void createContentAssistDecoration(StyledText styledText) {
decoration = new ControlDecoration(styledText, SWT.TOP | SWT.LEFT);
decoration.setShowHover(true);
decoration.setShowOnlyOnFocus(true);
final Image image = ImageDescriptor.createFromFile(XtextStyledTextCellEditor.class,
"images/content_assist_cue.gif").createImage();
decoration.setImage(image);
decoration.setDescriptionText("Content Assist Available (CTRL + Space)");
decoration.setMarginWidth(2);
styledText.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
if (decoration != null) {
decoration.dispose();
}
if (image != null) {
image.dispose();
}
}
});
}
protected ValidationJob createValidationJob() {
return new ValidationJob(validator, document, new AnnotationIssueProcessor(document,
sourceviewer.getAnnotationModel(), resolutionProvider), CheckMode.ALL);
}
protected void createFakeResourceContext(Injector injector) {
this.fakeResourceContext = new XtextFakeResourceContext(injector);
}
protected void createXtextSourceViewer() {
sourceviewer = new XtextSourceViewerEx(styledText, preferenceStoreAccess.getPreferenceStore());
sourceviewer.configure(configuration);
sourceviewer.setDocument(document, new AnnotationModel());
SourceViewerDecorationSupport support = new SourceViewerDecorationSupport(sourceviewer, null,
new DefaultMarkerAnnotationAccess(), getSharedColors());
configureSourceViewerDecorationSupport(support);
}
protected ISharedTextColors getSharedColors() {
return EditorsPlugin.getDefault().getSharedTextColors();
}
/**
* Creates decoration support for the sourceViewer. code is entirely copied
* from {@link XtextEditor} and its super class
* {@link AbstractDecoratedTextEditor}.
*
*/
protected void configureSourceViewerDecorationSupport(SourceViewerDecorationSupport support) {
MarkerAnnotationPreferences annotationPreferences = new MarkerAnnotationPreferences();
List<AnnotationPreference> prefs = annotationPreferences.getAnnotationPreferences();
for (AnnotationPreference annotationPreference : prefs) {
support.setAnnotationPreference(annotationPreference);
}
support.setCharacterPairMatcher(characterPairMatcher);
support.setMatchingCharacterPainterPreferenceKeys(BracketMatchingPreferencesInitializer.IS_ACTIVE_KEY,
BracketMatchingPreferencesInitializer.COLOR_KEY);
support.install(preferenceStoreAccess.getPreferenceStore());
}
protected void initXtextDocument(XtextFakeResourceContext context) {
document.setInput(context.getFakeResource());
IDocumentPartitioner partitioner = documentPartitioner.get();
partitioner.connect(document);
document.setDocumentPartitioner(partitioner);
}
public void setVisibleRegion(int start, int length) {
sourceviewer.setVisibleRegion(start, length);
}
public void resetVisibleRegion() {
sourceviewer.resetVisibleRegion();
}
private void installHighlightingHelper() {
if (xtextStyledTextHighlightingHelper != null) {
xtextStyledTextHighlightingHelper.install(this, sourceviewer);
}
}
private void uninstallHighlightingHelper() {
if (xtextStyledTextHighlightingHelper != null) {
xtextStyledTextHighlightingHelper.uninstall();
}
}
public void dispose() {
uninstallHighlightingHelper();
document.disposeInput();
}
protected XtextSourceViewerConfiguration getXtextSourceViewerConfiguration() {
return configuration;
}
protected XtextDocument getXtextDocument() {
return document;
}
protected XtextSourceViewer getXtextSourceviewer() {
return sourceviewer;
}
public IParseResult getXtextParseResult() {
return document.readOnly(new IUnitOfWork<IParseResult, XtextResource>() {
public IParseResult exec(XtextResource state) throws Exception {
return state.getParseResult();
}
});
}
public IContentAssistant getContentAssistant() {
return getXtextSourceviewer().getContentAssistant();
}
public List<Issue> getXtextValidationIssues() {
return validationJob.createIssues(new NullProgressMonitor());
}
public void updateFakeResourceContext() {
fakeResourceContext.updateFakeResourceContext(contextFakeResourceProvider);
}
protected IXtextFakeContextResourcesProvider getFakeResourceContextProvider() {
return contextFakeResourceProvider;
}
public XtextFakeResourceContext getFakeResourceContext() {
return fakeResourceContext;
}
private class XtextStyledTextSelectionProvider implements ISelectionProvider {
public void setSelection(ISelection selection) {
}
public void removeSelectionChangedListener(ISelectionChangedListener listener) {
}
public void addSelectionChangedListener(ISelectionChangedListener listener) {
}
public ISelection getSelection() {
if (styledText.isDisposed())
return StructuredSelection.EMPTY;
int offset = styledText.getCaretOffset() - 1;
XtextResource fakeResource = StyledTextXtextAdapter.this.getFakeResourceContext().getFakeResource();
IParseResult parseResult = fakeResource.getParseResult();
if (parseResult == null)
return StructuredSelection.EMPTY;
ICompositeNode rootNode = parseResult.getRootNode();
ILeafNode selectedNode = NodeModelUtils.findLeafNodeAtOffset(rootNode, offset);
final EObject selectedObject = NodeModelUtils.findActualSemanticObjectFor(selectedNode);
if (selectedObject == null) {
return StructuredSelection.EMPTY;
}
return new StructuredSelection(selectedObject);
}
}
private class ChangeSelectionProviderOnFocusGain implements FocusListener, DisposeListener {
private ISelectionProvider selectionProviderOnFocusGain;
private ISelectionProvider selectionProviderOnFocusLost;
private IWorkbenchPartSite site;
public ChangeSelectionProviderOnFocusGain(IWorkbenchPartSite site,
ISelectionProvider selectionProviderOnFocusGain) {
this.selectionProviderOnFocusGain = selectionProviderOnFocusGain;
this.site = site;
}
public void focusLost(FocusEvent e) {
if (selectionProviderOnFocusLost != null) {
site.setSelectionProvider(selectionProviderOnFocusLost);
}
}
public void focusGained(FocusEvent e) {
selectionProviderOnFocusLost = site.getSelectionProvider();
site.setSelectionProvider(selectionProviderOnFocusGain);
}
public void widgetDisposed(DisposeEvent e) {
if (selectionProviderOnFocusLost != null) {
site.setSelectionProvider(selectionProviderOnFocusLost);
}
((StyledText) e.getSource()).removeFocusListener(this);
((StyledText) e.getSource()).removeDisposeListener(this);
}
}
}