Bug 575348: [SourceEditor] Add CommentScanner (for presentation)

Change-Id: I13cfb6abb330939c703bdec558a96f597bab9777
diff --git a/ltk/org.eclipse.statet.ltk.core/META-INF/MANIFEST.MF b/ltk/org.eclipse.statet.ltk.core/META-INF/MANIFEST.MF
index ff5fb2e..521b666 100644
--- a/ltk/org.eclipse.statet.ltk.core/META-INF/MANIFEST.MF
+++ b/ltk/org.eclipse.statet.ltk.core/META-INF/MANIFEST.MF
@@ -13,7 +13,8 @@
  org.eclipse.core.contenttype,
  org.eclipse.core.filesystem,
  org.eclipse.core.resources;visibility:=reexport,
- org.eclipse.statet.ecommons.text.core;visibility:=reexport,
+ org.eclipse.statet.ecommons.text.core;bundle-version="[4.5.0,4.6.0)";visibility:=reexport,
+ org.eclipse.statet.ecommons.preferences.core;bundle-version="[4.5.0,4.6.0)",
  org.eclipse.core.filebuffers,
  org.eclipse.ltk.core.refactoring,
  org.eclipse.jface.text;visibility:=reexport,
diff --git a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/issues/core/Tasks.java b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/issues/core/Tasks.java
new file mode 100644
index 0000000..c7386e3
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/issues/core/Tasks.java
@@ -0,0 +1,73 @@
+/*=============================================================================#
+ # 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.issues.core;
+
+import java.util.List;
+
+import org.eclipse.statet.jcommons.collections.ImCollections;
+import org.eclipse.statet.jcommons.collections.ImList;
+import org.eclipse.statet.jcommons.lang.NonNull;
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+
+import org.eclipse.statet.ecommons.preferences.core.Preference.EnumListPref;
+import org.eclipse.statet.ecommons.preferences.core.Preference.StringArrayPref;
+import org.eclipse.statet.ecommons.preferences.core.PreferenceAccess;
+
+import org.eclipse.statet.ltk.issues.core.impl.BasicTaskTag;
+
+
+@NonNullByDefault
+public class Tasks {
+	
+	
+	public static final String KEYWORD_PREF_KEY= "TaskTags.keyword"; //$NON-NLS-1$
+	public static final String PRIORITY_PREF_KEY= "TaskTags.priority"; //$NON-NLS-1$
+	
+	
+	private static StringArrayPref createKeywordPref(final String qualifier) {
+		return new StringArrayPref(qualifier, KEYWORD_PREF_KEY);
+	}
+	
+	private static EnumListPref<TaskPriority> createPriorityPref(final String qualifier) {
+		return new EnumListPref<>(qualifier, PRIORITY_PREF_KEY, TaskPriority.class);
+	}
+	
+	
+	public static ImList<TaskTag> loadTaskTags(final String qualifier, final PreferenceAccess prefs) {
+		final @NonNull String[] keywords= prefs.getPreferenceValue(createKeywordPref(qualifier));
+		final List<TaskPriority> priorities= prefs.getPreferenceValue(createPriorityPref(qualifier));
+		
+		if (keywords.length == priorities.size()) {
+			final var array= new @NonNull TaskTag[keywords.length];
+			for (int i= 0; i < array.length; i++) {
+				array[i]= new BasicTaskTag(keywords[i], priorities.get(i));
+			}
+			return ImCollections.newList(array);
+		}
+		else {
+			return ImCollections.emptyList();
+		}
+	}
+	
+	public static ImList<String> getKeywords(final ImList<TaskTag> taskTags) {
+		final var array= new @NonNull String[taskTags.size()];
+		for (int i= 0; i < array.length; i++) {
+			array[i]= taskTags.get(i).getKeyword();
+		}
+		return ImCollections.newList(array);
+	}
+	
+	
+}
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/presentation/CommentScanner.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/presentation/CommentScanner.java
new file mode 100644
index 0000000..bc132d2
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/presentation/CommentScanner.java
@@ -0,0 +1,138 @@
+/*=============================================================================#
+ # Copyright (c) 2005, 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.presentation;
+
+import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullLateInit;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jface.text.rules.IRule;
+import org.eclipse.jface.text.rules.IToken;
+import org.eclipse.jface.text.rules.IWordDetector;
+import org.eclipse.jface.text.rules.WordRule;
+
+import org.eclipse.statet.jcommons.collections.ImList;
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+
+import org.eclipse.statet.ecommons.preferences.core.PreferenceAccess;
+import org.eclipse.statet.ecommons.text.ui.presentation.AbstractRuleBasedScanner;
+import org.eclipse.statet.ecommons.text.ui.presentation.ITextPresentationConstants;
+import org.eclipse.statet.ecommons.text.ui.presentation.TextStyleManager;
+import org.eclipse.statet.ecommons.ui.ISettingsChangedHandler;
+
+import org.eclipse.statet.ltk.issues.core.Tasks;
+
+
+/**
+ * Scanner for comments. Provides support for task tags.
+ */
+@NonNullByDefault
+public class CommentScanner extends AbstractRuleBasedScanner implements ISettingsChangedHandler {
+	
+	
+	private static class TaskTagDetector implements IWordDetector {
+		
+		@Override
+		public boolean isWordStart(final char c) {
+			return Character.isLetterOrDigit(c);
+		}
+		
+		@Override
+		public boolean isWordPart(final char c) {
+			return Character.isLetterOrDigit(c);
+		}
+	}
+	
+	private static class TaskTagRule extends WordRule {
+		
+		private final IToken token;
+		
+		public TaskTagRule(final IToken token, final IToken defaultToken) {
+			super(new TaskTagDetector(), defaultToken);
+			this.token= token;
+		}
+		
+		public void setKeywords(final ImList<String> tags) {
+			this.fWords.clear();
+			for (final String tag : tags) {
+				addWord(tag, this.token);
+			}
+		}
+	}
+	
+	
+	private final String prefQualifier;
+	private final PreferenceAccess prefs;
+	
+	private TaskTagRule taskTagRule= nonNullLateInit();
+	
+	private final String commentTokenKey;
+	private final String taskTokenKey;
+	
+	
+	public CommentScanner(final TextStyleManager<?> textStyles,
+			final String commentTokenKey, final String taskTokenKey,
+			final String prefQualifier, final PreferenceAccess prefs) {
+		super(textStyles);
+		
+		this.commentTokenKey= commentTokenKey;
+		this.taskTokenKey= taskTokenKey;
+		initRules();
+		
+		this.prefQualifier= prefQualifier;
+		this.prefs= prefs;
+		loadTaskTags();
+	}
+	
+	public CommentScanner(final TextStyleManager<?> textStyles,
+			final String commentTokenKey, final String taskTokenKey,
+			final PreferenceAccess prefs) {
+		this(textStyles, commentTokenKey, taskTokenKey, PREF_QUALIFIER, prefs);
+	}
+	
+	
+	@Override
+	protected void createRules(final List<IRule> rules) {
+		final IToken defaultToken= getToken(this.commentTokenKey);
+		final IToken taskToken= getToken(this.taskTokenKey);
+		
+		setDefaultReturnToken(defaultToken);
+		
+		// Add rule for Task Tags.
+		this.taskTagRule= new TaskTagRule(taskToken, defaultToken);
+		rules.add(this.taskTagRule);
+	}
+	
+	
+	// TODO
+	private static final String GROUP_ID= "statet.task_tags"; //$NON-NLS-1$
+	private static final String PREF_QUALIFIER= "org.eclipse.statet.ide.core/managment"; //$NON-NLS-1$
+	
+	@Override
+	public void handleSettingsChanged(final Set<String> groupIds, final Map<String, Object> options) {
+		if (groupIds.contains(GROUP_ID)) {
+			loadTaskTags();
+			options.put(ITextPresentationConstants.SETTINGSCHANGE_AFFECTSPRESENTATION_KEY, Boolean.TRUE);
+		}
+	}
+	
+	private void loadTaskTags() {
+		final var taskTags= Tasks.loadTaskTags(this.prefQualifier, this.prefs);
+		this.taskTagRule.setKeywords(Tasks.getKeywords(taskTags));
+	}
+	
+}