blob: 4c9be6ab1827a645af708a5d1ae38aa24e958ffa [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2018 IBM Corporation and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
*******************************************************************************/
package org.eclipse.dltk.internal.ui.editor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.compare.rangedifferencer.IRangeComparator;
import org.eclipse.compare.rangedifferencer.RangeDifference;
import org.eclipse.compare.rangedifferencer.RangeDifferencer;
import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.ITextFileBufferManager;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
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.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.core.IExternalSourceModule;
import org.eclipse.dltk.core.IMember;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.core.IScriptProject;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.ISourceRange;
import org.eclipse.dltk.core.ISourceReference;
import org.eclipse.dltk.core.ModelException;
import org.eclipse.dltk.internal.corext.util.Messages;
import org.eclipse.dltk.internal.ui.DelegatedOpen;
import org.eclipse.dltk.internal.ui.IDLTKStatusConstants;
import org.eclipse.dltk.internal.ui.text.LineComparator;
import org.eclipse.dltk.ui.DLTKUILanguageManager;
import org.eclipse.dltk.ui.DLTKUIPlugin;
import org.eclipse.dltk.ui.IDLTKUILanguageToolkit;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.swt.SWT;
import org.eclipse.ui.IEditorDescriptor;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.ide.IGotoMarker;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
import org.eclipse.ui.texteditor.TextEditorAction;
public class EditorUtility {
/**
* Returns the DLTK project for a given editor input or <code>null</code> if no
* corresponding DLTK project exists.
*
* @param input the editor input
* @return the corresponding DLTK project
*/
public static IScriptProject getScriptProject(IEditorInput input) {
IScriptProject dProject = null;
if (input instanceof IFileEditorInput) {
IProject project = ((IFileEditorInput) input).getFile().getProject();
if (project != null) {
dProject = DLTKCore.create(project);
if (!dProject.exists())
dProject = null;
}
} else if (input instanceof ExternalStorageEditorInput) {
IModelElement element = input.getAdapter(IModelElement.class);
if (element != null) {
IScriptProject project = element.getScriptProject();
if (project != null && project.exists()) {
return project;
}
}
}
return dProject;
}
/**
* Returns the given editor's input as model element.
*
* @param editor the editor
* @param primaryOnly if <code>true</code> only primary working copies will be
* returned
* @return the given editor's input as model element or <code>null</code> if
* none
*/
public static ISourceModule getEditorInputModelElement(IEditorPart editor, boolean primaryOnly) {
IEditorInput editorInput = editor.getEditorInput();
if (editorInput == null)
return null;
ISourceModule je = DLTKUIPlugin.getEditorInputModelElement(editorInput);
if (je != null || primaryOnly)
return je;
return DLTKUIPlugin.getDefault().getWorkingCopyManager().getWorkingCopy(editorInput, primaryOnly);
}
/**
* Opens a Script editor for an element such as <code>IModelElement</code>,
* <code>IFile</code>, or <code>IStorage</code>. The editor is activated by
* default.
*
* @return the IEditorPart or null if wrong element type or opening failed
*/
public static IEditorPart openInEditor(Object inputElement) throws ModelException, PartInitException {
return openInEditor(inputElement, true);
}
/**
* Opens a Script editor for an element (IModelElement, IFile, IStorage...)
*
* @return the IEditorPart or null if wrong element type or opening failed
*/
public static IEditorPart openInEditor(Object inputElement, boolean activate)
throws ModelException, PartInitException {
if (inputElement instanceof IFile) {
return openInEditor((IFile) inputElement, activate);
} else if (inputElement instanceof DelegatedOpen) {
return ((DelegatedOpen) inputElement).openInEditor(activate);
}
IEditorInput input = getEditorInput(inputElement);
if (input != null) {
if (inputElement instanceof IModelElement) {
// first try to get it from the system.
String editorId = null;
IDLTKUILanguageToolkit toolkit = DLTKUILanguageManager.getLanguageToolkit((IModelElement) inputElement);
if (toolkit != null) {
editorId = toolkit.getEditorId(inputElement);
}
if (editorId == null) { // Transitional code
editorId = getEditorID(input, inputElement);
}
if (editorId != null) {
return openInEditor(input, editorId, activate);
}
} else
return openInEditor(input, getEditorID(input, inputElement), activate);
}
if (inputElement instanceof IModelElement) {
IModelElement modelElement = (IModelElement) inputElement;
ISourceModule cu = (ISourceModule) (modelElement).getAncestor(IModelElement.SOURCE_MODULE);
if (cu != null) {
/*
* Support for non-primary or RSE working copy. Try to reveal it in the active
* editor.
*/
IWorkbenchPage page = DLTKUIPlugin.getActivePage();
if (page != null) {
IEditorPart editor = page.getActiveEditor();
if (editor != null) {
IModelElement editorCU = EditorUtility.getEditorInputModelElement(editor, false);
if (editorCU == cu) {
EditorUtility.revealInEditor(editor, modelElement);
return editor;
}
}
}
}
}
return null;
}
public static String getEditorID(IEditorInput input, Object inputObject) {
IEditorDescriptor editorDescriptor;
try {
if (input instanceof IFileEditorInput) {
editorDescriptor = IDE.getEditorDescriptor(((IFileEditorInput) input).getFile(), true, false);
} else if (input instanceof ExternalStorageEditorInput) {
editorDescriptor = IDE.getEditorDescriptor(input.getName(), true, false);
} else {
editorDescriptor = IDE.getEditorDescriptor(input.getName(), true, false);
}
} catch (PartInitException e) {
return null;
}
if (editorDescriptor != null)
return editorDescriptor.getId();
return null;
}
private static IEditorInput getEditorInput(IModelElement element) {
while (element != null) {
IEditorInput adapter = element.getAdapter(IEditorInput.class);
if (adapter != null) {
return adapter;
}
if (element instanceof IExternalSourceModule) {
ISourceModule unit = ((ISourceModule) element).getPrimary();
if (unit instanceof IStorage) {
return new ExternalStorageEditorInput((IStorage) unit);
}
} else if (element instanceof ISourceModule) {
ISourceModule unit = ((ISourceModule) element).getPrimary();
IResource resource = unit.getResource();
if (resource instanceof IFile && ((IFile) resource).exists())
return new FileEditorInput((IFile) resource);
}
element = element.getParent();
}
return null;
}
public static IEditorInput getEditorInput(Object input) {
if (input instanceof IModelElement)
return getEditorInput((IModelElement) input);
if (input instanceof IFile)
return new FileEditorInput((IFile) input);
if (DLTKCore.DEBUG) {
System.err.println("Add archive entry and external source folder editor input.."); //$NON-NLS-1$
}
if (input instanceof IStorage) {
return new ExternalStorageEditorInput((IStorage) input);
}
return null;
}
/**
* Selects a Script Element in an editor
*/
public static void revealInEditor(IEditorPart part, IModelElement element) {
if (element == null)
return;
if (part instanceof IScriptEditor) {
((IScriptEditor) part).setSelection(element);
return;
}
// Support for non-Script editor
try {
ISourceRange range = null;
if (element instanceof IExternalSourceModule) {
} else if (element instanceof ISourceModule) {
range = null;
}
// else if (element instanceof IClassFile)
// range= null;
// else if (element instanceof ILocalVariable)
// range= ((ILocalVariable)element).getNameRange();
else if (element instanceof IMember)
range = ((IMember) element).getNameRange();
// else if (element instanceof ITypeParameter)
// range= ((ITypeParameter)element).getNameRange();
else if (element instanceof ISourceReference)
range = ((ISourceReference) element).getSourceRange();
if (range != null)
revealInEditor(part, range.getOffset(), range.getLength());
} catch (ModelException e) {
// don't reveal
}
}
/**
* Selects and reveals the given line in the given editor part.
*
* @param editorPart
* @param lineNumber
* @throws CoreException
*/
public static void revealInEditor(IEditorPart editorPart, int lineNumber) throws CoreException {
if (editorPart instanceof ITextEditor && lineNumber >= 0) {
final ITextEditor textEditor = (ITextEditor) editorPart;
final IDocumentProvider provider = textEditor.getDocumentProvider();
final IEditorInput input = editorPart.getEditorInput();
provider.connect(input);
final IDocument document = provider.getDocument(input);
try {
final IRegion line = document.getLineInformation(lineNumber);
textEditor.selectAndReveal(line.getOffset(), line.getLength());
} catch (BadLocationException e) {
}
provider.disconnect(input);
}
}
/**
* Selects and reveals the given region in the given editor part.
*/
public static void revealInEditor(IEditorPart part, IRegion region) {
if (part != null && region != null)
revealInEditor(part, region.getOffset(), region.getLength());
}
/**
* Selects and reveals the given offset and length in the given editor part.
*/
public static void revealInEditor(IEditorPart editor, final int offset, final int length) {
if (editor instanceof ITextEditor) {
((ITextEditor) editor).selectAndReveal(offset, length);
return;
}
// Support for non-text editor - try IGotoMarker interface
if (editor instanceof IGotoMarker) {
final IEditorInput input = editor.getEditorInput();
if (input instanceof IFileEditorInput) {
final IGotoMarker gotoMarkerTarget = (IGotoMarker) editor;
WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
@Override
protected void execute(IProgressMonitor monitor) throws CoreException {
IMarker marker = null;
try {
marker = ((IFileEditorInput) input).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$
}
} else if (input instanceof ExternalStorageEditorInput) {
System.err.println("TODO: Add external storage editor input reveal..."); //$NON-NLS-1$
}
return;
}
/*
* Workaround: send out a text selection XXX: Needs to be improved, see
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=32214
*/
if (editor != null && editor.getEditorSite().getSelectionProvider() != null) {
IEditorSite site = editor.getEditorSite();
if (site == null)
return;
ISelectionProvider provider = editor.getEditorSite().getSelectionProvider();
if (provider == null)
return;
provider.setSelection(new TextSelection(offset, length));
}
}
private static IEditorPart openInEditor(IFile file, boolean activate) throws PartInitException {
if (file != null) {
IWorkbenchPage p = DLTKUIPlugin.getActivePage();
if (p != null) {
IEditorPart editorPart = IDE.openEditor(p, file, activate);
initializeHighlightRange(editorPart);
return editorPart;
}
}
return null;
}
private static IEditorPart openInEditor(IEditorInput input, String editorID, boolean activate)
throws PartInitException {
if (input != null) {
IWorkbenchPage p = DLTKUIPlugin.getActivePage();
if (p != null) {
IEditorPart editorPart = p.openEditor(input, editorID, activate);
initializeHighlightRange(editorPart);
return editorPart;
}
}
return null;
}
private static void initializeHighlightRange(IEditorPart editorPart) {
if (editorPart instanceof ITextEditor) {
IAction toggleAction = editorPart.getEditorSite().getActionBars()
.getGlobalActionHandler(ITextEditorActionDefinitionIds.TOGGLE_SHOW_SELECTED_ELEMENT_ONLY);
boolean enable = toggleAction != null;
// if (enable && editorPart instanceof Editor)
// enable=
// DLTKUIPlugin.getDefault().getPreferenceStore().getBoolean(
// PreferenceConstants.EDITOR_SHOW_SEGMENTS);
// else
if (DLTKCore.DEBUG) {
System.err.println("Add initializeHighlightRange support of preferences."); //$NON-NLS-1$
}
enable = enable && toggleAction.isEnabled() && toggleAction.isChecked();
if (enable) {
if (toggleAction instanceof TextEditorAction) {
// Reset the action
((TextEditorAction) toggleAction).setEditor(null);
// Restore the action
((TextEditorAction) toggleAction).setEditor((ITextEditor) editorPart);
} else {
// Un-check
toggleAction.run();
// Check
toggleAction.run();
}
}
}
}
/**
* Tests if a CU is currently shown in an editor
*
* @return the IEditorPart if shown, null if element is not open in an editor
*/
public static IEditorPart isOpenInEditor(Object inputElement) {
IEditorInput input = getEditorInput(inputElement);
if (input != null) {
IWorkbenchPage p = DLTKUIPlugin.getActivePage();
if (p != null) {
return p.findEditor(input);
}
}
return null;
}
public static IEditorPart[] getDirtyEditors() {
Set<IEditorInput> inputs = new HashSet<>();
List<IEditorPart> result = new ArrayList<>(0);
IWorkbench workbench = PlatformUI.getWorkbench();
IWorkbenchWindow[] windows = workbench.getWorkbenchWindows();
for (int i = 0; i < windows.length; i++) {
IWorkbenchPage[] pages = windows[i].getPages();
for (int x = 0; x < pages.length; x++) {
IEditorPart[] editors = pages[x].getDirtyEditors();
for (int z = 0; z < editors.length; z++) {
IEditorPart ep = editors[z];
IEditorInput input = ep.getEditorInput();
if (!inputs.contains(input)) {
inputs.add(input);
result.add(ep);
}
}
}
}
return result.toArray(new IEditorPart[result.size()]);
}
/**
* If the current active editor edits ascriptelement return it, else return null
*/
public static IModelElement getActiveEditorModelInput() {
IWorkbenchPage page = DLTKUIPlugin.getActivePage();
if (page != null) {
IEditorPart part = page.getActiveEditor();
if (part != null) {
IEditorInput editorInput = part.getEditorInput();
if (editorInput != null) {
return DLTKUIPlugin.getEditorInputModelElement(editorInput);
}
}
}
return null;
}
/**
* Appends to modifier string of the given SWT modifier bit to the given
* modifierString.
*
* @param modifierString the modifier string
* @param modifier an int with SWT modifier bit
* @return the concatenated modifier string
*
*/
private static String appendModifierString(String modifierString, int modifier) {
if (modifierString == null)
modifierString = ""; //$NON-NLS-1$
String newModifierString = Action.findModifierString(modifier);
if (modifierString.length() == 0)
return newModifierString;
return Messages.format(DLTKEditorMessages.EditorUtility_concatModifierStrings, modifierString,
newModifierString);
}
/**
* Returns the modifier string for the given SWT modifier modifier bits.
*
* @param stateMask the SWT modifier bits
* @return the modifier string
*
*/
public static String getModifierString(int stateMask) {
String modifierString = ""; //$NON-NLS-1$
if ((stateMask & SWT.CTRL) == SWT.CTRL)
modifierString = appendModifierString(modifierString, SWT.CTRL);
if ((stateMask & SWT.ALT) == SWT.ALT)
modifierString = appendModifierString(modifierString, SWT.ALT);
if ((stateMask & SWT.SHIFT) == SWT.SHIFT)
modifierString = appendModifierString(modifierString, SWT.SHIFT);
if ((stateMask & SWT.COMMAND) == SWT.COMMAND)
modifierString = appendModifierString(modifierString, SWT.COMMAND);
return modifierString;
}
/**
* Maps the localized modifier name to a code in the same manner as
* #findModifier.
*
* @param modifierName the modifier name
* @return the SWT modifier bit, or <code>0</code> if no match was found
*
*/
public static int findLocalizedModifier(String modifierName) {
if (modifierName == null)
return 0;
if (modifierName.equalsIgnoreCase(Action.findModifierString(SWT.CTRL)))
return SWT.CTRL;
if (modifierName.equalsIgnoreCase(Action.findModifierString(SWT.SHIFT)))
return SWT.SHIFT;
if (modifierName.equalsIgnoreCase(Action.findModifierString(SWT.ALT)))
return SWT.ALT;
if (modifierName.equalsIgnoreCase(Action.findModifierString(SWT.COMMAND)))
return SWT.COMMAND;
return 0;
}
/**
* Return the regions of all lines which have changed in the given buffer since
* the last save occurred. Each region in the result spans over the size of at
* least one line. If successive lines have changed a region spans over the size
* of all successive lines. The regions include line delimiters.
*
* @param buffer the buffer to compare contents from
* @param monitor to report progress to
* @return the regions of the changed lines
* @throws CoreException if something goes wrong
* @since 3.0
*/
public static IRegion[] calculateChangedLineRegions(final ITextFileBuffer buffer, final IProgressMonitor monitor)
throws CoreException {
final IRegion[][] result = new IRegion[1][];
final IStatus[] errorStatus = new IStatus[] { Status.OK_STATUS };
try {
SafeRunner.run(new ISafeRunnable() {
@Override
public void handleException(Throwable exception) {
DLTKUIPlugin.log(new Status(IStatus.ERROR, DLTKUIPlugin.PLUGIN_ID,
IDLTKStatusConstants.EDITOR_CHANGED_REGION_CALCULATION, exception.getLocalizedMessage(),
exception));
String msg = DLTKEditorMessages.CompilationUnitDocumentProvider_error_calculatingChangedRegions;
errorStatus[0] = new Status(IStatus.ERROR, DLTKUIPlugin.PLUGIN_ID,
IDLTKStatusConstants.EDITOR_CHANGED_REGION_CALCULATION, msg, exception);
result[0] = null;
}
@Override
public void run() throws Exception {
monitor.beginTask(
DLTKEditorMessages.CompilationUnitDocumentProvider_calculatingChangedRegions_message, 20);
IFileStore fileStore = buffer.getFileStore();
ITextFileBufferManager fileBufferManager = FileBuffers.createTextFileBufferManager();
fileBufferManager.connectFileStore(fileStore, getSubProgressMonitor(monitor, 15));
try {
IDocument currentDocument = buffer.getDocument();
IDocument oldDocument = ((ITextFileBuffer) fileBufferManager.getFileStoreFileBuffer(fileStore))
.getDocument();
result[0] = getChangedLineRegions(oldDocument, currentDocument);
} finally {
fileBufferManager.disconnectFileStore(fileStore, getSubProgressMonitor(monitor, 5));
monitor.done();
}
}
/**
* Return regions of all lines which differ comparing <code>oldDocument</code>s
* content with <code>currentDocument</code>s content. Successive lines are
* merged into one region.
*
* @param oldDocument a document containing the old content
* @param currentDocument a document containing the current content
* @return the changed regions
* @throws BadLocationException if fetching the line information fails
*/
private IRegion[] getChangedLineRegions(IDocument oldDocument, IDocument currentDocument)
throws BadLocationException {
/*
* Do not change the type of those local variables. We use Object here in order
* to prevent loading of the Compare plug-in at load time of this class.
*/
Object leftSide = new LineComparator(oldDocument);
Object rightSide = new LineComparator(currentDocument);
RangeDifference[] differences = RangeDifferencer.findDifferences((IRangeComparator) leftSide,
(IRangeComparator) rightSide);
// It holds that:
// 1. Ranges are sorted:
// forAll r1,r2 element differences: indexOf(r1)<indexOf(r2)
// -> r1.rightStart()<r2.rightStart();
// 2. Successive changed lines are merged into on
// RangeDifference
// forAll r1,r2 element differences:
// r1.rightStart()<r2.rightStart() ->
// r1.rightEnd()<r2.rightStart
ArrayList<IRegion> regions = new ArrayList<>();
for (int i = 0; i < differences.length; i++) {
RangeDifference curr = differences[i];
if (curr.kind() == RangeDifference.CHANGE && curr.rightLength() > 0) {
int startLine = curr.rightStart();
int endLine = curr.rightEnd() - 1;
IRegion startLineRegion = currentDocument.getLineInformation(startLine);
if (startLine == endLine) {
regions.add(startLineRegion);
} else {
IRegion endLineRegion = currentDocument.getLineInformation(endLine);
int startOffset = startLineRegion.getOffset();
int endOffset = endLineRegion.getOffset() + endLineRegion.getLength();
regions.add(new Region(startOffset, endOffset - startOffset));
}
}
}
return regions.toArray(new IRegion[regions.size()]);
}
});
} finally {
if (!errorStatus[0].isOK())
throw new CoreException(errorStatus[0]);
}
return result[0];
}
/**
* Creates and returns a new sub-progress monitor for the given parent monitor.
*
* @param monitor the parent progress monitor
* @param ticks the number of work ticks allocated from the parent monitor
* @return the new sub-progress monitor
* @since 3.0
*/
private static IProgressMonitor getSubProgressMonitor(IProgressMonitor monitor, int ticks) {
if (monitor != null)
return new SubProgressMonitor(monitor, ticks, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK);
return new NullProgressMonitor();
}
}