package org.xrepl.ui.embedded; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.core.expressions.EvaluationResult; import org.eclipse.core.expressions.Expression; import org.eclipse.core.expressions.IEvaluationContext; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IMenuListener; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.commands.ActionHandler; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentPartitioner; import org.eclipse.jface.text.ISynchronizable; import org.eclipse.jface.text.ITextOperationTarget; 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.AnnotationRulerColumn; import org.eclipse.jface.text.source.CompositeRuler; import org.eclipse.jface.text.source.IAnnotationAccess; import org.eclipse.jface.text.source.IAnnotationAccessExtension; import org.eclipse.jface.text.source.ICharacterPairMatcher; import org.eclipse.jface.text.source.IOverviewRuler; import org.eclipse.jface.text.source.ISharedTextColors; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.text.source.IVerticalRuler; import org.eclipse.jface.text.source.IVerticalRulerColumn; import org.eclipse.jface.text.source.OverviewRuler; import org.eclipse.jface.text.source.SourceViewer; import org.eclipse.jface.text.source.projection.ProjectionSupport; import org.eclipse.jface.text.source.projection.ProjectionViewer; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.SelectionChangedEvent; 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.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Menu; import org.eclipse.ui.ISources; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.contexts.IContextActivation; import org.eclipse.ui.contexts.IContextService; import org.eclipse.ui.editors.text.EditorsUI; import org.eclipse.ui.handlers.IHandlerActivation; import org.eclipse.ui.handlers.IHandlerService; import org.eclipse.ui.internal.editors.text.EditorsPlugin; import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants; import org.eclipse.ui.texteditor.AnnotationPreference; import org.eclipse.ui.texteditor.DefaultMarkerAnnotationAccess; import org.eclipse.ui.texteditor.ITextEditorActionConstants; import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; import org.eclipse.ui.texteditor.IUpdate; import org.eclipse.ui.texteditor.MarkerAnnotationPreferences; import org.eclipse.ui.texteditor.SourceViewerDecorationSupport; import org.eclipse.xtext.Constants; import org.eclipse.xtext.IGrammarAccess; import org.eclipse.xtext.resource.XtextResource; 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.IXtextDocument; 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.IValidationIssueProcessor; import org.eclipse.xtext.ui.editor.validation.ValidationJob; import org.eclipse.xtext.util.StringInputStream; import org.eclipse.xtext.validation.CheckMode; import org.eclipse.xtext.validation.IResourceValidator; import org.eclipse.xtext.validation.Issue; import org.xrepl.XreplResourceSetProvider; import com.google.common.collect.Iterators; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Provider; import com.google.inject.name.Named; public class EmbeddedXtextEditor { private static final String XTEXT_UI_FORMAT_ACTION = "org.eclipse.xtext.ui.FormatAction"; private static final String XTEXT_UI_TOGGLE_SL_COMMENT_ACTION = "org.eclipse.xtext.ui.ToggleCommentAction"; private Composite fControl; private int fStyle; private XtextSourceViewer fSourceViewer; private XtextResource fResource; private XtextDocument fDocument; @Inject @Named(Constants.FILE_EXTENSIONS) private String fFileExtension; private XtextSourceViewerConfiguration fViewerConfiguration; @Inject private HighlightingHelper fHighlightingHelper; @Inject private Provider<XtextResource> xtextResourceProvider; @Inject private IGrammarAccess fGrammarAccess; @Inject private XtextSourceViewer.Factory fSourceViewerFactory; @Inject private Provider<XtextSourceViewerConfiguration> fSourceViewerConfigurationProvider; @Inject private Provider<XtextDocument> fDocumentProvider; @Inject private Provider<IDocumentPartitioner> documentPartitioner; @Inject private IResourceValidator fResourceValidator; @Inject private IPreferenceStoreAccess fPreferenceStoreAccess; @Inject private ICharacterPairMatcher characterPairMatcher; @Inject(optional = true) private AnnotationPainter.IDrawingStrategy projectionAnnotationDrawingStrategy; private EmbeddedFoldingStructureProvider fFoldingStructureProvider; private IOverviewRuler fOverviewRuler; private IAnnotationAccess fAnnotationAccess; /** * Creates a new EmbeddedXtextEditor. It must have the SWT.V_SCROLL style at * least not to throw NPE when computing overview ruler. * * @param control * the parent composite that will contain the editor * @param injector * the Guice injector to get Xtext configuration elements * @param job * the synchronization job that will be scheduled/rescheduled at * each modification of the editor text. It may be use to * reconcile the content of the editor with something else. * @param style * the SWT style of the {@link SourceViewer} of this editor. * @param fileExtension * the file extension (without the DOT) of the textual DSL to * edit */ @SuppressWarnings("restriction") public EmbeddedXtextEditor(Composite control, Injector injector, int style) { fControl = control; fStyle = style; fAnnotationPreferences = EditorsPlugin.getDefault() .getMarkerAnnotationPreferences(); fFoldingStructureProvider = new EmbeddedFoldingStructureProvider(); injector.injectMembers(this); createEditor(fControl); } /** * Creates a new EmbeddedXtextEditor. * * Equivalent to EmbeddedXtextEditor(control, injector, job, fileExtension, * SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); * * @param control * the parent composite that will contain the editor * @param injector * the Guice injector to get Xtext configuration elements * @param job * the synchronization job that will be scheduled/rescheduled at * each modification of the editor text. It may be use to * reconcile the content of the editor with something else. * @param fileExtension * the file extension (without the DOT) of the textual DSL to * edit * @param fileExtension */ public EmbeddedXtextEditor(Composite control, Injector injector) { this(control, injector, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); } public Composite getControl() { return fControl; } public XtextSourceViewer getViewer() { return fSourceViewer; } public XtextResource getResource() { return fResource; } public IXtextDocument getDocument() { return fDocument; } /** * Should be called only once, during initialization. * * Then, you should call {@link #updateText(String, String, String)}; * * @param document * @param prefix * @param text * @param suffix */ protected void setText(XtextDocument document, String text) { document.set(text); fResource = createResource(text); document.setInput(fResource); AnnotationModel annotationModel = new AnnotationModel(); if (document instanceof ISynchronizable) { Object lock = ((ISynchronizable) document).getLockObject(); if (lock == null) { lock = new Object(); ((ISynchronizable) document).setLockObject(lock); } ((ISynchronizable) annotationModel).setLockObject(lock); } fSourceViewer.setDocument(document, annotationModel); } private XtextResource createResource(String content) { XtextResource result = createResource(); try { result.load(new StringInputStream(content, result.getEncoding()), Collections.emptyMap()); } catch (Exception e) { throw new RuntimeException(e); } return result; } private void createEditor(Composite parent) { createViewer(parent); Control control = fSourceViewer.getControl(); GridData data = new GridData(SWT.FILL, SWT.FILL, true, true); control.setLayoutData(data); createActions(); MenuManager manager = new MenuManager(null, null); manager.setRemoveAllWhenShown(true); manager.addMenuListener(new IMenuListener() { public void menuAboutToShow(IMenuManager mgr) { EmbeddedXtextEditor.this.menuAboutToShow(mgr); } }); StyledText text = fSourceViewer.getTextWidget(); Menu menu = manager.createContextMenu(text); text.setMenu(menu); } private void menuAboutToShow(IMenuManager menu) { menu.add(new Separator(ITextEditorActionConstants.GROUP_EDIT)); menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, fActions.get(ITextEditorActionConstants.CUT)); menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, fActions.get(ITextEditorActionConstants.COPY)); menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, fActions.get(ITextEditorActionConstants.PASTE)); } private void createViewer(Composite parent) { createSourceViewer(parent); installFoldingSupport(fSourceViewer); setText(fDocument, ""); fHighlightingHelper.install(fViewerConfiguration, fSourceViewer); } /** * Creates the vertical ruler to be used by this editor. Subclasses may * re-implement this method. * * @return the vertical ruler */ private IVerticalRuler createVerticalRuler() { return new CompositeRuler(); } /** The editor's vertical ruler. */ private IVerticalRuler fVerticalRuler; /** * Creates the annotation ruler column. Subclasses may re-implement or * extend. * * @param ruler * the composite ruler that the column will be added * @return an annotation ruler column * @since 3.2 */ protected IVerticalRulerColumn createAnnotationRulerColumn( CompositeRuler ruler) { return new AnnotationRulerColumn(VERTICAL_RULER_WIDTH, getAnnotationAccess()); } private void createSourceViewer(Composite parent) { fVerticalRuler = createVerticalRuler(); fSourceViewer = fSourceViewerFactory.createSourceViewer(parent, fVerticalRuler, getOverviewRuler(), true, fStyle); fViewerConfiguration = fSourceViewerConfigurationProvider.get(); fSourceViewer.configure(fViewerConfiguration); installProjectionSupport(fSourceViewer); // make sure the source viewer decoration support is initialized getSourceViewerDecorationSupport(fSourceViewer); fSourceViewer.getTextWidget().addFocusListener( new SourceViewerFocusListener()); fSourceViewerDecorationSupport.install(fPreferenceStoreAccess .getPreferenceStore()); parent.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { fSourceViewerDecorationSupport.dispose(); } }); fDocument = fDocumentProvider.get(); if (fDocument != null) { IDocumentPartitioner partitioner = documentPartitioner.get(); partitioner.connect(fDocument); fDocument.setDocumentPartitioner(partitioner); } ValidationJob job = new ValidationJob(fResourceValidator, fDocument, new IValidationIssueProcessor() { private AnnotationIssueProcessor annotationIssueProcessor; public void processIssues(List<Issue> issues, IProgressMonitor monitor) { if (annotationIssueProcessor == null) { annotationIssueProcessor = new AnnotationIssueProcessor( fDocument, fSourceViewer .getAnnotationModel(), new IssueResolutionProvider.NullImpl()); } if (annotationIssueProcessor != null) annotationIssueProcessor.processIssues(issues, monitor); } }, CheckMode.FAST_ONLY); fDocument.setValidationJob(job); fSourceViewer .addSelectionChangedListener(new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent event) { updateSelectionDependentActions(); } }); } private static final String ERROR_ANNOTATION_TYPE = "org.eclipse.xtext.ui.editor.error"; private static final String WARNING_ANNOTATION_TYPE = "org.eclipse.xtext.ui.editor.warning"; private ProjectionSupport installProjectionSupport( ProjectionViewer projectionViewer) { ProjectionSupport projectionSupport = new ProjectionSupport( projectionViewer, getAnnotationAccess(), getSharedColors()); projectionSupport .addSummarizableAnnotationType(WARNING_ANNOTATION_TYPE); //$NON-NLS-1$ projectionSupport.addSummarizableAnnotationType(ERROR_ANNOTATION_TYPE); //$NON-NLS-1$ projectionSupport .setAnnotationPainterDrawingStrategy(projectionAnnotationDrawingStrategy); projectionSupport.install(); return projectionSupport; } /** * Helper for managing the decoration support of this editor's viewer. * * <p> * This field should not be referenced by subclasses. It is * <code>protected</code> for API compatibility reasons and will be made * <code>private</code> soon. Use * {@link #getSourceViewerDecorationSupport(ISourceViewer)} instead. * </p> */ private SourceViewerDecorationSupport fSourceViewerDecorationSupport; private void installFoldingSupport(ProjectionViewer projectionViewer) { fFoldingStructureProvider.install(this, projectionViewer); projectionViewer.doOperation(ProjectionViewer.TOGGLE); fFoldingStructureProvider.initialize(); } /** * Returns the source viewer decoration support. * * @param viewer * the viewer for which to return a decoration support * @return the source viewer decoration support */ private SourceViewerDecorationSupport getSourceViewerDecorationSupport( ISourceViewer viewer) { if (fSourceViewerDecorationSupport == null) { fSourceViewerDecorationSupport = new SourceViewerDecorationSupport( viewer, getOverviewRuler(), getAnnotationAccess(), getSharedColors()); configureSourceViewerDecorationSupport(fSourceViewerDecorationSupport); } return fSourceViewerDecorationSupport; } /** * Configures the decoration support for this editor's source viewer. * Subclasses may override this method, but should call their superclass' * implementation at some point. * * @param support * the decoration support to configure */ private void configureSourceViewerDecorationSupport( SourceViewerDecorationSupport support) { Iterator<AnnotationPreference> e = Iterators.filter( fAnnotationPreferences.getAnnotationPreferences().iterator(), AnnotationPreference.class); while (e.hasNext()) support.setAnnotationPreference((AnnotationPreference) e.next()); support.setCursorLinePainterPreferenceKeys( AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE_COLOR); support.setMarginPainterPreferenceKeys( AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLOR, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLUMN); // support.setSymbolicFontName(getFontPropertyPreferenceKey()); if (characterPairMatcher != null) { support.setCharacterPairMatcher(characterPairMatcher); support.setMatchingCharacterPainterPreferenceKeys( BracketMatchingPreferencesInitializer.IS_ACTIVE_KEY, BracketMatchingPreferencesInitializer.COLOR_KEY); } } /** * Returns the overview ruler. * * @return the overview ruler */ private IOverviewRuler getOverviewRuler() { if (fOverviewRuler == null && (fStyle & SWT.V_SCROLL) != 0) fOverviewRuler = createOverviewRuler(getSharedColors()); return fOverviewRuler; } /** The width of the vertical ruler. */ private static final int VERTICAL_RULER_WIDTH = 12; /** * Returns the annotation access. * * @return the annotation access */ private IAnnotationAccess getAnnotationAccess() { if (fAnnotationAccess == null) fAnnotationAccess = createAnnotationAccess(); return fAnnotationAccess; } /** * Creates the annotation access for this editor. * * @return the created annotation access */ private IAnnotationAccess createAnnotationAccess() { return new DefaultMarkerAnnotationAccess() { @Override public int getLayer(Annotation annotation) { if (annotation.isMarkedDeleted()) { return IAnnotationAccessExtension.DEFAULT_LAYER; } return super.getLayer(annotation); } }; } /** * The annotation preferences. */ private MarkerAnnotationPreferences fAnnotationPreferences; private IOverviewRuler createOverviewRuler(ISharedTextColors sharedColors) { IOverviewRuler ruler = new OverviewRuler(getAnnotationAccess(), VERTICAL_RULER_WIDTH, sharedColors); Iterator<?> e = fAnnotationPreferences.getAnnotationPreferences() .iterator(); while (e.hasNext()) { AnnotationPreference preference = (AnnotationPreference) e.next(); if (preference.contributesToHeader()) ruler.addHeaderAnnotationType(preference.getAnnotationType()); } return ruler; } private ISharedTextColors getSharedColors() { return EditorsUI.getSharedTextColors(); } /** * Updates the text of this editor with the given String * * @param text */ public void update(String text) { IDocument document = fSourceViewer.getDocument(); fSourceViewer.setRedraw(false); document.set(text); fSourceViewer.setVisibleRegion(0, text.length()); fSourceViewer.setRedraw(true); } private void createActions() { { TextViewerAction action = new TextViewerAction(fSourceViewer, ITextOperationTarget.CUT); action.setText("Cut"); setAction(ITextEditorActionConstants.CUT, action); setAsSelectionDependantAction(action); } { TextViewerAction action = new TextViewerAction(fSourceViewer, ITextOperationTarget.COPY); action.setText("Copy"); setAction(ITextEditorActionConstants.COPY, action); setAsSelectionDependantAction(action); } { TextViewerAction action = new TextViewerAction(fSourceViewer, ITextOperationTarget.PASTE); action.setText("Paste"); setAction(ITextEditorActionConstants.PASTE, action); setAsSelectionDependantAction(action); } { TextViewerAction action = new TextViewerAction(fSourceViewer, ISourceViewer.CONTENTASSIST_PROPOSALS){ public void run() { super.run(); }; @Override public void runWithEvent(Event event) { super.runWithEvent(event); } }; action.setText("Content Assist"); setAction(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS, action); setAsContextDependantAction(action); } if (fViewerConfiguration.getContentFormatter(fSourceViewer) != null) { TextViewerAction action = new TextViewerAction(fSourceViewer, ISourceViewer.FORMAT); action.setText("Format"); setAction(XTEXT_UI_FORMAT_ACTION, action); setAsContextDependantAction(action); } { ToggleSLCommentAction action = new ToggleSLCommentAction( fSourceViewer); //$NON-NLS-1$ setAction(XTEXT_UI_TOGGLE_SL_COMMENT_ACTION, action); setAsContextDependantAction(action); action.configure(fSourceViewer, fViewerConfiguration); } } private void setAction(String actionID, IAction action) { if (action.getId() == null) action.setId(actionID); // make sure the action ID has been set fActions.put(actionID, action); } private void setAsContextDependantAction(IAction action) { fActionHandlers.add(new ActionHandler(action)); } private void setAsSelectionDependantAction(IAction action) { fSelectionDependentActions.add(action); } private void updateSelectionDependentActions() { for (IAction action : fSelectionDependentActions) { if (action instanceof IUpdate) { ((IUpdate) action).update(); } } } protected void updateAction(IAction action) { } private Map<String, IAction> fActions = Maps.newHashMap(); private List<IAction> fSelectionDependentActions = Lists.newArrayList(); private List<ActionHandler> fActionHandlers = Lists.newArrayList(); private ResourceSet resourceSet; /** * Source viewer focus listener that activates/deactivates action handlers * on focus state change. * * @author Mika�l Barbero * */ private final class SourceViewerFocusListener implements FocusListener { private static final String EMBEDEDXTEXT_EDITOR_CONTEXT = "org.xrepl.console.context"; private final Expression fExpression; private final List<IHandlerActivation> fHandlerActivations; private IContextActivation fContextActivation; private IContextService contextService; public SourceViewerFocusListener() { fExpression = new Expression() { @Override public EvaluationResult evaluate(IEvaluationContext context) throws CoreException { Object values = context.getVariable(ISources.ACTIVE_PART_ID_NAME); if (values.equals("org.eclipse.ui.console.ConsoleView")) { return EvaluationResult.TRUE; }else{ return EvaluationResult.FALSE; } } }; fHandlerActivations = Lists.newArrayList(); fSourceViewer.getControl().addDisposeListener( new DisposeListener() { public void widgetDisposed(DisposeEvent e) { IHandlerService handlerService = handlerService(); handlerService .deactivateHandlers(fHandlerActivations); fHandlerActivations.clear(); } }); } public void focusLost(FocusEvent e) { if (fContextActivation != null) { contextService().deactivateContext(fContextActivation); } handlerService().deactivateHandlers(fHandlerActivations); } protected IHandlerService handlerService() { IHandlerService handlerService = (IHandlerService) PlatformUI .getWorkbench().getAdapter(IHandlerService.class); return handlerService; } public void focusGained(FocusEvent e) { fContextActivation = contextService().activateContext( EMBEDEDXTEXT_EDITOR_CONTEXT); IHandlerService handlerService = handlerService(); for (ActionHandler actionHandler : fActionHandlers) { System.out.println(actionHandler.getAction().getId()); fHandlerActivations.add(handlerService.activateHandler( actionHandler.getAction().getId(), actionHandler, fExpression)); } } protected IContextService contextService() { if (contextService == null) { contextService = (IContextService) PlatformUI.getWorkbench() .getActiveWorkbenchWindow().getActivePage() .getActivePart().getSite() .getService(IContextService.class); } return contextService; } } @Inject public void setResourceSetProvider(XreplResourceSetProvider resourceSetProvider){ this.resourceSet = resourceSetProvider.get(); } protected XtextResource createResource() { return (XtextResource) resourceSet.createResource(URI.createURI(fGrammarAccess.getGrammar().getName() + "." + fFileExtension)); } public void setFocus() { getViewer().getTextWidget().setFocus(); } }