Bug 575504: [SourceEditor] Improve presentation of issues in vertical
ruler

Change-Id: I487f5e745fedc0f6e1688e0bc1e2e57ec1cca1dc
diff --git a/ltk/org.eclipse.statet.ltk.core/plugin.xml b/ltk/org.eclipse.statet.ltk.core/plugin.xml
index 233b520..025df8a 100644
--- a/ltk/org.eclipse.statet.ltk.core/plugin.xml
+++ b/ltk/org.eclipse.statet.ltk.core/plugin.xml
@@ -25,8 +25,24 @@
          name="LKT Model Adapters"
          schema="schema/ModelAdapters.exsd"/>
    
-   <extension-point id="org.eclipse.statet.ltk.ContentTypeActivation" 
+   <extension-point id="org.eclipse.statet.ltk.ContentTypeActivation"
          name="Additional activation for content types"
          schema="schema/ContentTypeActivation.exsd"/>
    
+   <extension point="org.eclipse.core.resources.markers"
+         id="org.eclipse.statet.ltk.resourceMarkers.SourceTask">
+      <super type="org.eclipse.core.resources.taskmarker"/>
+      <super type="org.eclipse.core.resources.textmarker"/>
+      <persistent value="true"/>
+   </extension>
+   <extension
+         point="org.eclipse.core.resources.markers"
+         id="org.eclipse.statet.ltk.resourceMarkers.SourceProblem">
+      <super type="org.eclipse.core.resources.problemmarker"/>
+      <super type="org.eclipse.core.resources.textmarker"/>
+      <persistent value="true"/>
+      <attribute name="categoryId"/>
+      <attribute name="code"/>
+   </extension>
+   
 </plugin>
diff --git a/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/error-away.png b/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/error-away.png
new file mode 100644
index 0000000..f4464e1
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/error-away.png
Binary files differ
diff --git a/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/error-away@2x.png b/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/error-away@2x.png
new file mode 100644
index 0000000..62f47a7
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/error-away@2x.png
Binary files differ
diff --git a/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/error.png b/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/error.png
new file mode 100644
index 0000000..487b5d7
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/error.png
Binary files differ
diff --git a/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/error@2x.png b/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/error@2x.png
new file mode 100644
index 0000000..12f6520
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/error@2x.png
Binary files differ
diff --git a/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/info-away.png b/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/info-away.png
new file mode 100644
index 0000000..0431f32
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/info-away.png
Binary files differ
diff --git a/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/info-away@2x.png b/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/info-away@2x.png
new file mode 100644
index 0000000..b219762
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/info-away@2x.png
Binary files differ
diff --git a/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/info-obj.png b/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/info-obj.png
new file mode 100644
index 0000000..7c96dd9
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/info-obj.png
Binary files differ
diff --git a/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/info@2x.png b/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/info@2x.png
new file mode 100644
index 0000000..5384d0c
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/info@2x.png
Binary files differ
diff --git a/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/warning-away.png b/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/warning-away.png
new file mode 100644
index 0000000..961b3e6
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/warning-away.png
Binary files differ
diff --git a/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/warning-away@2x.png b/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/warning-away@2x.png
new file mode 100644
index 0000000..4162496
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/warning-away@2x.png
Binary files differ
diff --git a/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/warning.png b/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/warning.png
new file mode 100644
index 0000000..f3129b3
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/warning.png
Binary files differ
diff --git a/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/warning@2x.png b/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/warning@2x.png
new file mode 100644
index 0000000..0c46027
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.ui/icons/obj_16/warning@2x.png
Binary files differ
diff --git a/ltk/org.eclipse.statet.ltk.ui/plugin.xml b/ltk/org.eclipse.statet.ltk.ui/plugin.xml
index 5fd6aba..bddfeb0 100644
--- a/ltk/org.eclipse.statet.ltk.ui/plugin.xml
+++ b/ltk/org.eclipse.statet.ltk.ui/plugin.xml
@@ -757,13 +757,26 @@
    <extension
          point="org.eclipse.ui.editors.annotationTypes">
       <type
+            name="org.eclipse.statet.ltk.editorAnnotations.ErrorProblem"
+            super="org.eclipse.ui.workbench.texteditor.error"
+            markerType="org.eclipse.statet.ltk.resourceMarkers.SourceProblem"
+            markerSeverity="2"/>
+      <type
+            name="org.eclipse.statet.ltk.editorAnnotations.WarningProblem"
+            super="org.eclipse.ui.workbench.texteditor.warning"
+            markerType="org.eclipse.statet.ltk.resourceMarkers.SourceProblem"
+            markerSeverity="1"/>
+      <type
+            name="org.eclipse.statet.ltk.editorAnnotations.InfoProblem"
+            super="org.eclipse.ui.workbench.texteditor.info"
+            markerType="org.eclipse.statet.ltk.resourceMarkers.SourceProblem"
+            markerSeverity="0"/>
+      <type
             name="org.eclipse.statet.ecommons.text.editorAnnotations.CommonOccurrences"
-            super="org.eclipse.jdt.ui.occurrences">
-      </type>
+            super="org.eclipse.jdt.ui.occurrences"/>
       <type
             name="org.eclipse.statet.ecommons.text.editorAnnotations.WriteOccurrences"
-            super="org.eclipse.jdt.ui.occurrences.write">
-      </type>
+            super="org.eclipse.jdt.ui.occurrences.write"/>
       <type
             name="org.eclipse.statet.ecommons.text.editorAnnotations.ContentAssistOverwrite">
       </type>
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/internal/ltk/ui/LtkUIPlugin.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/internal/ltk/ui/LtkUIPlugin.java
index 9f6ae25..61393a4 100644
--- a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/internal/ltk/ui/LtkUIPlugin.java
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/internal/ltk/ui/LtkUIPlugin.java
@@ -29,12 +29,15 @@
 import org.eclipse.ui.statushandlers.StatusManager;
 
 import org.eclipse.statet.jcommons.lang.Disposable;
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
 
 import org.eclipse.statet.ecommons.ui.util.ImageRegistryUtil;
 
 import org.eclipse.statet.ltk.ui.LtkUI;
+import org.eclipse.statet.ltk.ui.LtkUIResources;
 
 
+@NonNullByDefault
 public class LtkUIPlugin extends AbstractUIPlugin {
 	
 	
@@ -136,9 +139,16 @@
 		}
 		final ImageRegistryUtil util= new ImageRegistryUtil(this);
 		
-		util.register(LtkUI.OBJ_TEXT_TEMPLATE_IMAGE_ID, ImageRegistryUtil.T_OBJ, "text-template.png"); //$NON-NLS-1$
-		util.register(LtkUI.OBJ_TEXT_AT_TAG_IMAGE_ID, ImageRegistryUtil.T_OBJ, "text-at_tag.png"); //$NON-NLS-1$
-		util.register(LtkUI.OBJ_TEXT_LINKEDRENAME_IMAGE_ID, ImageRegistryUtil.T_OBJ, "linked_rename.png"); //$NON-NLS-1$
+		util.register(LtkUIResources.OBJ_ERROR_IMAGE_ID, ImageRegistryUtil.T_OBJ, "error.png"); //$NON-NLS-1$
+		util.register(LtkUIResources.OBJ_ERROR_AWAY_IMAGE_ID, ImageRegistryUtil.T_OBJ, "error-away.png"); //$NON-NLS-1$
+		util.register(LtkUIResources.OBJ_WARNING_IMAGE_ID, ImageRegistryUtil.T_OBJ, "warning.png"); //$NON-NLS-1$
+		util.register(LtkUIResources.OBJ_WARNING_AWAY_IMAGE_ID, ImageRegistryUtil.T_OBJ, "warning-away.png"); //$NON-NLS-1$
+		util.register(LtkUIResources.OBJ_INFO_IMAGE_ID, ImageRegistryUtil.T_OBJ, "info.png"); //$NON-NLS-1$
+		util.register(LtkUIResources.OBJ_INFO_AWAY_IMAGE_ID, ImageRegistryUtil.T_OBJ, "info-away.png"); //$NON-NLS-1$
+		
+		util.register(LtkUIResources.OBJ_TEXT_TEMPLATE_IMAGE_ID, ImageRegistryUtil.T_OBJ, "text-template.png"); //$NON-NLS-1$
+		util.register(LtkUIResources.OBJ_TEXT_AT_TAG_IMAGE_ID, ImageRegistryUtil.T_OBJ, "text-at_tag.png"); //$NON-NLS-1$
+		util.register(LtkUIResources.OBJ_TEXT_LINKEDRENAME_IMAGE_ID, ImageRegistryUtil.T_OBJ, "linked_rename.png"); //$NON-NLS-1$
 	}
 	
 	
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/internal/ltk/ui/sourceediting/AnnotationPresentationConfig.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/internal/ltk/ui/sourceediting/AnnotationPresentationConfig.java
new file mode 100644
index 0000000..5f25dff
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/internal/ltk/ui/sourceediting/AnnotationPresentationConfig.java
@@ -0,0 +1,86 @@
+/*=============================================================================#
+ # Copyright (c) 2021 Stephan Wahlbrink and others.
+ # 
+ # This program and the accompanying materials are made available under the
+ # terms of the Eclipse Public License 2.0 which is available at
+ # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ # which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ # 
+ # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ # 
+ # Contributors:
+ #     Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.internal.ltk.ui.sourceediting;
+
+import org.eclipse.jface.text.source.IAnnotationAccessExtension;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.ui.editors.text.EditorsUI;
+import org.eclipse.ui.texteditor.AnnotationPreference;
+
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+
+import org.eclipse.statet.ltk.ui.LtkUI;
+
+
+@NonNullByDefault
+public class AnnotationPresentationConfig {
+	
+	private final int level;
+	
+	private final @Nullable String defaultImageId;
+	private @Nullable Image defaultImage;
+	
+	private final @Nullable String awayImageId;
+	private @Nullable Image awayImage;
+	
+	
+	public AnnotationPresentationConfig(final @Nullable String referenceType, final int levelDiff,
+			final @Nullable String defaultImageId, final @Nullable String awayImageId) {
+		final AnnotationPreference preference= EditorsUI.getAnnotationPreferenceLookup().getAnnotationPreference(referenceType);
+		
+		if (levelDiff != Integer.MIN_VALUE) {
+			this.level= ((preference != null) ?
+							preference.getPresentationLayer() :
+							IAnnotationAccessExtension.DEFAULT_LAYER ) +
+					levelDiff;
+		}
+		else {
+			this.level= 0;
+		}
+		
+		this.defaultImageId= defaultImageId;
+		this.awayImageId= awayImageId;
+	}
+	
+	public final int getLevel() {
+		return this.level;
+	}
+	
+	public final @Nullable Image getImage() {
+		Image image= this.defaultImage;
+		if (image == null) {
+			final String imageId= this.defaultImageId;
+			if (imageId != null) {
+				image= LtkUI.getUIResources().getImage(imageId);
+				this.defaultImage= image;
+			}
+		}
+		return image;
+	}
+	
+	public final @Nullable Image getAwayImage() {
+		Image image= this.awayImage;
+		if (image == null) {
+			final String imageId= this.awayImageId;
+			if (imageId != null) {
+				image= LtkUI.getUIResources().getImage(imageId);
+				this.awayImage= image;
+			}
+		}
+		return image;
+	}
+	
+}
\ No newline at end of file
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/LtkUI.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/LtkUI.java
index 3b6ab9c..679aaa9 100644
--- a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/LtkUI.java
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/LtkUI.java
@@ -18,8 +18,6 @@
 
 import org.eclipse.statet.ecommons.ui.util.UIResources;
 
-import org.eclipse.statet.internal.ltk.ui.LtkUIPlugin;
-
 
 @NonNullByDefault
 public class LtkUI {
@@ -28,20 +26,8 @@
 	public static final String BUNDLE_ID= "org.eclipse.statet.ltk.ui"; //$NON-NLS-1$
 	
 	
-	private static final String NS= BUNDLE_ID;
-	
-	public static final String OBJ_TEXT_TEMPLATE_IMAGE_ID= NS + "/image/obj/text.template"; //$NON-NLS-1$
-	public static final String OBJ_TEXT_AT_TAG_IMAGE_ID= NS + "/image/obj/text.at_tag"; //$NON-NLS-1$
-	
-	public static final String OBJ_TEXT_LINKEDRENAME_IMAGE_ID= NS + "/image/obj/assist.linked_rename"; //$NON-NLS-1$
-	
-	
-	private static class UIInstance {
-		private static final UIResources RESOURCES= new UIResources(LtkUIPlugin.getInstance().getImageRegistry());
-	}
-	
 	public static UIResources getUIResources() {
-		return UIInstance.RESOURCES;
+		return LtkUIResources.INSTANCE;
 	}
 	
 	
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/LtkUIResources.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/LtkUIResources.java
new file mode 100644
index 0000000..d814ee3
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/LtkUIResources.java
@@ -0,0 +1,51 @@
+/*=============================================================================#
+ # Copyright (c) 2009, 2021 Stephan Wahlbrink and others.
+ # 
+ # This program and the accompanying materials are made available under the
+ # terms of the Eclipse Public License 2.0 which is available at
+ # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ # which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ # 
+ # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ # 
+ # Contributors:
+ #     Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.ltk.ui;
+
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+
+import org.eclipse.statet.ecommons.ui.util.UIResources;
+
+import org.eclipse.statet.internal.ltk.ui.LtkUIPlugin;
+
+
+@NonNullByDefault
+public class LtkUIResources extends UIResources {
+	
+	
+	private static final String NS= LtkUI.BUNDLE_ID;
+	
+	public static final String OBJ_ERROR_IMAGE_ID= NS + "/image/obj/Error"; //$NON-NLS-1$
+	public static final String OBJ_ERROR_AWAY_IMAGE_ID= NS + "/image/obj/Error.away"; //$NON-NLS-1$
+	public static final String OBJ_WARNING_IMAGE_ID= NS + "/image/obj/Warning"; //$NON-NLS-1$
+	public static final String OBJ_WARNING_AWAY_IMAGE_ID= NS + "/image/obj/Warning.away"; //$NON-NLS-1$
+	public static final String OBJ_INFO_IMAGE_ID= NS + "/image/obj/Info"; //$NON-NLS-1$
+	public static final String OBJ_INFO_AWAY_IMAGE_ID= NS + "/image/obj/Info.away"; //$NON-NLS-1$
+	
+	public static final String OBJ_TEXT_TEMPLATE_IMAGE_ID= NS + "/image/obj/text.template"; //$NON-NLS-1$
+	public static final String OBJ_TEXT_AT_TAG_IMAGE_ID= NS + "/image/obj/text.at_tag"; //$NON-NLS-1$
+	
+	public static final String OBJ_TEXT_LINKEDRENAME_IMAGE_ID= NS + "/image/obj/assist.linked_rename"; //$NON-NLS-1$
+	
+	
+	static final LtkUIResources INSTANCE= new LtkUIResources();
+	
+	
+	private LtkUIResources() {
+		super(LtkUIPlugin.getInstance().getImageRegistry());
+	}
+	
+	
+}
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceAnnotationModel.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceAnnotationModel.java
index 7ceb6a1..e791b8d 100644
--- a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceAnnotationModel.java
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceAnnotationModel.java
@@ -19,15 +19,23 @@
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import org.eclipse.core.resources.IMarker;
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.jface.text.BadLocationException;
 import org.eclipse.jface.text.Position;
+import org.eclipse.jface.text.source.Annotation;
+import org.eclipse.ui.texteditor.MarkerAnnotation;
+import org.eclipse.ui.texteditor.MarkerUtilities;
 import org.eclipse.ui.texteditor.ResourceMarkerAnnotationModel;
 
+import org.eclipse.statet.jcommons.collections.ImCollections;
 import org.eclipse.statet.jcommons.collections.ImList;
+import org.eclipse.statet.jcommons.collections.ImSet;
+import org.eclipse.statet.jcommons.lang.NonNull;
 import org.eclipse.statet.jcommons.lang.NonNullByDefault;
 import org.eclipse.statet.jcommons.lang.Nullable;
 
@@ -35,12 +43,11 @@
 import org.eclipse.statet.ltk.issues.core.Issue;
 import org.eclipse.statet.ltk.issues.core.IssueRequestor;
 import org.eclipse.statet.ltk.issues.core.IssueTypeSet;
+import org.eclipse.statet.ltk.issues.core.IssueTypeSet.IssueCategory;
 import org.eclipse.statet.ltk.issues.core.IssueTypeSet.ProblemCategory;
-import org.eclipse.statet.ltk.issues.core.IssueTypeSet.ProblemTypes;
 import org.eclipse.statet.ltk.issues.core.IssueTypeSet.TaskCategory;
 import org.eclipse.statet.ltk.issues.core.Problem;
 import org.eclipse.statet.ltk.issues.core.impl.BasicIssueRequestor;
-import org.eclipse.statet.ltk.ui.sourceediting.SourceProblemAnnotation.PresentationConfig;
 
 
 /**
@@ -51,6 +58,96 @@
 public abstract class SourceAnnotationModel extends ResourceMarkerAnnotationModel {
 	
 	
+	private static class PositionMap<V> implements Iterable<PositionMap.Entry<V>> {
+		
+		static class Entry<V> {
+			
+			final Position position;
+			
+			ImList<V> annotations;
+			
+			public Entry(final Position position, final V value) {
+				this.position= position;
+				this.annotations= ImCollections.newList(value);
+			}
+			
+		}
+		
+		
+		private final List<Entry<V>> list= new ArrayList<>();
+		private int anchor= 0;
+		
+		
+		public PositionMap() {
+		}
+		
+		
+		private int indexOf(final Position position) {
+			final int length= this.list.size();
+			for (int i= 0; i < length; i++) {
+				final var entry= this.list.get(i);
+				if (entry.position.equals(position)) {
+					return i;
+				}
+			}
+			return -1;
+		}
+		
+		public @Nullable ImList<V> get(final Position position) {
+			final int length= this.list.size();
+			// behind anchor
+			for (int i= this.anchor; i < length; i++) {
+				final var entry= this.list.get(i);
+				if (entry.position.equals(position)) {
+					this.anchor= i;
+					return entry.annotations;
+				}
+			}
+			// before anchor
+			for (int i= 0; i < this.anchor; i++) {
+				final var entry= this.list.get(i);
+				if (entry.position.equals(position)) {
+					this.anchor= i;
+					return entry.annotations;
+				}
+			}
+			return null;
+		}
+		
+		@Override
+		public Iterator<Entry<V>> iterator() {
+			return this.list.iterator();
+		}
+		
+		public void add(final Position position, final V value) {
+			final int index= indexOf(position);
+			if (index >= 0) {
+				final var entry= this.list.get(index);
+				entry.annotations= ImCollections.addElement(entry.annotations, value);
+			}
+			else {
+				this.list.add(new Entry<>(position, value));
+			}
+		}
+		
+		public void remove(final Position position, final Object value) {
+			final int index= indexOf(position);
+			if (index >= 0) {
+				final var entry= this.list.get(index);
+				entry.annotations= ImCollections.removeElement(entry.annotations, value);
+				if (entry.annotations.isEmpty()) {
+					this.list.remove(index);
+				}
+			}
+		}
+		
+		public void clear() {
+			this.list.clear();
+		}
+		
+	}
+	
+	
 	protected class SourceAnnotationIssueRequestor extends BasicIssueRequestor {
 		
 		
@@ -84,11 +181,12 @@
 	
 	private final AtomicInteger reportingCounter= new AtomicInteger();
 	
-	private final List<SourceProblemAnnotation> problemAnnotations= new ArrayList<>();
+	private final List<SourceIssueEditorAnnotation> editorAnnotations= new ArrayList<>();
 	
-//	private ReverseMap reverseMap= new ReverseMap();
-//	private List previouslyOverlaid= null;
-//	private List currentlyOverlaid= new ArrayList();
+	private final PositionMap<SourceIssueMarkerAnnotation<?>> markerAnnotations= new PositionMap<>();
+	
+	private @Nullable ImSet<IssueCategory<?>> reportedConfig;
+	private List<SourceIssueMarkerAnnotation<?>> overlaidMarkerAnnotations= new ArrayList<>();
 	
 	
 	public SourceAnnotationModel(final IResource resource, final IssueTypeSet issueTypeSet) {
@@ -101,14 +199,26 @@
 		return this.issueTypeSet;
 	}
 	
-	protected abstract boolean isHandlingTemporaryProblems(ProblemCategory category);
 	
-//	@Override
-//	protected MarkerAnnotation createMarkerAnnotation(IMarker marker) {
-//		if (JavaMarkerAnnotation.isJavaAnnotation(marker))
-//			return new JavaMarkerAnnotation(marker);
-//		return super.createMarkerAnnotation(marker);
-//	}
+	protected abstract boolean isHandlingTemporaryProblems(ProblemCategory issueCategory);
+	
+	
+	@Override
+	protected @Nullable MarkerAnnotation createMarkerAnnotation(final IMarker marker) {
+		String markerType= MarkerUtilities.getMarkerType(marker);
+		if (markerType != null) {
+			markerType= markerType.intern();
+			final var issueCategory= this.issueTypeSet.getCategory(Ltk.PERSISTENCE_CONTEXT, markerType);
+			if (issueCategory != null) {
+				final var annotationType= issueCategory.mapType(Ltk.PERSISTENCE_CONTEXT, Ltk.EDITOR_CONTEXT,
+						markerType );
+				if (annotationType != null) {
+					return new SourceIssueMarkerAnnotation<>(issueCategory, annotationType, marker);
+				}
+			}
+		}
+		return super.createMarkerAnnotation(marker);
+	}
 	
 //	@Override
 //	protected AnnotationModelEvent createAnnotationModelEvent() {
@@ -124,116 +234,77 @@
 		return new SourceAnnotationIssueRequestor(getIssueTypeSet());
 	}
 	
-	public void clearProblems(final @Nullable String category) {
-		synchronized (getLockObject()) {
-			if (this.problemAnnotations.size() > 0) {
-				if (category == null) {
-					removeAnnotations(this.problemAnnotations, true, true);
-					this.problemAnnotations.clear();
-				}
-				else {
-					final Iterator<SourceProblemAnnotation> iter= this.problemAnnotations.iterator();
-					List<SourceProblemAnnotation> toRemove= null;
-					while (iter.hasNext()) {
-						final SourceProblemAnnotation annotation= iter.next();
-						if (annotation.getProblem().getCategoryId() == category) {
-							iter.remove();
-							if (toRemove == null) {
-								toRemove= new ArrayList<>();
-							}
-							toRemove.add(annotation);
-						}
-					}
-					if (toRemove != null) {
-						removeAnnotations(toRemove, true, true);
-					}
-				}
-			}
-		}
-	}
-	
 	private void reportIssues(final ImList<BasicIssueRequestor.ProblemBatch> problemBatches) {
-		boolean reportedIssuesChanged= false;
-		
 		synchronized (getLockObject()) {
 			if (this.reportingCounter.decrementAndGet() != 0) {
 				return;
 			}
-//			this.previouslyOverlaid= this.currentlyOverlaid;
-//			this.currentlyOverlaid= new ArrayList();
 			
-			if (this.problemAnnotations.size() > 0) {
-				reportedIssuesChanged= true;
-				removeAnnotations(this.problemAnnotations, false, true);
-				this.problemAnnotations.clear();
+			{	final Set<IssueCategory<?>> prevConfig= this.reportedConfig;
+				final @NonNull IssueCategory<?>[] enabledCagetories= new  @NonNull IssueCategory[problemBatches.size()];
+				int numEnabled= 0;
+				for (final var problemBatch : problemBatches) {
+					final var issueCategory= problemBatch.getCategory();
+					if (problemBatch.isEnabled()) {
+						enabledCagetories[numEnabled++]= issueCategory;
+						if (prevConfig != null && !prevConfig.contains(issueCategory)) {
+							resetMarkerAnnotationsControl(issueCategory, true);
+						}
+					}
+					else {
+						if (prevConfig != null && prevConfig.contains(issueCategory)) {
+							resetMarkerAnnotationsControl(issueCategory, false);
+						}
+					}
+				}
+				this.reportedConfig= ImCollections.newIdentitySet(enabledCagetories, 0, numEnabled);
+			}
+			
+			final var prevControlledAnnotations= this.overlaidMarkerAnnotations;
+			this.overlaidMarkerAnnotations= new ArrayList<>();
+			
+			if (this.editorAnnotations.size() > 0) {
+				removeAnnotations(this.editorAnnotations, false, true);
+				this.editorAnnotations.clear();
 			}
 			
 			for (final var problemBatch : problemBatches) {
-				final ProblemTypes types= nonNullAssert(
+				final var problemTypes= nonNullAssert(
 						problemBatch.getCategory().getTypes(Ltk.EDITOR_CONTEXT) );
-				for (final Problem problem : problemBatch.getAcceptedIssues()) {
-					final Position position= createPosition(problem);
-					if (position != null) {
-						try {
-							final var annotation= new SourceProblemAnnotation(
-									types.getType(problem.getSeverity()), problem,
-									getPresentationConfig(problem) );
-//							overlayMarkers(position, annotation);
-							if (annotation != null) {
+				if (problemBatch.isEnabled()) {
+					for (final Problem problem : problemBatch.getAcceptedIssues()) {
+						final Position position= createPosition(problem);
+						if (position != null) {
+							try {
+								final var annotation= new SourceIssueEditorAnnotation(
+										problemBatch.getCategory(),
+										problemTypes.getType(problem.getSeverity()),
+										problem );
+								installMarkerAnnotationOverlays(position, annotation);
 								addAnnotation(annotation, position, false);
-								this.problemAnnotations.add(annotation);
-								reportedIssuesChanged= true;
+								this.editorAnnotations.add(annotation);
 							}
-						} catch (final BadLocationException x) {
-							// ignore invalid position
+							catch (final BadLocationException x) {
+							}
 						}
 					}
 				}
 			}
 			
-//			removeMarkerOverlays(isCanceled);
-//			this.previouslyOverlaid= null;
+			if (prevControlledAnnotations != null && !prevControlledAnnotations.isEmpty()) {
+				prevControlledAnnotations.removeAll(this.overlaidMarkerAnnotations);
+				for (final var problemBatch : problemBatches) {
+					if (problemBatch.isEnabled()) {
+						removeMarkerAnnotationOverlays(prevControlledAnnotations,
+								problemBatch.getCategory() );
+					}
+				}
+			}
 		}
 		
-		if (reportedIssuesChanged) {
-			fireModelChanged();
-		}
+		fireModelChanged();
 	}
 	
-//	private void overlayMarkers(Position position, ProblemAnnotation problemAnnotation) {
-//		Object value= getAnnotations(position);
-//		if (value instanceof List) {
-//			List list= (List) value;
-//			for (Iterator e= list.iterator(); e.hasNext();)
-//				setOverlay(e.next(), problemAnnotation);
-//		} else {
-//			setOverlay(value, problemAnnotation);
-//		}
-//	}
-//
-//	private void setOverlay(Object value, ProblemAnnotation problemAnnotation) {
-//		if (value instanceof  JavaMarkerAnnotation) {
-//			JavaMarkerAnnotation annotation= (JavaMarkerAnnotation) value;
-//			if (annotation.isProblem()) {
-//				annotation.setOverlay(problemAnnotation);
-//				this.previouslyOverlaid.remove(annotation);
-//				this.currentlyOverlaid.add(annotation);
-//			}
-//		} else {
-//		}
-//	}
-//
-//	private void removeMarkerOverlays(boolean isCanceled) {
-//		if (isCanceled) {
-//			this.currentlyOverlaid.addAll(this.previouslyOverlaid);
-//		} else if (this.previouslyOverlaid != null) {
-//			Iterator e= this.previouslyOverlaid.iterator();
-//			while (e.hasNext()) {
-//				JavaMarkerAnnotation annotation= (JavaMarkerAnnotation) e.next();
-//				annotation.setOverlay(null);
-//			}
-//		}
-//	}
 	
 	protected Position createPosition(final Issue issue) {
 		final int start= issue.getSourceStartOffset();
@@ -244,62 +315,101 @@
 		return new Position(start, end-start);
 	}
 	
-	protected PresentationConfig getPresentationConfig(final Problem problem) {
-		switch (problem.getSeverity()) {
-		case Problem.SEVERITY_ERROR:
-			return SourceProblemAnnotation.ERROR_CONFIG;
-		case Problem.SEVERITY_WARNING:
-			return SourceProblemAnnotation.WARNING_CONFIG;
-		default:
-			return SourceProblemAnnotation.INFO_CONFIG;
+	
+	private void installMarkerAnnotationOverlays(final Position position,
+			final SourceIssueEditorAnnotation problemAnnotation) {
+		final var annotations= this.markerAnnotations.get(position);
+		if (annotations != null) {
+			final var issueCategory= problemAnnotation.getIssueCategory();
+			for (final var markerAnnotation : annotations) {
+				if (markerAnnotation.getIssueCategory() == issueCategory) {
+					markerAnnotation.setOverlay(problemAnnotation);
+					this.overlaidMarkerAnnotations.add(markerAnnotation);
+				}
+			}
 		}
 	}
 	
-//	@Override
-//	protected void addAnnotation(Annotation annotation, Position position, boolean fireModelChanged) throws BadLocationException {
-//		super.addAnnotation(annotation, position, fireModelChanged);
-//
-//		synchronized (getLockObject()) {
-//			Object cached= this.reverseMap.get(position);
-//			if (cached == null)
-//				this.reverseMap.put(position, annotation);
-//			else if (cached instanceof List) {
-//				List list= (List) cached;
-//				list.add(annotation);
-//			} else if (cached instanceof Annotation) {
-//				List list= new ArrayList(2);
-//				list.add(cached);
-//				list.add(annotation);
-//				this.reverseMap.put(position, list);
-//			}
-//		}
-//	}
-//
-//	@Override
-//	protected void removeAllAnnotations(boolean fireModelChanged) {
-//		super.removeAllAnnotations(fireModelChanged);
-//		synchronized (getLockObject()) {
-//			this.reverseMap.clear();
-//		}
-//	}
-//
-//	@Override
-//	protected void removeAnnotation(Annotation annotation, boolean fireModelChanged) {
-//		Position position= getPosition(annotation);
-//		synchronized (getLockObject()) {
-//			Object cached= this.reverseMap.get(position);
-//			if (cached instanceof List) {
-//				List list= (List) cached;
-//				list.remove(annotation);
-//				if (list.size() == 1) {
-//					this.reverseMap.put(position, list.get(0));
-//					list.clear();
-//				}
-//			} else if (cached instanceof Annotation) {
-//				this.reverseMap.remove(position);
-//			}
-//		}
-//		super.removeAnnotation(annotation, fireModelChanged);
-//	}
+	private void removeMarkerAnnotationOverlays(final List<SourceIssueMarkerAnnotation<?>> annotations,
+			final IssueCategory<?> issueCategory) {
+		for (final var markerAnnotation : annotations) {
+			if (markerAnnotation.getIssueCategory() == issueCategory) {
+				markerAnnotation.setOverlay(null);
+			}
+		}
+	}
+	
+	private void resetMarkerAnnotationsControl(final IssueCategory<?> issueCategory,
+			final boolean isControlled) {
+		final var modelEvent= getAnnotationModelEvent();
+		if (isControlled) {
+			for (final var entry : this.markerAnnotations) {
+				for (final var markerAnnotation : entry.annotations) {
+					if (markerAnnotation.getIssueCategory() == issueCategory
+							&& !markerAnnotation.isControlled()) {
+						markerAnnotation.setOverlay(null);
+						modelEvent.annotationChanged(markerAnnotation);
+					}
+				}
+			}
+		}
+		else {
+			for (final var entry : this.markerAnnotations) {
+				for (final var markerAnnotation : entry.annotations) {
+					if (markerAnnotation.getIssueCategory() == issueCategory
+							&& markerAnnotation.isControlled()) {
+						markerAnnotation.disableOverlay();
+						modelEvent.annotationChanged(markerAnnotation);
+					}
+				}
+			}
+		}
+	}
+	
+	
+	@Override
+	protected void addAnnotation(final Annotation annotation, final Position position,
+			final boolean fireModelChanged) throws BadLocationException {
+		if (annotation instanceof SourceIssueMarkerAnnotation) {
+			final var markerAnnotation= (SourceIssueMarkerAnnotation<?>)annotation;
+			synchronized (getLockObject()) {
+				final var config= this.reportedConfig;
+				if (config != null && config.contains(markerAnnotation.getIssueCategory())) {
+					markerAnnotation.setOverlay(null);
+					this.overlaidMarkerAnnotations.add(markerAnnotation); // force check
+				}
+				this.markerAnnotations.add(position, markerAnnotation);
+			}
+		}
+		
+		super.addAnnotation(annotation, position, fireModelChanged);
+	}
+	
+	@Override
+	protected void removeAnnotation(final Annotation annotation, final boolean fireModelChanged) {
+		if (annotation instanceof SourceIssueMarkerAnnotation) {
+			final var markerAnnotation= (SourceIssueMarkerAnnotation<?>)annotation;
+			final var position= getPosition(markerAnnotation);
+			if (position != null) {
+				synchronized (getLockObject()) {
+					if (markerAnnotation.isControlled()) {
+						markerAnnotation.disableOverlay();
+					}
+					this.markerAnnotations.remove(position, markerAnnotation);
+				}
+			}
+		}
+		
+		super.removeAnnotation(annotation, fireModelChanged);
+	}
+	
+	@Override
+	protected void removeAllAnnotations(final boolean fireModelChanged) {
+		super.removeAllAnnotations(fireModelChanged);
+		
+		synchronized (getLockObject()) {
+			this.markerAnnotations.clear();
+		}
+	}
 	
 }
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceEditor1.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceEditor1.java
index 03f479e..fce88bc 100644
--- a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceEditor1.java
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceEditor1.java
@@ -41,6 +41,7 @@
 import org.eclipse.jface.text.ITextViewerExtension5;
 import org.eclipse.jface.text.link.ILinkedModeListener;
 import org.eclipse.jface.text.link.LinkedModeModel;
+import org.eclipse.jface.text.source.Annotation;
 import org.eclipse.jface.text.source.IAnnotationModel;
 import org.eclipse.jface.text.source.ISourceViewer;
 import org.eclipse.jface.text.source.IVerticalRuler;
@@ -70,6 +71,7 @@
 import org.eclipse.ui.texteditor.ITextEditorActionConstants;
 import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
 import org.eclipse.ui.texteditor.IUpdate;
+import org.eclipse.ui.texteditor.MarkerAnnotation;
 import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
 import org.eclipse.ui.texteditor.templates.ITemplatesPage;
 import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
@@ -1034,6 +1036,19 @@
 		return null;
 	}
 	
+	@Override
+	protected void updateMarkerViews(Annotation annotation) {
+		if (annotation instanceof SourceIssueAnnotation<?>) {
+			for (final var overliad : ((SourceIssueAnnotation<?>)annotation).getOverlaidAnnotations()) {
+				if (overliad instanceof MarkerAnnotation) {
+					annotation= overliad;
+					break;
+				}
+			}
+		}
+		super.updateMarkerViews(annotation);
+	}
+	
 	
 	// inject annotation painter workaround
 	@Override
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceIssueAnnotation.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceIssueAnnotation.java
new file mode 100644
index 0000000..1f8e6d7
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceIssueAnnotation.java
@@ -0,0 +1,57 @@
+/*=============================================================================#
+ # Copyright (c) 2021 Stephan Wahlbrink and others.
+ # 
+ # This program and the accompanying materials are made available under the
+ # terms of the Eclipse Public License 2.0 which is available at
+ # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ # which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ # 
+ # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ # 
+ # Contributors:
+ #     Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.ltk.ui.sourceediting;
+
+import org.eclipse.jface.text.source.Annotation;
+
+import org.eclipse.statet.jcommons.collections.ImList;
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+
+import org.eclipse.statet.ltk.issues.core.Issue;
+import org.eclipse.statet.ltk.issues.core.IssueTypeSet;
+
+
+@NonNullByDefault
+public interface SourceIssueAnnotation<TIssue extends Issue> {
+	
+	
+	/**
+	 * Returns the category of the issue presented by this annotation.
+	 * 
+	 * @return the issue category.
+	 */
+	IssueTypeSet.IssueCategory<TIssue> getIssueCategory();
+	
+	/**
+	 * Returns the annotation type.
+	 * 
+	 * @return the type.
+	 */
+	String getType();
+	
+	boolean isMarkedDeleted();
+	
+	
+	/**
+	 * Returns the annotation overlaying this annotation.
+	 * 
+	 * @return the overlay annotation if overlaid, otherwise {@code null}.
+	 */
+	@Nullable SourceIssueAnnotation<TIssue> getOverlay();
+	
+	ImList<Annotation> getOverlaidAnnotations();
+	
+}
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceIssueEditorAnnotation.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceIssueEditorAnnotation.java
new file mode 100644
index 0000000..8f0e331
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceIssueEditorAnnotation.java
@@ -0,0 +1,172 @@
+/*=============================================================================#
+ # Copyright (c) 2008, 2021 Stephan Wahlbrink and others.
+ # 
+ # This program and the accompanying materials are made available under the
+ # terms of the Eclipse Public License 2.0 which is available at
+ # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ # which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ # 
+ # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ # 
+ # Contributors:
+ #     Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.ltk.ui.sourceediting;
+
+import org.eclipse.jface.text.quickassist.IQuickFixableAnnotation;
+import org.eclipse.jface.text.source.Annotation;
+import org.eclipse.jface.text.source.IAnnotationPresentation;
+import org.eclipse.jface.text.source.ImageUtilities;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Canvas;
+
+import org.eclipse.statet.jcommons.collections.ImCollections;
+import org.eclipse.statet.jcommons.collections.ImList;
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+
+import org.eclipse.statet.internal.ltk.ui.sourceediting.AnnotationPresentationConfig;
+import org.eclipse.statet.ltk.core.Ltk;
+import org.eclipse.statet.ltk.issues.core.IssueTypeSet;
+import org.eclipse.statet.ltk.issues.core.IssueTypeSet.IssueCategory;
+import org.eclipse.statet.ltk.issues.core.IssueTypeSet.ProblemTypes;
+import org.eclipse.statet.ltk.issues.core.Problem;
+import org.eclipse.statet.ltk.ui.LtkUIResources;
+
+
+/**
+ * Annotation representing an {@link Problem}.
+ */
+@NonNullByDefault
+public class SourceIssueEditorAnnotation extends Annotation
+		implements SourceIssueAnnotation<Problem>, IAnnotationPresentation, IQuickFixableAnnotation {
+	
+	
+	static final AnnotationPresentationConfig ERROR_CONFIG= new AnnotationPresentationConfig(
+			"org.eclipse.ui.workbench.texteditor.error", +1, //$NON-NLS-1$
+			LtkUIResources.OBJ_ERROR_IMAGE_ID, LtkUIResources.OBJ_ERROR_AWAY_IMAGE_ID );
+	static final AnnotationPresentationConfig WARNING_CONFIG= new AnnotationPresentationConfig(
+			"org.eclipse.ui.workbench.texteditor.warning", +1, //$NON-NLS-1$
+			LtkUIResources.OBJ_WARNING_IMAGE_ID, LtkUIResources.OBJ_WARNING_AWAY_IMAGE_ID );
+	static final AnnotationPresentationConfig INFO_CONFIG= new AnnotationPresentationConfig(
+			"org.eclipse.ui.workbench.texteditor.info", +1, //$NON-NLS-1$
+			LtkUIResources.OBJ_INFO_IMAGE_ID, LtkUIResources.OBJ_INFO_AWAY_IMAGE_ID );
+	static final AnnotationPresentationConfig FALLBACK_CONFIG = new AnnotationPresentationConfig(
+			null, Integer.MIN_VALUE,
+			null, null );
+	
+	static AnnotationPresentationConfig getProblemPresentationConfig(final int severity) {
+		switch (severity) {
+		case Problem.SEVERITY_ERROR:
+			return ERROR_CONFIG;
+		case Problem.SEVERITY_WARNING:
+			return WARNING_CONFIG;
+		case Problem.SEVERITY_INFO:
+			return INFO_CONFIG;
+		default:
+			return FALLBACK_CONFIG;
+		}
+	}
+	
+	static AnnotationPresentationConfig getIssuePresentationConfig(
+			final IssueCategory<?> issueCategory, final String type) {
+		if (issueCategory instanceof IssueTypeSet.ProblemCategory) {
+			final ProblemTypes problemTypes= ((IssueTypeSet.ProblemCategory)issueCategory).getTypes(Ltk.EDITOR_CONTEXT);
+			if (problemTypes != null) {
+				return getProblemPresentationConfig(problemTypes.getSeverity(type));
+			}
+		}
+		return FALLBACK_CONFIG;
+	}
+	
+	
+	private final IssueTypeSet.IssueCategory<Problem> issueCategory;
+	private final Problem issue;
+	
+	private boolean isQuickFixable= false;
+	private boolean isQuickFixableStateSet= false;
+	
+	private final AnnotationPresentationConfig presentationConfig;
+	
+	
+	public SourceIssueEditorAnnotation(final IssueTypeSet.IssueCategory<Problem> issueCategory,
+			final String type, final Problem issue) {
+		super(type, false, null);
+		this.issueCategory= issueCategory;
+		this.issue= issue;
+		
+		this.presentationConfig= getProblemPresentationConfig(issue.getSeverity());
+	}
+	
+	
+	@Override
+	public IssueCategory<Problem> getIssueCategory() {
+		return this.issueCategory;
+	}
+	
+	@Override
+	public String getText() {
+		return this.issue.getMessage();
+	}
+	
+	public Problem getIssue() {
+		return this.issue;
+	}
+	
+	
+	@Override
+	public int getLayer() {
+		return this.presentationConfig.getLevel();
+	}
+	
+	@Override
+	public void paint(final GC gc, final Canvas canvas, final Rectangle bounds) {
+		final Image image= this.presentationConfig.getImage();
+		if (image != null) {
+			ImageUtilities.drawImage(image, gc, canvas, bounds, SWT.CENTER, SWT.TOP);
+		}
+	}
+	
+	
+	@Override
+	public void setQuickFixable(final boolean state) {
+		this.isQuickFixable= state;
+		this.isQuickFixableStateSet= true;
+	}
+	
+	@Override
+	public boolean isQuickFixableStateSet() {
+		return this.isQuickFixableStateSet;
+	}
+	
+	@Override
+	public boolean isQuickFixable() {
+		return this.isQuickFixable;
+	}
+	
+	
+	private ImList<Annotation> overlaidAnnotations= ImCollections.emptyList();
+	
+	@Override
+	public @Nullable SourceIssueAnnotation<Problem> getOverlay() {
+		return null;
+	}
+	
+	@Override
+	public ImList<Annotation> getOverlaidAnnotations() {
+		return this.overlaidAnnotations;
+	}
+	
+	public void addOverlaidAnnotation(final Annotation annotation) {
+		this.overlaidAnnotations= ImCollections.addElement(this.overlaidAnnotations, annotation);
+	}
+	
+	public void removeOverlaidAnnotation(final Annotation annotation) {
+		this.overlaidAnnotations= ImCollections.removeElement(this.overlaidAnnotations, annotation);
+	}
+	
+}
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceIssueMarkerAnnotation.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceIssueMarkerAnnotation.java
new file mode 100644
index 0000000..f19a12e
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceIssueMarkerAnnotation.java
@@ -0,0 +1,140 @@
+/*=============================================================================#
+ # Copyright (c) 2021 Stephan Wahlbrink and others.
+ # 
+ # This program and the accompanying materials are made available under the
+ # terms of the Eclipse Public License 2.0 which is available at
+ # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ # which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ # 
+ # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ # 
+ # Contributors:
+ #     Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.ltk.ui.sourceediting;
+
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.jface.text.source.Annotation;
+import org.eclipse.jface.text.source.IAnnotationPresentation;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.texteditor.MarkerAnnotation;
+
+import org.eclipse.statet.jcommons.collections.ImCollections;
+import org.eclipse.statet.jcommons.collections.ImList;
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+
+import org.eclipse.statet.internal.ltk.ui.sourceediting.AnnotationPresentationConfig;
+import org.eclipse.statet.ltk.issues.core.Issue;
+import org.eclipse.statet.ltk.issues.core.IssueTypeSet;
+
+
+@NonNullByDefault
+final class SourceIssueMarkerAnnotation<TIssue extends Issue> extends MarkerAnnotation
+		implements SourceIssueAnnotation<TIssue>, IAnnotationPresentation {
+	
+	
+	final IssueTypeSet.IssueCategory<TIssue> issueCategory;
+	
+	private @Nullable SourceIssueEditorAnnotation overlay;
+	
+	private boolean isControlled;
+	
+	private final AnnotationPresentationConfig presentationConfig;
+	
+	
+	public SourceIssueMarkerAnnotation(final IssueTypeSet.IssueCategory<TIssue> issueCategory,
+			final String type, final IMarker marker) {
+		super(type, marker);
+		this.issueCategory= issueCategory;
+		
+		this.presentationConfig= SourceIssueEditorAnnotation.getIssuePresentationConfig(
+				issueCategory, type );
+	}
+	
+	
+	@Override
+	public IssueTypeSet.IssueCategory<TIssue> getIssueCategory() {
+		return this.issueCategory;
+	}
+	
+	
+	boolean isControlled() {
+		return this.isControlled;
+	}
+	
+	@Override
+	public boolean isMarkedDeleted() {
+		return (this.isControlled || super.isMarkedDeleted());
+	}
+	
+	@Override
+	@SuppressWarnings({ "unchecked", "rawtypes" })
+	public @Nullable SourceIssueAnnotation<TIssue> getOverlay() {
+		return (SourceIssueAnnotation)this.overlay;
+	}
+	
+	@Override
+	public ImList<Annotation> getOverlaidAnnotations() {
+		return ImCollections.emptyList();
+	}
+	
+	void disableOverlay() {
+		this.isControlled= false;
+		final var prev= this.overlay;
+		if (prev != null) {
+			prev.removeOverlaidAnnotation(this);
+		}
+		this.overlay= null;
+	}
+	
+	void setOverlay(final @Nullable SourceIssueEditorAnnotation annotation) {
+		this.isControlled= true;
+		final var prev= this.overlay;
+		if (prev != null) {
+			prev.removeOverlaidAnnotation(this);
+		}
+		this.overlay= annotation;
+		if (annotation != null) {
+			annotation.addOverlaidAnnotation(this);
+		}
+	}
+	
+	
+	@Override
+	@SuppressWarnings("deprecation")
+	public int getLayer() {
+		return super.getLayer();
+	}
+	
+	@Override
+	@SuppressWarnings("deprecation")
+	public void paint(final GC gc, final Canvas canvas, final Rectangle r) {
+		if (getOverlay() != null) {
+			return;
+		}
+		super.paint(gc, canvas, r);
+	}
+	
+	@Override
+	@SuppressWarnings("deprecation")
+	protected @Nullable Image getImage(final Display display) {
+		Image image;
+		if (isMarkedDeleted()) {
+			image= this.presentationConfig.getAwayImage();
+		}
+		else {
+			image= this.presentationConfig.getImage();
+		}
+		if (image != null) {
+			return image;
+		}
+		return super.getImage(display);
+	}
+	
+}
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceProblemAnnotation.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceProblemAnnotation.java
deleted file mode 100644
index ebe629a..0000000
--- a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceProblemAnnotation.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*=============================================================================#
- # Copyright (c) 2008, 2021 Stephan Wahlbrink and others.
- # 
- # This program and the accompanying materials are made available under the
- # terms of the Eclipse Public License 2.0 which is available at
- # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
- # which is available at https://www.apache.org/licenses/LICENSE-2.0.
- # 
- # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
- # 
- # Contributors:
- #     Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
- #=============================================================================*/
-
-package org.eclipse.statet.ltk.ui.sourceediting;
-
-import org.eclipse.jface.text.quickassist.IQuickFixableAnnotation;
-import org.eclipse.jface.text.source.Annotation;
-import org.eclipse.jface.text.source.IAnnotationAccessExtension;
-import org.eclipse.jface.text.source.IAnnotationPresentation;
-import org.eclipse.jface.text.source.ImageUtilities;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.graphics.GC;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.widgets.Canvas;
-import org.eclipse.ui.PlatformUI;
-import org.eclipse.ui.editors.text.EditorsUI;
-import org.eclipse.ui.texteditor.AnnotationPreference;
-import org.eclipse.ui.texteditor.DefaultMarkerAnnotationAccess;
-
-import org.eclipse.statet.jcommons.lang.NonNullByDefault;
-import org.eclipse.statet.jcommons.lang.Nullable;
-
-import org.eclipse.statet.ltk.issues.core.Problem;
-
-
-/**
- * Annotation representing an {@link Problem}.
- */
-@NonNullByDefault
-public class SourceProblemAnnotation extends Annotation
-		implements IAnnotationPresentation, IQuickFixableAnnotation {
-	
-	public static final String TASK_ANNOTATION_TYPE= "org.eclipse.ui.workbench.texteditor.task"; //$NON-NLS-1$
-	public static final String SPELLING_ANNOTATION_TYPE= "org.eclipse.ui.workbench.texteditor.spelling"; //$NON-NLS-1$
-	
-	
-	public static class PresentationConfig {
-		
-		private final int level;
-		
-		private @Nullable String imageKey;
-		private @Nullable Image image;
-		
-		
-		private PresentationConfig(final String referenceType, final int levelDiff) {
-			final AnnotationPreference preference= EditorsUI.getAnnotationPreferenceLookup().getAnnotationPreference(referenceType);
-			
-			if (levelDiff != Integer.MIN_VALUE) {
-				this.level= ((preference != null) ?
-								preference.getPresentationLayer() :
-								IAnnotationAccessExtension.DEFAULT_LAYER ) +
-						levelDiff;
-			}
-			else {
-				this.level= 0;
-			}
-			
-			if (preference != null) {
-				final String symbolicImageName= preference.getSymbolicImageName();
-				if (symbolicImageName != null) {
-					this.imageKey= DefaultMarkerAnnotationAccess.getSharedImageName(preference.getSymbolicImageName());
-				}
-			}
-		}
-		
-		public final int getLevel() {
-			return this.level;
-		}
-		
-		public final @Nullable Image getImage() {
-			Image image= this.image;
-			final String key;
-			if (image == null && (key= this.imageKey) != null) {
-				image= PlatformUI.getWorkbench().getSharedImages().getImage(key);
-				this.image= image;
-			}
-			return image;
-		}
-		
-	}
-	
-	
-	public static final PresentationConfig ERROR_CONFIG= new PresentationConfig("org.eclipse.ui.workbench.texteditor.error", +1); //$NON-NLS-1$
-	public static final PresentationConfig WARNING_CONFIG= new PresentationConfig("org.eclipse.ui.workbench.texteditor.warning", +1); //$NON-NLS-1$
-	public static final PresentationConfig INFO_CONFIG= new PresentationConfig("org.eclipse.ui.workbench.texteditor.info", +1); //$NON-NLS-1$
-	
-	
-	private final Problem problem;
-	
-	private boolean isQuickFixable= false;
-	private boolean isQuickFixableStateSet= false;
-	
-	private final PresentationConfig config;
-	
-	
-	public SourceProblemAnnotation(final String type, final Problem problem, final PresentationConfig config) {
-		super(type, false, null);
-		this.problem= problem;
-		this.config= config;
-	}
-	
-	
-	@Override
-	public String getText() {
-		return this.problem.getMessage();
-	}
-	
-	public Problem getProblem() {
-		return this.problem;
-	}
-	
-	
-	@Override
-	public int getLayer() {
-		return this.config.getLevel();
-	}
-	
-	@Override
-	public void paint(final GC gc, final Canvas canvas, final Rectangle bounds) {
-		final Image image= this.config.getImage();
-		if (image != null) {
-			ImageUtilities.drawImage(image, gc, canvas, bounds, SWT.CENTER, SWT.TOP);
-		}
-	}
-	
-	
-	@Override
-	public void setQuickFixable(final boolean state) {
-		this.isQuickFixable= state;
-		this.isQuickFixableStateSet= true;
-	}
-	
-	@Override
-	public boolean isQuickFixableStateSet() {
-		return this.isQuickFixableStateSet;
-	}
-	
-	@Override
-	public boolean isQuickFixable() {
-		return this.isQuickFixable;
-	}
-	
-}
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/LinkedNamesAssistProposal.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/LinkedNamesAssistProposal.java
index 0909a87..eb6faba 100644
--- a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/LinkedNamesAssistProposal.java
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/LinkedNamesAssistProposal.java
@@ -41,6 +41,7 @@
 import org.eclipse.statet.jcommons.text.core.TextRegion;
 
 import org.eclipse.statet.ltk.ui.LtkUI;
+import org.eclipse.statet.ltk.ui.LtkUIResources;
 import org.eclipse.statet.ltk.ui.sourceediting.SourceEditor;
 import org.eclipse.statet.ltk.ui.sourceediting.TextEditToolSynchronizer;
 
@@ -100,7 +101,7 @@
 	
 	@Override
 	public Image getImage() {
-		return LtkUI.getUIResources().getImage(LtkUI.OBJ_TEXT_LINKEDRENAME_IMAGE_ID);
+		return LtkUI.getUIResources().getImage(LtkUIResources.OBJ_TEXT_LINKEDRENAME_IMAGE_ID);
 	}
 	
 	
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/TemplateCompletionComputer.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/TemplateCompletionComputer.java
index 43c8c42..dd71a63 100644
--- a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/TemplateCompletionComputer.java
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/TemplateCompletionComputer.java
@@ -40,8 +40,8 @@
 import org.eclipse.statet.jcommons.text.core.SearchPattern;
 import org.eclipse.statet.jcommons.text.core.TextRegion;
 
-import org.eclipse.statet.internal.ltk.ui.LtkUIPlugin;
 import org.eclipse.statet.ltk.ui.LtkUI;
+import org.eclipse.statet.ltk.ui.LtkUIResources;
 import org.eclipse.statet.ltk.ui.sourceediting.SourceEditor;
 import org.eclipse.statet.ltk.ui.sourceediting.assist.SourceProposal.ProposalParameters;
 import org.eclipse.statet.ltk.ui.sourceediting.assist.TemplateProposal.TemplateProposalParameters;
@@ -288,7 +288,7 @@
 	}
 	
 	protected @Nullable Image getImage(final Template template) {
-		return LtkUIPlugin.getInstance().getImageRegistry().get(LtkUI.OBJ_TEXT_TEMPLATE_IMAGE_ID);
+		return LtkUI.getUIResources().getImage(LtkUIResources.OBJ_TEXT_TEMPLATE_IMAGE_ID);
 	}
 	
 }
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/TemplateProposal.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/TemplateProposal.java
index c512933..5aec5d3 100644
--- a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/TemplateProposal.java
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/TemplateProposal.java
@@ -59,8 +59,8 @@
 import org.eclipse.statet.ecommons.text.ui.DefaultBrowserInformationInput;
 import org.eclipse.statet.ecommons.text.ui.PositionBasedCompletionProposal;
 
-import org.eclipse.statet.internal.ltk.ui.LtkUIPlugin;
 import org.eclipse.statet.ltk.ui.LtkUI;
+import org.eclipse.statet.ltk.ui.LtkUIResources;
 import org.eclipse.statet.ltk.ui.sourceediting.TextEditToolSynchronizer;
 import org.eclipse.statet.ltk.ui.templates.IWorkbenchTemplateContext;
 import org.eclipse.statet.ltk.ui.util.LTKSelectionUtils;
@@ -155,7 +155,7 @@
 	
 	public TemplateProposal(final TemplateProposalParameters<?> parameters) {
 		this(parameters,
-				LtkUIPlugin.getInstance().getImageRegistry().get(LtkUI.OBJ_TEXT_TEMPLATE_IMAGE_ID) );
+				LtkUI.getUIResources().getImage(LtkUIResources.OBJ_TEXT_TEMPLATE_IMAGE_ID) );
 	}