comparison 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
comparison
equal deleted inserted replaced
128:8df1d4193877 129:eb30df5ca28b
1 /*******************************************************************************
2 * Copyright (c) 2006, 2007 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * Christian Plesner Hansen (plesner@quenta.org) - initial API and implementation
10 * Port to the D programming language:
11 * Frank Benoit <benoit@tionex.de>
12 *******************************************************************************/
13 module dwtx.jface.text.source.DefaultCharacterPairMatcher;
14
15 import dwt.dwthelper.utils;
16 import java.util.HashSet;
17 import java.util.Set;
18
19 import dwtx.core.runtime.Assert;
20 import dwtx.jface.text.BadLocationException;
21 import dwtx.jface.text.IDocument;
22 import dwtx.jface.text.IDocumentExtension3;
23 import dwtx.jface.text.IRegion;
24 import dwtx.jface.text.ITypedRegion;
25 import dwtx.jface.text.Region;
26 import dwtx.jface.text.TextUtilities;
27
28 /**
29 * A character pair matcher that matches a specified set of character
30 * pairs against each other. Only characters that occur in the same
31 * partitioning are matched.
32 *
33 * @since 3.3
34 */
35 public class DefaultCharacterPairMatcher : ICharacterPairMatcher {
36
37 private int fAnchor= -1;
38 private final CharPairs fPairs;
39 private final String fPartitioning;
40
41 /**
42 * Creates a new character pair matcher that matches the specified
43 * characters within the specified partitioning. The specified
44 * list of characters must have the form
45 * <blockquote>{ <i>start</i>, <i>end</i>, <i>start</i>, <i>end</i>, ..., <i>start</i>, <i>end</i> }</blockquote>
46 * For instance:
47 * <pre>
48 * char[] chars = new char[] {'(', ')', '{', '}', '[', ']'};
49 * new SimpleCharacterPairMatcher(chars, ...);
50 * </pre>
51 *
52 * @param chars a list of characters
53 * @param partitioning the partitioning to match within
54 */
55 public DefaultCharacterPairMatcher(char[] chars, String partitioning) {
56 Assert.isLegal(chars.length % 2 is 0);
57 Assert.isNotNull(partitioning);
58 fPairs= new CharPairs(chars);
59 fPartitioning= partitioning;
60 }
61
62 /**
63 * Creates a new character pair matcher that matches characters
64 * within the default partitioning. The specified list of
65 * characters must have the form
66 * <blockquote>{ <i>start</i>, <i>end</i>, <i>start</i>, <i>end</i>, ..., <i>start</i>, <i>end</i> }</blockquote>
67 * For instance:
68 * <pre>
69 * char[] chars = new char[] {'(', ')', '{', '}', '[', ']'};
70 * new SimpleCharacterPairMatcher(chars);
71 * </pre>
72 *
73 * @param chars a list of characters
74 */
75 public DefaultCharacterPairMatcher(char[] chars) {
76 this(chars, IDocumentExtension3.DEFAULT_PARTITIONING);
77 }
78
79 /* @see ICharacterPairMatcher#match(IDocument, int) */
80 public IRegion match(IDocument doc, int offset) {
81 if (doc is null || offset < 0 || offset > doc.getLength()) return null;
82 try {
83 return performMatch(doc, offset);
84 } catch (BadLocationException ble) {
85 return null;
86 }
87 }
88
89 /*
90 * Performs the actual work of matching for #match(IDocument, int).
91 */
92 private IRegion performMatch(IDocument doc, int caretOffset) throws BadLocationException {
93 final int charOffset= caretOffset - 1;
94 final char prevChar= doc.getChar(Math.max(charOffset, 0));
95 if (!fPairs.contains(prevChar)) return null;
96 final bool isForward= fPairs.isStartCharacter(prevChar);
97 fAnchor= isForward ? ICharacterPairMatcher.LEFT : ICharacterPairMatcher.RIGHT;
98 final int searchStartPosition= isForward ? caretOffset : caretOffset - 2;
99 final int adjustedOffset= isForward ? charOffset : caretOffset;
100 final String partition= TextUtilities.getContentType(doc, fPartitioning, charOffset, false);
101 final DocumentPartitionAccessor partDoc= new DocumentPartitionAccessor(doc, fPartitioning, partition);
102 int endOffset= findMatchingPeer(partDoc, prevChar, fPairs.getMatching(prevChar),
103 isForward, isForward ? doc.getLength() : -1,
104 searchStartPosition);
105 if (endOffset is -1) return null;
106 final int adjustedEndOffset= isForward ? endOffset + 1: endOffset;
107 if (adjustedEndOffset is adjustedOffset) return null;
108 return new Region(Math.min(adjustedOffset, adjustedEndOffset),
109 Math.abs(adjustedEndOffset - adjustedOffset));
110 }
111
112 /**
113 * Searches <code>doc</code> for the specified end character, <code>end</code>.
114 *
115 * @param doc the document to search
116 * @param start the opening matching character
117 * @param end the end character to search for
118 * @param searchForward search forwards or backwards?
119 * @param boundary a boundary at which the search should stop
120 * @param startPos the start offset
121 * @return the index of the end character if it was found, otherwise -1
122 * @throws BadLocationException
123 */
124 private int findMatchingPeer(DocumentPartitionAccessor doc, char start, char end, bool searchForward, int boundary, int startPos) throws BadLocationException {
125 int pos= startPos;
126 while (pos !is boundary) {
127 final char c= doc.getChar(pos);
128 if (doc.isMatch(pos, end)) {
129 return pos;
130 } else if (c is start && doc.inPartition(pos)) {
131 pos= findMatchingPeer(doc, start, end, searchForward, boundary,
132 doc.getNextPosition(pos, searchForward));
133 if (pos is -1) return -1;
134 }
135 pos= doc.getNextPosition(pos, searchForward);
136 }
137 return -1;
138 }
139
140 /* @see ICharacterPairMatcher#getAnchor() */
141 public int getAnchor() {
142 return fAnchor;
143 }
144
145 /* @see ICharacterPairMatcher#dispose() */
146 public void dispose() { }
147
148 /* @see ICharacterPairMatcher#clear() */
149 public void clear() {
150 fAnchor= -1;
151 }
152
153 /**
154 * Utility class that wraps a document and gives access to
155 * partitioning information. A document is tied to a particular
156 * partition and, when considering whether or not a position is a
157 * valid match, only considers position within its partition.
158 */
159 private static class DocumentPartitionAccessor {
160
161 private final IDocument fDocument;
162 private final String fPartitioning, fPartition;
163 private ITypedRegion fCachedPartition;
164
165 /**
166 * Creates a new partitioned document for the specified document.
167 *
168 * @param doc the document to wrap
169 * @param partitioning the partitioning used
170 * @param partition the partition managed by this document
171 */
172 public DocumentPartitionAccessor(IDocument doc, String partitioning,
173 String partition) {
174 fDocument= doc;
175 fPartitioning= partitioning;
176 fPartition= partition;
177 }
178
179 /**
180 * Returns the character at the specified position in this document.
181 *
182 * @param pos an offset within this document
183 * @return the character at the offset
184 * @throws BadLocationException
185 */
186 public char getChar(int pos) throws BadLocationException {
187 return fDocument.getChar(pos);
188 }
189
190 /**
191 * Returns true if the character at the specified position is a
192 * valid match for the specified end character. To be a valid
193 * match, it must be in the appropriate partition and equal to the
194 * end character.
195 *
196 * @param pos an offset within this document
197 * @param end the end character to match against
198 * @return true exactly if the position represents a valid match
199 * @throws BadLocationException
200 */
201 public bool isMatch(int pos, char end) throws BadLocationException {
202 return getChar(pos) is end && inPartition(pos);
203 }
204
205 /**
206 * Returns true if the specified offset is within the partition
207 * managed by this document.
208 *
209 * @param pos an offset within this document
210 * @return true if the offset is within this document's partition
211 */
212 public bool inPartition(int pos) {
213 final ITypedRegion partition= getPartition(pos);
214 return partition !is null && partition.getType().equals(fPartition);
215 }
216
217 /**
218 * Returns the next position to query in the search. The position
219 * is not guaranteed to be in this document's partition.
220 *
221 * @param pos an offset within the document
222 * @param searchForward the direction of the search
223 * @return the next position to query
224 */
225 public int getNextPosition(int pos, bool searchForward) {
226 final ITypedRegion partition= getPartition(pos);
227 if (partition is null) return simpleIncrement(pos, searchForward);
228 if (fPartition.equals(partition.getType()))
229 return simpleIncrement(pos, searchForward);
230 if (searchForward) {
231 int end= partition.getOffset() + partition.getLength();
232 if (pos < end)
233 return end;
234 } else {
235 int offset= partition.getOffset();
236 if (pos > offset)
237 return offset - 1;
238 }
239 return simpleIncrement(pos, searchForward);
240 }
241
242 private int simpleIncrement(int pos, bool searchForward) {
243 return pos + (searchForward ? 1 : -1);
244 }
245
246 /**
247 * Returns partition information about the region containing the
248 * specified position.
249 *
250 * @param pos a position within this document.
251 * @return positioning information about the region containing the
252 * position
253 */
254 private ITypedRegion getPartition(int pos) {
255 if (fCachedPartition is null || !contains(fCachedPartition, pos)) {
256 Assert.isTrue(pos >= 0 && pos <= fDocument.getLength());
257 try {
258 fCachedPartition= TextUtilities.getPartition(fDocument, fPartitioning, pos, false);
259 } catch (BadLocationException e) {
260 fCachedPartition= null;
261 }
262 }
263 return fCachedPartition;
264 }
265
266 private static bool contains(IRegion region, int pos) {
267 int offset= region.getOffset();
268 return offset <= pos && pos < offset + region.getLength();
269 }
270
271 }
272
273 /**
274 * Utility class that encapsulates access to matching character pairs.
275 */
276 private static class CharPairs {
277
278 private final char[] fPairs;
279
280 public CharPairs(char[] pairs) {
281 fPairs= pairs;
282 }
283
284 /**
285 * Returns true if the specified character pair occurs in one
286 * of the character pairs.
287 *
288 * @param c a character
289 * @return true exactly if the character occurs in one of the pairs
290 */
291 public bool contains(char c) {
292 return getAllCharacters().contains(new Character(c));
293 }
294
295 private Set/*<Character>*/ fCharsCache= null;
296 /**
297 * @return A set containing all characters occurring in character pairs.
298 */
299 private Set/*<Character>*/ getAllCharacters() {
300 if (fCharsCache is null) {
301 Set/*<Character>*/ set= new HashSet/*<Character>*/();
302 for (int i= 0; i < fPairs.length; i++)
303 set.add(new Character(fPairs[i]));
304 fCharsCache= set;
305 }
306 return fCharsCache;
307 }
308
309 /**
310 * Returns true if the specified character opens a character pair
311 * when scanning in the specified direction.
312 *
313 * @param c a character
314 * @param searchForward the direction of the search
315 * @return whether or not the character opens a character pair
316 */
317 public bool isOpeningCharacter(char c, bool searchForward) {
318 for (int i= 0; i < fPairs.length; i += 2) {
319 if (searchForward && getStartChar(i) is c) return true;
320 else if (!searchForward && getEndChar(i) is c) return true;
321 }
322 return false;
323 }
324
325 /**
326 * Returns true of the specified character is a start character.
327 *
328 * @param c a character
329 * @return true exactly if the character is a start character
330 */
331 public bool isStartCharacter(char c) {
332 return this.isOpeningCharacter(c, true);
333 }
334
335 /**
336 * Returns the matching character for the specified character.
337 *
338 * @param c a character occurring in a character pair
339 * @return the matching character
340 */
341 public char getMatching(char c) {
342 for (int i= 0; i < fPairs.length; i += 2) {
343 if (getStartChar(i) is c) return getEndChar(i);
344 else if (getEndChar(i) is c) return getStartChar(i);
345 }
346 Assert.isTrue(false);
347 return '\0';
348 }
349
350 private char getStartChar(int i) {
351 return fPairs[i];
352 }
353
354 private char getEndChar(int i) {
355 return fPairs[i + 1];
356 }
357
358 }
359
360 }