Mercurial > projects > dwt-addons
diff dwtx/jface/text/source/DefaultCharacterPairMatcher.d @ 129:eb30df5ca28b
Added JFace Text sources
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sat, 23 Aug 2008 19:10:48 +0200 |
parents | |
children | c4fb132a086c |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/text/source/DefaultCharacterPairMatcher.d Sat Aug 23 19:10:48 2008 +0200 @@ -0,0 +1,360 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Christian Plesner Hansen (plesner@quenta.org) - initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.jface.text.source.DefaultCharacterPairMatcher; + +import dwt.dwthelper.utils; +import java.util.HashSet; +import java.util.Set; + +import dwtx.core.runtime.Assert; +import dwtx.jface.text.BadLocationException; +import dwtx.jface.text.IDocument; +import dwtx.jface.text.IDocumentExtension3; +import dwtx.jface.text.IRegion; +import dwtx.jface.text.ITypedRegion; +import dwtx.jface.text.Region; +import dwtx.jface.text.TextUtilities; + +/** + * A character pair matcher that matches a specified set of character + * pairs against each other. Only characters that occur in the same + * partitioning are matched. + * + * @since 3.3 + */ +public class DefaultCharacterPairMatcher : ICharacterPairMatcher { + + private int fAnchor= -1; + private final CharPairs fPairs; + private final String fPartitioning; + + /** + * Creates a new character pair matcher that matches the specified + * characters within the specified partitioning. The specified + * list of characters must have the form + * <blockquote>{ <i>start</i>, <i>end</i>, <i>start</i>, <i>end</i>, ..., <i>start</i>, <i>end</i> }</blockquote> + * For instance: + * <pre> + * char[] chars = new char[] {'(', ')', '{', '}', '[', ']'}; + * new SimpleCharacterPairMatcher(chars, ...); + * </pre> + * + * @param chars a list of characters + * @param partitioning the partitioning to match within + */ + public DefaultCharacterPairMatcher(char[] chars, String partitioning) { + Assert.isLegal(chars.length % 2 is 0); + Assert.isNotNull(partitioning); + fPairs= new CharPairs(chars); + fPartitioning= partitioning; + } + + /** + * Creates a new character pair matcher that matches characters + * within the default partitioning. The specified list of + * characters must have the form + * <blockquote>{ <i>start</i>, <i>end</i>, <i>start</i>, <i>end</i>, ..., <i>start</i>, <i>end</i> }</blockquote> + * For instance: + * <pre> + * char[] chars = new char[] {'(', ')', '{', '}', '[', ']'}; + * new SimpleCharacterPairMatcher(chars); + * </pre> + * + * @param chars a list of characters + */ + public DefaultCharacterPairMatcher(char[] chars) { + this(chars, IDocumentExtension3.DEFAULT_PARTITIONING); + } + + /* @see ICharacterPairMatcher#match(IDocument, int) */ + public IRegion match(IDocument doc, int offset) { + if (doc is null || offset < 0 || offset > doc.getLength()) return null; + try { + return performMatch(doc, offset); + } catch (BadLocationException ble) { + return null; + } + } + + /* + * Performs the actual work of matching for #match(IDocument, int). + */ + private IRegion performMatch(IDocument doc, int caretOffset) throws BadLocationException { + final int charOffset= caretOffset - 1; + final char prevChar= doc.getChar(Math.max(charOffset, 0)); + if (!fPairs.contains(prevChar)) return null; + final bool isForward= fPairs.isStartCharacter(prevChar); + fAnchor= isForward ? ICharacterPairMatcher.LEFT : ICharacterPairMatcher.RIGHT; + final int searchStartPosition= isForward ? caretOffset : caretOffset - 2; + final int adjustedOffset= isForward ? charOffset : caretOffset; + final String partition= TextUtilities.getContentType(doc, fPartitioning, charOffset, false); + final DocumentPartitionAccessor partDoc= new DocumentPartitionAccessor(doc, fPartitioning, partition); + int endOffset= findMatchingPeer(partDoc, prevChar, fPairs.getMatching(prevChar), + isForward, isForward ? doc.getLength() : -1, + searchStartPosition); + if (endOffset is -1) return null; + final int adjustedEndOffset= isForward ? endOffset + 1: endOffset; + if (adjustedEndOffset is adjustedOffset) return null; + return new Region(Math.min(adjustedOffset, adjustedEndOffset), + Math.abs(adjustedEndOffset - adjustedOffset)); + } + + /** + * Searches <code>doc</code> for the specified end character, <code>end</code>. + * + * @param doc the document to search + * @param start the opening matching character + * @param end the end character to search for + * @param searchForward search forwards or backwards? + * @param boundary a boundary at which the search should stop + * @param startPos the start offset + * @return the index of the end character if it was found, otherwise -1 + * @throws BadLocationException + */ + private int findMatchingPeer(DocumentPartitionAccessor doc, char start, char end, bool searchForward, int boundary, int startPos) throws BadLocationException { + int pos= startPos; + while (pos !is boundary) { + final char c= doc.getChar(pos); + if (doc.isMatch(pos, end)) { + return pos; + } else if (c is start && doc.inPartition(pos)) { + pos= findMatchingPeer(doc, start, end, searchForward, boundary, + doc.getNextPosition(pos, searchForward)); + if (pos is -1) return -1; + } + pos= doc.getNextPosition(pos, searchForward); + } + return -1; + } + + /* @see ICharacterPairMatcher#getAnchor() */ + public int getAnchor() { + return fAnchor; + } + + /* @see ICharacterPairMatcher#dispose() */ + public void dispose() { } + + /* @see ICharacterPairMatcher#clear() */ + public void clear() { + fAnchor= -1; + } + + /** + * Utility class that wraps a document and gives access to + * partitioning information. A document is tied to a particular + * partition and, when considering whether or not a position is a + * valid match, only considers position within its partition. + */ + private static class DocumentPartitionAccessor { + + private final IDocument fDocument; + private final String fPartitioning, fPartition; + private ITypedRegion fCachedPartition; + + /** + * Creates a new partitioned document for the specified document. + * + * @param doc the document to wrap + * @param partitioning the partitioning used + * @param partition the partition managed by this document + */ + public DocumentPartitionAccessor(IDocument doc, String partitioning, + String partition) { + fDocument= doc; + fPartitioning= partitioning; + fPartition= partition; + } + + /** + * Returns the character at the specified position in this document. + * + * @param pos an offset within this document + * @return the character at the offset + * @throws BadLocationException + */ + public char getChar(int pos) throws BadLocationException { + return fDocument.getChar(pos); + } + + /** + * Returns true if the character at the specified position is a + * valid match for the specified end character. To be a valid + * match, it must be in the appropriate partition and equal to the + * end character. + * + * @param pos an offset within this document + * @param end the end character to match against + * @return true exactly if the position represents a valid match + * @throws BadLocationException + */ + public bool isMatch(int pos, char end) throws BadLocationException { + return getChar(pos) is end && inPartition(pos); + } + + /** + * Returns true if the specified offset is within the partition + * managed by this document. + * + * @param pos an offset within this document + * @return true if the offset is within this document's partition + */ + public bool inPartition(int pos) { + final ITypedRegion partition= getPartition(pos); + return partition !is null && partition.getType().equals(fPartition); + } + + /** + * Returns the next position to query in the search. The position + * is not guaranteed to be in this document's partition. + * + * @param pos an offset within the document + * @param searchForward the direction of the search + * @return the next position to query + */ + public int getNextPosition(int pos, bool searchForward) { + final ITypedRegion partition= getPartition(pos); + if (partition is null) return simpleIncrement(pos, searchForward); + if (fPartition.equals(partition.getType())) + return simpleIncrement(pos, searchForward); + if (searchForward) { + int end= partition.getOffset() + partition.getLength(); + if (pos < end) + return end; + } else { + int offset= partition.getOffset(); + if (pos > offset) + return offset - 1; + } + return simpleIncrement(pos, searchForward); + } + + private int simpleIncrement(int pos, bool searchForward) { + return pos + (searchForward ? 1 : -1); + } + + /** + * Returns partition information about the region containing the + * specified position. + * + * @param pos a position within this document. + * @return positioning information about the region containing the + * position + */ + private ITypedRegion getPartition(int pos) { + if (fCachedPartition is null || !contains(fCachedPartition, pos)) { + Assert.isTrue(pos >= 0 && pos <= fDocument.getLength()); + try { + fCachedPartition= TextUtilities.getPartition(fDocument, fPartitioning, pos, false); + } catch (BadLocationException e) { + fCachedPartition= null; + } + } + return fCachedPartition; + } + + private static bool contains(IRegion region, int pos) { + int offset= region.getOffset(); + return offset <= pos && pos < offset + region.getLength(); + } + + } + + /** + * Utility class that encapsulates access to matching character pairs. + */ + private static class CharPairs { + + private final char[] fPairs; + + public CharPairs(char[] pairs) { + fPairs= pairs; + } + + /** + * Returns true if the specified character pair occurs in one + * of the character pairs. + * + * @param c a character + * @return true exactly if the character occurs in one of the pairs + */ + public bool contains(char c) { + return getAllCharacters().contains(new Character(c)); + } + + private Set/*<Character>*/ fCharsCache= null; + /** + * @return A set containing all characters occurring in character pairs. + */ + private Set/*<Character>*/ getAllCharacters() { + if (fCharsCache is null) { + Set/*<Character>*/ set= new HashSet/*<Character>*/(); + for (int i= 0; i < fPairs.length; i++) + set.add(new Character(fPairs[i])); + fCharsCache= set; + } + return fCharsCache; + } + + /** + * Returns true if the specified character opens a character pair + * when scanning in the specified direction. + * + * @param c a character + * @param searchForward the direction of the search + * @return whether or not the character opens a character pair + */ + public bool isOpeningCharacter(char c, bool searchForward) { + for (int i= 0; i < fPairs.length; i += 2) { + if (searchForward && getStartChar(i) is c) return true; + else if (!searchForward && getEndChar(i) is c) return true; + } + return false; + } + + /** + * Returns true of the specified character is a start character. + * + * @param c a character + * @return true exactly if the character is a start character + */ + public bool isStartCharacter(char c) { + return this.isOpeningCharacter(c, true); + } + + /** + * Returns the matching character for the specified character. + * + * @param c a character occurring in a character pair + * @return the matching character + */ + public char getMatching(char c) { + for (int i= 0; i < fPairs.length; i += 2) { + if (getStartChar(i) is c) return getEndChar(i); + else if (getEndChar(i) is c) return getStartChar(i); + } + Assert.isTrue(false); + return '\0'; + } + + private char getStartChar(int i) { + return fPairs[i]; + } + + private char getEndChar(int i) { + return fPairs[i + 1]; + } + + } + +}