/* * Copyright (c) 2002-2004 Widespace, OU and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Common Public License v1.0 * which accompanies this distribution, and is available at * http://solareclipse.sourceforge.net/legal/cpl-v10.html * * Contributors: * Igor Malinin - initial contribution * * $Id: MultiViewPartitioner.java,v 1.2 2004-09-22 18:51:51 jsurfer Exp $ */ package net.sourceforge.phpeclipse.ui.text.rules; import java.util.ArrayList; import java.util.List; import org.eclipse.jface.text.Assert; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentPartitioner; import org.eclipse.jface.text.IDocumentPartitioningListener; import org.eclipse.jface.text.IDocumentPartitioningListenerExtension; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITypedRegion; import org.eclipse.jface.text.TypedRegion; import org.eclipse.jface.text.rules.IPartitionTokenScanner; /** * Advanced partitioner which maintains partitions as views to connected document. Views have own partitioners themselves. This * class is designed as a base for complex partitioners such as for JSP, PHP, ASP, etc. languages. * * @author Igor Malinin */ public abstract class MultiViewPartitioner extends AbstractPartitioner { class ViewListener implements IDocumentPartitioningListener, IDocumentPartitioningListenerExtension { /* * @see org.eclipse.jface.text.IDocumentPartitioningListener#documentPartitioningChanged(IDocument) */ public void documentPartitioningChanged(IDocument document) { IDocumentView view = (IDocumentView) document; int start = view.getParentOffset(0); int end = view.getParentOffset(view.getLength()); rememberRegion(start, end - start); } /* * @see org.eclipse.jface.text.IDocumentPartitioningListenerExtension#documentPartitioningChanged(IDocument, IRegion) */ public void documentPartitioningChanged(IDocument document, IRegion region) { IDocumentView view = (IDocumentView) document; int offset = region.getOffset(); int start = view.getParentOffset(offset); int end = view.getParentOffset(offset + region.getLength()); rememberRegion(start, end - start); } } private ViewListener viewListener = new ViewListener(); private OuterDocumentView outerDocument; private DocumentEvent outerDocumentEvent; public MultiViewPartitioner(IPartitionTokenScanner scanner) { super(scanner); } public void setOuterPartitioner(IDocumentPartitioner partitioner) { if (outerDocument == null) { if (partitioner == null) { return; } outerDocument = new OuterDocumentView(document, nodes); outerDocument.addDocumentPartitioningListener(viewListener); } IDocumentPartitioner old = outerDocument.getDocumentPartitioner(); if (old != null) { outerDocument.setDocumentPartitioner(null); old.disconnect(); } if (partitioner != null) { partitioner.connect(outerDocument); } outerDocument.setDocumentPartitioner(partitioner); if (partitioner == null) { outerDocument.removeDocumentPartitioningListener(viewListener); outerDocument = null; } } /** * Create subpartitioner. * * @param contentType * name of inner partition or null for outer partition */ protected abstract IDocumentPartitioner createPartitioner(String contentType); protected void addInnerRegion(FlatNode position) { if (outerDocument != null) { if (DEBUG) { Assert.isTrue(position.offset >= 0, Integer.toString(position.offset)); } DocumentEvent event = new DocumentEvent(outerDocument, outerDocument.getLocalOffset(position.offset), position.length, null); outerDocument.fireDocumentAboutToBeChanged(event); super.addInnerRegion(position); outerDocument.fireDocumentChanged(event); } else { super.addInnerRegion(position); } if (position instanceof ViewNode) { // TODO: revisit condition IDocumentPartitioner partitioner = createPartitioner(position.type); if (partitioner != null) { InnerDocumentView innerDocument = new InnerDocumentView(document, (ViewNode) position); ((ViewNode) position).view = innerDocument; partitioner.connect(innerDocument); innerDocument.setDocumentPartitioner(partitioner); innerDocument.addDocumentPartitioningListener(viewListener); } } } protected void removeInnerRegion(FlatNode position) { try { if (outerDocument != null) { DocumentEvent event = null; if (position.offset >= 0) { event = new DocumentEvent(outerDocument, outerDocument.getLocalOffset(position.offset), 0, document.get(position.offset, position.length)); outerDocument.fireDocumentAboutToBeChanged(event); } super.removeInnerRegion(position); if (position.offset >= 0) { outerDocument.fireDocumentChanged(event); } } else { super.removeInnerRegion(position); } if (position instanceof ViewNode) { // TODO: revisit condition InnerDocumentView innerDocument = ((ViewNode) position).view; if (innerDocument != null) { IDocumentPartitioner partitioner = innerDocument.getDocumentPartitioner(); innerDocument.removeDocumentPartitioningListener(viewListener); innerDocument.setDocumentPartitioner(null); partitioner.disconnect(); } } } catch (BadLocationException e) { } } protected void deleteInnerRegion(FlatNode position) { super.deleteInnerRegion(position); if (position instanceof ViewNode) { // TODO: revisit condition InnerDocumentView innerDocument = ((ViewNode) position).view; if (innerDocument != null) { IDocumentPartitioner partitioner = innerDocument.getDocumentPartitioner(); innerDocument.removeDocumentPartitioningListener(viewListener); innerDocument.setDocumentPartitioner(null); partitioner.disconnect(); } } } public void connect(IDocument document) { // outerDocument = new OuterDocumentView(document, innerPositions); super.connect(document); setOuterPartitioner(createPartitioner(null)); // IDocumentPartitioner partitioner = // partitioner.connect(outerDocument); // outerDocument.setDocumentPartitioner(partitioner); // outerDocument.addDocumentPartitioningListener(viewListener); } public void disconnect() { try { if (outerDocument != null) { outerDocument.removeDocumentPartitioningListener(viewListener); IDocumentPartitioner partitioner = outerDocument.getDocumentPartitioner(); outerDocument.setDocumentPartitioner(null); partitioner.disconnect(); } } finally { // TODO: cleanup listeners outerDocument = null; } } /* * @see org.eclipse.jface.text.IDocumentPartitioner#documentAboutToBeChanged(DocumentEvent) */ public void documentAboutToBeChanged(DocumentEvent event) { super.documentAboutToBeChanged(event); outerDocumentEvent = null; int offset = event.getOffset(); int length = event.getLength(); int end = offset + length; // find left partition int first = computeFlatNodeIndex(offset); if (first > 0) { FlatNode p = (FlatNode) nodes.get(first - 1); int right = p.offset + p.length; if (offset < right) { // change overlaps with partition InnerDocumentView innerDocument = null; if (p instanceof ViewNode) { // TODO: revisit condition innerDocument = ((ViewNode) p).view; } if (end < right) { if (innerDocument != null) { // cahnge completely inside partition int start = innerDocument.getLocalOffset(offset); innerDocument.fireDocumentAboutToBeChanged(new DocumentEvent(innerDocument, start, length, event.getText())); } return; } if (innerDocument != null) { // cut partition at right int start = innerDocument.getLocalOffset(offset); innerDocument.fireDocumentAboutToBeChanged(new DocumentEvent(innerDocument, start, innerDocument.getLength() - start, null)); } } } // find right partition int last = computeFlatNodeIndex(end); if (last > 0) { FlatNode p = (FlatNode) nodes.get(last - 1); if (p instanceof ViewNode) { // TODO: revisit condition InnerDocumentView innerDocument = ((ViewNode) p).view; if (innerDocument != null) { int right = p.offset + p.length; if (end < right) { // cut partition at left int cut = innerDocument.getLocalOffset(end); innerDocument.fireDocumentAboutToBeChanged(new DocumentEvent(innerDocument, 0, cut, null)); } } } } if (outerDocument != null) { int left = outerDocument.getLocalOffset(offset); int right = outerDocument.getLocalOffset(end); String text = event.getText(); if (left != right || text != null && text.length() > 0) { outerDocumentEvent = new DocumentEvent(outerDocument, left, right - left, text); outerDocument.fireDocumentAboutToBeChanged(outerDocumentEvent); } } } protected int fixupPartitions(DocumentEvent event) { int offset = event.getOffset(); int length = event.getLength(); int end = offset + length; // fixup/notify inner views laying on change boundaries int first = computeFlatNodeIndex(offset); if (first > 0) { FlatNode p = (FlatNode) nodes.get(first - 1); int right = p.offset + p.length; if (offset < right) { // change overlaps with partition if (end < right) { // cahnge completely inside partition String text = event.getText(); p.length -= length; if (text != null) { p.length += text.length(); } if (p instanceof ViewNode) { // TODO: revisit condition InnerDocumentView innerDocument = ((ViewNode) p).view; if (innerDocument != null) { int start = innerDocument.getLocalOffset(offset); innerDocument.fireDocumentChanged(new DocumentEvent(innerDocument, start, length, text)); } } } else { // cut partition at right int cut = p.offset + p.length - offset; p.length -= cut; if (p instanceof ViewNode) { // TODO: revisit condition InnerDocumentView innerDocument = ((ViewNode) p).view; if (innerDocument != null) { int start = innerDocument.getLocalOffset(offset); // TODO: ???fireDocumentAboutToBeChanged??? innerDocument.fireDocumentChanged(new DocumentEvent(innerDocument, start, cut, null)); } } } } } int last = computeFlatNodeIndex(end); if (last > 0 && first != last) { FlatNode p = (FlatNode) nodes.get(last - 1); int right = p.offset + p.length; if (end < right) { // cut partition at left int cut = end - p.offset; p.length -= cut; p.offset = offset; String text = event.getText(); if (text != null) { p.offset += text.length(); } if (p instanceof ViewNode) { // TODO: revisit condition InnerDocumentView innerDocument = ((ViewNode) p).view; if (innerDocument != null) { // TODO: ???fireDocumentAboutToBeChanged??? innerDocument.fireDocumentChanged(new DocumentEvent(innerDocument, 0, cut, null)); } } --last; } } // fixup inner views laying afrer change String text = event.getText(); if (text != null) { length -= text.length(); } for (int i = last, size = nodes.size(); i < size; i++) { ((FlatNode) nodes.get(i)).offset -= length; } // delete inner views laying completely inside change boundaries if (first < last) { do { deleteInnerRegion((FlatNode) nodes.get(--last)); } while (first < last); rememberRegion(offset, 0); } // notify outer view if (outerDocumentEvent != null) { outerDocument.fireDocumentChanged(outerDocumentEvent); } return first; } /* * @see org.eclipse.jface.text.IDocumentPartitioner#computePartitioning(int, int) */ protected String getContentType(String parent, String view) { if (view != null) { return view; } if (parent != null) { return parent; } return IDocument.DEFAULT_CONTENT_TYPE; } /* * @see org.eclipse.jface.text.IDocumentPartitioner#computePartitioning(int, int) */ public ITypedRegion[] computePartitioning(int offset, int length) { List list = new ArrayList(); int end = offset + length; int index = computeFlatNodeIndex(offset); while (true) { FlatNode prev = (index > 0) ? (FlatNode) nodes.get(index - 1) : null; if (prev != null) { if (prev.overlapsWith(offset, length)) { addInnerPartitions(list, offset, length, prev); } if (end <= prev.offset + prev.length) { break; } } FlatNode next = (index < nodes.size()) ? (FlatNode) nodes.get(index) : null; if (next == null || offset < next.offset) { addOuterPartitions(list, offset, length, prev, next); } if (next == null) { break; } ++index; } return (TypedRegion[]) list.toArray(new TypedRegion[list.size()]); } private void addOuterPartitions(List list, int offset, int length, FlatNode prev, FlatNode next) { // limit region int start = offset; int end = offset + length; if (prev != null && start < prev.offset + prev.length) { start = prev.offset + prev.length; } if (next != null && next.offset < end) { end = next.offset; } if (start == end) { return; } if (outerDocument == null) { list.add(new TypedRegion(start, end - start, getContentType(null, IDocument.DEFAULT_CONTENT_TYPE))); return; } try { // convert to outer offsets start = outerDocument.getLocalOffset(start); end = outerDocument.getLocalOffset(end); if (end - start >= 0) {//jsurfer insert line ITypedRegion[] regions = outerDocument.computePartitioning(start, end - start); for (int i = 0; i < regions.length; i++) { ITypedRegion region = regions[i]; // convert back to parent offsets start = outerDocument.getParentOffset(region.getOffset()); end = start + region.getLength(); if (prev != null) { offset = prev.offset + prev.length; if (start < offset) { start = offset; } } if (next != null) { offset = next.offset; if (offset < end) { end = offset; } } list.add(new TypedRegion(start, end - start, getContentType(null, region.getType()))); } } } catch (BadLocationException x) { } } private void addInnerPartitions(List list, int offset, int length, FlatNode position) { InnerDocumentView innerDocument = null; if (position instanceof ViewNode) { // TODO: revisit condition innerDocument = ((ViewNode) position).view; } if (innerDocument == null) { // simple partition list.add(new TypedRegion(position.offset, position.length, getContentType(position.type, null))); return; } // multiplexing to inner view try { // limit region int start = Math.max(offset, position.offset); int end = Math.min(offset + length, position.offset + position.length); // convert to document offsets length = end - start; offset = innerDocument.getLocalOffset(start); ITypedRegion[] regions = innerDocument.computePartitioning(offset, length); for (int i = 0; i < regions.length; i++) { ITypedRegion region = regions[i]; // convert back to parent offsets offset = innerDocument.getParentOffset(region.getOffset()); length = region.getLength(); list.add(new TypedRegion(offset, length, getContentType(position.type, region.getType()))); } } catch (BadLocationException x) { } } /* * @see org.eclipse.jface.text.IDocumentPartitioner#getPartition(int) */ public ITypedRegion getPartition(int offset) { if (nodes.size() == 0) { return getOuterPartition(offset, null, null); } int index = computeFlatNodeIndex(offset); if (index < nodes.size()) { FlatNode next = (FlatNode) nodes.get(index); if (offset == next.offset) { return getInnerPartition(offset, next); } if (index == 0) { return getOuterPartition(offset, null, next); } FlatNode prev = (FlatNode) nodes.get(index - 1); if (prev.includes(offset)) { return getInnerPartition(offset, prev); } return getOuterPartition(offset, prev, next); } FlatNode prev = (FlatNode) nodes.get(nodes.size() - 1); if (prev.includes(offset)) { return getInnerPartition(offset, prev); } return getOuterPartition(offset, prev, null); } protected ITypedRegion getOuterPartition(int offset, FlatNode prev, FlatNode next) { try { int start, end; String type; if (outerDocument == null) { start = 0; end = document.getLength(); type = getContentType(null, IDocument.DEFAULT_CONTENT_TYPE); } else { ITypedRegion region = outerDocument.getPartition(outerDocument.getLocalOffset(offset)); start = region.getOffset(); end = start + region.getLength(); // convert to parent offset start = outerDocument.getParentOffset(start); end = outerDocument.getParentOffset(end); type = getContentType(null, region.getType()); } if (prev != null) { offset = prev.offset + prev.length; if (start < offset) { start = offset; } } if (next != null) { offset = next.offset; if (offset < end) { end = offset; } } return new TypedRegion(start, end - start, type); } catch (BadLocationException x) { throw new IllegalArgumentException(); } } protected ITypedRegion getInnerPartition(int offset, FlatNode position) { if (position instanceof ViewNode) { // TODO: revisit condition InnerDocumentView innerDocument = ((ViewNode) position).view; if (innerDocument != null) { // multiplexing to inner view try { // convert to inner offset ITypedRegion region = innerDocument.getPartition(innerDocument.getLocalOffset(offset)); // convert to parent offset offset = innerDocument.getParentOffset(region.getOffset()); return new TypedRegion(offset, region.getLength(), getContentType(position.type, region.getType())); } catch (BadLocationException x) { } } } // simple partition return new TypedRegion(position.offset, position.length, position.type); } }