Mercurial > projects > dwt-addons
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 } |