/******************************************************************************* * Copyright (c) 2000, 2016 QNX Software Systems 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 * * Contributors: * QNX Software Systems - Initial API and implementation * Anton Leherbauer, Wind River Systems, Inc. * Red Hat Inc. - convert to use with Automake editor *******************************************************************************/ package org.eclipse.cdt.internal.autotools.ui.editors.automake; import java.util.ResourceBundle; import org.eclipse.cdt.autotools.core.AutotoolsPlugin; import org.eclipse.cdt.autotools.ui.AutotoolsUIPlugin; import org.eclipse.cdt.internal.autotools.ui.MakeUIMessages; import org.eclipse.cdt.make.core.makefile.IDirective; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.ListenerList; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.FindReplaceDocumentAdapter; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextOperationTarget; import org.eclipse.jface.text.rules.IWordDetector; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.text.source.IVerticalRuler; import org.eclipse.jface.text.source.SourceViewerConfiguration; import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel; 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.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.IPartService; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.editors.text.EditorsUI; import org.eclipse.ui.editors.text.TextEditor; import org.eclipse.ui.texteditor.ChainedPreferenceStore; import org.eclipse.ui.texteditor.DefaultRangeIndicator; import org.eclipse.ui.texteditor.ITextEditorActionConstants; import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; import org.eclipse.ui.texteditor.TextOperationAction; import org.eclipse.ui.views.contentoutline.IContentOutlinePage; public class MakefileEditor extends TextEditor implements ISelectionChangedListener, IReconcilingParticipant { /** * The page that shows the outline. */ protected MakefileContentOutlinePage page; ProjectionSupport projectionSupport; ProjectionMakefileUpdater fProjectionMakefileUpdater; private FindReplaceDocumentAdapter fFindReplaceDocumentAdapter; /** * Reconciling listeners. * @since 3.0 */ private ListenerList<IReconcilingParticipant> fReconcilingListeners = new ListenerList<>(ListenerList.IDENTITY); MakefileSourceConfiguration getMakefileSourceConfiguration() { SourceViewerConfiguration configuration = getSourceViewerConfiguration(); if (configuration instanceof MakefileSourceConfiguration) { return (MakefileSourceConfiguration)configuration; } return null; } public MakefileContentOutlinePage getOutlinePage() { if (page == null) { page = new MakefileContentOutlinePage(this); page.addSelectionChangedListener(this); page.setInput(getEditorInput()); } return page; } public MakefileEditor() { super(); } @Override protected void initializeEditor() { setRangeIndicator(new DefaultRangeIndicator()); setEditorContextMenuId("#MakefileEditorContext"); //$NON-NLS-1$ setRulerContextMenuId("#MakefileRulerContext"); //$NON-NLS-1$ setDocumentProvider(AutomakeEditorFactory.getDefault().getAutomakefileDocumentProvider()); IPreferenceStore[] stores = new IPreferenceStore[2]; stores[0] = AutotoolsPlugin.getDefault().getPreferenceStore(); stores[1] = EditorsUI.getPreferenceStore(); ChainedPreferenceStore chainedStore = new ChainedPreferenceStore(stores); setPreferenceStore(chainedStore); setSourceViewerConfiguration(new MakefileSourceConfiguration(chainedStore, this)); } @Override public void dispose() { if (fProjectionMakefileUpdater != null) { fProjectionMakefileUpdater.uninstall(); fProjectionMakefileUpdater= null; } super.dispose(); } boolean isFoldingEnabled() { return AutotoolsPlugin.getDefault().getPreferenceStore().getBoolean(MakefileEditorPreferenceConstants.EDITOR_FOLDING_ENABLED); } @Override protected boolean isTabsToSpacesConversionEnabled() { // always false for Makefiles // see http://bugs.eclipse.org/186106 return false; } @Override public void createPartControl(Composite parent) { super.createPartControl(parent); ProjectionViewer projectionViewer = (ProjectionViewer) getSourceViewer(); projectionSupport = new ProjectionSupport(projectionViewer, getAnnotationAccess(), getSharedColors()); projectionSupport.addSummarizableAnnotationType("org.eclipse.ui.workbench.texteditor.error"); //$NON-NLS-1$ projectionSupport.addSummarizableAnnotationType("org.eclipse.ui.workbench.texteditor.warning"); //$NON-NLS-1$ projectionSupport.install(); if (isFoldingEnabled()) { projectionViewer.doOperation(ProjectionViewer.TOGGLE); } // ProjectionAnnotationModel model= (ProjectionAnnotationModel) getAdapter(ProjectionAnnotationModel.class); fProjectionMakefileUpdater= new ProjectionMakefileUpdater(); if (fProjectionMakefileUpdater != null) { fProjectionMakefileUpdater.install(this, projectionViewer); fProjectionMakefileUpdater.initialize(); } } @Override protected ISourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, int styles) { ISourceViewer viewer = new ProjectionViewer(parent, ruler, getOverviewRuler(), isOverviewRulerVisible(), styles); // ensure decoration support has been created and configured. getSourceViewerDecorationSupport(viewer); return viewer; } @SuppressWarnings("unchecked") @Override public <T> T getAdapter(Class<T> key) { if (ProjectionAnnotationModel.class.equals(key)) { if (projectionSupport != null) { Object result = projectionSupport.getAdapter(getSourceViewer(), key); if (result != null) { return (T) result; } } } else if (key.equals(IContentOutlinePage.class)) { return (T) getOutlinePage(); } return super.getAdapter(key); } @Override public void doSave(IProgressMonitor monitor) { super.doSave(monitor); if (page != null) { page.update(); } } @Override protected void createActions() { super.createActions(); ResourceBundle bundle = MakeUIMessages.getResourceBundle(); IAction a = new TextOperationAction(bundle, "ContentAssistProposal.", this, ISourceViewer.CONTENTASSIST_PROPOSALS); //$NON-NLS-1$ a.setActionDefinitionId(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS); setAction("ContentAssistProposal", a); //$NON-NLS-1$ a = new TextOperationAction(bundle, "ContentAssistTip.", this, ISourceViewer.CONTENTASSIST_CONTEXT_INFORMATION); //$NON-NLS-1$ a.setActionDefinitionId(ITextEditorActionDefinitionIds.CONTENT_ASSIST_CONTEXT_INFORMATION); setAction("ContentAssistTip", a); //$NON-NLS-1$ a = new TextOperationAction(bundle, "Comment.", this, ITextOperationTarget.PREFIX); //$NON-NLS-1$ a.setActionDefinitionId(IMakefileEditorActionDefinitionIds.COMMENT); setAction("Comment", a); //$NON-NLS-1$ markAsStateDependentAction("Comment", true); //$NON-NLS-1$ a = new TextOperationAction(bundle, "Uncomment.", this, ITextOperationTarget.STRIP_PREFIX); //$NON-NLS-1$ a.setActionDefinitionId(IMakefileEditorActionDefinitionIds.UNCOMMENT); setAction("Uncomment", a); //$NON-NLS-1$ markAsStateDependentAction("Uncomment", true); //$NON-NLS-1$ a = new OpenDeclarationAction(this); a.setActionDefinitionId(IMakefileEditorActionDefinitionIds.OPEN_DECLARATION); setAction("OpenDeclarationAction", a); //$NON-NLS-1$ markAsStateDependentAction("OpenDeclarationAction", true); //$NON-NLS-1$ } @Override public void selectionChanged(SelectionChangedEvent event) { ISelection selection = event.getSelection(); if (selection.isEmpty()) { resetHighlightRange(); } else if (selection instanceof IStructuredSelection){ if (!isActivePart() && AutotoolsUIPlugin.getActivePage() != null) { AutotoolsUIPlugin.getActivePage().bringToTop(this); } Object element = ((IStructuredSelection) selection).getFirstElement(); if (element instanceof IDirective) { IDirective statement = (IDirective)element; setSelection(statement, !isActivePart()); } } } /** * Returns whether the editor is active. */ private boolean isActivePart() { IWorkbenchWindow window= getSite().getWorkbenchWindow(); IPartService service= window.getPartService(); IWorkbenchPart part= service.getActivePart(); return part != null && part.equals(this); } /** * Returns the find/replace document adapter. * * @return the find/replace document adapter. */ private FindReplaceDocumentAdapter getFindRepalceDocumentAdapter() { if (fFindReplaceDocumentAdapter == null) { IDocument doc = getDocumentProvider().getDocument(getEditorInput()); fFindReplaceDocumentAdapter= new FindReplaceDocumentAdapter(doc); } return fFindReplaceDocumentAdapter; } public void setSelection(IDirective directive, boolean moveCursor) { int startLine = directive.getStartLine() - 1; int endLine = directive.getEndLine() - 1; try { IDocument doc = getDocumentProvider().getDocument(getEditorInput()); int start = doc.getLineOffset(startLine); int len = doc.getLineLength(endLine) - 1; int length = (doc.getLineOffset(endLine) + len) - start; setHighlightRange(start, length, true); if (moveCursor) { // Let see if we can move the cursor at the position also String var = directive.toString().trim(); IWordDetector detector = new MakefileWordDetector(); for (len = 0; len < var.length(); len++) { char c = var.charAt(len); //if (! (Character.isLetterOrDigit(c) || c == '.' || c == '_')) { if (!(detector.isWordPart(c) || detector.isWordStart(c) || c == '-' || c == '_')) { break; } } if (len > 0) { var = var.substring(0, len); } IRegion region = getFindRepalceDocumentAdapter().find(start, var, true, true, true, false); if (region != null) { len = region.getOffset(); length = region.getLength(); getSourceViewer().revealRange(len, length); // Selected region begins one index after offset getSourceViewer().setSelectedRange(len, length); } } } catch (IllegalArgumentException x) { resetHighlightRange(); } catch (BadLocationException e) { resetHighlightRange(); } } @Override protected void editorContextMenuAboutToShow(IMenuManager menu) { super.editorContextMenuAboutToShow(menu); addAction(menu, ITextEditorActionConstants.GROUP_EDIT, "Comment"); //$NON-NLS-1$ addAction(menu, ITextEditorActionConstants.GROUP_EDIT, "Uncomment"); //$NON-NLS-1$ //addAction(menu, ITextEditorActionConstants.GROUP_EDIT, "OpenDeclarationAction"); //$NON-NLS-1$ } /** * Adds the given listener. * Has no effect if an identical listener was not already registered. * * @param listener The reconcile listener to be added * @since 3.0 */ final void addReconcilingParticipant(IReconcilingParticipant listener) { synchronized (fReconcilingListeners) { fReconcilingListeners.add(listener); } } /** * Removes the given listener. * Has no effect if an identical listener was not already registered. * * @param listener the reconcile listener to be removed * @since 3.0 */ final void removeReconcilingParticipant(IReconcilingParticipant listener) { synchronized (fReconcilingListeners) { fReconcilingListeners.remove(listener); } } @Override public void reconciled() { // Notify listeners for (IReconcilingParticipant listener: fReconcilingListeners) { listener.reconciled(); } } @Override protected void performRevert() { ProjectionViewer projectionViewer= (ProjectionViewer) getSourceViewer(); projectionViewer.setRedraw(false); try { boolean projectionMode= projectionViewer.isProjectionMode(); if (projectionMode) { projectionViewer.disableProjection(); if (fProjectionMakefileUpdater != null) fProjectionMakefileUpdater.uninstall(); } super.performRevert(); if (projectionMode) { if (fProjectionMakefileUpdater != null) fProjectionMakefileUpdater.install(this, projectionViewer); projectionViewer.enableProjection(); } } finally { projectionViewer.setRedraw(true); } } @Override protected void handlePreferenceStoreChanged(PropertyChangeEvent event) { ISourceViewer sourceViewer= getSourceViewer(); if (sourceViewer == null) return; String property = event.getProperty(); MakefileSourceConfiguration makeConf = getMakefileSourceConfiguration(); if (makeConf != null) { if (makeConf.affectsBehavior(event)) { makeConf.adaptToPreferenceChange(event); sourceViewer.invalidateTextPresentation(); } } if (MakefileEditorPreferenceConstants.EDITOR_FOLDING_ENABLED.equals(property)) { if (sourceViewer instanceof ProjectionViewer) { ProjectionViewer projectionViewer= (ProjectionViewer) sourceViewer; if (fProjectionMakefileUpdater != null) fProjectionMakefileUpdater.uninstall(); // either freshly enabled or provider changed fProjectionMakefileUpdater= new ProjectionMakefileUpdater(); if (fProjectionMakefileUpdater != null) { fProjectionMakefileUpdater.install(this, projectionViewer); } } return; } super.handlePreferenceStoreChanged(event); } @Override protected String[] collectContextMenuPreferencePages() { // Add Makefile Editor relevant pages String[] parentPrefPageIds = super.collectContextMenuPreferencePages(); String[] prefPageIds = new String[parentPrefPageIds.length + 2]; int nIds = 0; prefPageIds[nIds++] = "org.eclipse.cdt.make.ui.preferences.MakeFileEditorPreferencePage"; //$NON-NLS-1$ prefPageIds[nIds++] = "org.eclipse.cdt.make.ui.preferences.MakefileSettingPreferencePage"; //$NON-NLS-1$ System.arraycopy(parentPrefPageIds, 0, prefPageIds, nIds, parentPrefPageIds.length); return prefPageIds; } }