129
|
1 /*******************************************************************************
|
|
2 * Copyright (c) 2000, 2008 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 * IBM Corporation - initial API and implementation
|
|
10 * Cagatay Calli <ccalli@gmail.com> - [find/replace] retain caps when replacing - https://bugs.eclipse.org/bugs/show_bug.cgi?id=28949
|
|
11 * Cagatay Calli <ccalli@gmail.com> - [find/replace] define & fix behavior of retain caps with other escapes and text before \C - https://bugs.eclipse.org/bugs/show_bug.cgi?id=217061
|
|
12 * Port to the D programming language:
|
|
13 * Frank Benoit <benoit@tionex.de>
|
|
14 *******************************************************************************/
|
|
15 module dwtx.jface.text.FindReplaceDocumentAdapter;
|
|
16
|
|
17 import dwt.dwthelper.utils;
|
|
18
|
|
19 import java.util.regex.Matcher;
|
|
20 import java.util.regex.Pattern;
|
|
21 import java.util.regex.PatternSyntaxException;
|
|
22
|
|
23 import dwtx.core.runtime.Assert;
|
|
24
|
|
25
|
|
26 /**
|
|
27 * Provides search and replace operations on
|
|
28 * {@link dwtx.jface.text.IDocument}.
|
|
29 * <p>
|
|
30 * Replaces
|
|
31 * {@link dwtx.jface.text.IDocument#search(int, String, bool, bool, bool)}.
|
|
32 *
|
|
33 * @since 3.0
|
|
34 */
|
|
35 public class FindReplaceDocumentAdapter : CharSequence {
|
|
36
|
|
37 /**
|
|
38 * Internal type for operation codes.
|
|
39 */
|
|
40 private static class FindReplaceOperationCode {
|
|
41 }
|
|
42
|
|
43 // Find/replace operation codes.
|
|
44 private static final FindReplaceOperationCode FIND_FIRST= new FindReplaceOperationCode();
|
|
45 private static final FindReplaceOperationCode FIND_NEXT= new FindReplaceOperationCode();
|
|
46 private static final FindReplaceOperationCode REPLACE= new FindReplaceOperationCode();
|
|
47 private static final FindReplaceOperationCode REPLACE_FIND_NEXT= new FindReplaceOperationCode();
|
|
48
|
|
49 /**
|
|
50 * Retain case mode constants.
|
|
51 * @since 3.4
|
|
52 */
|
|
53 private static final int RC_MIXED= 0;
|
|
54 private static final int RC_UPPER= 1;
|
|
55 private static final int RC_LOWER= 2;
|
|
56 private static final int RC_FIRSTUPPER= 3;
|
|
57
|
|
58
|
|
59 /**
|
|
60 * The adapted document.
|
|
61 */
|
|
62 private IDocument fDocument;
|
|
63
|
|
64 /**
|
|
65 * State for findReplace.
|
|
66 */
|
|
67 private FindReplaceOperationCode fFindReplaceState= null;
|
|
68
|
|
69 /**
|
|
70 * The matcher used in findReplace.
|
|
71 */
|
|
72 private Matcher fFindReplaceMatcher;
|
|
73
|
|
74 /**
|
|
75 * The match offset from the last findReplace call.
|
|
76 */
|
|
77 private int fFindReplaceMatchOffset;
|
|
78
|
|
79 /**
|
|
80 * Retain case mode
|
|
81 */
|
|
82 private int fRetainCaseMode;
|
|
83
|
|
84 /**
|
|
85 * Constructs a new find replace document adapter.
|
|
86 *
|
|
87 * @param document the adapted document
|
|
88 */
|
|
89 public FindReplaceDocumentAdapter(IDocument document) {
|
|
90 Assert.isNotNull(document);
|
|
91 fDocument= document;
|
|
92 }
|
|
93
|
|
94 /**
|
|
95 * Returns the location of a given string in this adapter's document based on a set of search criteria.
|
|
96 *
|
|
97 * @param startOffset document offset at which search starts
|
|
98 * @param findString the string to find
|
|
99 * @param forwardSearch the search direction
|
|
100 * @param caseSensitive indicates whether lower and upper case should be distinguished
|
|
101 * @param wholeWord indicates whether the findString should be limited by white spaces as
|
|
102 * defined by Character.isWhiteSpace. Must not be used in combination with <code>regExSearch</code>.
|
|
103 * @param regExSearch if <code>true</code> findString represents a regular expression
|
|
104 * Must not be used in combination with <code>wholeWord</code>.
|
|
105 * @return the find or replace region or <code>null</code> if there was no match
|
|
106 * @throws BadLocationException if startOffset is an invalid document offset
|
|
107 * @throws PatternSyntaxException if a regular expression has invalid syntax
|
|
108 */
|
|
109 public IRegion find(int startOffset, String findString, bool forwardSearch, bool caseSensitive, bool wholeWord, bool regExSearch) throws BadLocationException {
|
|
110 Assert.isTrue(!(regExSearch && wholeWord));
|
|
111
|
|
112 // Adjust offset to special meaning of -1
|
|
113 if (startOffset is -1 && forwardSearch)
|
|
114 startOffset= 0;
|
|
115 if (startOffset is -1 && !forwardSearch)
|
|
116 startOffset= length() - 1;
|
|
117
|
|
118 return findReplace(FIND_FIRST, startOffset, findString, null, forwardSearch, caseSensitive, wholeWord, regExSearch);
|
|
119 }
|
|
120
|
|
121 /**
|
|
122 * Stateful findReplace executes a FIND, REPLACE, REPLACE_FIND or FIND_FIRST operation.
|
|
123 * In case of REPLACE and REPLACE_FIND it sends a <code>DocumentEvent</code> to all
|
|
124 * registered <code>IDocumentListener</code>.
|
|
125 *
|
|
126 * @param startOffset document offset at which search starts
|
|
127 * this value is only used in the FIND_FIRST operation and otherwise ignored
|
|
128 * @param findString the string to find
|
|
129 * this value is only used in the FIND_FIRST operation and otherwise ignored
|
|
130 * @param replaceText the string to replace the current match
|
|
131 * this value is only used in the REPLACE and REPLACE_FIND operations and otherwise ignored
|
|
132 * @param forwardSearch the search direction
|
|
133 * @param caseSensitive indicates whether lower and upper case should be distinguished
|
|
134 * @param wholeWord indicates whether the findString should be limited by white spaces as
|
|
135 * defined by Character.isWhiteSpace. Must not be used in combination with <code>regExSearch</code>.
|
|
136 * @param regExSearch if <code>true</code> this operation represents a regular expression
|
|
137 * Must not be used in combination with <code>wholeWord</code>.
|
|
138 * @param operationCode specifies what kind of operation is executed
|
|
139 * @return the find or replace region or <code>null</code> if there was no match
|
|
140 * @throws BadLocationException if startOffset is an invalid document offset
|
|
141 * @throws IllegalStateException if a REPLACE or REPLACE_FIND operation is not preceded by a successful FIND operation
|
|
142 * @throws PatternSyntaxException if a regular expression has invalid syntax
|
|
143 */
|
|
144 private IRegion findReplace(final FindReplaceOperationCode operationCode, int startOffset, String findString, String replaceText, bool forwardSearch, bool caseSensitive, bool wholeWord, bool regExSearch) throws BadLocationException {
|
|
145
|
|
146 // Validate option combinations
|
|
147 Assert.isTrue(!(regExSearch && wholeWord));
|
|
148
|
|
149 // Validate state
|
|
150 if ((operationCode is REPLACE || operationCode is REPLACE_FIND_NEXT) && (fFindReplaceState !is FIND_FIRST && fFindReplaceState !is FIND_NEXT))
|
|
151 throw new IllegalStateException("illegal findReplace state: cannot replace without preceding find"); //$NON-NLS-1$
|
|
152
|
|
153 if (operationCode is FIND_FIRST) {
|
|
154 // Reset
|
|
155
|
|
156 if (findString is null || findString.length() is 0)
|
|
157 return null;
|
|
158
|
|
159 // Validate start offset
|
|
160 if (startOffset < 0 || startOffset >= length())
|
|
161 throw new BadLocationException();
|
|
162
|
|
163 int patternFlags= 0;
|
|
164
|
|
165 if (regExSearch) {
|
|
166 patternFlags |= Pattern.MULTILINE;
|
|
167 findString= substituteLinebreak(findString);
|
|
168 }
|
|
169
|
|
170 if (!caseSensitive)
|
|
171 patternFlags |= Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE;
|
|
172
|
|
173 if (wholeWord)
|
|
174 findString= "\\b" + findString + "\\b"; //$NON-NLS-1$ //$NON-NLS-2$
|
|
175
|
|
176 if (!regExSearch && !wholeWord)
|
|
177 findString= asRegPattern(findString);
|
|
178
|
|
179 fFindReplaceMatchOffset= startOffset;
|
|
180 if (fFindReplaceMatcher !is null && fFindReplaceMatcher.pattern().pattern().equals(findString) && fFindReplaceMatcher.pattern().flags() is patternFlags) {
|
|
181 /*
|
|
182 * Commented out for optimization:
|
|
183 * The call is not needed since FIND_FIRST uses find(int) which resets the matcher
|
|
184 */
|
|
185 // fFindReplaceMatcher.reset();
|
|
186 } else {
|
|
187 Pattern pattern= Pattern.compile(findString, patternFlags);
|
|
188 fFindReplaceMatcher= pattern.matcher(this);
|
|
189 }
|
|
190 }
|
|
191
|
|
192 // Set state
|
|
193 fFindReplaceState= operationCode;
|
|
194
|
|
195 if (operationCode is REPLACE || operationCode is REPLACE_FIND_NEXT) {
|
|
196 if (regExSearch) {
|
|
197 Pattern pattern= fFindReplaceMatcher.pattern();
|
|
198 String prevMatch= fFindReplaceMatcher.group();
|
|
199 try {
|
|
200 replaceText= interpretReplaceEscapes(replaceText, prevMatch);
|
|
201 Matcher replaceTextMatcher= pattern.matcher(prevMatch);
|
|
202 replaceText= replaceTextMatcher.replaceFirst(replaceText);
|
|
203 } catch (IndexOutOfBoundsException ex) {
|
|
204 throw new PatternSyntaxException(ex.getLocalizedMessage(), replaceText, -1);
|
|
205 }
|
|
206 }
|
|
207
|
|
208 int offset= fFindReplaceMatcher.start();
|
|
209 int length= fFindReplaceMatcher.group().length();
|
|
210
|
|
211 if (fDocument instanceof IRepairableDocumentExtension
|
|
212 && ((IRepairableDocumentExtension)fDocument).isLineInformationRepairNeeded(offset, length, replaceText)) {
|
|
213 String message= TextMessages.getString("FindReplaceDocumentAdapter.incompatibleLineDelimiter"); //$NON-NLS-1$
|
|
214 throw new PatternSyntaxException(message, replaceText, offset);
|
|
215 }
|
|
216
|
|
217 fDocument.replace(offset, length, replaceText);
|
|
218
|
|
219 if (operationCode is REPLACE) {
|
|
220 return new Region(offset, replaceText.length());
|
|
221 }
|
|
222 }
|
|
223
|
|
224 if (operationCode !is REPLACE) {
|
|
225 if (forwardSearch) {
|
|
226
|
|
227 bool found= false;
|
|
228 if (operationCode is FIND_FIRST)
|
|
229 found= fFindReplaceMatcher.find(startOffset);
|
|
230 else
|
|
231 found= fFindReplaceMatcher.find();
|
|
232
|
|
233 if (operationCode is REPLACE_FIND_NEXT)
|
|
234 fFindReplaceState= FIND_NEXT;
|
|
235
|
|
236 if (found && fFindReplaceMatcher.group().length() > 0)
|
|
237 return new Region(fFindReplaceMatcher.start(), fFindReplaceMatcher.group().length());
|
|
238 return null;
|
|
239 }
|
|
240
|
|
241 // backward search
|
|
242 bool found= fFindReplaceMatcher.find(0);
|
|
243 int index= -1;
|
|
244 int length= -1;
|
|
245 while (found && fFindReplaceMatcher.start() + fFindReplaceMatcher.group().length() <= fFindReplaceMatchOffset + 1) {
|
|
246 index= fFindReplaceMatcher.start();
|
|
247 length= fFindReplaceMatcher.group().length();
|
|
248 found= fFindReplaceMatcher.find(index + 1);
|
|
249 }
|
|
250 fFindReplaceMatchOffset= index;
|
|
251 if (index > -1) {
|
|
252 // must set matcher to correct position
|
|
253 fFindReplaceMatcher.find(index);
|
|
254 return new Region(index, length);
|
|
255 }
|
|
256 return null;
|
|
257 }
|
|
258
|
|
259 return null;
|
|
260 }
|
|
261
|
|
262 /**
|
|
263 * Substitutes \R in a regex find pattern with (?>\r\n?|\n)
|
|
264 *
|
|
265 * @param findString the original find pattern
|
|
266 * @return the transformed find pattern
|
|
267 * @throws PatternSyntaxException if \R is added at an illegal position (e.g. in a character set)
|
|
268 * @since 3.4
|
|
269 */
|
|
270 private String substituteLinebreak(String findString) throws PatternSyntaxException {
|
|
271 int length= findString.length();
|
|
272 StringBuffer buf= new StringBuffer(length);
|
|
273
|
|
274 int inCharGroup= 0;
|
|
275 int inBraces= 0;
|
|
276 bool inQuote= false;
|
|
277 for (int i= 0; i < length; i++) {
|
|
278 char ch= findString.charAt(i);
|
|
279 switch (ch) {
|
|
280 case '[':
|
|
281 buf.append(ch);
|
|
282 if (! inQuote)
|
|
283 inCharGroup++;
|
|
284 break;
|
|
285
|
|
286 case ']':
|
|
287 buf.append(ch);
|
|
288 if (! inQuote)
|
|
289 inCharGroup--;
|
|
290 break;
|
|
291
|
|
292 case '{':
|
|
293 buf.append(ch);
|
|
294 if (! inQuote && inCharGroup is 0)
|
|
295 inBraces++;
|
|
296 break;
|
|
297
|
|
298 case '}':
|
|
299 buf.append(ch);
|
|
300 if (! inQuote && inCharGroup is 0)
|
|
301 inBraces--;
|
|
302 break;
|
|
303
|
|
304 case '\\':
|
|
305 if (i + 1 < length) {
|
|
306 char ch1= findString.charAt(i + 1);
|
|
307 if (inQuote) {
|
|
308 if (ch1 is 'E')
|
|
309 inQuote= false;
|
|
310 buf.append(ch).append(ch1);
|
|
311 i++;
|
|
312
|
|
313 } else if (ch1 is 'R') {
|
|
314 if (inCharGroup > 0 || inBraces > 0) {
|
|
315 String msg= TextMessages.getString("FindReplaceDocumentAdapter.illegalLinebreak"); //$NON-NLS-1$
|
|
316 throw new PatternSyntaxException(msg, findString, i);
|
|
317 }
|
|
318 buf.append("(?>\\r\\n?|\\n)"); //$NON-NLS-1$
|
|
319 i++;
|
|
320
|
|
321 } else {
|
|
322 if (ch1 is 'Q') {
|
|
323 inQuote= true;
|
|
324 }
|
|
325 buf.append(ch).append(ch1);
|
|
326 i++;
|
|
327 }
|
|
328 } else {
|
|
329 buf.append(ch);
|
|
330 }
|
|
331 break;
|
|
332
|
|
333 default:
|
|
334 buf.append(ch);
|
|
335 break;
|
|
336 }
|
|
337
|
|
338 }
|
|
339 return buf.toString();
|
|
340 }
|
|
341
|
|
342 /**
|
|
343 * Interprets current Retain Case mode (all upper-case,all lower-case,capitalized or mixed)
|
|
344 * and appends the character <code>ch</code> to <code>buf</code> after processing.
|
|
345 *
|
|
346 * @param buf the output buffer
|
|
347 * @param ch the character to process
|
|
348 * @since 3.4
|
|
349 */
|
|
350 private void interpretRetainCase(StringBuffer buf, char ch) {
|
|
351 if (fRetainCaseMode is RC_UPPER)
|
|
352 buf.append(Character.toUpperCase(ch));
|
|
353 else if (fRetainCaseMode is RC_LOWER)
|
|
354 buf.append(Character.toLowerCase(ch));
|
|
355 else if (fRetainCaseMode is RC_FIRSTUPPER) {
|
|
356 buf.append(Character.toUpperCase(ch));
|
|
357 fRetainCaseMode= RC_MIXED;
|
|
358 } else
|
|
359 buf.append(ch);
|
|
360 }
|
|
361
|
|
362 /**
|
|
363 * Interprets escaped characters in the given replace pattern.
|
|
364 *
|
|
365 * @param replaceText the replace pattern
|
|
366 * @param foundText the found pattern to be replaced
|
|
367 * @return a replace pattern with escaped characters substituted by the respective characters
|
|
368 * @since 3.4
|
|
369 */
|
|
370 private String interpretReplaceEscapes(String replaceText, String foundText) {
|
|
371 int length= replaceText.length();
|
|
372 bool inEscape= false;
|
|
373 StringBuffer buf= new StringBuffer(length);
|
|
374
|
|
375 /* every string we did not check looks mixed at first
|
|
376 * so initialize retain case mode with RC_MIXED
|
|
377 */
|
|
378 fRetainCaseMode= RC_MIXED;
|
|
379
|
|
380 for (int i= 0; i < length; i++) {
|
|
381 final char ch= replaceText.charAt(i);
|
|
382 if (inEscape) {
|
|
383 i= interpretReplaceEscape(ch, i, buf, replaceText, foundText);
|
|
384 inEscape= false;
|
|
385
|
|
386 } else if (ch is '\\') {
|
|
387 inEscape= true;
|
|
388
|
|
389 } else if (ch is '$') {
|
|
390 buf.append(ch);
|
|
391
|
|
392 /*
|
|
393 * Feature in java.util.regex.Matcher#replaceFirst(String):
|
|
394 * $00, $000, etc. are interpreted as $0 and
|
|
395 * $01, $001, etc. are interpreted as $1, etc. .
|
|
396 * If we support \0 as replacement pattern for capturing group 0,
|
|
397 * it would not be possible any more to write a replacement pattern
|
|
398 * that appends 0 to a capturing group (like $0\0).
|
|
399 * The fix is to interpret \00 and $00 as $0\0, and
|
|
400 * \01 and $01 as $0\1, etc.
|
|
401 */
|
|
402 if (i + 2 < length) {
|
|
403 char ch1= replaceText.charAt(i + 1);
|
|
404 char ch2= replaceText.charAt(i + 2);
|
|
405 if (ch1 is '0' && '0' <= ch2 && ch2 <= '9') {
|
|
406 buf.append("0\\"); //$NON-NLS-1$
|
|
407 i++; // consume the 0
|
|
408 }
|
|
409 }
|
|
410 } else {
|
|
411 interpretRetainCase(buf, ch);
|
|
412 }
|
|
413 }
|
|
414
|
|
415 if (inEscape) {
|
|
416 // '\' as last character is invalid, but we still add it to get an error message
|
|
417 buf.append('\\');
|
|
418 }
|
|
419 return buf.toString();
|
|
420 }
|
|
421
|
|
422 /**
|
|
423 * Interprets the escaped character <code>ch</code> at offset <code>i</code>
|
|
424 * of the <code>replaceText</code> and appends the interpretation to <code>buf</code>.
|
|
425 *
|
|
426 * @param ch the escaped character
|
|
427 * @param i the offset
|
|
428 * @param buf the output buffer
|
|
429 * @param replaceText the original replace pattern
|
|
430 * @param foundText the found pattern to be replaced
|
|
431 * @return the new offset
|
|
432 * @since 3.4
|
|
433 */
|
|
434 private int interpretReplaceEscape(final char ch, int i, StringBuffer buf, String replaceText, String foundText) {
|
|
435 int length= replaceText.length();
|
|
436 switch (ch) {
|
|
437 case 'r':
|
|
438 buf.append('\r');
|
|
439 break;
|
|
440 case 'n':
|
|
441 buf.append('\n');
|
|
442 break;
|
|
443 case 't':
|
|
444 buf.append('\t');
|
|
445 break;
|
|
446 case 'f':
|
|
447 buf.append('\f');
|
|
448 break;
|
|
449 case 'a':
|
|
450 buf.append('\u0007');
|
|
451 break;
|
|
452 case 'e':
|
|
453 buf.append('\u001B');
|
|
454 break;
|
|
455 case 'R': //see http://www.unicode.org/unicode/reports/tr18/#Line_Boundaries
|
|
456 buf.append(TextUtilities.getDefaultLineDelimiter(fDocument));
|
|
457 break;
|
|
458 /*
|
|
459 * \0 for octal is not supported in replace string, since it
|
|
460 * would conflict with capturing group \0, etc.
|
|
461 */
|
|
462 case '0':
|
|
463 buf.append('$').append(ch);
|
|
464 /*
|
|
465 * See explanation in "Feature in java.util.regex.Matcher#replaceFirst(String)"
|
|
466 * in interpretReplaceEscape(String) above.
|
|
467 */
|
|
468 if (i + 1 < length) {
|
|
469 char ch1= replaceText.charAt(i + 1);
|
|
470 if ('0' <= ch1 && ch1 <= '9') {
|
|
471 buf.append('\\');
|
|
472 }
|
|
473 }
|
|
474 break;
|
|
475
|
|
476 case '1':
|
|
477 case '2':
|
|
478 case '3':
|
|
479 case '4':
|
|
480 case '5':
|
|
481 case '6':
|
|
482 case '7':
|
|
483 case '8':
|
|
484 case '9':
|
|
485 buf.append('$').append(ch);
|
|
486 break;
|
|
487
|
|
488 case 'c':
|
|
489 if (i + 1 < length) {
|
|
490 char ch1= replaceText.charAt(i + 1);
|
|
491 interpretRetainCase(buf, (char)(ch1 ^ 64));
|
|
492 i++;
|
|
493 } else {
|
|
494 String msg= TextMessages.getFormattedString("FindReplaceDocumentAdapter.illegalControlEscape", "\\c"); //$NON-NLS-1$ //$NON-NLS-2$
|
|
495 throw new PatternSyntaxException(msg, replaceText, i);
|
|
496 }
|
|
497 break;
|
|
498
|
|
499 case 'x':
|
|
500 if (i + 2 < length) {
|
|
501 int parsedInt;
|
|
502 try {
|
|
503 parsedInt= Integer.parseInt(replaceText.substring(i + 1, i + 3), 16);
|
|
504 if (parsedInt < 0)
|
|
505 throw new NumberFormatException();
|
|
506 } catch (NumberFormatException e) {
|
|
507 String msg= TextMessages.getFormattedString("FindReplaceDocumentAdapter.illegalHexEscape", replaceText.substring(i - 1, i + 3)); //$NON-NLS-1$
|
|
508 throw new PatternSyntaxException(msg, replaceText, i);
|
|
509 }
|
|
510 interpretRetainCase(buf, (char) parsedInt);
|
|
511 i+= 2;
|
|
512 } else {
|
|
513 String msg= TextMessages.getFormattedString("FindReplaceDocumentAdapter.illegalHexEscape", replaceText.substring(i - 1, length)); //$NON-NLS-1$
|
|
514 throw new PatternSyntaxException(msg, replaceText, i);
|
|
515 }
|
|
516 break;
|
|
517
|
|
518 case 'u':
|
|
519 if (i + 4 < length) {
|
|
520 int parsedInt;
|
|
521 try {
|
|
522 parsedInt= Integer.parseInt(replaceText.substring(i + 1, i + 5), 16);
|
|
523 if (parsedInt < 0)
|
|
524 throw new NumberFormatException();
|
|
525 } catch (NumberFormatException e) {
|
|
526 String msg= TextMessages.getFormattedString("FindReplaceDocumentAdapter.illegalUnicodeEscape", replaceText.substring(i - 1, i + 5)); //$NON-NLS-1$
|
|
527 throw new PatternSyntaxException(msg, replaceText, i);
|
|
528 }
|
|
529 interpretRetainCase(buf, (char) parsedInt);
|
|
530 i+= 4;
|
|
531 } else {
|
|
532 String msg= TextMessages.getFormattedString("FindReplaceDocumentAdapter.illegalUnicodeEscape", replaceText.substring(i - 1, length)); //$NON-NLS-1$
|
|
533 throw new PatternSyntaxException(msg, replaceText, i);
|
|
534 }
|
|
535 break;
|
|
536
|
|
537 case 'C':
|
|
538 if(foundText.toUpperCase().equals(foundText)) // is whole match upper-case?
|
|
539 fRetainCaseMode= RC_UPPER;
|
|
540 else if (foundText.toLowerCase().equals(foundText)) // is whole match lower-case?
|
|
541 fRetainCaseMode= RC_LOWER;
|
|
542 else if(Character.isUpperCase(foundText.charAt(0))) // is first character upper-case?
|
|
543 fRetainCaseMode= RC_FIRSTUPPER;
|
|
544 else
|
|
545 fRetainCaseMode= RC_MIXED;
|
|
546 break;
|
|
547
|
|
548 default:
|
|
549 // unknown escape k: append uninterpreted \k
|
|
550 buf.append('\\').append(ch);
|
|
551 break;
|
|
552 }
|
|
553 return i;
|
|
554 }
|
|
555
|
|
556 /**
|
|
557 * Converts a non-regex string to a pattern
|
|
558 * that can be used with the regex search engine.
|
|
559 *
|
|
560 * @param string the non-regex pattern
|
|
561 * @return the string converted to a regex pattern
|
|
562 */
|
|
563 private String asRegPattern(String string) {
|
|
564 StringBuffer out= new StringBuffer(string.length());
|
|
565 bool quoting= false;
|
|
566
|
|
567 for (int i= 0, length= string.length(); i < length; i++) {
|
|
568 char ch= string.charAt(i);
|
|
569 if (ch is '\\') {
|
|
570 if (quoting) {
|
|
571 out.append("\\E"); //$NON-NLS-1$
|
|
572 quoting= false;
|
|
573 }
|
|
574 out.append("\\\\"); //$NON-NLS-1$
|
|
575 continue;
|
|
576 }
|
|
577 if (!quoting) {
|
|
578 out.append("\\Q"); //$NON-NLS-1$
|
|
579 quoting= true;
|
|
580 }
|
|
581 out.append(ch);
|
|
582 }
|
|
583 if (quoting)
|
|
584 out.append("\\E"); //$NON-NLS-1$
|
|
585
|
|
586 return out.toString();
|
|
587 }
|
|
588
|
|
589 /**
|
|
590 * Substitutes the previous match with the given text.
|
|
591 * Sends a <code>DocumentEvent</code> to all registered <code>IDocumentListener</code>.
|
|
592 *
|
|
593 * @param text the substitution text
|
|
594 * @param regExReplace if <code>true</code> <code>text</code> represents a regular expression
|
|
595 * @return the replace region or <code>null</code> if there was no match
|
|
596 * @throws BadLocationException if startOffset is an invalid document offset
|
|
597 * @throws IllegalStateException if a REPLACE or REPLACE_FIND operation is not preceded by a successful FIND operation
|
|
598 * @throws PatternSyntaxException if a regular expression has invalid syntax
|
|
599 *
|
|
600 * @see DocumentEvent
|
|
601 * @see IDocumentListener
|
|
602 */
|
|
603 public IRegion replace(String text, bool regExReplace) throws BadLocationException {
|
|
604 return findReplace(REPLACE, -1, null, text, false, false, false, regExReplace);
|
|
605 }
|
|
606
|
|
607 // ---------- CharSequence implementation ----------
|
|
608
|
|
609 /*
|
|
610 * @see java.lang.CharSequence#length()
|
|
611 */
|
|
612 public int length() {
|
|
613 return fDocument.getLength();
|
|
614 }
|
|
615
|
|
616 /*
|
|
617 * @see java.lang.CharSequence#charAt(int)
|
|
618 */
|
|
619 public char charAt(int index) {
|
|
620 try {
|
|
621 return fDocument.getChar(index);
|
|
622 } catch (BadLocationException e) {
|
|
623 throw new IndexOutOfBoundsException();
|
|
624 }
|
|
625 }
|
|
626
|
|
627 /*
|
|
628 * @see java.lang.CharSequence#subSequence(int, int)
|
|
629 */
|
|
630 public CharSequence subSequence(int start, int end) {
|
|
631 try {
|
|
632 return fDocument.get(start, end - start);
|
|
633 } catch (BadLocationException e) {
|
|
634 throw new IndexOutOfBoundsException();
|
|
635 }
|
|
636 }
|
|
637
|
|
638 /*
|
|
639 * @see java.lang.Object#toString()
|
|
640 */
|
|
641 public String toString() {
|
|
642 return fDocument.get();
|
|
643 }
|
|
644 }
|