| /*=============================================================================# |
| # 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.ecommons.text.core.util; |
| |
| import java.util.ArrayList; |
| |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.jface.text.AbstractDocument; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.BadPartitioningException; |
| import org.eclipse.jface.text.DocumentEvent; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IDocumentListener; |
| import org.eclipse.jface.text.IDocumentPartitioner; |
| import org.eclipse.jface.text.IDocumentPartitionerExtension2; |
| import org.eclipse.jface.text.IDocumentPartitioningListener; |
| import org.eclipse.jface.text.ITypedRegion; |
| import org.eclipse.jface.text.Region; |
| import org.eclipse.jface.text.TypedRegion; |
| |
| import org.eclipse.statet.jcommons.lang.NonNull; |
| import org.eclipse.statet.jcommons.lang.NonNullByDefault; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| import org.eclipse.statet.jcommons.text.core.TextRegion; |
| |
| import org.eclipse.statet.ecommons.text.core.FragmentDocument; |
| import org.eclipse.statet.ecommons.text.core.JFaceTextRegion; |
| import org.eclipse.statet.internal.ecommons.text.core.ECommonsTextCorePlugin; |
| |
| |
| /** |
| * @since de.walware.ecommons.text 1.1 |
| */ |
| @NonNullByDefault |
| public abstract class AbstractFragmentDocument extends AbstractSynchronizableDocument |
| implements FragmentDocument { |
| |
| |
| private static final byte NO_CHANGE= 0; |
| private static final byte PREFIX_CHANGE= 1; |
| private static final byte FRAGMENT_CHANGE= 2; |
| |
| |
| private class PartitionerMapper implements IDocumentPartitioner { |
| |
| private final IDocumentPartitioner masterPartitioner; |
| |
| public PartitionerMapper(final IDocumentPartitioner partitioner) { |
| this.masterPartitioner= partitioner; |
| } |
| |
| @Override |
| public void connect(final IDocument document) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public void disconnect() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public void documentAboutToBeChanged(final DocumentEvent event) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public boolean documentChanged(final DocumentEvent event) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public ITypedRegion[] computePartitioning(final int offset, final int length) { |
| return remap(this.masterPartitioner.computePartitioning( |
| AbstractFragmentDocument.this.offsetInMaster + offset, length)); |
| } |
| |
| @Override |
| public String getContentType(final int offset) { |
| return this.masterPartitioner.getContentType(AbstractFragmentDocument.this.offsetInMaster + offset); |
| } |
| |
| @Override |
| public String[] getLegalContentTypes() { |
| return this.masterPartitioner.getLegalContentTypes(); |
| } |
| |
| @Override |
| public ITypedRegion getPartition(final int offset) { |
| return remap(this.masterPartitioner.getPartition(AbstractFragmentDocument.this.offsetInMaster + offset)); |
| } |
| } |
| |
| private class PartitionerMapper2 extends PartitionerMapper implements IDocumentPartitionerExtension2 { |
| |
| private final IDocumentPartitionerExtension2 masterPartitioner2; |
| |
| public PartitionerMapper2(final IDocumentPartitioner partitioner) { |
| super(partitioner); |
| this.masterPartitioner2= (IDocumentPartitionerExtension2) partitioner; |
| } |
| |
| @Override |
| public ITypedRegion[] computePartitioning(final int offset, final int length, final boolean includeZeroLengthPartitions) { |
| return this.masterPartitioner2.computePartitioning(AbstractFragmentDocument.this.offsetInMaster + offset, length, includeZeroLengthPartitions); |
| } |
| |
| @Override |
| public String getContentType(final int offset, final boolean preferOpenPartitions) { |
| return this.masterPartitioner2.getContentType(AbstractFragmentDocument.this.offsetInMaster + offset, preferOpenPartitions); |
| } |
| |
| @Override |
| public String @Nullable [] getManagingPositionCategories() { |
| return this.masterPartitioner2.getManagingPositionCategories(); |
| } |
| |
| @Override |
| public ITypedRegion getPartition(final int offset, final boolean preferOpenPartitions) { |
| return this.masterPartitioner2.getPartition(AbstractFragmentDocument.this.offsetInMaster + offset, preferOpenPartitions); |
| } |
| |
| } |
| |
| |
| private final AbstractDocument master; |
| |
| private int offsetInMaster= 0; |
| |
| private byte change= NO_CHANGE; |
| |
| |
| /** |
| * The default constructor does not perform any configuration but leaves it to the clients who |
| * must first initialize the implementation plug-ins and then call <code>completeInitialization</code>. |
| * Results in the construction of an empty document. |
| */ |
| public AbstractFragmentDocument() { |
| super(); |
| |
| this.master= createMasterDocument(); |
| |
| this.master.addDocumentListener(new IDocumentListener() { |
| @Override |
| public void documentAboutToBeChanged(final DocumentEvent event) { |
| switch (AbstractFragmentDocument.this.change) { |
| case PREFIX_CHANGE: |
| AbstractFragmentDocument.this.offsetInMaster+= - event.fLength + event.fText.length(); |
| return; |
| case FRAGMENT_CHANGE: |
| return; |
| default: |
| throw new UnsupportedOperationException(); |
| } |
| } |
| @Override |
| public void documentChanged(final DocumentEvent event) { |
| } |
| }); |
| this.master.addDocumentPartitioningListener(new IDocumentPartitioningListener() { |
| @Override |
| @SuppressWarnings("deprecation") |
| public void documentPartitioningChanged(final IDocument document) { |
| fireDocumentPartitioningChanged(new Region(0, getLength())); |
| } |
| }); |
| } |
| |
| |
| @Override |
| protected void completeInitialization() { |
| super.completeInitialization(); |
| |
| addDocumentListener(new IDocumentListener() { |
| @Override |
| public void documentAboutToBeChanged(final DocumentEvent event) { |
| } |
| @Override |
| public void documentChanged(final DocumentEvent event) { |
| if (AbstractFragmentDocument.this.change != NO_CHANGE) { |
| throw new IllegalStateException(String.valueOf(AbstractFragmentDocument.this.change)); |
| } |
| |
| AbstractFragmentDocument.this.change= FRAGMENT_CHANGE; |
| try { |
| replaceInMaster(AbstractFragmentDocument.this.offsetInMaster + event.fOffset, |
| event.fLength, event.fText ); |
| } |
| catch (final BadLocationException e) { |
| ECommonsTextCorePlugin.log(new Status(IStatus.ERROR, ECommonsTextCorePlugin.BUNDLE_ID, |
| "Failed to edit master document.", e )); |
| } |
| finally { |
| AbstractFragmentDocument.this.change= NO_CHANGE; |
| } |
| } |
| }); |
| } |
| |
| |
| protected abstract AbstractDocument createMasterDocument(); |
| |
| @Override |
| public AbstractDocument getMasterDocument() { |
| return this.master; |
| } |
| |
| @Override |
| public int getOffsetInMasterDocument() { |
| return this.offsetInMaster; |
| } |
| |
| @Override |
| public TextRegion getRegionInMasterDocument() { |
| synchronized (getLockObject()) { |
| return JFaceTextRegion.newByStartLength(this.offsetInMaster, getLength()); |
| } |
| } |
| |
| protected void replaceInMaster(final int offset, final int length, final String text) |
| throws BadLocationException { |
| this.master.replace(offset, length, text); |
| } |
| |
| |
| protected void setPrefix(final String text) { |
| if (text == null) { |
| throw new NullPointerException(); |
| } |
| synchronized (getLockObject()) { |
| if (this.change != NO_CHANGE) { |
| throw new IllegalStateException(String.valueOf(this.change)); |
| } |
| |
| this.change= PREFIX_CHANGE; |
| try { |
| replaceInMaster(0, this.offsetInMaster, text); |
| this.offsetInMaster= text.length(); |
| } |
| catch (final BadLocationException e) { |
| ECommonsTextCorePlugin.log(new Status(IStatus.ERROR, ECommonsTextCorePlugin.BUNDLE_ID, |
| "Failed to edit master document.", e )); |
| } |
| finally { |
| this.change= 0; |
| } |
| } |
| } |
| |
| |
| @Override |
| public void setInitialLineDelimiter(final String lineDelimiter) { |
| this.master.setInitialLineDelimiter(lineDelimiter); |
| super.setInitialLineDelimiter(lineDelimiter); |
| } |
| |
| |
| @Override |
| public void setDocumentPartitioner(final @Nullable IDocumentPartitioner partitioner) { |
| this.master.setDocumentPartitioner(partitioner); |
| } |
| |
| @Override |
| public void setDocumentPartitioner(final String partitioning, final @Nullable IDocumentPartitioner partitioner) { |
| this.master.setDocumentPartitioner(partitioning, partitioner); |
| } |
| |
| @Override |
| public String[] getPartitionings() { |
| return this.master.getPartitionings(); |
| } |
| |
| @Override |
| public @Nullable IDocumentPartitioner getDocumentPartitioner() { |
| return getDocumentPartitioner(DEFAULT_PARTITIONING); |
| } |
| |
| @Override |
| public @Nullable IDocumentPartitioner getDocumentPartitioner(final String partitioning) { |
| final IDocumentPartitioner masterPartitioner= this.master.getDocumentPartitioner(partitioning); |
| if (masterPartitioner == null) { |
| return null; |
| } |
| if (masterPartitioner instanceof IDocumentPartitionerExtension2) { |
| return new PartitionerMapper2(masterPartitioner); |
| } |
| else { |
| return new PartitionerMapper(masterPartitioner); |
| } |
| } |
| |
| @Override |
| public String[] getLegalContentTypes() { |
| return this.master.getLegalContentTypes(); |
| } |
| |
| @Override |
| public String[] getLegalContentTypes(final String partitioning) throws BadPartitioningException { |
| return this.master.getLegalContentTypes(partitioning); |
| } |
| |
| @Override |
| public ITypedRegion[] computePartitioning(final int offset, final int length) throws BadLocationException { |
| return remap(this.master.computePartitioning(this.offsetInMaster + offset, length)); |
| } |
| |
| @Override |
| public ITypedRegion[] computePartitioning(final String partitioning, final int offset, final int length, |
| final boolean includeZeroLengthPartitions) throws BadLocationException, BadPartitioningException { |
| return remap(this.master.computePartitioning(partitioning, this.offsetInMaster + offset, length, includeZeroLengthPartitions)); |
| } |
| |
| @Override |
| public ITypedRegion getPartition(final int offset) throws BadLocationException { |
| return remap(this.master.getPartition(this.offsetInMaster + offset)); |
| } |
| |
| @Override |
| public ITypedRegion getPartition(final String partitioning, final int offset, |
| final boolean preferOpenPartitions) throws BadLocationException, BadPartitioningException { |
| return remap(this.master.getPartition(partitioning, this.offsetInMaster + offset, preferOpenPartitions)); |
| } |
| |
| @Override |
| public String getContentType(final int offset) throws BadLocationException { |
| return this.master.getContentType(this.offsetInMaster + offset); |
| } |
| |
| @Override |
| public String getContentType(final String partitioning, final int offset, |
| final boolean preferOpenPartitions) throws BadLocationException, BadPartitioningException { |
| return this.master.getContentType(partitioning, this.offsetInMaster + offset, preferOpenPartitions); |
| } |
| |
| |
| private ITypedRegion[] remap(final ITypedRegion[] masterRegions) { |
| final ArrayList<ITypedRegion> regions= new ArrayList<>(masterRegions.length); |
| for (final ITypedRegion masterRegion : masterRegions) { |
| final ITypedRegion region= remap(masterRegion); |
| if (region != null) { |
| regions.add(region); |
| } |
| } |
| return regions.toArray(new @NonNull ITypedRegion[regions.size()]); |
| } |
| |
| private @Nullable ITypedRegion remap(final ITypedRegion masterRegion) { |
| int offset= masterRegion.getOffset() - this.offsetInMaster; |
| int length= masterRegion.getLength(); |
| if (offset + length >= 0) { |
| if (offset < 0) { |
| length= length + offset; |
| offset= 0; |
| } |
| // System.out.println("partitiong mapping: ("+masterRegion.getOffset()+", "+masterRegion.getLength()+") -["+this.offsetInMaster+"]-> ("+offset+", "+length+")"); |
| return new TypedRegion(offset, length, masterRegion.getType()); |
| } |
| |
| { final BadPartitioningException e= new BadPartitioningException("Failed to map partition from master to fragment document."); |
| e.fillInStackTrace(); |
| ECommonsTextCorePlugin.log(new Status(IStatus.ERROR, ECommonsTextCorePlugin.BUNDLE_ID, |
| "Failed to compute partition.", e )); |
| return null; |
| } |
| } |
| |
| } |