/******************************************************************************* * Copyright (c) 2002, 2015 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 * Red Hat Inc. - convert to use with Autoconf Editor * Ed Swartz (NOKIA) - refactoring *******************************************************************************/ package org.eclipse.cdt.internal.autotools.ui.editors.autoconf; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.cdt.autotools.core.AutotoolsPlugin; import org.eclipse.cdt.autotools.ui.editors.AutoconfEditor; import org.eclipse.cdt.autotools.ui.editors.parser.AutoconfCaseElement; import org.eclipse.cdt.autotools.ui.editors.parser.AutoconfElement; import org.eclipse.cdt.autotools.ui.editors.parser.AutoconfElifElement; import org.eclipse.cdt.autotools.ui.editors.parser.AutoconfElseElement; import org.eclipse.cdt.autotools.ui.editors.parser.AutoconfForElement; import org.eclipse.cdt.autotools.ui.editors.parser.AutoconfIfElement; import org.eclipse.cdt.autotools.ui.editors.parser.AutoconfMacroArgumentElement; import org.eclipse.cdt.autotools.ui.editors.parser.AutoconfMacroElement; import org.eclipse.cdt.autotools.ui.editors.parser.AutoconfRootElement; import org.eclipse.cdt.autotools.ui.editors.parser.AutoconfSelectElement; import org.eclipse.cdt.autotools.ui.editors.parser.AutoconfUntilElement; import org.eclipse.cdt.autotools.ui.editors.parser.AutoconfWhileElement; import org.eclipse.cdt.internal.autotools.ui.editors.automake.IReconcilingParticipant; import org.eclipse.cdt.internal.autotools.ui.preferences.AutotoolsEditorPreferenceConstants; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.source.Annotation; import org.eclipse.jface.text.source.IAnnotationModel; import org.eclipse.jface.text.source.projection.IProjectionListener; import org.eclipse.jface.text.source.projection.ProjectionAnnotation; import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel; import org.eclipse.jface.text.source.projection.ProjectionViewer; import org.eclipse.ui.texteditor.IDocumentProvider; /** * ProjectionMakefileUpdater */ public class ProjectionFileUpdater implements IProjectionListener { private static class AutoconfProjectionAnnotation extends ProjectionAnnotation { private AutoconfElement fElement; private boolean fIsComment; public AutoconfProjectionAnnotation(AutoconfElement element, boolean isCollapsed, boolean isComment) { super(isCollapsed); fElement = element; fIsComment = isComment; } public AutoconfElement getElement() { return fElement; } public void setElement(AutoconfElement element) { fElement = element; } public boolean isComment() { return fIsComment; } } public void install(AutoconfEditor editor, ProjectionViewer viewer) { fEditor= editor; fViewer= viewer; fViewer.addProjectionListener(this); } public void uninstall() { if (isInstalled()) { projectionDisabled(); fViewer.removeProjectionListener(this); fViewer= null; fEditor= null; } } protected boolean isInstalled() { return fEditor != null; } private class ReconcilerParticipant implements IReconcilingParticipant { @Override public void reconciled() { processReconcile(); } } private IDocument fCachedDocument; private AutoconfEditor fEditor; private ProjectionViewer fViewer; private IReconcilingParticipant fParticipant; private boolean fAllowCollapsing = false; private boolean fCollapseMacroDef = false; private boolean fCollapseCase = false; private boolean fCollapseConditional = false; private boolean fCollapseLoop = false; @Override public void projectionEnabled() { // http://home.ott.oti.com/teams/wswb/anon/out/vms/index.html // projectionEnabled messages are not always paired with projectionDisabled // i.e. multiple enabled messages may be sent out. // we have to make sure that we disable first when getting an enable // message. projectionDisabled(); initialize(); fParticipant= new ReconcilerParticipant(); fEditor.addReconcilingParticipant(fParticipant); } @Override public void projectionDisabled() { fCachedDocument= null; if (fParticipant != null) { fEditor.addReconcilingParticipant(fParticipant); fParticipant= null; } } public void initialize() { if (!isInstalled()) return; initializePreferences(); try { IDocumentProvider provider= fEditor.getDocumentProvider(); fCachedDocument= provider.getDocument(fEditor.getEditorInput()); fAllowCollapsing= true; // IWorkingCopyManager manager= AutomakeEditorFactory.getDefault().getWorkingCopyManager(); AutoconfElement fInput= fEditor.getRootElement(); if (fInput != null) { ProjectionAnnotationModel model = fEditor.getAdapter(ProjectionAnnotationModel.class); if (model != null) { Map<AutoconfProjectionAnnotation, Position> additions= computeAdditions(fInput); model.removeAllAnnotations(); model.replaceAnnotations(null, additions); } } } finally { fCachedDocument= null; fAllowCollapsing= false; } } private void initializePreferences() { //FIXME: what to do with Makefile editor preferences IPreferenceStore store = AutotoolsPlugin.getDefault().getPreferenceStore(); fCollapseMacroDef = store.getBoolean(AutotoolsEditorPreferenceConstants.EDITOR_FOLDING_MACRODEF); fCollapseCase = store.getBoolean(AutotoolsEditorPreferenceConstants.EDITOR_FOLDING_CASE); fCollapseConditional = store.getBoolean(AutotoolsEditorPreferenceConstants.EDITOR_FOLDING_CONDITIONAL); fCollapseLoop = store.getBoolean(AutotoolsEditorPreferenceConstants.EDITOR_FOLDING_LOOP); } private Map<AutoconfProjectionAnnotation, Position> computeAdditions(AutoconfElement root) { Map<AutoconfProjectionAnnotation, Position> map= new HashMap<>(); if (root instanceof AutoconfRootElement) computeAdditions(root.getChildren(), map); return map; } private void computeAdditions(Object[] elements, Map<AutoconfProjectionAnnotation, Position> map) { for (int i= 0; i < elements.length; i++) { AutoconfElement element= (AutoconfElement)elements[i]; computeAdditions(element, map); if (element.hasChildren()) { computeAdditions(element.getChildren(), map); } } } private void computeAdditions(AutoconfElement element, Map<AutoconfProjectionAnnotation, Position> map) { boolean createProjection= false; @SuppressWarnings("unused") boolean collapse= false; if (element instanceof AutoconfIfElement || element instanceof AutoconfElseElement || element instanceof AutoconfElifElement) { collapse= fAllowCollapsing && fCollapseConditional; createProjection= true; } else if (element instanceof AutoconfMacroElement) { collapse= fAllowCollapsing && fCollapseMacroDef; createProjection= true; } else if (element instanceof AutoconfMacroArgumentElement) { collapse= fAllowCollapsing && fCollapseMacroDef; createProjection= true; } else if (element instanceof AutoconfCaseElement) { collapse= fAllowCollapsing && fCollapseCase; createProjection= true; } else if (element instanceof AutoconfForElement || element instanceof AutoconfWhileElement || element instanceof AutoconfUntilElement || element instanceof AutoconfSelectElement) { collapse = fAllowCollapsing && fCollapseLoop; createProjection = true; } if (createProjection) { Position position= createProjectionPosition(element); if (position != null) { map.put(new AutoconfProjectionAnnotation(element, fAllowCollapsing, true), position); } } } private Position createProjectionPosition(AutoconfElement element) { if (fCachedDocument == null) return null; int offset = 0; try { int startLine = 0; int endLine = 0; startLine = fCachedDocument.getLineOfOffset(element.getStartOffset()); endLine = fCachedDocument.getLineOfOffset(element.getEndOffset()); if (startLine != endLine) { offset= fCachedDocument.getLineOffset(startLine); int endOffset = fCachedDocument.getLineOffset(endLine) + fCachedDocument.getLineLength(endLine); return new Position(offset, endOffset - offset); } } catch (BadLocationException x) { // We should only get here if we try and read the line past EOF return new Position(offset, fCachedDocument.getLength() - 1); } return null; } public void processReconcile() { if (!isInstalled()) return; ProjectionAnnotationModel model= fEditor.getAdapter(ProjectionAnnotationModel.class); if (model == null) return; try { IDocumentProvider provider= fEditor.getDocumentProvider(); fCachedDocument= provider.getDocument(fEditor.getEditorInput()); fAllowCollapsing= false; Map<AutoconfProjectionAnnotation, Position> additions= new HashMap<>(); List<AutoconfProjectionAnnotation> deletions= new ArrayList<>(); List<AutoconfProjectionAnnotation> updates = new ArrayList<>(); Map<AutoconfProjectionAnnotation, Position> updated= computeAdditions(fEditor.getRootElement()); Map<AutoconfElement, List<AutoconfProjectionAnnotation>> previous= createAnnotationMap(model); Iterator<AutoconfProjectionAnnotation> e= updated.keySet().iterator(); while (e.hasNext()) { AutoconfProjectionAnnotation annotation= e.next(); AutoconfElement element= annotation.getElement(); Position position= updated.get(annotation); List<AutoconfProjectionAnnotation> annotations= previous.get(element); if (annotations == null) { additions.put(annotation, position); } else { Iterator<AutoconfProjectionAnnotation> x= annotations.iterator(); while (x.hasNext()) { AutoconfProjectionAnnotation a= x.next(); if (annotation.isComment() == a.isComment()) { Position p= model.getPosition(a); if (p != null && !position.equals(p)) { p.setOffset(position.getOffset()); p.setLength(position.getLength()); updates.add(a); } x.remove(); break; } } if (annotations.isEmpty()) previous.remove(element); } } Iterator<List<AutoconfProjectionAnnotation>> e2 = previous.values().iterator(); while (e2.hasNext()) { List<AutoconfProjectionAnnotation> list= e2.next(); int size= list.size(); for (int i= 0; i < size; i++) deletions.add(list.get(i)); } match(model, deletions, additions, updates); Annotation[] removals= new Annotation[deletions.size()]; deletions.toArray(removals); Annotation[] changes= new Annotation[updates.size()]; updates.toArray(changes); model.modifyAnnotations(removals, additions, changes); } finally { fCachedDocument= null; fAllowCollapsing= true; } } private void match(ProjectionAnnotationModel model, List<AutoconfProjectionAnnotation> deletions, Map<AutoconfProjectionAnnotation, Position> additions, List<AutoconfProjectionAnnotation> changes) { if (deletions.isEmpty() || (additions.isEmpty() && changes.isEmpty())) return; List<AutoconfProjectionAnnotation> newDeletions= new ArrayList<>(); List<AutoconfProjectionAnnotation> newChanges= new ArrayList<>(); Iterator<AutoconfProjectionAnnotation> deletionIterator= deletions.iterator(); outer: while (deletionIterator.hasNext()) { AutoconfProjectionAnnotation deleted= deletionIterator.next(); Position deletedPosition= model.getPosition(deleted); if (deletedPosition == null) continue; Iterator<AutoconfProjectionAnnotation> changesIterator= changes.iterator(); while (changesIterator.hasNext()) { AutoconfProjectionAnnotation changed= changesIterator.next(); if (deleted.isComment() == changed.isComment()) { Position changedPosition= model.getPosition(changed); if (changedPosition == null) continue; if (deletedPosition.getOffset() == changedPosition.getOffset()) { deletedPosition.setLength(changedPosition.getLength()); deleted.setElement(changed.getElement()); deletionIterator.remove(); newChanges.add(deleted); changesIterator.remove(); newDeletions.add(changed); continue outer; } } } Iterator<AutoconfProjectionAnnotation> additionsIterator= additions.keySet().iterator(); while (additionsIterator.hasNext()) { AutoconfProjectionAnnotation added= additionsIterator.next(); if (deleted.isComment() == added.isComment()) { Position addedPosition= additions.get(added); if (deletedPosition.getOffset() == addedPosition.getOffset()) { deletedPosition.setLength(addedPosition.getLength()); deleted.setElement(added.getElement()); deletionIterator.remove(); newChanges.add(deleted); additionsIterator.remove(); break; } } } } deletions.addAll(newDeletions); changes.addAll(newChanges); } private Map<AutoconfElement, List<AutoconfProjectionAnnotation>> createAnnotationMap(IAnnotationModel model) { Map<AutoconfElement, List<AutoconfProjectionAnnotation>> map= new HashMap<>(); Iterator<Annotation> e = model.getAnnotationIterator(); while (e.hasNext()) { Annotation annotation = e.next(); if (annotation instanceof AutoconfProjectionAnnotation) { AutoconfProjectionAnnotation directive= (AutoconfProjectionAnnotation) annotation; List<AutoconfProjectionAnnotation> list= map.get(directive.getElement()); if (list == null) { list= new ArrayList<>(2); map.put(directive.getElement(), list); } list.add(directive); } } return map; } }