129
|
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 }
|