/******************************************************************************* * Copyright (c) 2002, 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) * Sergey Prigogin (Google) *******************************************************************************/ package org.eclipse.cdt.internal.ui.editor; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.core.filebuffers.ITextFileBuffer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.ListenerList; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DefaultLineTracker; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentExtension3; import org.eclipse.jface.text.ILineTracker; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITypedRegion; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.TextUtilities; import org.eclipse.jface.text.source.Annotation; import org.eclipse.jface.text.source.AnnotationModelEvent; import org.eclipse.jface.text.source.IAnnotationModel; import org.eclipse.jface.text.source.IAnnotationModelListener; import org.eclipse.jface.text.source.IAnnotationModelListenerExtension; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.text.edits.DeleteEdit; import org.eclipse.text.edits.InsertEdit; import org.eclipse.text.edits.MalformedTreeException; import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.TextEdit; import org.eclipse.text.undo.DocumentUndoManagerRegistry; import org.eclipse.text.undo.IDocumentUndoManager; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.editors.text.ForwardingDocumentProvider; import org.eclipse.ui.editors.text.ILocationProvider; import org.eclipse.ui.editors.text.ILocationProviderExtension; import org.eclipse.ui.editors.text.TextFileDocumentProvider; import org.eclipse.ui.texteditor.IDocumentProvider; import org.eclipse.ui.texteditor.IMarkerUpdater; import org.eclipse.ui.texteditor.MarkerAnnotation; import org.eclipse.ui.texteditor.MarkerUtilities; import org.eclipse.ui.texteditor.ResourceMarkerAnnotationModel; import org.eclipse.ui.texteditor.spelling.SpellingAnnotation; import org.eclipse.cdt.core.ToolFactory; import org.eclipse.cdt.core.dom.ast.IASTNode; import org.eclipse.cdt.core.formatter.CodeFormatter; import org.eclipse.cdt.core.formatter.DefaultCodeFormatterConstants; import org.eclipse.cdt.core.model.CoreModel; import org.eclipse.cdt.core.model.ICModelMarker; import org.eclipse.cdt.core.model.ICProject; import org.eclipse.cdt.core.model.IProblemRequestor; import org.eclipse.cdt.core.model.ITranslationUnit; import org.eclipse.cdt.core.model.IWorkingCopy; import org.eclipse.cdt.core.parser.IPersistableProblem; import org.eclipse.cdt.core.parser.IProblem; import org.eclipse.cdt.ui.CDTUITools; import org.eclipse.cdt.ui.CUIPlugin; import org.eclipse.cdt.ui.PreferenceConstants; import org.eclipse.cdt.ui.text.ICPartitions; import org.eclipse.cdt.internal.core.model.TranslationUnit; import org.eclipse.cdt.internal.ui.text.IProblemRequestorExtension; import org.eclipse.cdt.internal.ui.text.spelling.CoreSpellingProblem; import org.eclipse.cdt.internal.ui.util.EditorUtility; /** * A document provider for C/C++ content. */ public class CDocumentProvider extends TextFileDocumentProvider { /** * Bundle of all required informations to allow working copy management. */ static protected class TranslationUnitInfo extends FileInfo { public IWorkingCopy fCopy; } /** * Annotation representing an {@code IProblem}. */ public static class ProblemAnnotation extends Annotation implements ICAnnotation { private static final String INDEXER_ANNOTATION_TYPE= "org.eclipse.cdt.ui.indexmarker"; //$NON-NLS-1$ private final ITranslationUnit fTranslationUnit; private final int fId; private final boolean fIsProblem; private final String[] fArguments; private final String fMarkerType; private List<ICAnnotation> fOverlaids; public ProblemAnnotation(IProblem problem, ITranslationUnit tu) { fTranslationUnit= tu; setText(problem.getMessage()); fId= problem.getID(); fIsProblem= problem.isError() || problem.isWarning(); fArguments= isProblem() ? problem.getArguments() : null; setType(problem instanceof CoreSpellingProblem ? SpellingAnnotation.TYPE : INDEXER_ANNOTATION_TYPE); if (problem instanceof IPersistableProblem) { fMarkerType= ((IPersistableProblem) problem).getMarkerType(); } else { fMarkerType= null; } } @Override public String[] getArguments() { return fArguments; } @Override public int getId() { return fId; } @Override public boolean isProblem() { return fIsProblem; } @Override public boolean hasOverlay() { return false; } @Override public ICAnnotation getOverlay() { return null; } @Override public void addOverlaid(ICAnnotation annotation) { if (fOverlaids == null) fOverlaids= new ArrayList<>(1); fOverlaids.add(annotation); } @Override public void removeOverlaid(ICAnnotation annotation) { if (fOverlaids != null) { fOverlaids.remove(annotation); if (fOverlaids.size() == 0) fOverlaids= null; } } @Override public Iterator<ICAnnotation> getOverlaidIterator() { if (fOverlaids != null) return fOverlaids.iterator(); return null; } @Override public ITranslationUnit getTranslationUnit() { return fTranslationUnit; } @Override public String getMarkerType() { return fMarkerType; } } /** * Internal structure for mapping positions to some value. * The reason for this specific structure is that positions can * change over time. Thus a lookup is based on value and not * on hash value. */ protected static class ReverseMap { static class Entry { Position fPosition; Object fValue; } private List<Entry> fList= new ArrayList<>(2); private int fAnchor; public ReverseMap() { } public Object get(Position position) { Entry entry; // behind anchor int length= fList.size(); for (int i= fAnchor; i < length; i++) { entry= fList.get(i); if (entry.fPosition.equals(position)) { fAnchor= i; return entry.fValue; } } // before anchor for (int i= 0; i < fAnchor; i++) { entry= fList.get(i); if (entry.fPosition.equals(position)) { fAnchor= i; return entry.fValue; } } return null; } private int getIndex(Position position) { Entry entry; int length= fList.size(); for (int i= 0; i < length; i++) { entry= fList.get(i); if (entry.fPosition.equals(position)) return i; } return -1; } public void put(Position position, Object value) { int index= getIndex(position); if (index == -1) { Entry entry= new Entry(); entry.fPosition= position; entry.fValue= value; fList.add(entry); } else { Entry entry= fList.get(index); entry.fValue= value; } } public void remove(Position position) { int index= getIndex(position); if (index > -1) fList.remove(index); } public void clear() { fList.clear(); fAnchor= 0; } } /** * A marker updater which removes problems markers with length 0. */ public static class ProblemMarkerUpdater implements IMarkerUpdater { /** * Default constructor (executable extension). */ public ProblemMarkerUpdater() { } @Override public String[] getAttribute() { return null; } @Override public String getMarkerType() { return ICModelMarker.C_MODEL_PROBLEM_MARKER; } @Override public boolean updateMarker(IMarker marker, IDocument document, Position position) { if (position == null) { return true; } if (position.isDeleted() || position.getLength() == 0) { return false; } return true; } } /** * Annotation model dealing with c marker annotations and temporary problems. * Also acts as a problem requester for its translation unit. Initially inactive. * Must be explicitly activated. */ protected static class TranslationUnitAnnotationModel extends ResourceMarkerAnnotationModel implements IProblemRequestor, IProblemRequestorExtension { private static class ProblemRequestorState { boolean fInsideReportingSequence; List<IProblem> fReportedProblems; } private final ThreadLocal<ProblemRequestorState> fProblemRequestorState= new ThreadLocal<>(); private int fStateCount; private ITranslationUnit fTranslationUnit; private List<ProblemAnnotation> fGeneratedAnnotations; private IProgressMonitor fProgressMonitor; private boolean fIsActive; private final ReverseMap fReverseMap= new ReverseMap(); private List<CMarkerAnnotation> fPreviouslyOverlaid; private List<CMarkerAnnotation> fCurrentlyOverlaid= new ArrayList<>(); public TranslationUnitAnnotationModel(IResource resource) { super(resource); } public void setTranslationUnit(ITranslationUnit unit) { fTranslationUnit= unit; } @Override protected MarkerAnnotation createMarkerAnnotation(IMarker marker) { String markerType= MarkerUtilities.getMarkerType(marker); if (markerType != null && markerType.startsWith(CMarkerAnnotation.C_MARKER_TYPE_PREFIX)) { return new CMarkerAnnotation(marker); } return super.createMarkerAnnotation(marker); } @Override protected Position createPositionFromMarker(IMarker marker) { int start= MarkerUtilities.getCharStart(marker); int end= MarkerUtilities.getCharEnd(marker); if (start > end) { end= start + end; start= end - start; end= end - start; } if (start == -1 && end == -1) { // marker line number is 1-based int line= MarkerUtilities.getLineNumber(marker); if (line > 0 && fDocument != null) { try { IRegion lineRegion= fDocument.getLineInformation(line - 1); start= lineRegion.getOffset(); end= start + lineRegion.getLength(); if (marker.isSubtypeOf(ICModelMarker.C_MODEL_PROBLEM_MARKER)) { // strip leading whitespace while (start < end && Character.isWhitespace(fDocument.getChar(start))) { ++start; } } } catch (BadLocationException x) { } catch (CoreException exc) { } } } if (start > -1 && end > -1) return new Position(start, end - start); return null; } @Override protected AnnotationModelEvent createAnnotationModelEvent() { return new TranslationUnitAnnotationModelEvent(this, getResource()); } protected Position createPositionFromProblem(IProblem problem) { int start= problem.getSourceStart(); if (start < 0) return null; int length= problem.getSourceEnd() - problem.getSourceStart() + 1; if (length < 0) return null; return new Position(start, length); } @Override public void beginReporting() { ProblemRequestorState state= fProblemRequestorState.get(); if (state == null) internalBeginReporting(false); } @Override public void beginReportingSequence() { ProblemRequestorState state= fProblemRequestorState.get(); if (state == null) internalBeginReporting(true); } /** * Sets up the infrastructure necessary for problem reporting. * * @param insideReportingSequence {@code true} if this method * call is issued from inside a reporting sequence */ private void internalBeginReporting(boolean insideReportingSequence) { if (fTranslationUnit != null) { ProblemRequestorState state= new ProblemRequestorState(); state.fInsideReportingSequence= insideReportingSequence; state.fReportedProblems= new ArrayList<>(); synchronized (getLockObject()) { fProblemRequestorState.set(state); ++fStateCount; } } } @Override public void acceptProblem(IProblem problem) { if (isActive()) { ProblemRequestorState state= fProblemRequestorState.get(); if (state != null && isReliable(problem)) { state.fReportedProblems.add(problem); } } } /** * A problem is not considered reliable if it belongs to an unreliable AST. */ private static boolean isReliable(IProblem problem) { if (problem instanceof IASTNode) { return !((IASTNode) problem).getTranslationUnit().isBasedOnIncompleteIndex(); } return true; } @Override public void endReporting() { ProblemRequestorState state= fProblemRequestorState.get(); if (state != null && !state.fInsideReportingSequence) internalEndReporting(state); } @Override public void endReportingSequence() { ProblemRequestorState state= fProblemRequestorState.get(); if (state != null && state.fInsideReportingSequence) internalEndReporting(state); } private void internalEndReporting(ProblemRequestorState state) { int stateCount= 0; synchronized (getLockObject()) { --fStateCount; stateCount= fStateCount; fProblemRequestorState.set(null); } if (stateCount == 0 && isActive()) reportProblems(state.fReportedProblems); } /** * Signals the end of problem reporting. */ private void reportProblems(List<IProblem> reportedProblems) { if (fProgressMonitor != null && fProgressMonitor.isCanceled()) return; boolean temporaryProblemsChanged= false; synchronized (getLockObject()) { boolean isCanceled= false; fPreviouslyOverlaid= fCurrentlyOverlaid; fCurrentlyOverlaid= new ArrayList<>(); if (fGeneratedAnnotations.size() > 0) { temporaryProblemsChanged= true; removeAnnotations(fGeneratedAnnotations, false, true); fGeneratedAnnotations.clear(); } if (reportedProblems != null) { for (IProblem problem : reportedProblems) { if (fProgressMonitor != null && fProgressMonitor.isCanceled()) { isCanceled= true; break; } Position position= createPositionFromProblem(problem); if (position != null) { try { ProblemAnnotation annotation= new ProblemAnnotation(problem, fTranslationUnit); overlayMarkers(position, annotation); addAnnotation(annotation, position, false); fGeneratedAnnotations.add(annotation); temporaryProblemsChanged= true; } catch (BadLocationException x) { // Ignore invalid position } } } } removeMarkerOverlays(isCanceled); fPreviouslyOverlaid= null; } if (temporaryProblemsChanged) fireModelChanged(); } private void removeMarkerOverlays(boolean isCanceled) { if (isCanceled) { fCurrentlyOverlaid.addAll(fPreviouslyOverlaid); } else if (fPreviouslyOverlaid != null) { Iterator<CMarkerAnnotation> e= fPreviouslyOverlaid.iterator(); while (e.hasNext()) { CMarkerAnnotation annotation= e.next(); annotation.setOverlay(null); } } } /** * Overlays value with problem annotation. * @param problemAnnotation */ private void setOverlay(Object value, ProblemAnnotation problemAnnotation) { if (value instanceof CMarkerAnnotation) { CMarkerAnnotation annotation= (CMarkerAnnotation) value; if (annotation.isProblem()) { annotation.setOverlay(problemAnnotation); fPreviouslyOverlaid.remove(annotation); fCurrentlyOverlaid.add(annotation); } } } private void overlayMarkers(Position position, ProblemAnnotation problemAnnotation) { Object value= getAnnotations(position); if (value instanceof List<?>) { List<?> list= (List<?>) value; for (Object element : list) { setOverlay(element, problemAnnotation); } } else { setOverlay(value, problemAnnotation); } } /** * Tells this annotation model to collect temporary problems from now on. */ private void startCollectingProblems() { fGeneratedAnnotations= new ArrayList<>(); } /** * Tells this annotation model to no longer collect temporary problems. */ private void stopCollectingProblems() { if (fGeneratedAnnotations != null) removeAnnotations(fGeneratedAnnotations, true, true); fGeneratedAnnotations= null; } @Override public boolean isActive() { return fIsActive; } @Override public void setProgressMonitor(IProgressMonitor monitor) { fProgressMonitor= monitor; } @Override public void setIsActive(boolean isActive) { if (fIsActive != isActive) { fIsActive= isActive; if (fIsActive) startCollectingProblems(); else stopCollectingProblems(); } } private Object getAnnotations(Position position) { synchronized (getLockObject()) { return fReverseMap.get(position); } } @Override @SuppressWarnings({ "unchecked" }) protected void addAnnotation(Annotation annotation, Position position, boolean fireModelChanged) throws BadLocationException { super.addAnnotation(annotation, position, fireModelChanged); synchronized (getLockObject()) { Object cached= fReverseMap.get(position); if (cached == null) { fReverseMap.put(position, annotation); } else if (cached instanceof List) { List<Annotation> list= (List<Annotation>) cached; list.add(annotation); } else if (cached instanceof Annotation) { List<Object> list= new ArrayList<>(2); list.add(cached); list.add(annotation); fReverseMap.put(position, list); } } } @Override protected void removeAllAnnotations(boolean fireModelChanged) { super.removeAllAnnotations(fireModelChanged); synchronized (getLockObject()) { fReverseMap.clear(); } } @Override protected void removeAnnotation(Annotation annotation, boolean fireModelChanged) { Position position= getPosition(annotation); synchronized (getLockObject()) { Object cached= fReverseMap.get(position); if (cached instanceof List<?>) { List<?> list= (List<?>) cached; list.remove(annotation); if (list.size() == 1) { fReverseMap.put(position, list.get(0)); list.clear(); } } else if (cached instanceof Annotation) { fReverseMap.remove(position); } } super.removeAnnotation(annotation, fireModelChanged); } } protected static class GlobalAnnotationModelListener implements IAnnotationModelListener, IAnnotationModelListenerExtension { private ListenerList<IAnnotationModelListener> fListenerList; public GlobalAnnotationModelListener() { fListenerList= new ListenerList<IAnnotationModelListener>(ListenerList.IDENTITY); } @Override public void modelChanged(IAnnotationModel model) { Object[] listeners= fListenerList.getListeners(); for (Object listener : listeners) { ((IAnnotationModelListener) listener).modelChanged(model); } } @Override public void modelChanged(AnnotationModelEvent event) { Object[] listeners= fListenerList.getListeners(); for (Object curr : listeners) { if (curr instanceof IAnnotationModelListenerExtension) { ((IAnnotationModelListenerExtension) curr).modelChanged(event); } } } public void addListener(IAnnotationModelListener listener) { fListenerList.add(listener); } public void removeListener(IAnnotationModelListener listener) { fListenerList.remove(listener); } } /** Preference key for temporary problems */ private static final String HANDLE_TEMPORARY_PROBLEMS= PreferenceConstants.EDITOR_EVALUATE_TEMPORARY_PROBLEMS; /** Internal property changed listener */ private IPropertyChangeListener fPropertyListener; /** Annotation model listener added to all created CU annotation models */ private GlobalAnnotationModelListener fGlobalAnnotationModelListener; public CDocumentProvider() { super(); IDocumentProvider parentProvider= new ExternalSearchDocumentProvider(); parentProvider= new ForwardingDocumentProvider(ICPartitions.C_PARTITIONING, new CDocumentSetupParticipant(), parentProvider); setParentDocumentProvider(parentProvider); fGlobalAnnotationModelListener= new GlobalAnnotationModelListener(); fPropertyListener= new IPropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent event) { if (HANDLE_TEMPORARY_PROBLEMS.equals(event.getProperty())) enableHandlingTemporaryProblems(); } }; CUIPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(fPropertyListener); } @Override public void connect(Object element) throws CoreException { super.connect(element); IDocument document= getDocument(element); if (document instanceof IDocumentExtension3) { IDocumentExtension3 extension= (IDocumentExtension3) document; if (extension.getDocumentPartitioner(ICPartitions.C_PARTITIONING) == null) new CDocumentSetupParticipant().setup(document); } } /** * Creates a translation unit from the given file. * * @param file the file from which to create the translation unit */ protected ITranslationUnit createTranslationUnit(IFile file) { Object element = CoreModel.getDefault().create(file); if (element instanceof ITranslationUnit) { return (ITranslationUnit) element; } if (element == null) { // Not in a source folder? ICProject cproject= CoreModel.getDefault().create(file.getProject()); if (cproject != null) { String contentTypeId= CoreModel.getRegistedContentTypeId(file.getProject(), file.getName()); if (contentTypeId != null) { return new TranslationUnit(cproject, file, contentTypeId); } } } return null; } @Override protected FileInfo createEmptyFileInfo() { return new TranslationUnitInfo(); } @Override protected IAnnotationModel createAnnotationModel(IFile file) { return new TranslationUnitAnnotationModel(file); } @Override protected FileInfo createFileInfo(Object element) throws CoreException { ITranslationUnit original = null; if (element instanceof IFileEditorInput) { IFileEditorInput input = (IFileEditorInput) element; original = createTranslationUnit(input.getFile()); } else if (element instanceof ITranslationUnitEditorInput) { ITranslationUnitEditorInput input = (ITranslationUnitEditorInput) element; original = input.getTranslationUnit(); } else if (element instanceof IAdaptable) { IAdaptable adaptable= (IAdaptable) element; ILocationProvider locationProvider= adaptable.getAdapter(ILocationProvider.class); if (locationProvider instanceof ILocationProviderExtension) { URI uri= ((ILocationProviderExtension) locationProvider).getURI(element); original= createTranslationUnit(uri); } if (original == null && locationProvider != null) { IPath location= locationProvider.getPath(element); original= createTranslationUnit(location); } } if (original == null) { return null; } FileInfo info = super.createFileInfo(element); if (!(info instanceof TranslationUnitInfo)) return null; TranslationUnitInfo tuInfo = (TranslationUnitInfo) info; setUpSynchronization(tuInfo); IProblemRequestor requestor= tuInfo.fModel instanceof IProblemRequestor ? (IProblemRequestor) tuInfo.fModel : null; tuInfo.fCopy = CDTUITools.getWorkingCopyManager().getSharedWorkingCopy(original, requestor, getProgressMonitor()); if (tuInfo.fModel instanceof TranslationUnitAnnotationModel) { TranslationUnitAnnotationModel model= (TranslationUnitAnnotationModel) tuInfo.fModel; model.setTranslationUnit(tuInfo.fCopy); } else { IPath location = original.getLocation(); if (location != null) { IResource markerResource = CUIPlugin.getWorkspace().getRoot(); IAnnotationModel originalModel = tuInfo.fModel; ExternalSearchAnnotationModel externalSearchModel = new ExternalSearchAnnotationModel(markerResource, location, IResource.DEPTH_ONE); tuInfo.fModel= externalSearchModel; IAnnotationModel fileBufferModel= tuInfo.fTextFileBuffer.getAnnotationModel(); if (fileBufferModel != null) { externalSearchModel.addAnnotationModel("fileBufferModel", fileBufferModel); //$NON-NLS-1$ } if (originalModel != null && originalModel != fileBufferModel) { externalSearchModel.addAnnotationModel("originalModel", originalModel); //$NON-NLS-1$ } } } if (tuInfo.fModel != null) tuInfo.fModel.addAnnotationModelListener(fGlobalAnnotationModelListener); if (requestor instanceof IProblemRequestorExtension) { IProblemRequestorExtension extension= (IProblemRequestorExtension) requestor; extension.setIsActive(isHandlingTemporaryProblems()); } return tuInfo; } /** * Tries to synthesize an ITranslationUnit out of thin air. * @param location the file system location of the file in question * @return a translation unit or {@code null} */ private ITranslationUnit createTranslationUnit(IPath location) { if (location == null) { return null; } IEditorInput input= EditorUtility.getEditorInputForLocation(location, null); if (input instanceof ITranslationUnitEditorInput) { return ((ITranslationUnitEditorInput) input).getTranslationUnit(); } return null; } /** * Tries to synthesize an ITranslationUnit out of thin air. * @param uri the URI of the file in question * @return a translation unit or {@code null} */ private ITranslationUnit createTranslationUnit(URI uri) { if (uri == null) { return null; } IEditorInput input= EditorUtility.getEditorInputForLocation(uri, null); if (input instanceof ITranslationUnitEditorInput) { return ((ITranslationUnitEditorInput) input).getTranslationUnit(); } return null; } @Override protected void disposeFileInfo(Object element, FileInfo info) { if (info instanceof TranslationUnitInfo) { TranslationUnitInfo tuInfo = (TranslationUnitInfo) info; tuInfo.fCopy.destroy(); if (tuInfo.fModel != null) tuInfo.fModel.removeAnnotationModelListener(fGlobalAnnotationModelListener); } super.disposeFileInfo(element, info); } protected void commitWorkingCopy(IProgressMonitor monitor, Object element, TranslationUnitInfo info, boolean overwrite) throws CoreException { SubMonitor progress = SubMonitor.convert(monitor, 100); try { IDocument document= info.fTextFileBuffer.getDocument(); IResource resource= info.fCopy.getResource(); if (resource instanceof IFile && !resource.exists()) { // The underlying resource has been deleted, just recreate the file, ignore the rest createFileFromDocument(progress.split(20), (IFile) resource, document); return; } try { CoreException saveActionException= null; try { performSaveActions(info.fCopy, info.fTextFileBuffer, progress.split(20)); } catch (CoreException e) { saveActionException = e; } commitFileBuffer(progress.split(20), info, overwrite); if (saveActionException != null) { CUIPlugin.log(saveActionException); } } catch (CoreException x) { // Inform about the failure fireElementStateChangeFailed(element); throw x; } catch (RuntimeException x) { // Inform about the failure fireElementStateChangeFailed(element); throw x; } } finally { monitor.done(); } } @Override protected DocumentProviderOperation createSaveOperation(final Object element, final IDocument document, final boolean overwrite) throws CoreException { final FileInfo info= getFileInfo(element); if (info instanceof TranslationUnitInfo) { return new DocumentProviderOperation() { @Override protected void execute(IProgressMonitor monitor) throws CoreException { commitWorkingCopy(monitor, element, (TranslationUnitInfo) info, overwrite); } @Override public ISchedulingRule getSchedulingRule() { if (info.fElement instanceof IFileEditorInput) { IFile file= ((IFileEditorInput) info.fElement).getFile(); return computeSchedulingRule(file); } return null; } }; } return null; } private TextEdit formatCode(ITranslationUnit tu, IDocument document, IRegion[] changedRegions, IProgressMonitor monitor) { TextEdit rootEdit = null; ICProject cProject = tu.getCProject(); Map<String, Object> options = new HashMap<String, Object>(cProject.getOptions(true)); options.put(DefaultCodeFormatterConstants.FORMATTER_TRANSLATION_UNIT, tu); CodeFormatter formatter = ToolFactory.createCodeFormatter(options); String code = document.get(); TextEdit[] formatEdits = formatter.format(CodeFormatter.K_TRANSLATION_UNIT, code, changedRegions, TextUtilities.getDefaultLineDelimiter(document)); for (TextEdit edit : formatEdits) { if (edit != null) { if (rootEdit == null) { rootEdit = new MultiTextEdit(); } rootEdit.addChild(edit); } } return rootEdit; } /** * Removes trailing whitespaces from changed lines and adds newline at the end of the file, * if the last line of the file was changed. * @throws BadLocationException */ private void performSaveActions(ITranslationUnit tu, ITextFileBuffer buffer, IProgressMonitor monitor) throws CoreException { if (shouldRemoveTrailingWhitespace() || shouldAddNewlineAtEof() || shouldFormatCode()) { SubMonitor progress = SubMonitor.convert(monitor, 2); IDocumentUndoManager undoManager= null; IRegion[] changedRegions= needsChangedRegions() ? EditorUtility.calculateChangedLineRegions(buffer, progress.split(1)) : null; IDocument document = buffer.getDocument(); try { if (shouldFormatCode() && tu != null) { if (!isLimitedFormatCode()) { // The whole file has been formatted. changedRegions = new IRegion[] { new Region(0, document.getLength()) }; } TextEdit edit = formatCode(tu, document, changedRegions, progress.split(1)); if (edit != null) { undoManager= DocumentUndoManagerRegistry.getDocumentUndoManager(document); undoManager.beginCompoundChange(); edit.apply(document); } } TextEdit edit = createTrailingWhitespaceEdit(document, changedRegions); if (edit != null) { if (undoManager == null) { undoManager= DocumentUndoManagerRegistry.getDocumentUndoManager(document); undoManager.beginCompoundChange(); } edit.apply(document); } } catch (MalformedTreeException | BadLocationException e) { String message= e.getMessage(); if (message == null) message= e.getClass().getSimpleName(); throw new CoreException(new Status(IStatus.ERROR, CUIPlugin.PLUGIN_ID, message, e)); } finally { if (undoManager != null) undoManager.endCompoundChange(); } } } private static boolean shouldFormatCode() { return PreferenceConstants.getPreferenceStore().getBoolean(PreferenceConstants.FORMAT_SOURCE_CODE); } private static boolean isLimitedFormatCode() { return PreferenceConstants.getPreferenceStore().getBoolean( PreferenceConstants.FORMAT_SOURCE_CODE_LIMIT_TO_EDITED_LINES); } private static boolean shouldAddNewlineAtEof() { return PreferenceConstants.getPreferenceStore().getBoolean( PreferenceConstants.ENSURE_NEWLINE_AT_EOF); } private static boolean shouldRemoveTrailingWhitespace() { return PreferenceConstants.getPreferenceStore().getBoolean( PreferenceConstants.REMOVE_TRAILING_WHITESPACE); } private static boolean isLimitedRemoveTrailingWhitespace() { return PreferenceConstants.getPreferenceStore().getBoolean( PreferenceConstants.REMOVE_TRAILING_WHITESPACE_LIMIT_TO_EDITED_LINES); } private static boolean needsChangedRegions() { return shouldRemoveTrailingWhitespace() && isLimitedRemoveTrailingWhitespace() || shouldFormatCode() && isLimitedFormatCode(); } /** * Creates a text edit for the save actions. * @return a text edit, or {@code null} if the save actions leave the file intact. */ private TextEdit createTrailingWhitespaceEdit(IDocument document, IRegion[] changedRegions) { TextEdit rootEdit = null; TextEdit lastWhitespaceEdit = null; try { if (shouldRemoveTrailingWhitespace()) { if (!isLimitedRemoveTrailingWhitespace()) { // Pretend that the whole document changed. changedRegions = new IRegion[] { new Region(0, document.getLength()) }; } // Remove trailing whitespace from changed lines. for (IRegion region : changedRegions) { int firstLine = document.getLineOfOffset(region.getOffset()); int lastLine = document.getLineOfOffset(region.getOffset() + region.getLength()); for (int line = firstLine; line <= lastLine; line++) { IRegion lineRegion = document.getLineInformation(line); if (lineRegion.getLength() == 0) { continue; } int lineStart = lineRegion.getOffset(); int lineEnd = lineStart + lineRegion.getLength(); // Find the rightmost none-whitespace character int charPos = lineEnd - 1; while (charPos >= lineStart && Character.isWhitespace(document.getChar(charPos))) charPos--; charPos++; if (charPos < lineEnd) { // check partition - don't remove whitespace inside strings ITypedRegion partition = TextUtilities.getPartition(document, ICPartitions.C_PARTITIONING, charPos, false); if (!ICPartitions.C_STRING.equals(partition.getType())) { lastWhitespaceEdit= new DeleteEdit(charPos, lineEnd - charPos); if (rootEdit == null) { rootEdit = new MultiTextEdit(); } rootEdit.addChild(lastWhitespaceEdit); } } } } } if (shouldAddNewlineAtEof()) { // Add newline at the end of the file. int endOffset = document.getLength(); IRegion lastLineRegion = document.getLineInformationOfOffset(endOffset); // Insert newline at the end of the document if the last line is not empty and // will not become empty after removal of trailing whitespace. if (lastLineRegion.getLength() != 0 && (lastWhitespaceEdit == null || lastWhitespaceEdit.getOffset() != lastLineRegion.getOffset() || lastWhitespaceEdit.getLength() != lastLineRegion.getLength())) { TextEdit edit = new InsertEdit(endOffset, TextUtilities.getDefaultLineDelimiter(document)); if (rootEdit == null) { rootEdit = edit; } else { rootEdit.addChild(edit); } } } } catch (BadLocationException e) { CUIPlugin.log(e); } return rootEdit; } // private static boolean isWhitespaceRegion(IDocument document, IRegion region) throws BadLocationException { // int end = region.getOffset() + region.getLength(); // for (int i = region.getOffset(); i < end; i++) { // if (!Character.isWhitespace(document.getChar(i))) { // return false; // } // } // return true; // } /** * Returns the preference whether handling temporary problems is enabled. */ protected boolean isHandlingTemporaryProblems() { IPreferenceStore store= CUIPlugin.getDefault().getPreferenceStore(); return store.getBoolean(HANDLE_TEMPORARY_PROBLEMS); } /** * Switches the state of problem acceptance according to the value in the preference store. */ protected void enableHandlingTemporaryProblems() { boolean enable= isHandlingTemporaryProblems(); for (Iterator<?> iter= getFileInfosIterator(); iter.hasNext();) { FileInfo info= (FileInfo) iter.next(); if (info.fModel instanceof IProblemRequestorExtension) { IProblemRequestorExtension extension= (IProblemRequestorExtension) info.fModel; extension.setIsActive(enable); } } } public void addGlobalAnnotationModelListener(IAnnotationModelListener listener) { fGlobalAnnotationModelListener.addListener(listener); } public void removeGlobalAnnotationModelListener(IAnnotationModelListener listener) { fGlobalAnnotationModelListener.removeListener(listener); } public IWorkingCopy getWorkingCopy(Object element) { FileInfo fileInfo = getFileInfo(element); if (fileInfo instanceof TranslationUnitInfo) { TranslationUnitInfo info = (TranslationUnitInfo) fileInfo; return info.fCopy; } return null; } public void shutdown() { // CUIPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(fPropertyListener); Iterator<?> e = getConnectedElementsIterator(); while (e.hasNext()) disconnect(e.next()); } public ILineTracker createLineTracker(Object element) { return new DefaultLineTracker(); } }