/*
* Copyright (c) 2012, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.tools.ui.internal.compare;
import com.google.dart.tools.core.DartCore;
import com.google.dart.tools.ui.DartToolsPlugin;
import com.google.dart.tools.ui.internal.text.editor.CompilationUnitEditor;
import com.google.dart.tools.ui.internal.text.editor.DartEditor.EclipsePreferencesAdapter;
import com.google.dart.tools.ui.text.DartPartitions;
import com.google.dart.tools.ui.text.DartSourceViewerConfiguration;
import com.google.dart.tools.ui.text.DartTextTools;
import org.eclipse.compare.CompareConfiguration;
import org.eclipse.compare.IResourceProvider;
import org.eclipse.compare.ITypedElement;
import org.eclipse.compare.contentmergeviewer.TextMergeViewer;
import org.eclipse.compare.structuremergeviewer.ICompareInput;
import org.eclipse.compare.structuremergeviewer.IDiffContainer;
import org.eclipse.compare.structuremergeviewer.IDiffElement;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentPartitioner;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.text.source.CompositeRuler;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IPageListener;
import org.eclipse.ui.IStorageEditorInput;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.actions.PartEventAction;
import org.eclipse.ui.contexts.IContextService;
import org.eclipse.ui.editors.text.EditorsUI;
import org.eclipse.ui.texteditor.AbstractTextEditor;
import org.eclipse.ui.texteditor.ChainedPreferenceStore;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.ui.texteditor.ITextEditorActionConstants;
import org.eclipse.ui.texteditor.ITextEditorExtension3;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
public class DartMergeViewer extends TextMergeViewer {
private class CompilationUnitEditorAdapter extends CompilationUnitEditor {
private boolean fInputSet = false;
private int fTextOrientation;
private boolean fEditable;
CompilationUnitEditorAdapter(int textOrientation) {
super();
fTextOrientation = textOrientation;
// TODO: has to be set here
setPreferenceStore(createChainedPreferenceStore(null));
}
@Override
public void close(boolean save) {
getDocumentProvider().disconnect(getEditorInput());
}
@Override
public void createActions() {
if (fInputSet) {
super.createActions();
// to avoid handler conflicts disable extra actions
// we're not handling by CompareHandlerService
// TODO(scheglov) do we need this?
// getCorrectionCommands().deregisterCommands();
getRefactorActionGroup().dispose();
// getGenerateActionGroup().dispose();
}
// else do nothing, we will create actions later, when input is available
}
@Override
public void createPartControl(Composite composite) {
SourceViewer sourceViewer = (SourceViewer) createDartSourceViewer(
composite,
new CompositeRuler(),
null,
false,
fTextOrientation | SWT.H_SCROLL | SWT.V_SCROLL,
createChainedPreferenceStore(null));
setSourceViewer(this, sourceViewer);
createNavigationActions();
getSelectionProvider().addSelectionChangedListener(getSelectionChangedListener());
}
@Override
public IWorkbenchPartSite getSite() {
return DartMergeViewer.this.getSite();
}
// called by org.eclipse.ui.texteditor.TextEditorAction.canModifyEditor()
@Override
public boolean isEditable() {
return fEditable;
}
@Override
public boolean isEditorInputModifiable() {
return fEditable;
}
@Override
public boolean isEditorInputReadOnly() {
return !fEditable;
}
@Override
protected void doSetInput(IEditorInput input) throws CoreException {
super.doSetInput(input);
// the editor input has been explicitly set
fInputSet = true;
}
// TODO(scheglov) no such method in super
// @Override
// protected void setActionsActivated(boolean state) {
// super.setActionsActivated(state);
// }
private void setEditable(boolean editable) {
fEditable = editable;
}
}
private IPropertyChangeListener fPreferenceChangeListener;
private IPreferenceStore fPreferenceStore;
private Map<SourceViewer, DartSourceViewerConfiguration> fSourceViewerConfiguration;
private Map<SourceViewer, CompilationUnitEditorAdapter> fEditor;
private ArrayList<SourceViewer> fSourceViewer;
private IWorkbenchPartSite fSite;
public DartMergeViewer(Composite parent, int styles, CompareConfiguration mp) {
super(parent, styles | SWT.LEFT_TO_RIGHT, mp);
}
// TODO(scheglov) will improve
// @Override
// public ITokenComparator createTokenComparator(String s) {
// return new DartTokenComparator(s, new ITokenComparatorFactory() {
// @Override
// public ITokenComparator createTokenComparator(String text) {
// return DartMergeViewer.super.createTokenComparator(text);
// }
// });
// }
@Override
@SuppressWarnings("rawtypes")
public Object getAdapter(Class adapter) {
if (adapter == ITextEditorExtension3.class) {
IEditorInput activeInput = (IEditorInput) super.getAdapter(IEditorInput.class);
if (activeInput != null) {
for (Iterator<CompilationUnitEditorAdapter> iterator = fEditor.values().iterator(); iterator.hasNext();) {
CompilationUnitEditorAdapter editor = iterator.next();
if (activeInput.equals(editor.getEditorInput())) {
return editor;
}
}
}
return null;
}
return super.getAdapter(adapter);
}
public IProject getDartProject(ICompareInput input) {
if (input == null) {
return null;
}
IResourceProvider rp = null;
ITypedElement te = input.getLeft();
if (te instanceof IResourceProvider) {
rp = (IResourceProvider) te;
}
if (rp == null) {
te = input.getRight();
if (te instanceof IResourceProvider) {
rp = (IResourceProvider) te;
}
}
if (rp == null) {
te = input.getAncestor();
if (te instanceof IResourceProvider) {
rp = (IResourceProvider) te;
}
}
return null;
}
@Override
public String getTitle() {
return CompareMessages.DartMergeViewer_title;
}
@Override
public void setInput(Object input) {
if (input instanceof ICompareInput) {
IProject project = getDartProject((ICompareInput) input);
if (project != null) {
setPreferenceStore(createChainedPreferenceStore(project));
}
}
super.setInput(input);
}
@Override
protected void configureTextViewer(TextViewer viewer) {
if (viewer instanceof SourceViewer) {
SourceViewer sourceViewer = (SourceViewer) viewer;
if (fSourceViewer == null) {
fSourceViewer = new ArrayList<SourceViewer>();
}
if (!fSourceViewer.contains(sourceViewer)) {
fSourceViewer.add(sourceViewer);
}
DartTextTools tools = DartCompareUtilities.getDartTextTools();
if (tools != null) {
IEditorInput editorInput = getEditorInput(sourceViewer);
sourceViewer.unconfigure();
if (editorInput == null) {
sourceViewer.configure(getSourceViewerConfiguration(sourceViewer, null));
return;
}
getSourceViewerConfiguration(sourceViewer, editorInput);
}
}
}
@Override
protected void createControls(Composite composite) {
super.createControls(composite);
IWorkbenchPart workbenchPart = getCompareConfiguration().getContainer().getWorkbenchPart();
if (workbenchPart != null) {
IContextService service = (IContextService) workbenchPart.getSite().getService(
IContextService.class);
if (service != null) {
service.activateContext("com.google.dart.tools.ui.dartEditorScope"); //$NON-NLS-1$
}
}
}
@Override
protected SourceViewer createSourceViewer(Composite parent, int textOrientation) {
SourceViewer sourceViewer;
if (getSite() != null) {
CompilationUnitEditorAdapter editor = new CompilationUnitEditorAdapter(textOrientation);
editor.createPartControl(parent);
ISourceViewer iSourceViewer = editor.getViewer();
Assert.isTrue(iSourceViewer instanceof SourceViewer);
sourceViewer = (SourceViewer) iSourceViewer;
if (fEditor == null) {
fEditor = new HashMap<SourceViewer, CompilationUnitEditorAdapter>(3);
}
fEditor.put(sourceViewer, editor);
} else {
sourceViewer = super.createSourceViewer(parent, textOrientation);
}
if (fSourceViewer == null) {
fSourceViewer = new ArrayList<SourceViewer>();
}
fSourceViewer.add(sourceViewer);
return sourceViewer;
}
@Override
protected int findInsertionPosition(char type, ICompareInput input) {
int pos = super.findInsertionPosition(type, input);
if (pos != 0) {
return pos;
}
if (input instanceof IDiffElement) {
// find the other (not deleted) element
DartNode otherDartElement = null;
ITypedElement otherElement = null;
switch (type) {
case 'L':
otherElement = input.getRight();
break;
case 'R':
otherElement = input.getLeft();
break;
}
if (otherElement instanceof DartNode) {
otherDartElement = (DartNode) otherElement;
}
// find the parent of the deleted elements
DartNode dartContainer = null;
IDiffElement diffElement = (IDiffElement) input;
IDiffContainer container = diffElement.getParent();
if (container instanceof ICompareInput) {
ICompareInput parent = (ICompareInput) container;
ITypedElement element = null;
switch (type) {
case 'L':
element = parent.getLeft();
break;
case 'R':
element = parent.getRight();
break;
}
if (element instanceof DartNode) {
dartContainer = (DartNode) element;
}
}
if (otherDartElement != null && dartContainer != null) {
Object[] children;
Position p;
switch (otherDartElement.getTypeCode()) {
case DartNode.PACKAGE:
return 0;
case DartNode.IMPORT_CONTAINER:
// we have to find the place after the package declaration
children = dartContainer.getChildren();
if (children.length > 0) {
DartNode packageDecl = null;
for (int i = 0; i < children.length; i++) {
DartNode child = (DartNode) children[i];
switch (child.getTypeCode()) {
case DartNode.PACKAGE:
packageDecl = child;
break;
case DartNode.CLASS:
return child.getRange().getOffset();
}
}
if (packageDecl != null) {
p = packageDecl.getRange();
return p.getOffset() + p.getLength();
}
}
return dartContainer.getRange().getOffset();
case DartNode.IMPORT:
// append after last import
p = dartContainer.getRange();
return p.getOffset() + p.getLength();
case DartNode.CLASS:
// append after last class
children = dartContainer.getChildren();
if (children.length > 0) {
for (int i = children.length - 1; i >= 0; i--) {
DartNode child = (DartNode) children[i];
switch (child.getTypeCode()) {
case DartNode.CLASS:
case DartNode.IMPORT_CONTAINER:
case DartNode.PACKAGE:
case DartNode.FIELD:
p = child.getRange();
return p.getOffset() + p.getLength();
}
}
}
return dartContainer.getAppendPosition().getOffset();
case DartNode.METHOD:
// append in next line after last child
children = dartContainer.getChildren();
if (children.length > 0) {
DartNode child = (DartNode) children[children.length - 1];
p = child.getRange();
return findEndOfLine(dartContainer, p.getOffset() + p.getLength());
}
// otherwise use position from parser
return dartContainer.getAppendPosition().getOffset();
case DartNode.FIELD:
// append after last field
children = dartContainer.getChildren();
if (children.length > 0) {
DartNode method = null;
for (int i = children.length - 1; i >= 0; i--) {
DartNode child = (DartNode) children[i];
switch (child.getTypeCode()) {
case DartNode.METHOD:
method = child;
break;
case DartNode.FIELD:
p = child.getRange();
return p.getOffset() + p.getLength();
}
}
if (method != null) {
return method.getRange().getOffset();
}
}
return dartContainer.getAppendPosition().getOffset();
}
}
if (dartContainer != null) {
// return end of container
Position p = dartContainer.getRange();
return p.getOffset() + p.getLength();
}
}
// we give up
return 0;
}
@Override
protected IDocumentPartitioner getDocumentPartitioner() {
return DartCompareUtilities.createDartPartitioner();
}
@Override
protected String getDocumentPartitioning() {
return DartPartitions.DART_PARTITIONING;
}
@Override
protected IEditorInput getEditorInput(ISourceViewer sourceViewer) {
IEditorInput editorInput = super.getEditorInput(sourceViewer);
if (editorInput == null) {
return null;
}
if (getSite() == null) {
return null;
}
if (!(editorInput instanceof IStorageEditorInput)) {
return null;
}
return editorInput;
}
@Override
protected void handleDispose(DisposeEvent event) {
setPreferenceStore(null);
fSourceViewer = null;
if (fEditor != null) {
for (Iterator<CompilationUnitEditorAdapter> iterator = fEditor.values().iterator(); iterator.hasNext();) {
CompilationUnitEditorAdapter editor = iterator.next();
editor.dispose();
}
fEditor = null;
}
fSite = null;
super.handleDispose(event);
}
@Override
protected boolean isEditorBacked(ITextViewer textViewer) {
return getSite() != null;
}
@Override
protected void setActionsActivated(SourceViewer sourceViewer, boolean state) {
if (fEditor != null) {
Object editor = fEditor.get(sourceViewer);
if (editor instanceof CompilationUnitEditorAdapter) {
CompilationUnitEditorAdapter cuea = (CompilationUnitEditorAdapter) editor;
// cuea.setActionsActivated(state);
IAction saveAction = cuea.getAction(ITextEditorActionConstants.SAVE);
if (saveAction instanceof IPageListener) {
PartEventAction partEventAction = (PartEventAction) saveAction;
IWorkbenchPart compareEditorPart = getCompareConfiguration().getContainer().getWorkbenchPart();
if (state) {
partEventAction.partActivated(compareEditorPart);
} else {
partEventAction.partDeactivated(compareEditorPart);
}
}
}
}
}
@Override
protected void setEditable(ISourceViewer sourceViewer, boolean state) {
super.setEditable(sourceViewer, state);
if (fEditor != null) {
Object editor = fEditor.get(sourceViewer);
if (editor instanceof CompilationUnitEditorAdapter) {
((CompilationUnitEditorAdapter) editor).setEditable(state);
}
}
}
private ChainedPreferenceStore createChainedPreferenceStore(IProject project) {
ArrayList<IPreferenceStore> stores = new ArrayList<IPreferenceStore>(4);
if (project != null) {
stores.add(new EclipsePreferencesAdapter(new ProjectScope(project), DartCore.PLUGIN_ID));
}
stores.add(DartToolsPlugin.getDefault().getPreferenceStore());
// stores.add(new PreferencesAdapter(DartToolsPlugin.getDartCorePluginPreferences()));
stores.add(EditorsUI.getPreferenceStore());
return new ChainedPreferenceStore(stores.toArray(new IPreferenceStore[stores.size()]));
}
private int findEndOfLine(DartNode container, int pos) {
int line;
IDocument doc = container.getDocument();
try {
line = doc.getLineOfOffset(pos);
pos = doc.getLineOffset(line + 1);
} catch (BadLocationException ex) {
// silently ignored
}
// ensure that position is within container range
Position containerRange = container.getRange();
int start = containerRange.getOffset();
int end = containerRange.getOffset() + containerRange.getLength();
if (pos < start) {
return start;
}
if (pos >= end) {
return end - 1;
}
return pos;
}
private IPreferenceStore getPreferenceStore() {
if (fPreferenceStore == null) {
setPreferenceStore(createChainedPreferenceStore(null));
}
return fPreferenceStore;
}
private IWorkbenchPartSite getSite() {
if (fSite == null) {
IWorkbenchPart workbenchPart = getCompareConfiguration().getContainer().getWorkbenchPart();
fSite = workbenchPart != null ? workbenchPart.getSite() : null;
}
return fSite;
}
private DartSourceViewerConfiguration getSourceViewerConfiguration(SourceViewer sourceViewer,
IEditorInput editorInput) {
if (fSourceViewerConfiguration == null) {
fSourceViewerConfiguration = new HashMap<SourceViewer, DartSourceViewerConfiguration>(3);
}
if (fPreferenceStore == null) {
getPreferenceStore();
}
DartTextTools tools = DartCompareUtilities.getDartTextTools();
DartSourceViewerConfiguration configuration = new DartSourceViewerConfiguration(
tools.getColorManager(),
fPreferenceStore,
null,
getDocumentPartitioning());
if (editorInput != null) {
// when input available, use editor
CompilationUnitEditorAdapter editor = fEditor.get(sourceViewer);
try {
editor.init((IEditorSite) editor.getSite(), editorInput);
editor.createActions();
configuration = new DartSourceViewerConfiguration(
tools.getColorManager(),
fPreferenceStore,
editor,
getDocumentPartitioning());
} catch (PartInitException e) {
DartToolsPlugin.log(e);
}
}
fSourceViewerConfiguration.put(sourceViewer, configuration);
return fSourceViewerConfiguration.get(sourceViewer);
}
private void handlePropertyChange(PropertyChangeEvent event) {
if (fSourceViewerConfiguration != null) {
for (Iterator<Entry<SourceViewer, DartSourceViewerConfiguration>> iterator = fSourceViewerConfiguration.entrySet().iterator(); iterator.hasNext();) {
Entry<SourceViewer, DartSourceViewerConfiguration> entry = iterator.next();
DartSourceViewerConfiguration configuration = entry.getValue();
if (configuration.affectsTextPresentation(event)) {
configuration.handlePropertyChangeEvent(event);
ITextViewer viewer = entry.getKey();
viewer.invalidateTextPresentation();
}
}
}
}
private void setPreferenceStore(IPreferenceStore ps) {
if (fPreferenceChangeListener != null) {
if (fPreferenceStore != null) {
fPreferenceStore.removePropertyChangeListener(fPreferenceChangeListener);
}
fPreferenceChangeListener = null;
}
fPreferenceStore = ps;
if (fPreferenceStore != null) {
fPreferenceChangeListener = new IPropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent event) {
handlePropertyChange(event);
}
};
fPreferenceStore.addPropertyChangeListener(fPreferenceChangeListener);
}
}
// no setter to private field AbstractTextEditor.fSourceViewer
private void setSourceViewer(ITextEditor editor, SourceViewer viewer) {
Field field = null;
try {
field = AbstractTextEditor.class.getDeclaredField("fSourceViewer"); //$NON-NLS-1$
} catch (SecurityException ex) {
DartToolsPlugin.log(ex);
} catch (NoSuchFieldException ex) {
DartToolsPlugin.log(ex);
}
field.setAccessible(true);
try {
field.set(editor, viewer);
} catch (IllegalArgumentException ex) {
DartToolsPlugin.log(ex);
} catch (IllegalAccessException ex) {
DartToolsPlugin.log(ex);
}
}
}