/* * Copyright 2015 Nokia Solutions and Networks * Licensed under the Apache License, Version 2.0, * see license.txt file for details. */ package org.robotframework.ide.eclipse.main.plugin.tableeditor.source; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IStorage; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.e4.core.services.events.IEventBroker; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextViewerExtension; import org.eclipse.jface.text.reconciler.IReconciler; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.text.source.IVerticalRuler; import org.eclipse.jface.text.source.IVerticalRulerExtension; import org.eclipse.jface.text.source.SourceViewer; 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.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.swt.custom.CaretEvent; import org.eclipse.swt.custom.CaretListener; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.contexts.IContextService; import org.eclipse.ui.editors.text.TextEditor; import org.eclipse.ui.texteditor.GotoLineAction; import org.eclipse.ui.texteditor.ITextEditorActionConstants; import org.eclipse.ui.texteditor.StatusLineContributionItem; import org.robotframework.ide.eclipse.main.plugin.RedPlugin; import org.robotframework.ide.eclipse.main.plugin.RedTheme; import org.robotframework.ide.eclipse.main.plugin.model.RobotModelEvents; import org.robotframework.ide.eclipse.main.plugin.model.RobotSuiteFile; import org.robotframework.ide.eclipse.main.plugin.tableeditor.RobotEditorSources; import org.robotframework.ide.eclipse.main.plugin.tableeditor.RobotFormEditorActionBarContributor; import org.robotframework.ide.eclipse.main.plugin.tableeditor.source.handler.ToggleBreakpointHandler; import org.robotframework.ide.eclipse.main.plugin.views.documentation.SourceDocumentationSelectionChangedListener; import org.robotframework.red.swt.StyledTextWrapper; import com.google.common.base.Supplier; public class SuiteSourceEditor extends TextEditor { private static final String SOURCE_PART_CONTEXT_ID = "org.robotframework.ide.eclipse.tableeditor.sources.context"; private SourceDocumentationSelectionChangedListener sourceDocSelectionChangedListener = null; @Inject @Named(RobotEditorSources.SUITE_FILE_MODEL) protected RobotSuiteFile fileModel; private SuiteSourceEditorFoldingSupport foldingSupport; public SourceViewer getViewer() { return (SourceViewer) getSourceViewer(); } @Override protected void initializeEditor() { super.initializeEditor(); setSourceViewerConfiguration(new SuiteSourceEditorConfiguration(this)); setDocumentProvider(new SuiteSourceDocumentProvider(new Supplier<RobotSuiteFile>() { @Override public RobotSuiteFile get() { return fileModel; } })); } @Override protected void createActions() { super.createActions(); final GotoLineAction gotoAction = new GotoLineAction(this); gotoAction.setActionDefinitionId(ITextEditorActionConstants.GOTO_LINE); setAction(ITextEditorActionConstants.GOTO_LINE, gotoAction); } @Override protected void doSetInput(final IEditorInput input) throws CoreException { super.doSetInput(input); if (input.getAdapter(IStorage.class) != null) { fileModel.reparseEverything(getDocument().get()); final IEventBroker eventBroker = PlatformUI.getWorkbench().getService(IEventBroker.class); eventBroker.post(RobotModelEvents.REPARSING_DONE, fileModel); } } @Override public void createPartControl(final Composite parent) { super.createPartControl(parent); final ProjectionViewer viewer = (ProjectionViewer) getSourceViewer(); installProjectionAndFolding(viewer); if (fileModel.getFile() != null) { new SuiteSourceCurrentCellHighlighter(this, fileModel, viewer.getDocument()).install(viewer); new SuiteSourceOccurrenceMarksHighlighter(fileModel, viewer.getDocument()).install(viewer); } installBreakpointTogglingOnDoubleClick(); installStatusBarUpdater(viewer); setFontFromPreference(viewer); addFontChangeListener(viewer); activateContext(); } @Override protected ISourceViewer createSourceViewer(final Composite parent, final IVerticalRuler ruler, final int styles) { final ISourceViewer viewer = new ProjectionViewer(parent, ruler, getOverviewRuler(), isOverviewRulerVisible(), styles); // ensure decoration support has been created and configured. getSourceViewerDecorationSupport(viewer); return viewer; } @Override protected boolean isEditorInputIncludedInContextMenu() { return false; } @Override protected void installTabsToSpacesConverter() { // we will install our own edit strategy for tabs converting } private void installProjectionAndFolding(final ProjectionViewer viewer) { // turn projection mode on new ProjectionSupport(viewer, getAnnotationAccess(), getSharedColors()).install(); viewer.doOperation(ProjectionViewer.TOGGLE); foldingSupport = new SuiteSourceEditorFoldingSupport(new StyledTextWrapper(viewer.getTextWidget()), viewer.getProjectionAnnotationModel()); } private void installStatusBarUpdater(final ProjectionViewer viewer) { viewer.getTextWidget().addCaretListener(new CaretListener() { @Override public void caretMoved(final CaretEvent event) { updateLineLocationStatusBar(event.caretOffset); updateLineDelimitersStatus(); } }); } private void updateLineLocationStatusBar(final int caretPostion) { try { final IDocument document = getDocument(); int lineNumber = document.getLineOfOffset(caretPostion); final int columnNumber = caretPostion - document.getLineOffset(lineNumber) + 1; lineNumber++; final StatusLineContributionItem find = (StatusLineContributionItem) getEditorSite().getActionBars() .getStatusLineManager() .find(ITextEditorActionConstants.STATUS_CATEGORY_INPUT_POSITION); find.setText(lineNumber + ":" + columnNumber); } catch (final BadLocationException e) { RedPlugin.logError("Unable to get position in source editor in order to update status bar", e); } } private void updateLineDelimitersStatus() { final IDocument document = getDocument(); final StatusLineContributionItem find = (StatusLineContributionItem) getEditorSite().getActionBars() .getStatusLineManager() .find(RobotFormEditorActionBarContributor.DELIMITERS_INFO_ID); final String delimiter = DocumentUtilities.getDelimiter(document); find.setText("\r\n".equals(delimiter) ? "CR+LF" : "LF"); } private void installBreakpointTogglingOnDoubleClick() { getVerticalRuler().getControl().addMouseListener(new MouseAdapter() { @Override public void mouseDoubleClick(final MouseEvent event) { try { final IFile file = (IFile) getEditorInput().getAdapter(IResource.class); final int line = computeBreakpointLineNumber(event.y); ToggleBreakpointHandler.E4ToggleBreakpointHandler.toggle(file, line); } catch (final CoreException e) { RedPlugin.logError("Unable to toggle breakpoint", e); } } }); } private int computeBreakpointLineNumber(final int eventY) { final StyledText text = getSourceViewer().getTextWidget(); final int lineHeight = text.getLineHeight(); final int line = (int) Math.round((eventY / (double) lineHeight)) + getSourceViewer().getTopIndex(); final int lineBottomPixel = text.getLinePixel(line) - lineHeight; if (eventY < lineBottomPixel) { return line - 1; } else if ((eventY - lineBottomPixel) > lineHeight) { return line + 1; } else { return line; } } private void activateContext() { final IContextService service = getSite().getService(IContextService.class); service.activateContext(SOURCE_PART_CONTEXT_ID); } @Override public void doSave(final IProgressMonitor progressMonitor) { super.doSave(progressMonitor); } @Override public void dispose() { super.dispose(); } public SourceViewerConfiguration getViewerConfiguration() { return super.getSourceViewerConfiguration(); } public IDocument getDocument() { return getDocumentProvider().getDocument(getEditorInput()); } RobotSuiteFile getFileModel() { return fileModel; } SuiteSourceEditorFoldingSupport getFoldingSupport() { return foldingSupport; } /** * Returns line number of cursor position. * * @return Line number indexed from 1 */ public int getCurrentLine() { final StyledText text = getSourceViewer().getTextWidget(); return text.getLineAtOffset(text.getSelection().x) + 1; } /** * Returns line number from ruler activity. * * @return Line number indexed from 1 */ public int getLineFromRulerActivity() { return getVerticalRuler().getLineOfLastMouseButtonActivity() + 1; } private void addFontChangeListener(final ProjectionViewer viewer) { PlatformUI.getWorkbench() .getThemeManager() .getCurrentTheme() .addPropertyChangeListener(new IPropertyChangeListener() { @Override public void propertyChange(final PropertyChangeEvent event) { if (RedTheme.RED_SOURCE_EDITOR_FONT.equals(event.getProperty())) { setFont(viewer, RedTheme.getRedSourceEditorFont()); } } }); } private void setFontFromPreference(final ProjectionViewer viewer) { final Font redSourceEditorFont = RedTheme.getRedSourceEditorFont(); final Font defaultTextEditorFont = RedTheme.getTextEditorFont(); if (!redSourceEditorFont.isDisposed() && !defaultTextEditorFont.isDisposed() && !redSourceEditorFont.getFontData()[0].equals(defaultTextEditorFont.getFontData()[0])) { setFont(viewer, RedTheme.getRedSourceEditorFont()); } } private void setFont(final ISourceViewer sourceViewer, final Font font) { if (sourceViewer.getDocument() != null) { final ISelectionProvider provider = sourceViewer.getSelectionProvider(); final ISelection selection = provider.getSelection(); final int topIndex = sourceViewer.getTopIndex(); final StyledText styledText = sourceViewer.getTextWidget(); Control parent = styledText; if (sourceViewer instanceof ITextViewerExtension) { final ITextViewerExtension extension = (ITextViewerExtension) sourceViewer; parent = extension.getControl(); } parent.setRedraw(false); styledText.setFont(font); if (getVerticalRuler() instanceof IVerticalRulerExtension) { final IVerticalRulerExtension e = (IVerticalRulerExtension) getVerticalRuler(); e.setFont(font); } provider.setSelection(selection); sourceViewer.setTopIndex(topIndex); if (parent instanceof Composite) { final Composite composite = (Composite) parent; composite.layout(true); } parent.setRedraw(true); } else { final StyledText styledText = sourceViewer.getTextWidget(); styledText.setFont(font); if (getVerticalRuler() instanceof IVerticalRulerExtension) { final IVerticalRulerExtension e = (IVerticalRulerExtension) getVerticalRuler(); e.setFont(font); } } } public void enableReconcilation() { final IReconciler reconciler = getSourceViewerConfiguration().getReconciler(getSourceViewer()); reconciler.install(getSourceViewer()); } public void disableReconcilation() { final IReconciler reconciler = getSourceViewerConfiguration().getReconciler(getSourceViewer()); reconciler.uninstall(); } public void notifyDocSelectionChangedListener(final IRegion region, final boolean isEditing) { if (sourceDocSelectionChangedListener != null) { sourceDocSelectionChangedListener.positionChanged(getDocument(), getFileModel(), region, isEditing); } } public void setSourceDocSelectionChangedListener(final SourceDocumentationSelectionChangedListener listener) { this.sourceDocSelectionChangedListener = listener; } public void removeSourceDocSelectionChangedListener() { this.sourceDocSelectionChangedListener = null; } }