[Text] Add FixDocumentPartitioner
diff --git a/ecommons/org.eclipse.statet.ecommons.text.core/src/org/eclipse/statet/ecommons/text/core/util/FixDocumentPartitioner.java b/ecommons/org.eclipse.statet.ecommons.text.core/src/org/eclipse/statet/ecommons/text/core/util/FixDocumentPartitioner.java
new file mode 100644
index 0000000..b501f43
--- /dev/null
+++ b/ecommons/org.eclipse.statet.ecommons.text.core/src/org/eclipse/statet/ecommons/text/core/util/FixDocumentPartitioner.java
@@ -0,0 +1,135 @@
+/*=============================================================================#
+ # Copyright (c) 2013, 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.core.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jface.text.DocumentEvent;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IDocumentPartitioner;
+import org.eclipse.jface.text.ITypedRegion;
+import org.eclipse.jface.text.TypedRegion;
+
+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.jcommons.lang.Nullable;
+
+
+@NonNullByDefault
+public class FixDocumentPartitioner implements IDocumentPartitioner {
+	
+	
+	private final ImList<String> contentTypes;
+	
+	private @Nullable IDocument document;
+	
+	private final List<ITypedRegion> partitions;
+	
+	
+	public FixDocumentPartitioner(final ImList<String> contentTypes) {
+		this.contentTypes= contentTypes;
+		this.partitions= new ArrayList<>();
+	}
+	
+	public FixDocumentPartitioner(final ImList<String> contentTypes,
+			final ImList<ITypedRegion> partitions) {
+		this.contentTypes= contentTypes;
+		this.partitions= partitions;
+	}
+	
+	
+	public void append(final String contentType, final int length) {
+		if (this.partitions.isEmpty()) {
+			this.partitions.add(new TypedRegion(0, length, contentType));
+		}
+		else {
+			final ITypedRegion previous= this.partitions.get(this.partitions.size() - 1);
+			if (previous.getType() == contentType) {
+				this.partitions.set(this.partitions.size() - 1, new TypedRegion(
+						previous.getOffset(), previous.getLength() + length, contentType ));
+			}
+			else {
+				this.partitions.add(new TypedRegion(
+						previous.getOffset() + previous.getLength(), length, contentType ));
+			}
+		}
+	}
+	
+	@Override
+	public void connect(final IDocument document) {
+		this.document= document;
+	}
+	
+	@Override
+	public void disconnect() {
+		this.document= null;
+	}
+	
+	@Override
+	public void documentAboutToBeChanged(final DocumentEvent event) {
+	}
+	
+	@Override
+	public boolean documentChanged(final DocumentEvent event) {
+		return true;
+	}
+	
+	@Override
+	public @NonNull String[] getLegalContentTypes() {
+		return this.contentTypes.toArray(new @NonNull String[this.contentTypes.size()]);
+	}
+	
+	private int indexOf(final int offset, final boolean prefereOpen) {
+		final int last= this.partitions.size() - 1;
+		int i= 0;
+		if (prefereOpen) {
+			for (; i < last; i++) {
+				final ITypedRegion partition= this.partitions.get(i);
+				if (offset < partition.getOffset() + partition.getLength()) {
+					return i;
+				}
+			}
+		}
+		// last or prefereOpen
+		for (; i <= last; i++) {
+			final ITypedRegion partition= this.partitions.get(i);
+			if (offset <= partition.getOffset() + partition.getLength()) {
+				return i;
+			}
+		}
+		throw new IndexOutOfBoundsException("offset: " + offset); //$NON-NLS-1$
+	}
+	
+	@Override
+	public String getContentType(final int offset) {
+		return this.partitions.get(indexOf(offset, false)).getType();
+	}
+	
+	@Override
+	public @NonNull ITypedRegion[] computePartitioning(final int offset, final int length) {
+		final int startIdx= indexOf(offset, false);
+		final int endIdx= indexOf(offset + length, true);
+		final List<ITypedRegion> list= this.partitions.subList(startIdx, endIdx);
+		return list.toArray(new @NonNull ITypedRegion[list.size()]);
+	}
+	
+	@Override
+	public ITypedRegion getPartition(final int offset) {
+		return this.partitions.get(indexOf(offset, false));
+	}
+	
+}