/*******************************************************************************
* Copyright (c) 2014, 2015 Cisco Systems, Inc. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*
*******************************************************************************/
package com.cisco.yangide.editor.editors;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IStorage;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jdt.internal.core.JarEntryFile;
import org.eclipse.jdt.internal.ui.javaeditor.JarEntryEditorInput;
import org.eclipse.jdt.ui.text.IColorManager;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension3;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.source.DefaultCharacterPairMatcher;
import org.eclipse.jface.text.source.ICharacterPairMatcher;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.ISourceViewerExtension2;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.text.source.SourceViewerConfiguration;
import org.eclipse.jface.text.source.projection.IProjectionListener;
import org.eclipse.jface.text.source.projection.ProjectionSupport;
import org.eclipse.jface.text.source.projection.ProjectionViewer;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.IPostSelectionProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionContext;
import org.eclipse.ui.actions.ActionGroup;
import org.eclipse.ui.editors.text.EditorsUI;
import org.eclipse.ui.editors.text.ITextEditorHelpContextIds;
import org.eclipse.ui.editors.text.TextEditor;
import org.eclipse.ui.texteditor.ChainedPreferenceStore;
import org.eclipse.ui.texteditor.ITextEditorActionConstants;
import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
import org.eclipse.ui.texteditor.TextOperationAction;
import org.eclipse.ui.views.contentoutline.ContentOutline;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
import com.cisco.yangide.core.YangCorePlugin;
import com.cisco.yangide.core.YangJarFileEntryResource;
import com.cisco.yangide.core.YangModelException;
import com.cisco.yangide.core.dom.ASTNamedNode;
import com.cisco.yangide.core.dom.ASTNode;
import com.cisco.yangide.core.dom.Module;
import com.cisco.yangide.core.model.YangModelManager;
import com.cisco.yangide.core.parser.YangParserUtil;
import com.cisco.yangide.editor.YangEditorPlugin;
import com.cisco.yangide.editor.actions.AddBlockCommentAction;
import com.cisco.yangide.editor.actions.IYangEditorActionDefinitionIds;
import com.cisco.yangide.editor.actions.OpenDeclarationAction;
import com.cisco.yangide.editor.actions.RemoveBlockCommentAction;
import com.cisco.yangide.editor.actions.ToggleCommentAction;
import com.cisco.yangide.editor.editors.text.YangFoldingStructureProvider;
import com.cisco.yangide.ui.YangUIPlugin;
import com.cisco.yangide.ui.preferences.IYangColorConstants;
/**
* Editor class
*
* @author Alexey Kholupko
*/
@SuppressWarnings("restriction")
public class YangEditor extends TextEditor implements IProjectionListener, IYangEditor {
public final static String EDITOR_ID = "com.cisco.yangide.editor.editors.YANGEditor";
// TODO extract logic to separate classes
public final static String EDITOR_MATCHING_BRACKETS = "matchingBrackets";
private IColorManager colorManager;
private ProjectionSupport projectionSupport;
private YangFoldingStructureProvider fFoldingStructureProvider;
private SemanticHighlightingManager fSemanticManager;
private YangEditorSelectionChangedListener editorSelectionChangedListener;
private YangOutlineSelectionChangedListener outlineSelectionChangedListener = new YangOutlineSelectionChangedListener();
private YangContentOutlinePage outlinePage;
private List<ActionGroup> actionGroups = new ArrayList<ActionGroup>();
private List<IReconcileHandler> reconcileHandlers = new ArrayList<>();
private abstract class AbstractSelectionChangedListener implements ISelectionChangedListener {
public void install(ISelectionProvider selectionProvider) {
try {
if (selectionProvider == null || getModule() == null) {
return;
}
} catch (YangModelException e) {
return;
}
if (selectionProvider instanceof IPostSelectionProvider) {
IPostSelectionProvider provider = (IPostSelectionProvider) selectionProvider;
provider.addPostSelectionChangedListener(this);
} else {
selectionProvider.addSelectionChangedListener(this);
}
}
public void uninstall(ISelectionProvider selectionProvider) {
try {
if (selectionProvider == null || getModule() == null) {
return;
}
} catch (YangModelException e) {
return;
}
if (selectionProvider instanceof IPostSelectionProvider) {
IPostSelectionProvider provider = (IPostSelectionProvider) selectionProvider;
provider.removePostSelectionChangedListener(this);
} else {
selectionProvider.removeSelectionChangedListener(this);
}
}
}
private class YangEditorSelectionChangedListener extends AbstractSelectionChangedListener {
@Override
public void selectionChanged(SelectionChangedEvent event) {
if (event.getSelection() instanceof ITextSelection) {
ITextSelection textSelection = (ITextSelection) event.getSelection();
try {
if (null != outlinePage) {
outlinePage.selectNode(getModule().getNodeAtPosition(textSelection.getOffset()));
}
} catch (YangModelException e) {
}
}
}
}
private class YangOutlineSelectionChangedListener extends AbstractSelectionChangedListener {
@Override
public void selectionChanged(SelectionChangedEvent event) {
if (isYangOutlinePageActive() && event.getSelection() instanceof IStructuredSelection) {
Object element = ((IStructuredSelection) event.getSelection()).getFirstElement();
if (element instanceof ASTNode) {
IRegion selection = getSelectionRegion((ASTNode) element);
if (selection != null) {
selectAndReveal(selection.getOffset(), selection.getLength());
}
}
}
}
}
/**
* Gets length and offset for selection of the provided {@code node}.
* @param node an ASTNode that specifies offset and length of selection
* @return an IRegion that corresponds to the given {@code node} or null if it was null
*/
public static IRegion getSelectionRegion(ASTNode node) {
if (node != null) {
if (node instanceof ASTNamedNode) {
ASTNamedNode namedNode = (ASTNamedNode) node;
return new Region(namedNode.getNameStartPosition(), namedNode.getNameLength());
} else {
return new Region(node.getStartPosition(), node.getLength());
}
}
return null;
}
public YangEditor() {
super();
colorManager = new YangColorManager(false);
setSourceViewerConfiguration(new YangSourceViewerConfiguration(YangEditorPlugin.getDefault()
.getCombinedPreferenceStore(), colorManager, this));
setDocumentProvider(new YangDocumentProvider());
}
@Override
protected void initializeEditor() {
setCompatibilityMode(false);
setEditorContextMenuId("#TextEditorContext"); //$NON-NLS-1$
setRulerContextMenuId("#TextRulerContext"); //$NON-NLS-1$
setHelpContextId(ITextEditorHelpContextIds.TEXT_EDITOR);
configureInsertMode(SMART_INSERT, true);
setInsertMode(INSERT);
setPreferenceStore(YangEditorPlugin.getDefault().getCombinedPreferenceStore());
}
@Override
public void dispose() {
if (editorSelectionChangedListener != null) {
editorSelectionChangedListener.uninstall(getSelectionProvider());
editorSelectionChangedListener = null;
}
colorManager.dispose();
for (ActionGroup actionGroup : actionGroups) {
actionGroup.dispose();
}
actionGroups.clear();
super.dispose();
uninstallSemanticHighlighting();
IEditorInput input = getEditorInput();
// revert index to file content instead of editor
if (input != null && input instanceof IFileEditorInput) {
IFile file = ((IFileEditorInput) input).getFile();
if (file != null) {
try {
YangModelManager.getYangModelManager().removeInfoAndChildren(YangCorePlugin.createYangFile(file));
YangModelManager.getIndexManager().addWorkingCopy(file);
} catch (YangModelException e) {
// ignore exception
}
}
}
}
/**
* @return the actionGroups
*/
public List<ActionGroup> getActionGroups() {
return actionGroups;
}
@Override
protected void doSetInput(IEditorInput input) throws CoreException {
ISourceViewer sourceViewer = getSourceViewer();
if (!(sourceViewer instanceof ISourceViewerExtension2)) {
setPreferenceStore(createCombinedPreferenceStore(input));
internalDoSetInput(input);
return;
}
// uninstall & unregister preference store listener
getSourceViewerDecorationSupport(sourceViewer).uninstall();
((ISourceViewerExtension2) sourceViewer).unconfigure();
setPreferenceStore(createCombinedPreferenceStore(input));
// install & register preference store listener
sourceViewer.configure(getSourceViewerConfiguration());
getSourceViewerDecorationSupport(sourceViewer).install(getPreferenceStore());
internalDoSetInput(input);
}
private void internalDoSetInput(IEditorInput input) throws CoreException {
super.doSetInput(input);
if (fEncodingSupport != null) {
fEncodingSupport.reset();
}
}
/**
* Creates and returns the preference store for this YANG editor with the given input.
*/
private IPreferenceStore createCombinedPreferenceStore(IEditorInput input) {
List<IPreferenceStore> stores = new ArrayList<IPreferenceStore>(3);
stores.add(YangUIPlugin.getDefault().getPreferenceStore());
stores.add(EditorsUI.getPreferenceStore());
stores.add(PlatformUI.getPreferenceStore());
return new ChainedPreferenceStore(stores.toArray(new IPreferenceStore[stores.size()]));
}
@Override
protected void configureSourceViewerDecorationSupport(SourceViewerDecorationSupport support) {
// Enable bracket highlighting in the preference store
IPreferenceStore store = YangUIPlugin.getDefault().getPreferenceStore();
store.setDefault(EDITOR_MATCHING_BRACKETS, true);
char[] matchChars = { '{', '}', '(', ')', '[', ']' }; // which brackets to match
ICharacterPairMatcher matcher = new DefaultCharacterPairMatcher(matchChars,
IDocumentExtension3.DEFAULT_PARTITIONING);
support.setCharacterPairMatcher(matcher);
support.setMatchingCharacterPainterPreferenceKeys(EDITOR_MATCHING_BRACKETS,
IYangColorConstants.EDITOR_MATCHING_BRACKETS_COLOR);
super.configureSourceViewerDecorationSupport(support);
}
@Override
protected void handlePreferenceStoreChanged(PropertyChangeEvent event) {
((YangSourceViewerConfiguration) getSourceViewerConfiguration()).handlePropertyChangeEvent(event);
getSourceViewer().invalidateTextPresentation();
super.handlePreferenceStoreChanged(event);
}
@Override
protected void initializeKeyBindingScopes() {
setKeyBindingScopes(new String[] { "com.cisco.yangide.ui.Context" }); //$NON-NLS-1$
}
@Override
public ISelectionProvider getSelectionProvider() {
return getSourceViewer().getSelectionProvider();
}
public IDocument getDocument() {
return getSourceViewer().getDocument();
}
@Override
protected void createActions() {
super.createActions();
IAction action = null;
ResourceBundle bundle = ResourceBundle.getBundle(YangEditorMessages.getBundleName());
action = new TextOperationAction(bundle, "ContentFormat_", this, ISourceViewer.FORMAT); //$NON-NLS-1$
action.setActionDefinitionId(IYangEditorActionDefinitionIds.FORMAT);
setAction("FormatDocument", action); //$NON-NLS-1$
action = getAction(ITextEditorActionConstants.CONTENT_ASSIST_CONTEXT_INFORMATION);
action = new OpenDeclarationAction(bundle, "OpenDeclaration_", this); //$NON-NLS-1$
action.setActionDefinitionId(IYangEditorActionDefinitionIds.OPEN_DECLARATION);
setAction("OpenDeclaration", action); //$NON-NLS-1$
markAsStateDependentAction("OpenDeclaration", true); //$NON-NLS-1$
markAsSelectionDependentAction("OpenDeclaration", true); //$NON-NLS-1$
action = new ToggleCommentAction(bundle, "ToggleComment_", this); //$NON-NLS-1$
action.setActionDefinitionId(IYangEditorActionDefinitionIds.TOGGLE_COMMENT);
setAction("ToggleComment", action); //$NON-NLS-1$
markAsStateDependentAction("ToggleComment", true); //$NON-NLS-1$
configureToggleCommentAction();
action = new AddBlockCommentAction(bundle, "AddBlockComment_", this); //$NON-NLS-1$
action.setActionDefinitionId(IYangEditorActionDefinitionIds.ADD_BLOCK_COMMENT);
setAction("AddBlockComment", action); //$NON-NLS-1$
markAsStateDependentAction("AddBlockComment", true); //$NON-NLS-1$
markAsSelectionDependentAction("AddBlockComment", true); //$NON-NLS-1$
action = new RemoveBlockCommentAction(bundle, "RemoveBlockComment_", this); //$NON-NLS-1$
action.setActionDefinitionId(IYangEditorActionDefinitionIds.REMOVE_BLOCK_COMMENT);
setAction("RemoveBlockComment", action); //$NON-NLS-1$
markAsStateDependentAction("RemoveBlockComment", true); //$NON-NLS-1$
markAsSelectionDependentAction("RemoveBlockComment", true); //$NON-NLS-1$
IConfigurationElement[] configs = Platform.getExtensionRegistry().getConfigurationElementsFor(
"com.cisco.yangide.editor.actionGroup");
for (IConfigurationElement config : configs) {
try {
IActionGroup actionGroup = (IActionGroup) config.createExecutableExtension("class");
actionGroup.init(this, config.getAttribute("groupName"));
actionGroups.add((ActionGroup) actionGroup);
} catch (CoreException e) {
YangEditorPlugin.log(e);
}
}
}
private void configureToggleCommentAction() {
IAction action = getAction("ToggleComment"); //$NON-NLS-1$
if (action instanceof ToggleCommentAction) {
ISourceViewer sourceViewer = getSourceViewer();
SourceViewerConfiguration configuration = getSourceViewerConfiguration();
((ToggleCommentAction) action).configure(sourceViewer, configuration);
}
}
@Override
public void createPartControl(Composite parent) {
super.createPartControl(parent);
ProjectionViewer projectionviewer = (ProjectionViewer) getSourceViewer();
projectionviewer.addProjectionListener(this);
projectionSupport = new ProjectionSupport(projectionviewer, getAnnotationAccess(), getSharedColors());
projectionSupport.install();
editorSelectionChangedListener = new YangEditorSelectionChangedListener();
editorSelectionChangedListener.install(getSelectionProvider());
// turn projection mode on
projectionviewer.doOperation(ProjectionViewer.TOGGLE);
SemanticHighlightings.initDefaults(YangUIPlugin.getDefault().getPreferenceStore());
installSemanticHighlighting();
}
@Override
protected ISourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, int styles) {
fAnnotationAccess = getAnnotationAccess();
fOverviewRuler = createOverviewRuler(getSharedColors());
ISourceViewer viewer = new YangSourceViewer(parent, ruler, getOverviewRuler(), isOverviewRulerVisible(), styles);
// ensure decoration support has been created and configured.
getSourceViewerDecorationSupport(viewer);
if (fFoldingStructureProvider != null) {
fFoldingStructureProvider.setDocument(getDocumentProvider().getDocument(getEditorInput()));
}
return viewer;
}
@Override
public void projectionEnabled() {
safelySanityCheckState(getEditorInput());
firePropertyChange(IEditorPart.PROP_INPUT);
fFoldingStructureProvider = new YangFoldingStructureProvider(this);
fFoldingStructureProvider.setDocument(getDocumentProvider().getDocument(getEditorInput()));
Module module = YangParserUtil.parseYangFile(getDocumentProvider().getDocument(getEditorInput()).get()
.toCharArray());
if (module != null) {
fFoldingStructureProvider.updateFoldingRegions(module);
}
// IPreferenceStore preferenceStore = AntUIPlugin.getDefault().getPreferenceStore();
// preferenceStore.setValue(AntEditorPreferenceConstants.EDITOR_FOLDING_ENABLED, true);
}
@Override
public void projectionDisabled() {
fFoldingStructureProvider = null;
// IPreferenceStore preferenceStore = AntUIPlugin.getDefault().getPreferenceStore();
// preferenceStore.setValue(AntEditorPreferenceConstants.EDITOR_FOLDING_ENABLED, false);
}
@Override
protected void editorContextMenuAboutToShow(IMenuManager menu) {
super.editorContextMenuAboutToShow(menu);
ActionContext context = new ActionContext(getSelectionProvider().getSelection());
for (ActionGroup actionGroup : actionGroups) {
actionGroup.setContext(context);
actionGroup.fillContextMenu(menu);
actionGroup.setContext(null);
}
}
@Override
public Object getAdapter(@SuppressWarnings("rawtypes") Class key) {
if (IContentOutlinePage.class.equals(key)) {
return getYangOulinePage();
}
if (projectionSupport != null) {
Object adapter = projectionSupport.getAdapter(getSourceViewer(), key);
if (adapter != null) {
return adapter;
}
}
return super.getAdapter(key);
}
private boolean isYangOutlinePageActive() {
IWorkbenchPart part = getSite().getWorkbenchWindow().getPartService().getActivePart();
return part instanceof ContentOutline && ((ContentOutline) part).getCurrentPage() == outlinePage;
}
private YangContentOutlinePage getYangOulinePage() {
if (null == outlinePage) {
outlinePage = new YangContentOutlinePage(this);
outlineSelectionChangedListener.install(outlinePage);
}
return outlinePage;
}
public void reconcile() {
updateOutline();
updateFoldingRegions();
for (IReconcileHandler reconcileHandler : reconcileHandlers) {
reconcileHandler.reconcile();
}
}
private void updateFoldingRegions() {
try {
if (fFoldingStructureProvider != null) {
fFoldingStructureProvider.updateFoldingRegions(getModule());
}
} catch (YangModelException e) {
YangUIPlugin.log(e);
}
}
private void updateOutline() {
if (null != outlinePage) {
outlinePage.updateOutline();
}
}
public void updateSemanticHigliting() {
if (fSemanticManager != null) {
fSemanticManager.getReconciler().refresh();
}
}
/**
* @return {@link Module} of the current editor input or <code>null</code> if editor input does
* not contains appropriate {@link Module}
* @throws YangModelException error during initialization of Module
*/
public Module getModule() throws YangModelException {
IEditorInput input = getEditorInput();
if (input == null) {
return null;
}
if (input instanceof IFileEditorInput) {
IFile file = ((IFileEditorInput) input).getFile();
return YangCorePlugin.createYangFile(file).getModule();
} else if (input instanceof JarEntryEditorInput) {
JarEntryEditorInput jarInput = (JarEntryEditorInput) input;
IStorage storage = jarInput.getStorage();
if (storage instanceof YangJarFileEntryResource) {
YangJarFileEntryResource jarEntry = (YangJarFileEntryResource) storage;
return YangCorePlugin.createJarEntry(jarEntry.getPath(), jarEntry.getEntry()).getModule();
} else if (storage instanceof JarEntryFile) {
JarEntryFile jarEntry = (JarEntryFile) storage;
return YangCorePlugin.createJarEntry(jarEntry.getPackageFragmentRoot().getPath(),
jarEntry.getFullPath().makeRelative().toString()).getModule();
}
}
return null;
}
public ISourceViewer getViewer() {
return this.getSourceViewer();
}
protected void installSemanticHighlighting() {
if (fSemanticManager == null) {
fSemanticManager = new SemanticHighlightingManager();
fSemanticManager.install(this, (SourceViewer) getSourceViewer(), colorManager, getPreferenceStore());
}
}
private void uninstallSemanticHighlighting() {
if (fSemanticManager != null) {
fSemanticManager.uninstall();
fSemanticManager = null;
}
}
public void installOccurrencesFinder(boolean b) {
// TODO implement mark occurrence functionality
}
public boolean isMarkingOccurrences() {
return false;
}
public void uninstallOccurrencesFinder() {
}
public void reconcileModel() {
((YangSourceViewer) getSourceViewer()).getReconciler().getReconcilingStrategy("").reconcile(null);
}
public void addReconcileHandler(IReconcileHandler reconcileHandler) {
reconcileHandlers.add(reconcileHandler);
}
public void removeReconcileHandler(IReconcileHandler reconcileHandler) {
reconcileHandlers.remove(reconcileHandler);
}
}