/*******************************************************************************
 * Copyright (c) 2000, 2019 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui.texteditor;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
import java.text.BreakIterator;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.util.Date;
import java.util.Iterator;

import org.eclipse.core.commands.operations.IOperationApprover;
import org.eclipse.core.commands.operations.IUndoContext;
import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.IFileBufferStatusCodes;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.GroupMarker;
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.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITextViewerExtension6;
import org.eclipse.jface.text.ITextViewerExtension8;
import org.eclipse.jface.text.JFaceTextUtil;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
import org.eclipse.jface.text.revisions.IRevisionRulerColumn;
import org.eclipse.jface.text.revisions.IRevisionRulerColumnExtension;
import org.eclipse.jface.text.revisions.IRevisionRulerColumnExtension.RenderingMode;
import org.eclipse.jface.text.revisions.RevisionInformation;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.AnnotationRulerColumn;
import org.eclipse.jface.text.source.ChangeRulerColumn;
import org.eclipse.jface.text.source.CompositeRuler;
import org.eclipse.jface.text.source.IAnnotationAccess;
import org.eclipse.jface.text.source.IAnnotationAccessExtension2;
import org.eclipse.jface.text.source.IAnnotationHover;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IChangeRulerColumn;
import org.eclipse.jface.text.source.IOverviewRuler;
import org.eclipse.jface.text.source.IOverviewRulerExtension;
import org.eclipse.jface.text.source.ISharedTextColors;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.ISourceViewerExtension;
import org.eclipse.jface.text.source.ISourceViewerExtension3;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.text.source.IVerticalRulerColumn;
import org.eclipse.jface.text.source.LineChangeHover;
import org.eclipse.jface.text.source.LineNumberChangeRulerColumn;
import org.eclipse.jface.text.source.LineNumberRulerColumn;
import org.eclipse.jface.text.source.OverviewRuler;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.custom.StyledTextPrintOptions;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorDescriptor;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IPageLayout;
import org.eclipse.ui.IURIEditorInput;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchCommandConstants;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ContributionItemFactory;
import org.eclipse.ui.actions.OpenWithMenu;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.ui.dialogs.PreferencesUtil;
import org.eclipse.ui.dialogs.SaveAsDialog;
import org.eclipse.ui.editors.text.DefaultEncodingSupport;
import org.eclipse.ui.editors.text.EditorsUI;
import org.eclipse.ui.editors.text.ForwardingDocumentProvider;
import org.eclipse.ui.editors.text.IEncodingSupport;
import org.eclipse.ui.editors.text.IStorageDocumentProvider;
import org.eclipse.ui.editors.text.ITextEditorHelpContextIds;
import org.eclipse.ui.ide.FileStoreEditorInput;
import org.eclipse.ui.ide.IDEActionFactory;
import org.eclipse.ui.ide.IGotoMarker;
import org.eclipse.ui.internal.editors.quickdiff.CompositeRevertAction;
import org.eclipse.ui.internal.editors.quickdiff.RestoreAction;
import org.eclipse.ui.internal.editors.quickdiff.RevertBlockAction;
import org.eclipse.ui.internal.editors.quickdiff.RevertLineAction;
import org.eclipse.ui.internal.editors.quickdiff.RevertSelectionAction;
import org.eclipse.ui.internal.editors.text.EditorsPlugin;
import org.eclipse.ui.internal.editors.text.NLSUtility;
import org.eclipse.ui.internal.editors.text.RefreshEditorAction;
import org.eclipse.ui.internal.texteditor.AnnotationColumn;
import org.eclipse.ui.internal.texteditor.BooleanPreferenceToggleAction;
import org.eclipse.ui.internal.texteditor.FocusedInformationPresenter;
import org.eclipse.ui.internal.texteditor.LineNumberColumn;
import org.eclipse.ui.internal.texteditor.TextChangeHover;
import org.eclipse.ui.keys.IBindingService;
import org.eclipse.ui.operations.NonLocalUndoUserApprover;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.part.IShowInSource;
import org.eclipse.ui.part.IShowInTargetList;
import org.eclipse.ui.part.ShowInContext;
import org.eclipse.ui.texteditor.rulers.IColumnSupport;
import org.eclipse.ui.texteditor.rulers.IContributedRulerColumn;
import org.eclipse.ui.texteditor.rulers.RulerColumnDescriptor;
import org.eclipse.ui.texteditor.rulers.RulerColumnPreferenceAdapter;
import org.eclipse.ui.texteditor.rulers.RulerColumnRegistry;
import org.eclipse.ui.views.markers.MarkerViewUtil;


/**
 * An intermediate editor comprising functionality not present in the leaner <code>AbstractTextEditor</code>,
 * but used in many heavy weight (and especially source editing) editors, such as line numbers,
 * change ruler, overview ruler, print margins, current line highlighting, etc.
 *
 * @since 3.0
 */
public abstract class AbstractDecoratedTextEditor extends StatusTextEditor {

	/**
	 * Preference key for showing the line number ruler.
	 */
	private final static String LINE_NUMBER_RULER= AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER;
	/**
	 * Preference key for showing the overview ruler.
	 */
	private final static String OVERVIEW_RULER= AbstractDecoratedTextEditorPreferenceConstants.EDITOR_OVERVIEW_RULER;
	/**
	 * Preference key for highlighting current line.
	 */
	private final static String CURRENT_LINE= AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE;
	/**
	 * Preference key for highlight color of current line.
	 */
	private final static String CURRENT_LINE_COLOR= AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE_COLOR;
	/**
	 * Preference key for showing print margin ruler.
	 */
	private final static String PRINT_MARGIN= AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN;
	/**
	 * Preference key for print margin ruler color.
	 */
	private final static String PRINT_MARGIN_COLOR= AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLOR;
	/**
	 * Preference key for print margin ruler column.
	 */
	private final static String PRINT_MARGIN_COLUMN= AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLUMN;
	/**
	 * Preference key for telling whether editors are allowed to override the global print margin preferences.
	 */
	private final static String PRINT_MARGIN_ALLOW_OVERRIDE= AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_ALLOW_OVERRIDE;
	/**
	 * Preference key to get whether the overwrite mode is disabled.
	 * @since 3.1
	 */
	private final static String DISABLE_OVERWRITE_MODE= AbstractDecoratedTextEditorPreferenceConstants.EDITOR_DISABLE_OVERWRITE_MODE;

	/**
	 * Menu id for the overview ruler context menu.
	 *
	 * @since 3.4
	 */
	public final static String DEFAULT_OVERVIEW_RULER_CONTEXT_MENU_ID= "#OverviewRulerContext"; //$NON-NLS-1$

	/**
	 * Preference key that controls whether to use saturated colors in the overview ruler.
	 *
	 * @since 3.8
	 */
	private static final String USE_SATURATED_COLORS_IN_OVERVIEW_RULER= AbstractDecoratedTextEditorPreferenceConstants.USE_SATURATED_COLORS_IN_OVERVIEW_RULER;


	/**
	 * Adapter class for <code>IGotoMarker</code>.
	 */
	private class GotoMarkerAdapter implements IGotoMarker {
		@Override
		public void gotoMarker(IMarker marker) {
			AbstractDecoratedTextEditor.this.gotoMarker(marker);
		}
	}


	/**
	 * The annotation preferences.
	 */
	private MarkerAnnotationPreferences fAnnotationPreferences;

	/**
	 * The overview ruler of this editor.
	 *
	 * <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 #getOverviewRuler()} instead.</p>
	 */
	protected IOverviewRuler fOverviewRuler;
	/**
	 * The overview ruler's context menu id.
	 *
	 * @since 3.4
	 */
	private String fOverviewRulerContextMenuId;

	/**
	 * Helper for accessing annotation from the perspective of this editor.
	 *
	 * <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 #getAnnotationAccess()} instead.</p>
	 */
	protected IAnnotationAccess fAnnotationAccess;
	/**
	 * 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>
	 */
	protected SourceViewerDecorationSupport fSourceViewerDecorationSupport;
	/**
	 * The line number column.
	 *
	 * <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 AbstractTextEditor#getVerticalRuler()} to access the vertical bar instead.</p>
	 */
	protected LineNumberRulerColumn fLineNumberRulerColumn;
	/**
	 * The delegating line number ruler contribution.
	 * @since 3.3
	 */
	private LineNumberColumn fLineColumn;
	/**
	 * The editor's implicit document provider.
	 */
	private IDocumentProvider fImplicitDocumentProvider;
	/**
	 * The editor's goto marker adapter.
	 */
	private Object fGotoMarkerAdapter= new GotoMarkerAdapter();
	/**
	 * Indicates whether this editor is updating views that show markers.
	 * @see #updateMarkerViews(Annotation)
	 * @since 3.2
	 */
	protected boolean fIsUpdatingMarkerViews= false;

	/**
	 * Indicates whether it wants to update the marker views after a gotoMarker call.
	 * @see #updateMarkerViews(Annotation)
	 * @see #gotoMarker(IMarker)
	 * @since 3.6
	 */
	private boolean fIsComingFromGotoMarker= false;

	/**
	 * Tells whether editing the current derived editor input is allowed.
	 * @since 3.3
	 */
	private boolean fIsEditingDerivedFileAllowed= true;
	/**
	 * Tells whether the derived state has been validated.
	 * @since 3.3
	 */
	private boolean fIsDerivedStateValidated= false;
	/**
	 * The focused information presenter, or <code>null</code> if not created yet.
	 * @since 3.5
	 */
	private FocusedInformationPresenter fInformationPresenter;

	/*
	 * Workaround for IllegalAccessError thrown because we are accessing
	 * a protected method in a different bundle from an inner class.
	 * @since 3.3
	 */
	private IVerticalRuler internalGetVerticalRuler() {
		return getVerticalRuler();
	}

	/**
	 * Creates a new text editor.
	 *
	 * @see #initializeEditor()
	 * @see #initializeKeyBindingScopes()
	 */
	public AbstractDecoratedTextEditor() {
		super();
		fAnnotationPreferences= EditorsPlugin.getDefault().getMarkerAnnotationPreferences();
		setRangeIndicator(new DefaultRangeIndicator());
		initializeKeyBindingScopes();
		initializeEditor();
	}

	/**
	 * Initializes this editor. Subclasses may re-implement. If sub-classes do
	 * not change the contract, this method should not be extended, i.e. do not
	 * call <code>super.initializeEditor()</code> in order to avoid the
	 * temporary creation of objects that are immediately overwritten by
	 * subclasses.
	 */
	protected void initializeEditor() {
		setPreferenceStore(EditorsPlugin.getDefault().getPreferenceStore());
	}

	/**
	 * Initializes the key binding scopes of this editor.
	 */
	protected void initializeKeyBindingScopes() {
		setKeyBindingScopes(new String[] { "org.eclipse.ui.textEditorScope" });  //$NON-NLS-1$
	}

	@Override
	public void dispose() {
		if (fSourceViewerDecorationSupport != null) {
			fSourceViewerDecorationSupport.dispose();
			fSourceViewerDecorationSupport= null;
		}

		fAnnotationAccess= null;
		fAnnotationPreferences= null;

		fLineNumberRulerColumn= null;
		fLineColumn= null;

		if (fInformationPresenter != null) {
			fInformationPresenter.uninstall();
			fInformationPresenter= null;
		}
		super.dispose();
	}

	@Override
	protected ISourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, int styles) {

		fAnnotationAccess= getAnnotationAccess();
		fOverviewRuler= createOverviewRuler(getSharedColors());

		ISourceViewer viewer= new SourceViewer(parent, ruler, getOverviewRuler(), isOverviewRulerVisible(), styles);
		// ensure decoration support has been created and configured.
		getSourceViewerDecorationSupport(viewer);

		return viewer;
	}

	protected ISharedTextColors getSharedColors() {
		return EditorsPlugin.getDefault().getSharedTextColors();
	}

	protected IOverviewRuler createOverviewRuler(ISharedTextColors sharedColors) {
		IOverviewRuler ruler= new OverviewRuler(getAnnotationAccess(), VERTICAL_RULER_WIDTH, sharedColors);

		Iterator<AnnotationPreference> e= fAnnotationPreferences.getAnnotationPreferences().iterator();
		while (e.hasNext()) {
			AnnotationPreference preference= e.next();
			if (preference.contributesToHeader())
				ruler.addHeaderAnnotationType(preference.getAnnotationType());
		}
		return ruler;
	}

	/**
	 * Creates the annotation access for this editor.
	 *
	 * @return the created annotation access
	 */
	protected IAnnotationAccess createAnnotationAccess() {
		return new DefaultMarkerAnnotationAccess();
	}

	/**
	 * 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
	 */
	protected void configureSourceViewerDecorationSupport(SourceViewerDecorationSupport support) {

		Iterator<AnnotationPreference> e= fAnnotationPreferences.getAnnotationPreferences().iterator();
		while (e.hasNext())
			support.setAnnotationPreference(e.next());

		support.setCursorLinePainterPreferenceKeys(CURRENT_LINE, CURRENT_LINE_COLOR);
		support.setMarginPainterPreferenceKeys(PRINT_MARGIN, PRINT_MARGIN_COLOR, PRINT_MARGIN_COLUMN);
	}

	/*
	 * @see org.eclipse.ui.texteditor.AbstractTextEditor.createPartControl(Composite)
	 */
	@Override
	public void createPartControl(Composite parent) {
		super.createPartControl(parent);
		if (fSourceViewerDecorationSupport != null)
			fSourceViewerDecorationSupport.install(getPreferenceStore());

		IColumnSupport columnSupport= getAdapter(IColumnSupport.class);

		if (isLineNumberRulerVisible()) {
			RulerColumnDescriptor lineNumberColumnDescriptor= RulerColumnRegistry.getDefault().getColumnDescriptor(LineNumberColumn.ID);
			if (lineNumberColumnDescriptor != null)
				columnSupport.setColumnVisible(lineNumberColumnDescriptor, true);
		}

		if (isPrefQuickDiffAlwaysOn())
			showChangeInformation(true);

		if (fOverviewRuler instanceof IOverviewRulerExtension rulerEx)
			rulerEx.setUseSaturatedColors(isPrefUseSaturatedColorsOn());

		if (!isOverwriteModeEnabled())
			enableOverwriteMode(false);

		if (!isRangeIndicatorEnabled()) {
			getSourceViewer().removeRangeIndication();
			getSourceViewer().setRangeIndicator(null);
		}

		// Assign the quick assist assistant to the annotation access.
		ISourceViewer viewer= getSourceViewer();
		if (fAnnotationAccess instanceof IAnnotationAccessExtension2 annotationEx2 && viewer instanceof ISourceViewerExtension3 viewerEx3)
			annotationEx2.setQuickAssistAssistant(viewerEx3.getQuickAssistAssistant());

		createOverviewRulerContextMenu();
	}

	/**
	 * Creates the context menu for the overview ruler.
	 * <p>
	 * Subclasses may extend or replace this method.
	 * </p>
	 *
	 * @since 3.4
	 */
	protected void createOverviewRulerContextMenu() {
		if (fOverviewRulerContextMenuId == null)
			fOverviewRulerContextMenuId= DEFAULT_OVERVIEW_RULER_CONTEXT_MENU_ID;

		if (fOverviewRuler == null || fOverviewRuler.getControl() == null)
			return;

		MenuManager menuManager= new MenuManager(fOverviewRulerContextMenuId, fOverviewRulerContextMenuId);
		menuManager.setRemoveAllWhenShown(true);
		menuManager.addMenuListener(getContextMenuListener());
		Menu menu= menuManager.createContextMenu(getOverviewRuler().getControl());
		getOverviewRuler().getControl().setMenu(menu);
		getEditorSite().registerContextMenu(fOverviewRulerContextMenuId, menuManager, getSelectionProvider(), false);
	}

	@Override
	protected IMenuListener createContextMenuListener() {
		final IMenuListener superListener= super.createContextMenuListener();
		return menu -> {
			if (!getOverviewRulerContextMenuId().equals(menu.getId())) {
				superListener.menuAboutToShow(menu);
				return;
			}
			setFocus();
			overviewRulerContextMenuAboutToShow(menu);
		};
	}

	@Override
	protected Control createStatusControl(Composite parent, final IStatus status) {
		Object adapter= getAdapter(IEncodingSupport.class);
		DefaultEncodingSupport encodingSupport= null;
		if (adapter instanceof DefaultEncodingSupport enc)
			encodingSupport= enc;

		if (encodingSupport == null || !encodingSupport.isEncodingError(status))
			return super.createStatusControl(parent, status);

		Shell shell= getSite().getShell();
		Display display= shell.getDisplay();
		Color bgColor= display.getSystemColor(SWT.COLOR_LIST_BACKGROUND);
		Color fgColor= display.getSystemColor(SWT.COLOR_LIST_FOREGROUND);

		Composite composite= new Composite(parent, SWT.NONE);
		composite.setLayout(new GridLayout());
		composite.setBackground(bgColor);
		composite.setForeground(fgColor);

		Control control= super.createStatusControl(composite, status);
		control.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

		Composite buttonComposite= new Composite(composite, SWT.NONE);
		buttonComposite.setLayout(new GridLayout());
		buttonComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
		buttonComposite.setBackground(bgColor);
		buttonComposite.setForeground(fgColor);

		encodingSupport.createStatusEncodingChangeControl(buttonComposite, status);

		return composite;
	}

	/**
	 * Tells whether the overview ruler is visible.
	 *
	 * @return whether the overview ruler is visible
	 */
	protected boolean isOverviewRulerVisible() {
		IPreferenceStore store= getPreferenceStore();
		return store != null ? store.getBoolean(OVERVIEW_RULER) : false;
	}

	@Override
	public void showChangeInformation(boolean show) {
		if (show == isChangeInformationShowing())
			return;

		IColumnSupport columnSupport= getAdapter(IColumnSupport.class);

		// only handle visibility of the combined column, but not the number/change only state
		if (show && fLineColumn == null) {
			RulerColumnDescriptor lineNumberColumnDescriptor= RulerColumnRegistry.getDefault().getColumnDescriptor(LineNumberColumn.ID);
			if (lineNumberColumnDescriptor != null)
				columnSupport.setColumnVisible(lineNumberColumnDescriptor, true);
		} else if (!show && fLineColumn != null && !isLineNumberRulerVisible()) {
			columnSupport.setColumnVisible(fLineColumn.getDescriptor(), false);
			fLineColumn= null;
		}
	}

	@Override
	public boolean isChangeInformationShowing() {
		return fLineColumn != null && fLineColumn.isShowingChangeInformation();
	}

	@Override
	public void showRevisionInformation(RevisionInformation info, String quickDiffProviderId) {
		if (info.getHoverControlCreator() == null)
			info.setHoverControlCreator(new RevisionHoverInformationControlCreator(false));
		if (info.getInformationPresenterControlCreator() == null)
			info.setInformationPresenterControlCreator(new RevisionHoverInformationControlCreator(true));

		showChangeInformation(true);
		if (fLineColumn != null)
			fLineColumn.showRevisionInformation(info, quickDiffProviderId);
	}

	/**
	 * Returns whether the line number ruler column should be
	 * visible according to the preference store settings. Subclasses may override this
	 * method to provide a custom preference setting.
	 *
	 * @return <code>true</code> if the line numbers should be visible
	 */
	protected boolean isLineNumberRulerVisible() {
		IPreferenceStore store= getPreferenceStore();
		return store != null ? store.getBoolean(LINE_NUMBER_RULER) : false;
	}

	/**
	 * Returns whether the overwrite mode is enabled according to the preference
	 * store settings. Subclasses may override this method to provide a custom
	 * preference setting.
	 *
	 * @return <code>true</code> if overwrite mode is enabled
	 * @since 3.1
	 */
	protected boolean isOverwriteModeEnabled() {
		IPreferenceStore store= getPreferenceStore();
		return store != null ? !store.getBoolean(DISABLE_OVERWRITE_MODE) : true;
	}

	/**
	 * Returns whether the range indicator is enabled according to the preference
	 * store settings. Subclasses may override this method to provide a custom
	 * preference setting.
	 *
	 * @return <code>true</code> if overwrite mode is enabled
	 * @since 3.1
	 */
	private boolean isRangeIndicatorEnabled() {
		IPreferenceStore store= getPreferenceStore();
		return store != null ? store.getBoolean(AbstractDecoratedTextEditorPreferenceConstants.SHOW_RANGE_INDICATOR) : true;
	}

	/**
	 * Returns whether quick diff info should be visible upon opening an editor
	 * according to the preference store settings.
	 *
	 * @return <code>true</code> if the line numbers should be visible
	 */
	protected boolean isPrefQuickDiffAlwaysOn() {
		IPreferenceStore store= getPreferenceStore();
		boolean setting= store != null ? store.getBoolean(AbstractDecoratedTextEditorPreferenceConstants.QUICK_DIFF_ALWAYS_ON) : false;
		return setting && isEditorInputModifiable();
	}

	/**
	 * Checks if the preference to use saturated colors is enabled for the overview ruler.
	 *
	 * @return <code>true</code> if the saturated colors preference is enabled, <code>false</code>
	 *         otherwise
	 * @since 3.8
	 */
	private boolean isPrefUseSaturatedColorsOn() {
		IPreferenceStore store= getPreferenceStore();
		return store != null ? store.getBoolean(USE_SATURATED_COLORS_IN_OVERVIEW_RULER) : false;
	}

	/**
	 * Initializes the given line number ruler column from the preference store.
	 *
	 * @param rulerColumn the ruler column to be initialized
	 */
	protected void initializeLineNumberRulerColumn(LineNumberRulerColumn rulerColumn) {
		/*
		 * Left for compatibility. See LineNumberColumn.
		 */
		if (fLineColumn != null)
			fLineColumn.initializeLineNumberRulerColumn(rulerColumn);
	}

	/**
	 * Creates a new line number ruler column that is appropriately initialized.
	 *
	 * @return the created line number column
	 */
	protected IVerticalRulerColumn createLineNumberRulerColumn() {
		/*
		 * Left for compatibility. See LineNumberColumn.
		 */
		fLineNumberRulerColumn= new LineNumberChangeRulerColumn(getSharedColors());
		((IChangeRulerColumn) fLineNumberRulerColumn).setHover(createChangeHover());
		initializeLineNumberRulerColumn(fLineNumberRulerColumn);
		return fLineNumberRulerColumn;
	}

	/**
	 * Creates and returns a <code>LineChangeHover</code> to be used on this editor's change
	 * ruler column. This default implementation returns a plain <code>LineChangeHover</code>.
	 * Subclasses may override.
	 *
	 * @return the change hover to be used by this editors quick diff display
	 */
	protected LineChangeHover createChangeHover() {
		return new TextChangeHover();
	}

	/**
	 * Creates a new change ruler column for quick diff display independent of the
	 * line number ruler column
	 *
	 * @return a new change ruler column
	 * @deprecated as of 3.3. Not called any longer, replaced by {@link #createLineNumberRulerColumn()}
	 */
	@Deprecated
	protected IChangeRulerColumn createChangeRulerColumn() {
		/*
		 * Left for compatibility. See LineNumberColumn.
		 */
		return new ChangeRulerColumn(getSharedColors());
	}

	/**
	 * Returns {@link #createCompositeRuler()}. Subclasses should not override this method, but
	 * rather <code>createCompositeRuler</code> if they want to contribute their own vertical ruler
	 * implementation. If not an instance of {@link CompositeRuler} is returned, the built-in ruler
	 * columns (line numbers, annotations) will not work.
	 *
	 * <p>May become <code>final</code> in the future.</p>
	 *
	 * @see AbstractTextEditor#createVerticalRuler()
	 */
	@Override
	protected IVerticalRuler createVerticalRuler() {
		return createCompositeRuler();
	}


	/**
	 * Creates a composite ruler to be used as the vertical ruler by this editor.
	 * Subclasses may re-implement this method.
	 *
	 * @return the vertical ruler
	 */
	protected CompositeRuler createCompositeRuler() {
		return new CompositeRuler();
	}


	/**
	 * 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());
	}

	@Override
	protected final IColumnSupport createColumnSupport() {
		return new ColumnSupport(this, RulerColumnRegistry.getDefault()) {
			@Override
			protected void initializeColumn(IContributedRulerColumn column) {
				super.initializeColumn(column);
				RulerColumnDescriptor descriptor= column.getDescriptor();
				IVerticalRuler ruler= internalGetVerticalRuler();
				if (ruler instanceof CompositeRuler compRuler) {
					if (AnnotationColumn.ID.equals(descriptor.getId())) {
						((AnnotationColumn) column).setDelegate(createAnnotationRulerColumn(compRuler));
					} else if (LineNumberColumn.ID.equals(descriptor.getId())) {
						fLineColumn= ((LineNumberColumn) column);
						fLineColumn.setForwarder(new LineNumberColumn.ICompatibilityForwarder() {
							@Override
							public IVerticalRulerColumn createLineNumberRulerColumn() {
								return AbstractDecoratedTextEditor.this.createLineNumberRulerColumn();
							}
							@Override
							public boolean isQuickDiffEnabled() {
								return AbstractDecoratedTextEditor.this.isPrefQuickDiffAlwaysOn();
							}
							@Override
							public boolean isLineNumberRulerVisible() {
								return AbstractDecoratedTextEditor.this.isLineNumberRulerVisible();
							}
						});
					}
				}
			}

			@Override
			public void dispose() {
				fLineColumn= null;
				super.dispose();
			}
		};
	}

	@Override
	protected void handlePreferenceStoreChanged(PropertyChangeEvent event) {

		try {

			ISourceViewer sourceViewer= getSourceViewer();
			if (sourceViewer == null)
				return;

			String property= event.getProperty();

			if (fSourceViewerDecorationSupport != null && fOverviewRuler != null && OVERVIEW_RULER.equals(property))  {
				if (isOverviewRulerVisible())
					showOverviewRuler();
				else
					hideOverviewRuler();
				return;
			}

			if (USE_SATURATED_COLORS_IN_OVERVIEW_RULER.equals(property)) {
				if (fOverviewRuler instanceof IOverviewRulerExtension overviewRulerEx) {
					overviewRulerEx.setUseSaturatedColors(isPrefUseSaturatedColorsOn());
					fOverviewRuler.update();
				}
			}

			if (DISABLE_OVERWRITE_MODE.equals(property)) {
				enableOverwriteMode(isOverwriteModeEnabled());
				return;
			}

			if (LINE_NUMBER_RULER.equals(property)) {
				// only handle visibility of the combined column, but not the number/change only state
				IColumnSupport columnSupport= getAdapter(IColumnSupport.class);
				if (isLineNumberRulerVisible() && fLineColumn == null) {
					RulerColumnDescriptor lineNumberColumnDescriptor= RulerColumnRegistry.getDefault().getColumnDescriptor(LineNumberColumn.ID);
					if (lineNumberColumnDescriptor != null)
						columnSupport.setColumnVisible(lineNumberColumnDescriptor, true);
				} else if (!isLineNumberRulerVisible() && fLineColumn != null && !fLineColumn.isShowingChangeInformation()) {
					columnSupport.setColumnVisible(fLineColumn.getDescriptor(), false);
					fLineColumn= null;
				}
				return;
			}

			if (AbstractDecoratedTextEditorPreferenceConstants.QUICK_DIFF_ALWAYS_ON.equals(property)) {
				showChangeInformation(isPrefQuickDiffAlwaysOn());
			}

			if (AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH.equals(property)) {
				IPreferenceStore store= getPreferenceStore();
				if (store != null)
					sourceViewer.getTextWidget().setTabs(store.getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH));
				if (isTabsToSpacesConversionEnabled()) {
					uninstallTabsToSpacesConverter();
					installTabsToSpacesConverter();
				}
				return;
			}

			if (AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_SPACING.equals(property)) {
				IPreferenceStore store= getPreferenceStore();
				if (store != null) {
					// translate ratio into height
					int linespace= getSourceViewerConfiguration().getLineSpacing(sourceViewer);
					sourceViewer.getTextWidget().setLineSpacing(linespace);
				}
				return;
			}

			if (AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SPACES_FOR_TABS.equals(property)
					|| AbstractDecoratedTextEditorPreferenceConstants.EDITOR_DELETE_SPACES_AS_TABS.equals(property)) {
				if (isTabsToSpacesConversionEnabled())
					installTabsToSpacesConverter();
				else
					uninstallTabsToSpacesConverter();
				return;
			}

			if (AbstractDecoratedTextEditorPreferenceConstants.EDITOR_UNDO_HISTORY_SIZE.equals(property) && sourceViewer instanceof ITextViewerExtension6 sourceViewerExt6) {
				IPreferenceStore store= getPreferenceStore();
				if (store != null)
					sourceViewerExt6.getUndoManager().setMaximalUndoLevel(store.getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_UNDO_HISTORY_SIZE));
				return;
			}

			if (AbstractDecoratedTextEditorPreferenceConstants.SHOW_RANGE_INDICATOR.equals(property)) {
				if (isRangeIndicatorEnabled()) {
					getSourceViewer().setRangeIndicator(getRangeIndicator());
				} else {
					getSourceViewer().removeRangeIndication();
					getSourceViewer().setRangeIndicator(null);
				}
			}

			if (sourceViewer instanceof ITextViewerExtension6 textViewer6) {
				HyperlinkDetectorDescriptor[] descriptor= EditorsUI.getHyperlinkDetectorRegistry().getHyperlinkDetectorDescriptors();
				for (HyperlinkDetectorDescriptor d : descriptor) {
					if (d.getId().equals(property) || (d.getId() + HyperlinkDetectorDescriptor.STATE_MASK_POSTFIX).equals(property)) {
						IHyperlinkDetector[] detectors= getSourceViewerConfiguration().getHyperlinkDetectors(sourceViewer);
						int stateMask= getSourceViewerConfiguration().getHyperlinkStateMask(sourceViewer);
						textViewer6.setHyperlinkDetectors(detectors, stateMask);
						return;
					}
				}
			}

		} finally {
			super.handlePreferenceStoreChanged(event);
		}
	}

	/**
	 * Shows the overview ruler.
	 */
	protected void showOverviewRuler() {
		if (fOverviewRuler != null) {
			if (getSourceViewer() instanceof ISourceViewerExtension sourceViewerExt) {
				sourceViewerExt.showAnnotationsOverview(true);
				fSourceViewerDecorationSupport.updateOverviewDecorations();
			}
		}
	}

	/**
	 * Hides the overview ruler.
	 */
	protected void hideOverviewRuler() {
		if (getSourceViewer() instanceof ISourceViewerExtension sourceViewerExt) {
			fSourceViewerDecorationSupport.hideAnnotationOverview();
			sourceViewerExt.showAnnotationsOverview(false);
		}
	}

	/**
	 * Returns the annotation access.
	 *
	 * @return the annotation access
	 */
	protected IAnnotationAccess getAnnotationAccess() {
		if (fAnnotationAccess == null)
			fAnnotationAccess= createAnnotationAccess();
		return fAnnotationAccess;
	}

	/**
	 * Returns the annotation preference lookup.
	 *
	 * @return the annotation preference lookup
	 */
	protected AnnotationPreferenceLookup getAnnotationPreferenceLookup() {
		return EditorsPlugin.getDefault().getAnnotationPreferenceLookup();
	}

	/**
	 * Returns the overview ruler.
	 *
	 * @return the overview ruler
	 */
	protected IOverviewRuler getOverviewRuler() {
		if (fOverviewRuler == null)
			fOverviewRuler= createOverviewRuler(getSharedColors());
		return fOverviewRuler;
	}

	/**
	 * Returns the source viewer decoration support.
	 *
	 * @param viewer the viewer for which to return a decoration support
	 * @return the source viewer decoration support
	 */
	protected SourceViewerDecorationSupport getSourceViewerDecorationSupport(ISourceViewer viewer) {
		if (fSourceViewerDecorationSupport == null) {
			fSourceViewerDecorationSupport= new SourceViewerDecorationSupport(viewer, getOverviewRuler(), getAnnotationAccess(), getSharedColors());
			configureSourceViewerDecorationSupport(fSourceViewerDecorationSupport);

			// Fix for overridden print margin column, see https://bugs.eclipse.org/468307
			if (!getPreferenceStore().getBoolean(PRINT_MARGIN_ALLOW_OVERRIDE))
				fSourceViewerDecorationSupport.setMarginPainterPreferenceKeys(PRINT_MARGIN, PRINT_MARGIN_COLOR, PRINT_MARGIN_COLUMN);
		}
		return fSourceViewerDecorationSupport;
	}

	/**
	 * Returns the annotation preferences.
	 *
	 * @return the annotation preferences
	 */
	protected MarkerAnnotationPreferences getAnnotationPreferences() {
		return fAnnotationPreferences;
	}


	/**
	 * If the editor can be saved all marker ranges have been changed according to
	 * the text manipulations. However, those changes are not yet propagated to the
	 * marker manager. Thus, when opening a marker, the marker's position in the editor
	 * must be determined as it might differ from the position stated in the marker.
	 *
	 * @param marker the marker to go to
	 * @deprecated visibility will be reduced, use <code>getAdapter(IGotoMarker.class) for accessing this method</code>
	 */
	@Deprecated
	public void gotoMarker(IMarker marker) {
		if (fIsUpdatingMarkerViews)
			return;

		if (getSourceViewer() == null)
			return;

		int start= MarkerUtilities.getCharStart(marker);
		int end= MarkerUtilities.getCharEnd(marker);

		boolean selectLine= start < 0 || end < 0;

		// look up the current range of the marker when the document has been edited
		IAnnotationModel model= getDocumentProvider().getAnnotationModel(getEditorInput());
		if (model instanceof AbstractMarkerAnnotationModel markerModel) {

			Position pos= markerModel.getMarkerPosition(marker);
			if (pos != null && !pos.isDeleted()) {
				// use position instead of marker values
				start= pos.getOffset();
				end= pos.getOffset() + pos.getLength();
			}

			if (pos != null && pos.isDeleted()) {
				// do nothing if position has been deleted
				return;
			}
		}

		IDocument document= getDocumentProvider().getDocument(getEditorInput());

		if (selectLine) {
			int line;
			try {
				if (start >= 0)
					line= document.getLineOfOffset(start);
				else {
					line= MarkerUtilities.getLineNumber(marker);
					// Marker line numbers are 1-based
					-- line;
					start= document.getLineOffset(line);
				}
				end= start + document.getLineLength(line) - 1;
			} catch (BadLocationException e) {
				return;
			}
		}

		int length= document.getLength();
		if (end <= length && start <= length) {
			fIsComingFromGotoMarker= true;
			selectAndReveal(start, end - start);
		}
	}

	@Override
	public boolean isEditable() {
		if (!super.isEditable())
			return false;
		return fIsEditingDerivedFileAllowed;
	}

	@Override
	public boolean validateEditorInputState() {
		if (!super.validateEditorInputState())
			return false;

		return validateEditorInputDerived();
	}

	/**
	 * Validates the editor input for derived state.
	 * If the given input is derived then this method
	 * can show a dialog asking whether to edit the
	 * derived file.
	 *
	 * @return <code>true</code> if the input is OK for editing, <code>false</code> otherwise
	 * @since 3.3
	 */
	private boolean validateEditorInputDerived() {
		if (fIsDerivedStateValidated)
			return fIsEditingDerivedFileAllowed;

		if (getDocumentProvider() instanceof IDocumentProviderExtension extension) {
			IStatus status= extension.getStatus(getEditorInput());
			String pluginId= status.getPlugin();
			boolean isDerivedStatus= status.getCode() == IFileBufferStatusCodes.DERIVED_FILE && (FileBuffers.PLUGIN_ID.equals(pluginId) || EditorsUI.PLUGIN_ID.equals(pluginId));
			if (!isDerivedStatus)
				return true;
		}

		final String warnKey= AbstractDecoratedTextEditorPreferenceConstants.EDITOR_WARN_IF_INPUT_DERIVED;
		IPreferenceStore store= getPreferenceStore();
		if (!store.getBoolean(warnKey))
			return true;

		MessageDialogWithToggle toggleDialog= MessageDialogWithToggle.openYesNoQuestion(
				getSite().getShell(),
				TextEditorMessages.AbstractDecoratedTextEditor_warning_derived_title,
				TextEditorMessages.AbstractDecoratedTextEditor_warning_derived_message,
				TextEditorMessages.AbstractDecoratedTextEditor_warning_derived_dontShowAgain,
				false,
				null,
				null);

		EditorsUI.getPreferenceStore().setValue(warnKey, !toggleDialog.getToggleState());
		fIsDerivedStateValidated= true;
		return fIsEditingDerivedFileAllowed= toggleDialog.getReturnCode() == IDialogConstants.YES_ID;
	}

	/*
	 * For an explanation why we override this method see http://bugs.eclipse.org/42230
	 *
	 * @see org.eclipse.ui.texteditor.StatusTextEditor#isErrorStatus(org.eclipse.core.runtime.IStatus)
	 */
	@Override
	protected boolean isErrorStatus(IStatus status) {
		if (!super.isErrorStatus(status))
			return false;

		if (!status.isMultiStatus())
			return !isReadOnlyLocalStatus(status);

		IStatus[] childrenStatus= status.getChildren();
		for (IStatus childrenStatu : childrenStatus) {
			if (childrenStatu.getSeverity() == IStatus.ERROR && !isReadOnlyLocalStatus(childrenStatu)) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Check whether the given status is a <code>IResourceStatus.READ_ONLY_LOCAL</code>
	 * error.
	 *
	 * @param status the status to be checked
	 * @return <code>true</code> if the given status is a <code>IResourceStatus.READ_ONLY_LOCAL</code> error
	 * @since 3.3
	 */
	private boolean isReadOnlyLocalStatus(IStatus status) {
		return status.getCode() == IResourceStatus.READ_ONLY_LOCAL;
	}

	@Override
	protected void createActions() {
		super.createActions();

		ResourceAction action= new AddMarkerAction(TextEditorMessages.getBundleForConstructedKeys(), "Editor.AddBookmark.", this, IMarker.BOOKMARK, true); //$NON-NLS-1$
		action.setHelpContextId(ITextEditorHelpContextIds.BOOKMARK_ACTION);
		action.setActionDefinitionId(IWorkbenchCommandConstants.EDIT_ADD_BOOKMARK);
		setAction(IDEActionFactory.BOOKMARK.getId(), action);

		action= new AddTaskAction(TextEditorMessages.getBundleForConstructedKeys(), "Editor.AddTask.", this); //$NON-NLS-1$
		action.setHelpContextId(ITextEditorHelpContextIds.ADD_TASK_ACTION);
		action.setActionDefinitionId(IWorkbenchCommandConstants.EDIT_ADD_TASK);
		setAction(IDEActionFactory.ADD_TASK.getId(), action);

		action= new ChangeEncodingAction(TextEditorMessages.getBundleForConstructedKeys(), "Editor.ChangeEncodingAction.", this); //$NON-NLS-1$
		action.setHelpContextId(ITextEditorHelpContextIds.CHANGE_ENCODING);
		action.setActionDefinitionId(ITextEditorActionDefinitionIds.CHANGE_ENCODING);
		setAction(ITextEditorActionConstants.CHANGE_ENCODING, action);
		markAsPropertyDependentAction(ITextEditorActionConstants.CHANGE_ENCODING, true);

		action= new ResourceAction(TextEditorMessages.getBundleForConstructedKeys(), "Editor.ToggleLineNumbersAction.", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			@Override
			public void run() {
				toggleLineNumberRuler();
			}
		};
		action.setActionDefinitionId(ITextEditorActionDefinitionIds.LINENUMBER_TOGGLE);
		setAction(ITextEditorActionConstants.LINENUMBERS_TOGGLE, action);

		action= new ResourceAction(TextEditorMessages.getBundleForConstructedKeys(), "Editor.ToggleQuickDiffAction.", IAction.AS_CHECK_BOX) { //$NON-NLS-1$
			@Override
			public void run() {
				toggleQuickDiffRuler();
			}
		};
		action.setActionDefinitionId(ITextEditorActionDefinitionIds.QUICKDIFF_TOGGLE);
		setAction(ITextEditorActionConstants.QUICKDIFF_TOGGLE, action);

		action= new RevertLineAction(this, false);
		action.setActionDefinitionId(ITextEditorActionDefinitionIds.QUICKDIFF_REVERTLINE);
		setAction(ITextEditorActionConstants.QUICKDIFF_REVERTLINE, action);

		action= new RevertSelectionAction(this, false);
		setAction(ITextEditorActionConstants.QUICKDIFF_REVERTSELECTION, action);

		action= new RevertBlockAction(this, false);
		setAction(ITextEditorActionConstants.QUICKDIFF_REVERTBLOCK, action);

		action= new RestoreAction(this, false);
		setAction(ITextEditorActionConstants.QUICKDIFF_REVERTDELETION, action);

		IAction action2= new CompositeRevertAction(this, new IAction[] {
												getAction(ITextEditorActionConstants.QUICKDIFF_REVERTSELECTION),
												getAction(ITextEditorActionConstants.QUICKDIFF_REVERTBLOCK),
												getAction(ITextEditorActionConstants.QUICKDIFF_REVERTDELETION),
												getAction(ITextEditorActionConstants.QUICKDIFF_REVERTLINE)});
		action2.setActionDefinitionId(ITextEditorActionDefinitionIds.QUICKDIFF_REVERT);
		setAction(ITextEditorActionConstants.QUICKDIFF_REVERT, action2);

		action= new ResourceAction(TextEditorMessages.getBundleForConstructedKeys(), "Editor.HideRevisionInformationAction.") { //$NON-NLS-1$
			@Override
			public void run() {
				if (fLineColumn != null)
					fLineColumn.hideRevisionInformation();
			}
		};
		setAction(ITextEditorActionConstants.REVISION_HIDE_INFO, action);

		action= new ResourceAction(TextEditorMessages.getBundleForConstructedKeys(), "Editor.CycleRevisionRenderingAction.") { //$NON-NLS-1$
			@Override
			public void run() {
				final RenderingMode[] modes= { IRevisionRulerColumnExtension.AGE, IRevisionRulerColumnExtension.AUTHOR, IRevisionRulerColumnExtension.AUTHOR_SHADED_BY_AGE};
				IPreferenceStore store= EditorsUI.getPreferenceStore();
				String current= store.getString(AbstractDecoratedTextEditorPreferenceConstants.REVISION_RULER_RENDERING_MODE);
				for (int i= 0; i < modes.length; i++) {
					String mode= modes[i].name();
					if (mode.equals(current)) {
						int nextIndex= (i + 1) % modes.length;
						RenderingMode nextMode= modes[nextIndex];
						store.setValue(AbstractDecoratedTextEditorPreferenceConstants.REVISION_RULER_RENDERING_MODE, nextMode.name());
					}
				}
			}
		};
		action.setActionDefinitionId(ITextEditorActionDefinitionIds.REVISION_RENDERING_CYCLE);
		setAction(ITextEditorActionConstants.REVISION_RENDERING_CYCLE, action);

		action= new BooleanPreferenceToggleAction(TextEditorMessages.getBundleForConstructedKeys(), "Editor.ToggleRevisionAuthorAction.", IAction.AS_CHECK_BOX, EditorsUI.getPreferenceStore(), AbstractDecoratedTextEditorPreferenceConstants.REVISION_RULER_SHOW_AUTHOR); //$NON-NLS-1$
		action.setActionDefinitionId(ITextEditorActionDefinitionIds.REVISION_AUTHOR_TOGGLE);
		setAction(ITextEditorActionConstants.REVISION_SHOW_AUTHOR_TOGGLE, action);

		action= new BooleanPreferenceToggleAction(TextEditorMessages.getBundleForConstructedKeys(), "Editor.ToggleRevisionIdAction.", IAction.AS_CHECK_BOX, EditorsUI.getPreferenceStore(), AbstractDecoratedTextEditorPreferenceConstants.REVISION_RULER_SHOW_REVISION); //$NON-NLS-1$
		action.setActionDefinitionId(ITextEditorActionDefinitionIds.REVISION_ID_TOGGLE);
		setAction(ITextEditorActionConstants.REVISION_SHOW_ID_TOGGLE, action);

		final Shell shell;
		if (getSourceViewer() != null)
			shell= getSourceViewer().getTextWidget().getShell();
		else
			shell= null;
		action= new ResourceAction(TextEditorMessages.getBundleForConstructedKeys(), "Editor.RulerPreferencesAction.") { //$NON-NLS-1$
			@Override
			public void run() {
				String[] preferencePages= collectRulerMenuPreferencePages();
				if (preferencePages.length > 0 && (shell == null || !shell.isDisposed()))
					PreferencesUtil.createPreferenceDialogOn(shell, preferencePages[0], preferencePages, null).open();
			}

		};
		setAction(ITextEditorActionConstants.RULER_PREFERENCES, action);

		action= new ResourceAction(TextEditorMessages.getBundleForConstructedKeys(), "Editor.ContextPreferencesAction.") { //$NON-NLS-1$
			@Override
			public void run() {
				String[] preferencePages= collectContextMenuPreferencePages();
				if (preferencePages.length > 0 && (shell == null || !shell.isDisposed()))
					PreferencesUtil.createPreferenceDialogOn(shell, preferencePages[0], preferencePages, null).open();
			}
		};
		setAction(ITextEditorActionConstants.CONTEXT_PREFERENCES, action);

		IAction showWhitespaceCharactersAction= getAction(ITextEditorActionConstants.SHOW_WHITESPACE_CHARACTERS);
		if (showWhitespaceCharactersAction instanceof ShowWhitespaceCharactersAction act)
			act.setPreferenceStore(EditorsUI.getPreferenceStore());

		setAction(ITextEditorActionConstants.REFRESH, new RefreshEditorAction(this));
		markAsPropertyDependentAction(ITextEditorActionConstants.REFRESH, true);

		// Override print action to provide additional options
		if (getAction(ITextEditorActionConstants.PRINT).isEnabled() && getSourceViewer() instanceof ITextViewerExtension8)
			createPrintAction();

		action= new ResourceAction(TextEditorMessages.getBundleForConstructedKeys(), "Editor.ShowChangeRulerInformation.", IAction.AS_PUSH_BUTTON) { //$NON-NLS-1$
			@Override
			public void run() {
				showChangeRulerInformation();
			}
		};
		action.setActionDefinitionId(ITextEditorActionDefinitionIds.SHOW_CHANGE_RULER_INFORMATION_ID);
		setAction(ITextEditorActionConstants.SHOW_CHANGE_RULER_INFORMATION, action);

		action= new ResourceAction(TextEditorMessages.getBundleForConstructedKeys(), "Editor.ShowRulerAnnotationInformation.", IAction.AS_PUSH_BUTTON) { //$NON-NLS-1$
			@Override
			public void run() {
				showRulerAnnotationInformation();
			}
		};
		action.setActionDefinitionId(ITextEditorActionDefinitionIds.SHOW_RULER_ANNOTATION_INFORMATION_ID);
		setAction(ITextEditorActionConstants.SHOW_RULER_ANNOTATION_INFORMATION, action);
	}

	/**
	 * Opens a sticky change ruler hover for the caret line. Does nothing if no change hover is
	 * available.
	 *
	 * @since 3.5
	 */
	private void showChangeRulerInformation() {
		IVerticalRuler ruler= getVerticalRuler();
		if (!(ruler instanceof CompositeRuler compositeRuler) || fLineColumn == null)
			return;

		// fake a mouse move (some hovers rely on this to determine the hovered line):
		int x= fLineColumn.getControl().getLocation().x;

		ISourceViewer sourceViewer= getSourceViewer();
		StyledText textWidget= sourceViewer.getTextWidget();
		int caretOffset= textWidget.getCaretOffset();
		int caretLine= textWidget.getLineAtOffset(caretOffset);
		int y= textWidget.getLinePixel(caretLine);

		compositeRuler.setLocationOfLastMouseButtonActivity(x, y);

		IAnnotationHover hover= fLineColumn.getHover();
		showFocusedRulerHover(hover, sourceViewer, caretOffset);
	}

	/**
	 * Opens a sticky annotation ruler hover for the caret line. Does nothing if no annotation hover
	 * is available.
	 *
	 * @since 3.6
	 */
	private void showRulerAnnotationInformation() {
		ISourceViewer sourceViewer= getSourceViewer();
		IAnnotationHover hover= getSourceViewerConfiguration().getAnnotationHover(sourceViewer);
		int caretOffset= sourceViewer.getTextWidget().getCaretOffset();

		showFocusedRulerHover(hover, sourceViewer, caretOffset);
	}

	/**
	 * Shows a focused hover at the specified offset.
	 * Does nothing if <code>hover</code> is <code>null</code> or cannot be shown.
	 *
	 * @param hover the hover to be shown, can be <code>null</code>
	 * @param sourceViewer the source viewer
	 * @param caretOffset the caret offset
	 *
	 * @since 3.6
	 */
	private void showFocusedRulerHover(IAnnotationHover hover, ISourceViewer sourceViewer, int caretOffset) {
		if (hover == null)
			return;

		int modelCaretOffset= widgetOffset2ModelOffset(sourceViewer, caretOffset);
		if (modelCaretOffset == -1)
			return;

		IDocument document= sourceViewer.getDocument();
		if (document == null)
			return;

		try {
			int line= document.getLineOfOffset(modelCaretOffset);
			if (fInformationPresenter == null) {
				fInformationPresenter= new FocusedInformationPresenter(sourceViewer, getSourceViewerConfiguration());
			}
			fInformationPresenter.openFocusedAnnotationHover(hover, line);
		} catch (BadLocationException e) {
			return;
		}
	}

	/**
	 * Creates and registers the print action.
	 *
	 * @since 3.4
	 */
	private void createPrintAction() {
		final ISourceViewer viewer= getSourceViewer();
		ResourceAction action= new ResourceAction(TextEditorMessages.getBundleForConstructedKeys(), "Editor.Print.") { //$NON-NLS-1$

			@Override
			public void run() {
				StyledTextPrintOptions options= new StyledTextPrintOptions();
				options.printTextFontStyle= true;
				options.printTextForeground= true;
				options.printTextBackground= true;
				options.jobName= getTitle();
				options.header= getTitle() + StyledTextPrintOptions.SEPARATOR + StyledTextPrintOptions.SEPARATOR
						+ DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.SHORT).format(new Date());
				options.footer= StyledTextPrintOptions.SEPARATOR + NLSUtility.format(TextEditorMessages.AbstractDecoratedTextEditor_printPageNumber, StyledTextPrintOptions.PAGE_TAG);

				if (isLineNumberRulerVisible()) {
					options.printLineNumbers= true;

					// Compute line number labels
					options.lineLabels= new String[viewer.getTextWidget().getLineCount()];
					for (int i= 0; i < options.lineLabels.length; i++)
						options.lineLabels[i]= String.valueOf(JFaceTextUtil.widgetLine2ModelLine(viewer, i) + 1);
				}

				((ITextViewerExtension8)viewer).print(options);
			}
		};

		action.setHelpContextId(IAbstractTextEditorHelpContextIds.PRINT_ACTION);
		action.setActionDefinitionId(IWorkbenchCommandConstants.FILE_PRINT);
		setAction(ITextEditorActionConstants.PRINT, action);
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> T getAdapter(Class<T> adapter) {
		if (IGotoMarker.class.equals(adapter))
			return (T) fGotoMarkerAdapter;

		if (IAnnotationAccess.class.equals(adapter))
			return (T) getAnnotationAccess();

		if (adapter == IShowInSource.class) {
			return (T) (IShowInSource) () -> {
				ISelection selection= null;
				ISelectionProvider selectionProvider= getSelectionProvider();
				if (selectionProvider != null)
					selection= selectionProvider.getSelection();
				return new ShowInContext(getEditorInput(), selection);
			};
		}

		if (IRevisionRulerColumn.class.equals(adapter)) {
			if (fLineNumberRulerColumn instanceof IRevisionRulerColumn)
				return (T) fLineNumberRulerColumn;
		}

		if (MarkerAnnotationPreferences.class.equals(adapter))
			return (T) EditorsPlugin.getDefault().getMarkerAnnotationPreferences();

		if (IShowInTargetList.class.equals(adapter))
			return (T) getShowInTargetList();

		return super.getAdapter(adapter);

	}

	/*
	 * If there is no explicit document provider set, the implicit one is
	 * re-initialized based on the given editor input.
	 *
	 * @see org.eclipse.ui.texteditor.AbstractTextEditor#setDocumentProvider(org.eclipse.ui.IEditorInput)
	 */
	@Override
	protected void setDocumentProvider(IEditorInput input) {
		fImplicitDocumentProvider= DocumentProviderRegistry.getDefault().getDocumentProvider(input);
		IDocumentProvider provider= super.getDocumentProvider();
		if (provider instanceof ForwardingDocumentProvider forwarder) {
			forwarder.setParentProvider(fImplicitDocumentProvider);
		}
	}

	@Override
	public IDocumentProvider getDocumentProvider() {
		IDocumentProvider provider= super.getDocumentProvider();
		if (provider == null)
			return fImplicitDocumentProvider;
		return provider;
	}

	@Override
	protected void disposeDocumentProvider() {
		super.disposeDocumentProvider();
		fImplicitDocumentProvider= null;
	}

	/*
	 * @see AbstractTextEditor#doSetInput(IEditorInput)
	 *
	 * This implementation also updates change information in the quick diff
	 * ruler.
	 */
	@Override
	protected void doSetInput(IEditorInput input) throws CoreException {
		fIsDerivedStateValidated= false;
		fIsEditingDerivedFileAllowed= true;

		if (fLineColumn != null)
			fLineColumn.hideRevisionInformation();

		super.doSetInput(input);

		RulerColumnDescriptor lineNumberColumnDescriptor= RulerColumnRegistry.getDefault().getColumnDescriptor(LineNumberColumn.ID);
		if (lineNumberColumnDescriptor != null) {
			IColumnSupport columnSupport= getAdapter(IColumnSupport.class);
			columnSupport.setColumnVisible(lineNumberColumnDescriptor, isLineNumberRulerVisible() || isPrefQuickDiffAlwaysOn());
		}
	}

	@Override
	protected void handleEditorInputChanged() {
		final IDocumentProvider provider= getDocumentProvider();
		IEditorInput input= getEditorInput();
		if (provider != null && input != null) {
			if (!isDirty() && input.getAdapter(IFile.class) != null) {
				if (Platform.getPreferencesService().getBoolean(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PREF_LIGHTWEIGHT_AUTO_REFRESH, false, null))
					return;
			}
		}
		super.handleEditorInputChanged();
	}

	/**
	 * This implementation asks the user for the workspace path of a file resource and saves the document there.
	 *
	 * @param progressMonitor the progress monitor to be used
	 * @since 3.2
	 */
	@Override
	protected void performSaveAs(IProgressMonitor progressMonitor) {
		Shell shell= PlatformUI.getWorkbench().getModalDialogShellProvider().getShell();
		final IEditorInput input= getEditorInput();

		IDocumentProvider provider= getDocumentProvider();
		final IEditorInput newInput;

		if (input instanceof IURIEditorInput && !(input instanceof IFileEditorInput)) {
			FileDialog dialog= new FileDialog(shell, SWT.SAVE);
			IPath oldPath= URIUtil.toPath(((IURIEditorInput)input).getURI());
			if (oldPath != null && !oldPath.isEmpty()) {
				dialog.setFileName(oldPath.lastSegment());
				dialog.setFilterPath(oldPath.removeLastSegments(1).toOSString());
			}

			String path= dialog.open();
			if (path == null) {
				if (progressMonitor != null)
					progressMonitor.setCanceled(true);
				return;
			}

			// Check whether file exists and if so, confirm overwrite
			final File localFile= new File(path);
			if (localFile.exists()) {
				MessageDialog overwriteDialog= new MessageDialog(
						shell,
						TextEditorMessages.AbstractDecoratedTextEditor_saveAs_overwrite_title,
						null,
						NLSUtility.format(TextEditorMessages.AbstractDecoratedTextEditor_saveAs_overwrite_message, path),
						MessageDialog.WARNING,
						new String[] { IDialogConstants.YES_LABEL, IDialogConstants.NO_LABEL },
						1); // 'No' is the default
				if (overwriteDialog.open() != Window.OK) {
					if (progressMonitor != null) {
						progressMonitor.setCanceled(true);
						return;
					}
				}
			}

			IFileStore fileStore;
			try {
				fileStore= EFS.getStore(localFile.toURI());
			} catch (CoreException ex) {
				EditorsPlugin.log(ex.getStatus());
				String title= TextEditorMessages.AbstractDecoratedTextEditor_error_saveAs_title;
				String msg= NLSUtility.format(TextEditorMessages.AbstractDecoratedTextEditor_error_saveAs_message, ex.getMessage());
				MessageDialog.openError(shell, title, msg);
				return;
			}

			IFile file= getWorkspaceFile(fileStore);
			if (file != null)
				newInput= new FileEditorInput(file);
			else
				newInput= new FileStoreEditorInput(fileStore);

		} else {
			SaveAsDialog dialog= new SaveAsDialog(shell);

			IFile original= (input instanceof IFileEditorInput fileEditorInput) ? fileEditorInput.getFile() : null;
			if (original != null)
				dialog.setOriginalFile(original);
			else
				dialog.setOriginalName(input.getName());

			dialog.create();

			if (provider.isDeleted(input) && original != null) {
				String message= NLSUtility.format(TextEditorMessages.AbstractDecoratedTextEditor_warning_saveAs_deleted, original.getName());
				dialog.setErrorMessage(null);
				dialog.setMessage(message, IMessageProvider.WARNING);
			}

			if (dialog.open() == Window.CANCEL) {
				if (progressMonitor != null)
					progressMonitor.setCanceled(true);
				return;
			}

			IPath filePath= dialog.getResult();
			if (filePath == null) {
				if (progressMonitor != null)
					progressMonitor.setCanceled(true);
				return;
			}

			IWorkspace workspace= ResourcesPlugin.getWorkspace();
			IFile file= workspace.getRoot().getFile(filePath);
			newInput= new FileEditorInput(file);

		}

		if (provider == null) {
			// editor has programmatically been  closed while the dialog was open
			return;
		}

		boolean success= false;
		try {

			provider.aboutToChange(newInput);
			provider.saveDocument(progressMonitor, newInput, provider.getDocument(input), true);
			success= true;

		} catch (CoreException x) {
			final IStatus status= x.getStatus();
			if (status == null || status.getSeverity() != IStatus.CANCEL) {
				String title= TextEditorMessages.AbstractDecoratedTextEditor_error_saveAs_title;
				String msg= NLSUtility.format(TextEditorMessages.AbstractDecoratedTextEditor_error_saveAs_message, x.getMessage());
				MessageDialog.openError(shell, title, msg);
			}
		} finally {
			provider.changed(newInput);
			if (success)
				setInput(newInput);
		}

		if (progressMonitor != null)
			progressMonitor.setCanceled(!success);
	}

	/**
	 * Presents an error dialog to the user when a problem happens during save.
	 * <p>
	 * Overrides the default behavior by showing a more advanced error dialog in case of encoding
	 * problems.
	 * </p>
	 *
	 * @param title the dialog title
	 * @param message the message to display
	 * @param exception the exception to handle
	 * @since 3.6
	 */
	@Override
	protected void openSaveErrorDialog(String title, String message, CoreException exception) {
		IStatus status= exception.getStatus();
		final IDocumentProvider documentProvider= getDocumentProvider();
		if (!(status.getCode() == IFileBufferStatusCodes.CHARSET_MAPPING_FAILED && documentProvider instanceof IStorageDocumentProvider)) {
			super.openSaveErrorDialog(title, message, exception);
			return;
		}

		final int saveAsUTF8ButtonId= IDialogConstants.OK_ID + IDialogConstants.CANCEL_ID + 1;
		final int selectUnmappableCharButtonId= saveAsUTF8ButtonId + 1;
		final Charset charset= getCharset();

		ErrorDialog errorDialog= new ErrorDialog(getSite().getShell(), title, message, status, IStatus.ERROR) {

			@Override
			protected void createButtonsForButtonBar(Composite parent) {
				super.createButtonsForButtonBar(parent);
				createButton(parent, saveAsUTF8ButtonId, TextEditorMessages.AbstractDecoratedTextEditor_save_error_Dialog_button_saveAsUTF8, false);
				if (charset != null)
					createButton(parent, selectUnmappableCharButtonId, TextEditorMessages.AbstractDecoratedTextEditor_save_error_Dialog_button_selectUnmappable, false);
			}

			@Override
			protected void buttonPressed(int id) {
				if (id == saveAsUTF8ButtonId || id == selectUnmappableCharButtonId) {
					setReturnCode(id);
					close();
				} else
					super.buttonPressed(id);
			}

			@Override
			protected boolean shouldShowDetailsButton() {
				return false;
			}

		};

		int returnCode= errorDialog.open();

		if (returnCode == saveAsUTF8ButtonId) {
			((IStorageDocumentProvider) documentProvider).setEncoding(getEditorInput(), StandardCharsets.UTF_8.name());
			IProgressMonitor monitor= getProgressMonitor();
			try {
				doSave(monitor);
			} finally {
				monitor.done();
			}
		} else if (returnCode == selectUnmappableCharButtonId) {
			CharsetEncoder encoder= charset.newEncoder();
			IDocument document= getDocumentProvider().getDocument(getEditorInput());
			int documentLength= document.getLength();
			int offset= 0;
			BreakIterator charBreakIterator= BreakIterator.getCharacterInstance();
			charBreakIterator.setText(document.get());
			while (offset < documentLength) {
				try {
					int next= charBreakIterator.next();
					String ch= document.get(offset, next - offset);
					if (!encoder.canEncode(ch)) {
						selectAndReveal(offset, next - offset);
						return;
					}
					offset= next;
				} catch (BadLocationException ex) {
					EditorsPlugin.log(ex);
					// Skip this character. Showing yet another dialog here is overkill
				}
			}
		}
	}


	/**
	 * Returns the charset of the current editor input.
	 *
	 * @return the charset of the current editor input or <code>null</code> if it fails
	 * @since 3.6
	 */
	private Charset getCharset() {
		IEncodingSupport encodingSupport= getAdapter(IEncodingSupport.class);
		if (encodingSupport == null)
			return null;
		try {
			return Charset.forName(encodingSupport.getEncoding());
		} catch (UnsupportedCharsetException ex) {
			return null;
		} catch (IllegalCharsetNameException ex) {
			return null;
		}
	}

	/**
	 * Checks whether there given file store points
	 * to a file in the workspace. Only returns a
	 * workspace file if there's a single match.
	 *
	 * @param fileStore the file store
	 * @return the <code>IFile</code> that matches the given file store
	 * @since 3.2
	 */
	private IFile getWorkspaceFile(IFileStore fileStore) {
		IWorkspaceRoot workspaceRoot= ResourcesPlugin.getWorkspace().getRoot();
		IFile[] files= workspaceRoot.findFilesForLocationURI(fileStore.toURI());
		if (files != null && files.length == 1)
			return files[0];
		return null;
	}

	/**
	 * Sets the ruler's overview context menu id.
	 *
	 * @param contextMenuId the overview ruler context menu id
	 * @since 3.4
	 */
	protected void setOverviewRulerContextMenuId(String contextMenuId) {
		Assert.isNotNull(contextMenuId);
		fOverviewRulerContextMenuId= contextMenuId;
	}

	/**
	 * Returns the ruler's overview context menu id. May return
	 * <code>null</code> before the editor's part has been created.
	 *
	 * @return the ruler's context menu id which may be <code>null</code>
	 * @since 3.4
	 */
	protected final String getOverviewRulerContextMenuId() {
		return fOverviewRulerContextMenuId;
	}

	/**
	 * Sets up the overview ruler context menu before it is made visible.
	 * <p>
	 * Subclasses may extend to add other actions.
	 * </p>
	 *
	 * @param menu the menu
	 * @since 3.4
	 */
	protected void overviewRulerContextMenuAboutToShow(IMenuManager menu) {
		final String preferenceLabel= findSelectedOverviewRulerAnnotationLabel();
		final Shell shell= getSite().getShell();
		IAction action= new ResourceAction(TextEditorMessages.getBundleForConstructedKeys(), "Editor.RulerPreferencesAction.") { //$NON-NLS-1$

			@Override
			public void run() {
				String[] preferencePages= collectOverviewRulerMenuPreferencePages();
				if (preferencePages.length > 0 && (shell == null || !shell.isDisposed())) {
					PreferencesUtil.createPreferenceDialogOn(shell, preferencePages[0], preferencePages, preferenceLabel).open();
				}
			}
		};

		menu.add(new Separator(ITextEditorActionConstants.GROUP_REST));
		menu.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
		menu.add(action);
	}

	private String findSelectedOverviewRulerAnnotationLabel() {
		IAnnotationModel model= getSourceViewer().getAnnotationModel();
		if (model == null) {
			return null;
		}
		Point selection= getSourceViewer().getSelectedRange();
		Annotation annotation= null;
		Iterator<Annotation> iter= model.getAnnotationIterator();
		while (iter.hasNext()) {
			annotation= iter.next();
			Position p= model.getPosition(annotation);
			if (p.getOffset() == selection.x && p.getLength() == selection.y)
				break;
		}

		if (annotation != null) {
			AnnotationPreference ap= getAnnotationPreferenceLookup().getAnnotationPreference(annotation);
			if (ap != null)
				return ap.getPreferenceLabel();
		}

		return null;
	}

	@Override
	protected void rulerContextMenuAboutToShow(IMenuManager menu) {
		/*
		 * XXX: workaround for reliable menu item ordering.
		 * This can be changed once the action contribution story converges,
		 * see http://dev.eclipse.org/viewcvs/index.cgi/~checkout~/platform-ui-home/R3_1/dynamic_teams/dynamic_teams.html#actionContributions
		 */
		// pre-install menus for contributions and call super
		menu.add(new Separator("debug")); //$NON-NLS-1$
		menu.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
		menu.add(new GroupMarker(ITextEditorActionConstants.GROUP_RESTORE));
		menu.add(new Separator("add")); //$NON-NLS-1$
		menu.add(new Separator(ITextEditorActionConstants.GROUP_RULERS));
		menu.add(new Separator(ITextEditorActionConstants.GROUP_REST));

		super.rulerContextMenuAboutToShow(menu);

		addRulerContributionActions(menu);

		/* quick diff */
		if (isEditorInputModifiable()) {
			IAction quickdiffAction= getAction(ITextEditorActionConstants.QUICKDIFF_TOGGLE);
			quickdiffAction.setChecked(isChangeInformationShowing());
			menu.appendToGroup(ITextEditorActionConstants.GROUP_RULERS, quickdiffAction);

			if (isChangeInformationShowing()) {
				TextEditorAction revertLine= new RevertLineAction(this, true);
				TextEditorAction revertSelection= new RevertSelectionAction(this, true);
				TextEditorAction revertBlock= new RevertBlockAction(this, true);
				TextEditorAction revertDeletion= new RestoreAction(this, true);

				revertSelection.update();
				revertBlock.update();
				revertLine.update();
				revertDeletion.update();

				// only add block action if selection action is not enabled
				if (revertSelection.isEnabled())
					menu.appendToGroup(ITextEditorActionConstants.GROUP_RESTORE, revertSelection);
				else if (revertBlock.isEnabled())
					menu.appendToGroup(ITextEditorActionConstants.GROUP_RESTORE, revertBlock);
				if (revertLine.isEnabled())
					menu.appendToGroup(ITextEditorActionConstants.GROUP_RESTORE, revertLine);
				if (revertDeletion.isEnabled())
					menu.appendToGroup(ITextEditorActionConstants.GROUP_RESTORE, revertDeletion);
			}
		}

		// revision info
		if (fLineColumn != null && fLineColumn.isShowingRevisionInformation()) {
			IMenuManager revisionMenu= new MenuManager(TextEditorMessages.AbstractDecoratedTextEditor_revisions_menu);
			menu.appendToGroup(ITextEditorActionConstants.GROUP_RULERS, revisionMenu);

			IAction hideRevisionInfoAction= getAction(ITextEditorActionConstants.REVISION_HIDE_INFO);
			revisionMenu.add(hideRevisionInfoAction);
			revisionMenu.add(new Separator());

			String[] labels= { TextEditorMessages.AbstractDecoratedTextEditor_revision_colors_option_by_date, TextEditorMessages.AbstractDecoratedTextEditor_revision_colors_option_by_author, TextEditorMessages.AbstractDecoratedTextEditor_revision_colors_option_by_author_and_date };
			final RenderingMode[] modes= { IRevisionRulerColumnExtension.AGE, IRevisionRulerColumnExtension.AUTHOR, IRevisionRulerColumnExtension.AUTHOR_SHADED_BY_AGE};
			final IPreferenceStore uiStore= EditorsUI.getPreferenceStore();
			String current= uiStore.getString(AbstractDecoratedTextEditorPreferenceConstants.REVISION_RULER_RENDERING_MODE);
			for (int i= 0; i < modes.length; i++) {
				final String mode= modes[i].name();
				IAction action= new Action(labels[i], IAction.AS_RADIO_BUTTON) {
					@Override
					public void run() {
						// set preference globally, LineNumberColumn reacts on preference change
						uiStore.setValue(AbstractDecoratedTextEditorPreferenceConstants.REVISION_RULER_RENDERING_MODE, mode);
					}
				};
				action.setChecked(mode.equals(current));
				revisionMenu.add(action);
			}

			revisionMenu.add(new Separator());

			IAction action= getAction(ITextEditorActionConstants.REVISION_SHOW_AUTHOR_TOGGLE);
			if (action instanceof IUpdate updateAction)
				updateAction.update();
			revisionMenu.add(action);
			action= getAction(ITextEditorActionConstants.REVISION_SHOW_ID_TOGGLE);
			if (action instanceof IUpdate updateAction)
				updateAction.update();
			revisionMenu.add(action);
		}

		IAction lineNumberAction= getAction(ITextEditorActionConstants.LINENUMBERS_TOGGLE);
		lineNumberAction.setChecked(fLineColumn != null && fLineColumn.isShowingLineNumbers());
		menu.appendToGroup(ITextEditorActionConstants.GROUP_RULERS, lineNumberAction);

		IAction preferencesAction= getAction(ITextEditorActionConstants.RULER_PREFERENCES);
		menu.appendToGroup(ITextEditorActionConstants.GROUP_RULERS, new Separator(ITextEditorActionConstants.GROUP_SETTINGS));
		menu.appendToGroup(ITextEditorActionConstants.GROUP_SETTINGS, preferencesAction);
	}

	/**
	 * Adds "show" actions for all contributed rulers that support it.
	 *
	 * @param menu the ruler context menu
	 * @since 3.3
	 */
	private void addRulerContributionActions(IMenuManager menu) {
		// store directly in generic editor preferences
		final IColumnSupport support= getAdapter(IColumnSupport.class);
		IPreferenceStore store= EditorsUI.getPreferenceStore();
		final RulerColumnPreferenceAdapter adapter= new RulerColumnPreferenceAdapter(store, AbstractTextEditor.PREFERENCE_RULER_CONTRIBUTIONS);
		for (RulerColumnDescriptor descriptor : RulerColumnRegistry.getDefault().getColumnDescriptors()) {
			if (!descriptor.isIncludedInMenu() || !support.isColumnSupported(descriptor))
				continue;
			final boolean isVisible= support.isColumnVisible(descriptor);
			IAction action= new Action(MessageFormat.format(TextEditorMessages.AbstractDecoratedTextEditor_show_ruler_label, descriptor.getName()), IAction.AS_CHECK_BOX) {
				@Override
				public void run() {
					if (descriptor.isGlobal())
						// column state is modified via preference listener of AbstractTextEditor
						adapter.setEnabled(descriptor, !isVisible);
					else
						// directly modify column for this editor instance
						support.setColumnVisible(descriptor, !isVisible);
				}
			};
			action.setChecked(isVisible);
			action.setImageDescriptor(descriptor.getIcon());
			menu.appendToGroup(ITextEditorActionConstants.GROUP_RULERS, action);
		}
	}

	/**
	 * Toggles the line number global preference and shows the line number ruler
	 * accordingly.
	 *
	 * @since 3.1
	 */
	private void toggleLineNumberRuler() {
		// globally
		IPreferenceStore store= EditorsUI.getPreferenceStore();
		store.setValue(LINE_NUMBER_RULER, !isLineNumberRulerVisible());
	}

	/**
	 * Toggles the quick diff global preference and shows the quick diff ruler
	 * accordingly.
	 *
	 * @since 3.1
	 */
	private void toggleQuickDiffRuler() {
		// change the visibility locally if this editor is not in sync with the global preference
		// toggle the preference if we are in sync.
		IPreferenceStore store= EditorsUI.getPreferenceStore();
		boolean current= store.getBoolean(AbstractDecoratedTextEditorPreferenceConstants.QUICK_DIFF_ALWAYS_ON);
		if (current == isChangeInformationShowing())
			store.setValue(AbstractDecoratedTextEditorPreferenceConstants.QUICK_DIFF_ALWAYS_ON, !current);
		else
			showChangeInformation(current);
	}

	@Override
	protected void editorContextMenuAboutToShow(IMenuManager menu) {
		super.editorContextMenuAboutToShow(menu);

		IAction preferencesAction= getAction(ITextEditorActionConstants.CONTEXT_PREFERENCES);
		menu.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, new Separator(ITextEditorActionConstants.GROUP_SETTINGS));
		menu.appendToGroup(ITextEditorActionConstants.GROUP_SETTINGS, preferencesAction);

		menu.appendToGroup(ITextEditorActionConstants.GROUP_SAVE, new Separator(ITextEditorActionConstants.GROUP_OPEN));

		IEditorInput editorInput= getEditorInput();
		if ((editorInput.getAdapter(IResource.class)) instanceof IFile) {
			MenuManager openWithSubMenu= new MenuManager(TextEditorMessages.AbstractDecoratedTextEditor_openWith_menu);
			final IWorkbenchPage page= getEditorSite().getPage();

			// XXX: Internal reference will get fixed during 3.7, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=307026
			openWithSubMenu.add(new OpenWithMenu(page, editorInput) {
				@Override
				protected void openEditor(IEditorDescriptor editorDescriptor, boolean openUsingDescriptor) {
					super.openEditor(editorDescriptor, openUsingDescriptor);
					ISelection selection= getSelectionProvider().getSelection();
					if (selection instanceof ITextSelection textSelection) {
						revealInEditor(page.getActiveEditor(), textSelection.getOffset(), textSelection.getLength());
					}
				}
			});
			menu.appendToGroup(ITextEditorActionConstants.GROUP_OPEN, openWithSubMenu);
		}

		MenuManager showInSubMenu= new MenuManager(getShowInMenuLabel());
		showInSubMenu.add(ContributionItemFactory.VIEWS_SHOW_IN.create(getEditorSite().getWorkbenchWindow()));
		menu.appendToGroup(ITextEditorActionConstants.GROUP_OPEN, showInSubMenu);
	}

	/**
	 * Selects and reveals the given offset and length in the given editor part.
	 *
	 * @param editor the editor part
	 * @param offset the offset
	 * @param length the length
	 * @since 3.7
	 */
	private static void revealInEditor(IEditorPart editor, final int offset, final int length) {
		if (editor instanceof ITextEditor textEditor) {
			textEditor.selectAndReveal(offset, length);
			return;
		}

		// Support for non-text editor - try IGotoMarker interface
		final IGotoMarker gotoMarkerTarget;
		if (editor instanceof IGotoMarker gotoMarker)
			gotoMarkerTarget= gotoMarker;
		else
			gotoMarkerTarget= editor != null ? editor.getAdapter(IGotoMarker.class) : null;
		if (gotoMarkerTarget != null) {
			final IEditorInput input= editor.getEditorInput();
			if (input instanceof IFileEditorInput fileInput) {
				WorkspaceModifyOperation op= new WorkspaceModifyOperation() {
					@Override
					protected void execute(IProgressMonitor monitor) throws CoreException {
						IMarker marker= null;
						try {
							marker= fileInput.getFile().createMarker(IMarker.TEXT);
							marker.setAttribute(IMarker.CHAR_START, offset);
							marker.setAttribute(IMarker.CHAR_END, offset + length);

							gotoMarkerTarget.gotoMarker(marker);

						} finally {
							if (marker != null)
								marker.delete();
						}
					}
				};

				try {
					op.run(null);
				} catch (InvocationTargetException ex) {
					// reveal failed
				} catch (InterruptedException e) {
					Assert.isTrue(false, "this operation can not be canceled"); //$NON-NLS-1$
				}
			}
			return;
		}
	}

	/**
	 * Returns the menu label for 'Show In' together with its key binding string.
	 *
	 * @return the 'Show In' menu label
	 * @since 3.2
	 */
	private String getShowInMenuLabel() {
		String keyBinding= null;

		IBindingService bindingService= PlatformUI.getWorkbench().getAdapter(IBindingService.class);
		if (bindingService != null)
			keyBinding= bindingService.getBestActiveBindingFormattedFor(IWorkbenchCommandConstants.NAVIGATE_SHOW_IN_QUICK_MENU);

		if (keyBinding == null)
			keyBinding= ""; //$NON-NLS-1$

		return NLSUtility.format(TextEditorMessages.AbstractDecoratedTextEditor_showIn_menu, keyBinding);
	}

	/**
	 * Creates and returns the list of target part IDs for the Show In menu
	 *
	 * @return the 'Show In' target part IDs
	 * @since 3.13
	 */
	protected String[] createShowInTargetList() {
		return new String[] { IPageLayout.ID_MINIMAP_VIEW };
	}

	/**
	 * Returns the Show In target list
	 *
	 * @return the IShowInTargetList adapter, or <code>null</code> if no targets are listed
	 */
	private IShowInTargetList getShowInTargetList() {
		final String[] targetList= createShowInTargetList();
		if (targetList != null && targetList.length > 0) {
			return () -> targetList;
		}
		return null;
	}

	/**
	 * Returns the preference page ids of the preference pages to be shown when executing the
	 * preferences action from the editor context menu. The first page will be selected.
	 * <p>
	 * Subclasses may extend or replace.
	 * </p>
	 *
	 * @return the preference page ids to show, may be empty
	 * @since 3.1
	 */
	protected String[] collectContextMenuPreferencePages() {
		return new String[] { "org.eclipse.ui.preferencePages.GeneralTextEditor", //$NON-NLS-1$
				"org.eclipse.ui.editors.preferencePages.Annotations", //$NON-NLS-1$
				"org.eclipse.ui.editors.preferencePages.QuickDiff", //$NON-NLS-1$
				"org.eclipse.ui.editors.preferencePages.Accessibility", //$NON-NLS-1$
				"org.eclipse.ui.editors.preferencePages.Spelling", //$NON-NLS-1$
				"org.eclipse.ui.editors.preferencePages.LinkedModePreferencePage", //$NON-NLS-1$
				"org.eclipse.ui.preferencePages.ColorsAndFonts", //$NON-NLS-1$
		};
	}

	/**
	 * Returns the preference page ids of the preference pages to be shown when executing the
	 * preferences action from the editor ruler context menu. The first page will be selected.
	 * <p>
	 * The default is to return the same list as <code>collectContextMenuPreferencePages</code>.
	 * </p>
	 * <p>
	 * Subclasses may extend or replace.
	 * </p>
	 *
	 * @return the preference page ids to show, may be empty
	 * @since 3.1
	 */
	protected String[] collectRulerMenuPreferencePages() {
		return collectContextMenuPreferencePages();
	}

	/**
	 * Returns the preference page ids of the preference pages to be shown when executing the
	 * preferences action from the editor overview ruler context menu. The first page will be
	 * selected.
	 * <p>
	 * The default is to select the 'Annotations' preference page.
	 * </p>
	 * <p>
	 * Subclasses may extend or replace.
	 * </p>
	 *
	 * @return the preference page ids to show, may be empty
	 * @since 3.4
	 */
	protected String[] collectOverviewRulerMenuPreferencePages() {
		return new String[] { "org.eclipse.ui.editors.preferencePages.Annotations", //$NON-NLS-1$
				"org.eclipse.ui.preferencePages.GeneralTextEditor", //$NON-NLS-1$
				"org.eclipse.ui.editors.preferencePages.Accessibility", //$NON-NLS-1$
				"org.eclipse.ui.editors.preferencePages.QuickDiff", //$NON-NLS-1$
				"org.eclipse.ui.preferencePages.ColorsAndFonts" //$NON-NLS-1$
		};
	}

	@Override
	protected IOperationApprover getUndoRedoOperationApprover(IUndoContext undoContext) {
		IEditorInput input= getEditorInput();
		if (input != null && input.getAdapter(IResource.class) != null)
			return new NonLocalUndoUserApprover(undoContext, this, new Object [] { input }, IResource.class);
		return super.getUndoRedoOperationApprover(undoContext);
	}

	/**
	 * Returns whether the given annotation is configured as a target for the
	 * "Go to Next/Previous Annotation" actions.
	 * <p>
	 * The annotation is a target if their annotation type is configured to be
	 * in the Next/Previous tool bar drop down menu and if it is checked.
	 * </p>
	 *
	 * @param annotation the annotation
	 * @return <code>true</code> if this is a target, <code>false</code> otherwise
	 * @since 3.2
	 */
	@Override
	protected boolean isNavigationTarget(Annotation annotation) {
		AnnotationPreference preference= getAnnotationPreferenceLookup().getAnnotationPreference(annotation);
//		See bug 41689
//		String key= forward ? preference.getIsGoToNextNavigationTargetKey() : preference.getIsGoToPreviousNavigationTargetKey();
		String key= preference == null ? null : preference.getIsGoToNextNavigationTargetKey();
		return (key != null && getPreferenceStore().getBoolean(key));
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * This extended implementation updates views that also show the
	 * select marker annotation.
	 * </p>
	 * @since 3.2
	 */
	@Override
	public Annotation gotoAnnotation(boolean forward) {
		Annotation annotation= super.gotoAnnotation(forward);
		if (annotation != null)
			updateMarkerViews(annotation);
		return annotation;
	}

	/**
	 * Updates visible views that show markers.
	 * <p>
	 * If the given annotation can be associated with a marker then this method tries select the
	 * this marker in views that show markers.
	 * </p>
	 *
	 * @param annotation the annotation
	 * @since 3.2
	 */
	protected void updateMarkerViews(Annotation annotation) {
		if (fIsComingFromGotoMarker) {
			fIsComingFromGotoMarker= false;
			return;
		}

		IMarker marker= null;
		if (annotation instanceof MarkerAnnotation markerAnnotation)
			marker= markerAnnotation.getMarker();

		if (marker != null) {
			try {
				fIsUpdatingMarkerViews= true;
				IWorkbenchPage page= getSite().getPage();
				MarkerViewUtil.showMarker(page, marker, false);
			} finally {
				fIsUpdatingMarkerViews= false;
			}
		}
	}

	/*
	 * @see org.eclipse.ui.texteditor.AbstractTextEditor#isTabConversionEnabled()
	 * @since 3.3
	 */
	@Override
	protected boolean isTabsToSpacesConversionEnabled() {
		return getPreferenceStore() != null && getPreferenceStore().getBoolean(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SPACES_FOR_TABS);
	}

	/**
	 * @since 3.13
	 */
	@Override
	protected boolean isSpacesAsTabsDeletionEnabled() {
		return getPreferenceStore() != null && getPreferenceStore().getBoolean(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_DELETE_SPACES_AS_TABS);
	}
}
