Bug 574748: [Text-UI] Add FixTokenScanner

Change-Id: Iea9aa813fa0729317963afbc74fc5aa3c0b1b6e5
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ecommons/text/ui/presentation/FixTokenScanner.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ecommons/text/ui/presentation/FixTokenScanner.java
new file mode 100644
index 0000000..9097d79
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ecommons/text/ui/presentation/FixTokenScanner.java
@@ -0,0 +1,120 @@
+/*=============================================================================#
+ # 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.ecommons.text.ui.presentation;
+
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.ITypedRegion;
+import org.eclipse.jface.text.rules.IToken;
+import org.eclipse.jface.text.rules.ITokenScanner;
+import org.eclipse.jface.text.rules.Token;
+
+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;
+
+
+@NonNullByDefault
+public class FixTokenScanner implements ITokenScanner {
+	
+	
+	private final TextStyleManager textStyles;
+	private final IToken defaultToken;
+	
+	private ImList<ITypedRegion> styleRegions;
+	
+	private int documentStartOffset;
+	private int documentEndOffset;
+	
+	private int regionIdx;
+	
+	private int tokenOffset;
+	private int tokenLength;
+	
+	
+	public FixTokenScanner(final TextStyleManager textStyles, final String defaultTokenKey) {
+		this.textStyles= textStyles;
+		this.defaultToken= textStyles.getToken(defaultTokenKey);
+		
+		this.styleRegions= ImCollections.emptyList();
+	}
+	
+	
+	public void setStyleRegions(final ImList<ITypedRegion> regions) {
+		this.styleRegions= regions;
+		this.regionIdx= 0;
+	}
+	
+	
+	@Override
+	public void setRange(final IDocument document, final int offset, final int length) {
+		this.documentStartOffset= offset;
+		this.documentEndOffset= length;
+		
+		this.regionIdx= 0;
+		this.tokenOffset= offset;
+		this.tokenLength= 0;
+	}
+	
+	@Override
+	public IToken nextToken() {
+		this.tokenOffset+= this.tokenLength;
+		
+		if (this.tokenOffset == this.documentEndOffset) {
+			return Token.EOF;
+		}
+		
+		final ITypedRegion region= nextRegion();
+		if (region != null) {
+			if (this.tokenOffset >= region.getOffset()) {
+				setTokenEndOffset(region.getOffset() + region.getLength());
+				return this.textStyles.getToken(region.getType());
+			}
+			setTokenEndOffset(region.getOffset());
+			return this.defaultToken;
+		}
+		else {
+			setTokenEndOffset(Integer.MAX_VALUE);
+			return this.defaultToken;
+		}
+	}
+	
+	private @Nullable ITypedRegion nextRegion() {
+		for (int idx= this.regionIdx; idx < this.styleRegions.size(); idx++) {
+			final ITypedRegion region= this.styleRegions.get(idx);
+			if (this.tokenOffset < region.getOffset() + region.getLength()) {
+				this.regionIdx= idx;
+				return region;
+			}
+		}
+		return null;
+	}
+	
+	private void setTokenEndOffset(final int offset) {
+		this.tokenLength= Math.min(offset, this.documentEndOffset) - this.tokenOffset;
+	}
+	
+	@Override
+	public int getTokenOffset() {
+		return this.tokenOffset;
+	}
+	
+	@Override
+	public int getTokenLength() {
+		return this.tokenLength;
+	}
+	
+	
+}