| /******************************************************************************* |
| * Copyright (c) 2011, 2016 GitHub 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 |
| * |
| * Contributors: |
| * Kevin Sawicki (GitHub Inc.) - initial API and implementation |
| * Thomas Wolf <thomas.wolf@paranor.ch> - turn it into a real text editor |
| *******************************************************************************/ |
| package org.eclipse.egit.ui.internal.commit; |
| |
| import static java.util.Arrays.asList; |
| import static org.eclipse.egit.ui.UIPreferences.THEME_DiffAddBackgroundColor; |
| import static org.eclipse.egit.ui.UIPreferences.THEME_DiffRemoveBackgroundColor; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| |
| import org.eclipse.core.resources.IMarker; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.SubMonitor; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.egit.core.AdapterUtils; |
| import org.eclipse.egit.ui.Activator; |
| import org.eclipse.egit.ui.UIUtils; |
| import org.eclipse.egit.ui.internal.UIText; |
| import org.eclipse.egit.ui.internal.commit.DiffRegionFormatter.DiffRegion; |
| import org.eclipse.egit.ui.internal.commit.DiffRegionFormatter.FileDiffRegion; |
| import org.eclipse.egit.ui.internal.history.FileDiff; |
| import org.eclipse.egit.ui.internal.repository.RepositoriesView; |
| import org.eclipse.jface.action.Action; |
| import org.eclipse.jface.action.ActionContributionItem; |
| import org.eclipse.jface.action.IAction; |
| import org.eclipse.jface.action.IContributionItem; |
| import org.eclipse.jface.action.IMenuManager; |
| import org.eclipse.jface.operation.IRunnableContext; |
| import org.eclipse.jface.preference.IPreferenceStore; |
| import org.eclipse.jface.preference.PreferenceStore; |
| import org.eclipse.jface.resource.ColorRegistry; |
| import org.eclipse.jface.resource.StringConverter; |
| import org.eclipse.jface.text.Document; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.Position; |
| import org.eclipse.jface.text.reconciler.IReconciler; |
| import org.eclipse.jface.text.source.Annotation; |
| import org.eclipse.jface.text.source.AnnotationModel; |
| import org.eclipse.jface.text.source.IAnnotationModel; |
| import org.eclipse.jface.text.source.IAnnotationModelExtension; |
| import org.eclipse.jface.text.source.ISourceViewer; |
| import org.eclipse.jface.text.source.IVerticalRuler; |
| import org.eclipse.jface.text.source.IVerticalRulerColumn; |
| import org.eclipse.jface.text.source.projection.ProjectionAnnotation; |
| import org.eclipse.jface.text.source.projection.ProjectionSupport; |
| import org.eclipse.jface.text.source.projection.ProjectionViewer; |
| import org.eclipse.jface.util.IPropertyChangeListener; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jface.viewers.StructuredSelection; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.revwalk.RevCommit; |
| import org.eclipse.swt.graphics.RGB; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.team.ui.history.IHistoryView; |
| import org.eclipse.ui.IEditorInput; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.editors.text.EditorsUI; |
| import org.eclipse.ui.editors.text.TextEditor; |
| import org.eclipse.ui.forms.IManagedForm; |
| import org.eclipse.ui.forms.editor.FormEditor; |
| import org.eclipse.ui.forms.editor.IFormPage; |
| import org.eclipse.ui.ide.IDE; |
| import org.eclipse.ui.part.IShowInSource; |
| import org.eclipse.ui.part.IShowInTargetList; |
| import org.eclipse.ui.part.ShowInContext; |
| import org.eclipse.ui.progress.UIJob; |
| import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants; |
| import org.eclipse.ui.texteditor.AbstractDocumentProvider; |
| import org.eclipse.ui.texteditor.ChainedPreferenceStore; |
| import org.eclipse.ui.texteditor.IDocumentProvider; |
| import org.eclipse.ui.texteditor.ITextEditorActionConstants; |
| import org.eclipse.ui.themes.IThemeManager; |
| import org.eclipse.ui.views.contentoutline.IContentOutlinePage; |
| |
| /** |
| * A {@link TextEditor} wrapped as an {@link IFormPage} and specialized to |
| * showing a unified diff of a whole commit. The editor has an associated |
| * {@link DiffEditorOutlinePage}. |
| */ |
| public class DiffEditorPage extends TextEditor |
| implements IFormPage, IShowInSource, IShowInTargetList { |
| |
| private static final String ADD_ANNOTATION_TYPE = "org.eclipse.egit.ui.commitEditor.diffAdded"; //$NON-NLS-1$ |
| |
| private static final String REMOVE_ANNOTATION_TYPE = "org.eclipse.egit.ui.commitEditor.diffRemoved"; //$NON-NLS-1$ |
| |
| private FormEditor formEditor; |
| |
| private String title; |
| |
| private String pageId; |
| |
| private int pageIndex = -1; |
| |
| private Control textControl; |
| |
| private DiffEditorOutlinePage outlinePage; |
| |
| private Annotation[] currentFoldingAnnotations; |
| |
| private Annotation[] currentOverviewAnnotations; |
| |
| /** An {@link IPreferenceStore} for the annotation colors.*/ |
| private ThemePreferenceStore overviewStore; |
| |
| private FileDiffRegion currentFileDiffRange; |
| |
| private OldNewLogicalLineNumberRulerColumn lineNumberColumn; |
| |
| private boolean plainLineNumbers = false; |
| |
| /** |
| * Creates a new {@link DiffEditorPage} with the given id and title, which |
| * is shown on the page's tab. |
| * |
| * @param editor |
| * containing this page |
| * @param id |
| * of the page |
| * @param title |
| * of the page |
| */ |
| public DiffEditorPage(FormEditor editor, String id, String title) { |
| pageId = id; |
| this.title = title; |
| setPartName(title); |
| initialize(editor); |
| } |
| |
| /** |
| * Creates a new {@link DiffEditorPage} with default id and title. |
| * |
| * @param editor |
| * containing the page |
| */ |
| public DiffEditorPage(FormEditor editor) { |
| this(editor, "diffPage", UIText.DiffEditorPage_Title); //$NON-NLS-1$ |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public Object getAdapter(Class adapter) { |
| // TODO Switch to generified signature once EGit's base dependency is |
| // Eclipse 4.6 |
| if (IContentOutlinePage.class.equals(adapter)) { |
| if (outlinePage == null) { |
| outlinePage = createOutlinePage(); |
| outlinePage.setInput( |
| getDocumentProvider().getDocument(getEditorInput())); |
| if (currentFileDiffRange != null) { |
| outlinePage.setSelection( |
| new StructuredSelection(currentFileDiffRange)); |
| } |
| } |
| return outlinePage; |
| } |
| return super.getAdapter(adapter); |
| } |
| |
| private DiffEditorOutlinePage createOutlinePage() { |
| DiffEditorOutlinePage page = new DiffEditorOutlinePage(); |
| page.addSelectionChangedListener( |
| event -> doSetSelection(event.getSelection())); |
| page.addOpenListener(event -> { |
| FormEditor editor = getEditor(); |
| editor.getSite().getPage().activate(editor); |
| editor.setActivePage(getId()); |
| doSetSelection(event.getSelection()); |
| }); |
| return page; |
| } |
| |
| @Override |
| public void dispose() { |
| // Nested editors are responsible for disposing their outline pages |
| // themselves. |
| if (outlinePage != null) { |
| outlinePage.dispose(); |
| outlinePage = null; |
| } |
| if (overviewStore != null) { |
| overviewStore.dispose(); |
| overviewStore = null; |
| } |
| super.dispose(); |
| } |
| |
| // TextEditor specifics: |
| |
| @Override |
| protected ISourceViewer createSourceViewer(Composite parent, |
| IVerticalRuler ruler, int styles) { |
| DiffViewer viewer = new DiffViewer(parent, ruler, getOverviewRuler(), |
| isOverviewRulerVisible(), styles); |
| getSourceViewerDecorationSupport(viewer); |
| ProjectionSupport projector = new ProjectionSupport(viewer, |
| getAnnotationAccess(), getSharedColors()); |
| projector.install(); |
| viewer.getTextWidget().addCaretListener(event -> { |
| if (outlinePage != null) { |
| FileDiffRegion region = getFileDiffRange(event.caretOffset); |
| if (region != null && !region.equals(currentFileDiffRange)) { |
| currentFileDiffRange = region; |
| outlinePage.setSelection(new StructuredSelection(region)); |
| } else { |
| currentFileDiffRange = region; |
| } |
| } |
| }); |
| return viewer; |
| } |
| |
| @Override |
| protected IVerticalRulerColumn createLineNumberRulerColumn() { |
| lineNumberColumn = new OldNewLogicalLineNumberRulerColumn( |
| plainLineNumbers); |
| initializeLineNumberRulerColumn(lineNumberColumn); |
| return lineNumberColumn; |
| } |
| |
| @Override |
| protected void initializeEditor() { |
| super.initializeEditor(); |
| overviewStore = new ThemePreferenceStore(); |
| setPreferenceStore(new ChainedPreferenceStore(new IPreferenceStore[] { |
| overviewStore, EditorsUI.getPreferenceStore() })); |
| setDocumentProvider(new DiffDocumentProvider()); |
| setSourceViewerConfiguration( |
| new DiffViewer.Configuration(getPreferenceStore()) { |
| @Override |
| public IReconciler getReconciler( |
| ISourceViewer sourceViewer) { |
| // Switch off spell-checking |
| return null; |
| } |
| }); |
| } |
| |
| @Override |
| protected void doSetInput(IEditorInput input) throws CoreException { |
| super.doSetInput(input); |
| if (input instanceof DiffEditorInput) { |
| setFolding(); |
| FileDiffRegion region = getFileDiffRange(0); |
| currentFileDiffRange = region; |
| setOverviewAnnotations(); |
| } else if (input instanceof CommitEditorInput) { |
| formatDiff(); |
| currentFileDiffRange = null; |
| } |
| if (outlinePage != null) { |
| outlinePage.setInput(getDocumentProvider().getDocument(input)); |
| if (currentFileDiffRange != null) { |
| outlinePage.setSelection( |
| new StructuredSelection(currentFileDiffRange)); |
| } |
| } |
| } |
| |
| @Override |
| protected void doSetSelection(ISelection selection) { |
| if (!selection.isEmpty() && selection instanceof StructuredSelection) { |
| Object selected = ((StructuredSelection) selection) |
| .getFirstElement(); |
| if (selected instanceof FileDiffRegion) { |
| FileDiffRegion newRange = (FileDiffRegion) selected; |
| if (!newRange.equals(currentFileDiffRange)) { |
| currentFileDiffRange = newRange; |
| selectAndReveal(newRange.getOffset(), 0); |
| } |
| return; |
| } |
| } |
| super.doSetSelection(selection); |
| } |
| |
| @Override |
| protected void editorContextMenuAboutToShow(IMenuManager menu) { |
| super.editorContextMenuAboutToShow(menu); |
| addAction(menu, ITextEditorActionConstants.GROUP_COPY, |
| ITextEditorActionConstants.SELECT_ALL); |
| // TextEditor always adds these, even if the document is not editable. |
| menu.remove(ITextEditorActionConstants.SHIFT_RIGHT); |
| menu.remove(ITextEditorActionConstants.SHIFT_LEFT); |
| } |
| |
| @Override |
| protected void rulerContextMenuAboutToShow(IMenuManager menu) { |
| super.rulerContextMenuAboutToShow(menu); |
| // AbstractDecoratedTextEditor's menu presumes a |
| // LineNumberChangeRulerColumn, which we don't have. |
| IContributionItem showLineNumbers = menu |
| .find(ITextEditorActionConstants.LINENUMBERS_TOGGLE); |
| boolean isShowingLineNumbers = EditorsUI.getPreferenceStore() |
| .getBoolean( |
| AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER); |
| if (showLineNumbers instanceof ActionContributionItem) { |
| ((ActionContributionItem) showLineNumbers).getAction() |
| .setChecked(isShowingLineNumbers); |
| } |
| if (isShowingLineNumbers) { |
| // Add an action to toggle between physical and logical line numbers |
| boolean plain = lineNumberColumn.isPlain(); |
| IAction togglePlain = new Action( |
| UIText.DiffEditorPage_ToggleLineNumbers, |
| IAction.AS_CHECK_BOX) { |
| |
| @Override |
| public void run() { |
| plainLineNumbers = !plain; |
| lineNumberColumn.setPlain(!plain); |
| } |
| }; |
| togglePlain.setChecked(!plain); |
| menu.appendToGroup(ITextEditorActionConstants.GROUP_RULERS, |
| togglePlain); |
| } |
| } |
| |
| // FormPage specifics: |
| |
| @Override |
| public void initialize(FormEditor editor) { |
| formEditor = editor; |
| } |
| |
| @Override |
| public FormEditor getEditor() { |
| return formEditor; |
| } |
| |
| @Override |
| public IManagedForm getManagedForm() { |
| return null; |
| } |
| |
| @Override |
| public void setActive(boolean active) { |
| if (active) { |
| setFocus(); |
| } |
| } |
| |
| @Override |
| public boolean isActive() { |
| return this.equals(formEditor.getActivePageInstance()); |
| } |
| |
| @Override |
| public boolean canLeaveThePage() { |
| return true; |
| } |
| |
| @Override |
| public Control getPartControl() { |
| return textControl; |
| } |
| |
| @Override |
| public String getId() { |
| return pageId; |
| } |
| |
| @Override |
| public int getIndex() { |
| return pageIndex; |
| } |
| |
| @Override |
| public void setIndex(int index) { |
| pageIndex = index; |
| } |
| |
| @Override |
| public boolean isEditor() { |
| return true; |
| } |
| |
| @Override |
| public boolean selectReveal(Object object) { |
| if (object instanceof IMarker) { |
| IDE.gotoMarker(this, (IMarker) object); |
| return true; |
| } else if (object instanceof ISelection) { |
| doSetSelection((ISelection) object); |
| return true; |
| } |
| return false; |
| } |
| |
| // WorkbenchPart specifics: |
| |
| @Override |
| public String getTitle() { |
| return title; |
| } |
| |
| @Override |
| public void createPartControl(Composite parent) { |
| super.createPartControl(parent); |
| Control[] children = parent.getChildren(); |
| textControl = children[children.length - 1]; |
| } |
| |
| // "Show In..." specifics: |
| |
| @Override |
| public ShowInContext getShowInContext() { |
| RepositoryCommit commit = AdapterUtils.adapt(getEditorInput(), |
| RepositoryCommit.class); |
| if (commit != null) { |
| return new ShowInContext(getEditorInput(), |
| new StructuredSelection(commit)); |
| } |
| return null; |
| } |
| |
| @Override |
| public String[] getShowInTargetIds() { |
| return new String[] { IHistoryView.VIEW_ID, RepositoriesView.VIEW_ID }; |
| } |
| |
| // Diff specifics: |
| |
| private void setFolding() { |
| ProjectionViewer viewer = (ProjectionViewer) getSourceViewer(); |
| if (viewer == null) { |
| return; |
| } |
| IDocument document = viewer.getDocument(); |
| if (document instanceof DiffDocument) { |
| FileDiffRegion[] regions = ((DiffDocument) document) |
| .getFileRegions(); |
| if (regions == null || regions.length <= 1) { |
| viewer.disableProjection(); |
| return; |
| } |
| viewer.enableProjection(); |
| Map<Annotation, Position> newAnnotations = new HashMap<>(); |
| for (FileDiffRegion region : regions) { |
| newAnnotations.put(new ProjectionAnnotation(), |
| new Position(region.getOffset(), region.getLength())); |
| } |
| viewer.getProjectionAnnotationModel().modifyAnnotations( |
| currentFoldingAnnotations, newAnnotations, null); |
| currentFoldingAnnotations = newAnnotations.keySet() |
| .toArray(new Annotation[newAnnotations.size()]); |
| } else { |
| viewer.disableProjection(); |
| currentFoldingAnnotations = null; |
| } |
| } |
| |
| private void setOverviewAnnotations() { |
| IDocumentProvider documentProvider = getDocumentProvider(); |
| IDocument document = documentProvider.getDocument(getEditorInput()); |
| if (!(document instanceof DiffDocument)) { |
| return; |
| } |
| IAnnotationModel annotationModel = documentProvider |
| .getAnnotationModel(getEditorInput()); |
| if (annotationModel == null) { |
| return; |
| } |
| DiffRegion[] diffs = ((DiffDocument) document).getRegions(); |
| if (diffs == null || diffs.length == 0) { |
| return; |
| } |
| Map<Annotation, Position> newAnnotations = new HashMap<>(); |
| for (DiffRegion region : diffs) { |
| if (DiffRegion.Type.ADD.equals(region.getType())) { |
| newAnnotations.put( |
| new Annotation(ADD_ANNOTATION_TYPE, true, null), |
| new Position(region.getOffset(), region.getLength())); |
| } else if (DiffRegion.Type.REMOVE.equals(region.getType())) { |
| newAnnotations.put( |
| new Annotation(REMOVE_ANNOTATION_TYPE, true, null), |
| new Position(region.getOffset(), region.getLength())); |
| } |
| } |
| if (annotationModel instanceof IAnnotationModelExtension) { |
| ((IAnnotationModelExtension) annotationModel).replaceAnnotations( |
| currentOverviewAnnotations, newAnnotations); |
| } else { |
| if (currentOverviewAnnotations != null) { |
| for (Annotation existing : currentOverviewAnnotations) { |
| annotationModel.removeAnnotation(existing); |
| } |
| } |
| for (Map.Entry<Annotation, Position> entry : newAnnotations |
| .entrySet()) { |
| annotationModel.addAnnotation(entry.getKey(), entry.getValue()); |
| } |
| } |
| currentOverviewAnnotations = newAnnotations.keySet() |
| .toArray(new Annotation[newAnnotations.size()]); |
| } |
| |
| private FileDiffRegion getFileDiffRange(int widgetOffset) { |
| DiffViewer viewer = (DiffViewer) getSourceViewer(); |
| if (viewer != null) { |
| int offset = viewer.widgetOffset2ModelOffset(widgetOffset); |
| IDocument document = getDocumentProvider() |
| .getDocument(getEditorInput()); |
| if (document instanceof DiffDocument) { |
| return ((DiffDocument) document).findFileRegion(offset); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Gets the full unified diff of a {@link RepositoryCommit}. |
| * |
| * @param commit |
| * to get the diff |
| * @return the diff as a sorted (by file path) array of {@link FileDiff}s |
| */ |
| protected FileDiff[] getDiffs(RepositoryCommit commit) { |
| List<FileDiff> diffResult = new ArrayList<>(); |
| |
| diffResult.addAll(asList(commit.getDiffs())); |
| |
| if (commit.getRevCommit().getParentCount() > 2) { |
| RevCommit untrackedCommit = commit.getRevCommit() |
| .getParent(StashEditorPage.PARENT_COMMIT_UNTRACKED); |
| diffResult |
| .addAll(asList(new RepositoryCommit(commit.getRepository(), |
| untrackedCommit).getDiffs())); |
| } |
| FileDiff[] result = diffResult.toArray(new FileDiff[diffResult.size()]); |
| Arrays.sort(result, FileDiff.PATH_COMPARATOR); |
| return result; |
| } |
| |
| /** |
| * Asynchronously gets the diff of the commit set on our |
| * {@link CommitEditorInput}, formats it into a {@link DiffDocument}, and |
| * then re-sets this editors's input to a {@link DiffEditorInput} which will |
| * cause this document to be shown. |
| */ |
| private void formatDiff() { |
| RepositoryCommit commit = AdapterUtils.adapt(getEditor(), |
| RepositoryCommit.class); |
| if (commit == null) { |
| return; |
| } |
| if (!commit.isStash() && commit.getRevCommit().getParentCount() > 1) { |
| setInput(new DiffEditorInput(commit, null)); |
| return; |
| } |
| |
| Job job = new Job(UIText.DiffEditorPage_TaskGeneratingDiff) { |
| |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| FileDiff diffs[] = getDiffs(commit); |
| DiffDocument document = new DiffDocument(); |
| try (DiffRegionFormatter formatter = new DiffRegionFormatter( |
| document)) { |
| SubMonitor progress = SubMonitor.convert(monitor, |
| diffs.length); |
| Repository repository = commit.getRepository(); |
| for (FileDiff diff : diffs) { |
| if (progress.isCanceled()) { |
| break; |
| } |
| progress.subTask(diff.getPath()); |
| try { |
| formatter.write(repository, diff); |
| } catch (IOException ignore) { |
| // Ignored |
| } |
| progress.worked(1); |
| } |
| document.connect(formatter); |
| } |
| new UIJob(UIText.DiffEditorPage_TaskUpdatingViewer) { |
| |
| @Override |
| public IStatus runInUIThread(IProgressMonitor uiMonitor) { |
| if (UIUtils.isUsable(getPartControl())) { |
| setInput(new DiffEditorInput(commit, document)); |
| } |
| return Status.OK_STATUS; |
| } |
| }.schedule(); |
| return Status.OK_STATUS; |
| } |
| }; |
| job.schedule(); |
| } |
| |
| /** |
| * An editor input that gives access to the document created by the diff |
| * formatter. |
| */ |
| private static class DiffEditorInput extends CommitEditorInput { |
| |
| private IDocument document; |
| |
| public DiffEditorInput(RepositoryCommit commit, IDocument diff) { |
| super(commit); |
| document = diff; |
| } |
| |
| public IDocument getDocument() { |
| return document; |
| } |
| |
| @Override |
| public String getName() { |
| return UIText.DiffEditorPage_Title; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| return super.equals(obj) && (obj instanceof DiffEditorInput) |
| && Objects.equals(document, |
| ((DiffEditorInput) obj).document); |
| } |
| |
| @Override |
| public int hashCode() { |
| return super.hashCode() ^ Objects.hashCode(document); |
| } |
| } |
| |
| /** |
| * A document provider that knows about {@link DiffEditorInput}. |
| */ |
| private static class DiffDocumentProvider extends AbstractDocumentProvider { |
| |
| @Override |
| public IStatus getStatus(Object element) { |
| if (element instanceof CommitEditorInput) { |
| RepositoryCommit commit = ((CommitEditorInput) element) |
| .getCommit(); |
| if (commit != null && !commit.isStash() |
| && commit.getRevCommit() != null |
| && commit.getRevCommit().getParentCount() > 1) { |
| return Activator.createErrorStatus( |
| UIText.DiffEditorPage_WarningNoDiffForMerge); |
| } |
| } |
| return Status.OK_STATUS; |
| } |
| |
| @Override |
| protected IDocument createDocument(Object element) |
| throws CoreException { |
| if (element instanceof DiffEditorInput) { |
| IDocument document = ((DiffEditorInput) element).getDocument(); |
| if (document != null) { |
| return document; |
| } |
| } |
| return new Document(); |
| } |
| |
| @Override |
| protected IAnnotationModel createAnnotationModel(Object element) |
| throws CoreException { |
| return new AnnotationModel(); |
| } |
| |
| @Override |
| protected void doSaveDocument(IProgressMonitor monitor, Object element, |
| IDocument document, boolean overwrite) throws CoreException { |
| // Cannot save |
| } |
| |
| @Override |
| protected IRunnableContext getOperationRunner( |
| IProgressMonitor monitor) { |
| return null; |
| } |
| |
| } |
| |
| /** |
| * An ephemeral {@link PreferenceStore} that sets the annotation colors |
| * based on the theme-dependent colors defined for the line backgrounds in a |
| * unified diff. This ensures that the annotation colors are always |
| * consistent with the line backgrounds. Plus the user doesn't have to |
| * configure several related colors, and the annotations update when the |
| * theme changes. |
| */ |
| private static class ThemePreferenceStore extends PreferenceStore { |
| |
| // The colors defined in plugin.xml for these annotations are not taken |
| // into account. We always use auto-computed colors based on the current |
| // settings for the line background colors. |
| |
| private static final String ADD_ANNOTATION_COLOR_PREFERENCE = "org.eclipse.egit.ui.commitEditor.diffAddedColor"; //$NON-NLS-1$ |
| |
| private static final String REMOVE_ANNOTATION_COLOR_PREFERENCE = "org.eclipse.egit.ui.commitEditor.diffRemovedColor"; //$NON-NLS-1$ |
| |
| private final IPropertyChangeListener listener = event -> { |
| String property = event.getProperty(); |
| if (IThemeManager.CHANGE_CURRENT_THEME.equals(property)) { |
| setColorRegistry(); |
| initColors(); |
| } else if (THEME_DiffAddBackgroundColor.equals(property) |
| || THEME_DiffRemoveBackgroundColor.equals(property)) { |
| initColors(); |
| } |
| }; |
| |
| private ColorRegistry currentColors; |
| |
| public ThemePreferenceStore() { |
| super(); |
| setColorRegistry(); |
| initColors(); |
| PlatformUI.getWorkbench().getThemeManager() |
| .addPropertyChangeListener(listener); |
| } |
| |
| private void setColorRegistry() { |
| if (currentColors != null) { |
| currentColors.removeListener(listener); |
| } |
| currentColors = PlatformUI.getWorkbench().getThemeManager() |
| .getCurrentTheme().getColorRegistry(); |
| currentColors.addListener(listener); |
| } |
| |
| private void initColors() { |
| // The overview ruler tones down colors. Since our background colors |
| // usually are already rather pale, let's saturate them more and |
| // brighten them, otherwise the annotations will be barely visible. |
| RGB rgb = adjust(currentColors.getRGB(THEME_DiffAddBackgroundColor), |
| 4.0); |
| setValue(ADD_ANNOTATION_COLOR_PREFERENCE, |
| StringConverter.asString(rgb)); |
| rgb = adjust(currentColors.getRGB(THEME_DiffRemoveBackgroundColor), |
| 4.0); |
| setValue(REMOVE_ANNOTATION_COLOR_PREFERENCE, |
| StringConverter.asString(rgb)); |
| } |
| |
| /** |
| * Increases the saturation (simple multiplier), and brightens dark |
| * colors. |
| * |
| * @param rgb |
| * to modify |
| * @param saturation |
| * multiplier |
| * @return A new {@link RGB} for the new saturated and possibly |
| * brightened color |
| */ |
| private RGB adjust(RGB rgb, double saturation) { |
| float[] hsb = rgb.getHSB(); |
| // We also brighten the color because otherwise the color |
| // manipulations in OverviewRuler result in a fill color barely |
| // discernible from a dark background. |
| return new RGB(hsb[0], (float) Math.min(hsb[1] * saturation, 1.0), |
| hsb[2] < 0.5 ? hsb[2] * 2 : hsb[2]); |
| } |
| |
| public void dispose() { |
| PlatformUI.getWorkbench().getThemeManager() |
| .removePropertyChangeListener(listener); |
| if (currentColors != null) { |
| currentColors.removeListener(listener); |
| currentColors = null; |
| } |
| } |
| } |
| } |