view dwtx/jface/text/FindReplaceDocumentAdapter.d @ 156:a9566845f1cb

...
author Frank Benoit <benoit@tionex.de>
date Mon, 25 Aug 2008 19:03:46 +0200
parents 000f9136b8f7
children 7926b636c282
line wrap: on
line source

/*******************************************************************************
 * Copyright (c) 2000, 2008 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:
 *     IBM Corporation - initial API and implementation
 *     Cagatay Calli <ccalli@gmail.com> - [find/replace] retain caps when replacing - https://bugs.eclipse.org/bugs/show_bug.cgi?id=28949
 *     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
 * Port to the D programming language:
 *     Frank Benoit <benoit@tionex.de>
 *******************************************************************************/
module dwtx.jface.text.FindReplaceDocumentAdapter;

import dwtx.jface.text.IDocumentPartitioningListener; // packageimport
import dwtx.jface.text.DefaultTextHover; // packageimport
import dwtx.jface.text.AbstractInformationControl; // packageimport
import dwtx.jface.text.TextUtilities; // packageimport
import dwtx.jface.text.IInformationControlCreatorExtension; // packageimport
import dwtx.jface.text.AbstractInformationControlManager; // packageimport
import dwtx.jface.text.ITextViewerExtension2; // packageimport
import dwtx.jface.text.IDocumentPartitioner; // packageimport
import dwtx.jface.text.DefaultIndentLineAutoEditStrategy; // packageimport
import dwtx.jface.text.ITextSelection; // packageimport
import dwtx.jface.text.Document; // packageimport
import dwtx.jface.text.FindReplaceDocumentAdapterContentProposalProvider; // packageimport
import dwtx.jface.text.ITextListener; // packageimport
import dwtx.jface.text.BadPartitioningException; // packageimport
import dwtx.jface.text.ITextViewerExtension5; // packageimport
import dwtx.jface.text.IDocumentPartitionerExtension3; // packageimport
import dwtx.jface.text.IUndoManager; // packageimport
import dwtx.jface.text.ITextHoverExtension2; // packageimport
import dwtx.jface.text.IRepairableDocument; // packageimport
import dwtx.jface.text.IRewriteTarget; // packageimport
import dwtx.jface.text.DefaultPositionUpdater; // packageimport
import dwtx.jface.text.RewriteSessionEditProcessor; // packageimport
import dwtx.jface.text.TextViewerHoverManager; // packageimport
import dwtx.jface.text.DocumentRewriteSession; // packageimport
import dwtx.jface.text.TextViewer; // packageimport
import dwtx.jface.text.ITextViewerExtension8; // packageimport
import dwtx.jface.text.RegExMessages; // packageimport
import dwtx.jface.text.IDelayedInputChangeProvider; // packageimport
import dwtx.jface.text.ITextOperationTargetExtension; // packageimport
import dwtx.jface.text.IWidgetTokenOwner; // packageimport
import dwtx.jface.text.IViewportListener; // packageimport
import dwtx.jface.text.GapTextStore; // packageimport
import dwtx.jface.text.MarkSelection; // packageimport
import dwtx.jface.text.IDocumentPartitioningListenerExtension; // packageimport
import dwtx.jface.text.IDocumentAdapterExtension; // packageimport
import dwtx.jface.text.IInformationControlExtension; // packageimport
import dwtx.jface.text.IDocumentPartitioningListenerExtension2; // packageimport
import dwtx.jface.text.DefaultDocumentAdapter; // packageimport
import dwtx.jface.text.ITextViewerExtension3; // packageimport
import dwtx.jface.text.IInformationControlCreator; // packageimport
import dwtx.jface.text.TypedRegion; // packageimport
import dwtx.jface.text.ISynchronizable; // packageimport
import dwtx.jface.text.IMarkRegionTarget; // packageimport
import dwtx.jface.text.TextViewerUndoManager; // packageimport
import dwtx.jface.text.IRegion; // packageimport
import dwtx.jface.text.IInformationControlExtension2; // packageimport
import dwtx.jface.text.IDocumentExtension4; // packageimport
import dwtx.jface.text.IDocumentExtension2; // packageimport
import dwtx.jface.text.IDocumentPartitionerExtension2; // packageimport
import dwtx.jface.text.Assert; // packageimport
import dwtx.jface.text.DefaultInformationControl; // packageimport
import dwtx.jface.text.IWidgetTokenOwnerExtension; // packageimport
import dwtx.jface.text.DocumentClone; // packageimport
import dwtx.jface.text.DefaultUndoManager; // packageimport
import dwtx.jface.text.IFindReplaceTarget; // packageimport
import dwtx.jface.text.IAutoEditStrategy; // packageimport
import dwtx.jface.text.ILineTrackerExtension; // packageimport
import dwtx.jface.text.IUndoManagerExtension; // packageimport
import dwtx.jface.text.TextSelection; // packageimport
import dwtx.jface.text.DefaultAutoIndentStrategy; // packageimport
import dwtx.jface.text.IAutoIndentStrategy; // packageimport
import dwtx.jface.text.IPainter; // packageimport
import dwtx.jface.text.IInformationControl; // packageimport
import dwtx.jface.text.IInformationControlExtension3; // packageimport
import dwtx.jface.text.ITextViewerExtension6; // packageimport
import dwtx.jface.text.IInformationControlExtension4; // packageimport
import dwtx.jface.text.DefaultLineTracker; // packageimport
import dwtx.jface.text.IDocumentInformationMappingExtension; // packageimport
import dwtx.jface.text.IRepairableDocumentExtension; // packageimport
import dwtx.jface.text.ITextHover; // packageimport
import dwtx.jface.text.ILineTracker; // packageimport
import dwtx.jface.text.Line; // packageimport
import dwtx.jface.text.ITextViewerExtension; // packageimport
import dwtx.jface.text.IDocumentAdapter; // packageimport
import dwtx.jface.text.TextEvent; // packageimport
import dwtx.jface.text.BadLocationException; // packageimport
import dwtx.jface.text.AbstractDocument; // packageimport
import dwtx.jface.text.AbstractLineTracker; // packageimport
import dwtx.jface.text.TreeLineTracker; // packageimport
import dwtx.jface.text.ITextPresentationListener; // packageimport
import dwtx.jface.text.Region; // packageimport
import dwtx.jface.text.ITextViewer; // packageimport
import dwtx.jface.text.IDocumentInformationMapping; // packageimport
import dwtx.jface.text.MarginPainter; // packageimport
import dwtx.jface.text.IPaintPositionManager; // packageimport
import dwtx.jface.text.TextPresentation; // packageimport
import dwtx.jface.text.IFindReplaceTargetExtension; // packageimport
import dwtx.jface.text.ISlaveDocumentManagerExtension; // packageimport
import dwtx.jface.text.ISelectionValidator; // packageimport
import dwtx.jface.text.IDocumentExtension; // packageimport
import dwtx.jface.text.PropagatingFontFieldEditor; // packageimport
import dwtx.jface.text.ConfigurableLineTracker; // packageimport
import dwtx.jface.text.SlaveDocumentEvent; // packageimport
import dwtx.jface.text.IDocumentListener; // packageimport
import dwtx.jface.text.PaintManager; // packageimport
import dwtx.jface.text.IFindReplaceTargetExtension3; // packageimport
import dwtx.jface.text.ITextDoubleClickStrategy; // packageimport
import dwtx.jface.text.IDocumentExtension3; // packageimport
import dwtx.jface.text.Position; // packageimport
import dwtx.jface.text.TextMessages; // packageimport
import dwtx.jface.text.CopyOnWriteTextStore; // packageimport
import dwtx.jface.text.WhitespaceCharacterPainter; // packageimport
import dwtx.jface.text.IPositionUpdater; // packageimport
import dwtx.jface.text.DefaultTextDoubleClickStrategy; // packageimport
import dwtx.jface.text.ListLineTracker; // packageimport
import dwtx.jface.text.ITextInputListener; // packageimport
import dwtx.jface.text.BadPositionCategoryException; // packageimport
import dwtx.jface.text.IWidgetTokenKeeperExtension; // packageimport
import dwtx.jface.text.IInputChangedListener; // packageimport
import dwtx.jface.text.ITextOperationTarget; // packageimport
import dwtx.jface.text.IDocumentInformationMappingExtension2; // packageimport
import dwtx.jface.text.ITextViewerExtension7; // packageimport
import dwtx.jface.text.IInformationControlExtension5; // packageimport
import dwtx.jface.text.IDocumentRewriteSessionListener; // packageimport
import dwtx.jface.text.JFaceTextUtil; // packageimport
import dwtx.jface.text.AbstractReusableInformationControlCreator; // packageimport
import dwtx.jface.text.TabsToSpacesConverter; // packageimport
import dwtx.jface.text.CursorLinePainter; // packageimport
import dwtx.jface.text.ITextHoverExtension; // packageimport
import dwtx.jface.text.IEventConsumer; // packageimport
import dwtx.jface.text.IDocument; // packageimport
import dwtx.jface.text.IWidgetTokenKeeper; // packageimport
import dwtx.jface.text.DocumentCommand; // packageimport
import dwtx.jface.text.TypedPosition; // packageimport
import dwtx.jface.text.IEditingSupportRegistry; // packageimport
import dwtx.jface.text.IDocumentPartitionerExtension; // packageimport
import dwtx.jface.text.AbstractHoverInformationControlManager; // packageimport
import dwtx.jface.text.IEditingSupport; // packageimport
import dwtx.jface.text.IMarkSelection; // packageimport
import dwtx.jface.text.ISlaveDocumentManager; // packageimport
import dwtx.jface.text.DocumentEvent; // packageimport
import dwtx.jface.text.DocumentPartitioningChangedEvent; // packageimport
import dwtx.jface.text.ITextStore; // packageimport
import dwtx.jface.text.JFaceTextMessages; // packageimport
import dwtx.jface.text.DocumentRewriteSessionEvent; // packageimport
import dwtx.jface.text.SequentialRewriteTextStore; // packageimport
import dwtx.jface.text.DocumentRewriteSessionType; // packageimport
import dwtx.jface.text.TextAttribute; // packageimport
import dwtx.jface.text.ITextViewerExtension4; // packageimport
import dwtx.jface.text.ITypedRegion; // packageimport


import dwt.dwthelper.utils;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import dwtx.core.runtime.Assert;


/**
 * Provides search and replace operations on
 * {@link dwtx.jface.text.IDocument}.
 * <p>
 * Replaces
 * {@link dwtx.jface.text.IDocument#search(int, String, bool, bool, bool)}.
 *
 * @since 3.0
 */
public class FindReplaceDocumentAdapter : CharSequence {

    /**
     * Internal type for operation codes.
     */
    private static class FindReplaceOperationCode {
    }

    // Find/replace operation codes.
    private static const FindReplaceOperationCode FIND_FIRST= new FindReplaceOperationCode();
    private static const FindReplaceOperationCode FIND_NEXT= new FindReplaceOperationCode();
    private static const FindReplaceOperationCode REPLACE= new FindReplaceOperationCode();
    private static const FindReplaceOperationCode REPLACE_FIND_NEXT= new FindReplaceOperationCode();

    /**
     * Retain case mode constants.
     * @since 3.4
     */
    private static const int RC_MIXED= 0;
    private static const int RC_UPPER= 1;
    private static const int RC_LOWER= 2;
    private static const int RC_FIRSTUPPER= 3;


    /**
     * The adapted document.
     */
    private IDocument fDocument;

    /**
     * State for findReplace.
     */
    private FindReplaceOperationCode fFindReplaceState= null;

    /**
     * The matcher used in findReplace.
     */
    private Matcher fFindReplaceMatcher;

    /**
     * The match offset from the last findReplace call.
     */
    private int fFindReplaceMatchOffset;

    /**
     * Retain case mode
     */
    private int fRetainCaseMode;

    /**
     * Constructs a new find replace document adapter.
     *
     * @param document the adapted document
     */
    public this(IDocument document) {
        Assert.isNotNull(document);
        fDocument= document;
    }

    /**
     * Returns the location of a given string in this adapter's document based on a set of search criteria.
     *
     * @param startOffset document offset at which search starts
     * @param findString the string to find
     * @param forwardSearch the search direction
     * @param caseSensitive indicates whether lower and upper case should be distinguished
     * @param wholeWord indicates whether the findString should be limited by white spaces as
     *          defined by Character.isWhiteSpace. Must not be used in combination with <code>regExSearch</code>.
     * @param regExSearch if <code>true</code> findString represents a regular expression
     *          Must not be used in combination with <code>wholeWord</code>.
     * @return the find or replace region or <code>null</code> if there was no match
     * @throws BadLocationException if startOffset is an invalid document offset
     * @throws PatternSyntaxException if a regular expression has invalid syntax
     */
    public IRegion find(int startOffset, String findString, bool forwardSearch, bool caseSensitive, bool wholeWord, bool regExSearch)  {
        Assert.isTrue(!(regExSearch && wholeWord));

        // Adjust offset to special meaning of -1
        if (startOffset is -1 && forwardSearch)
            startOffset= 0;
        if (startOffset is -1 && !forwardSearch)
            startOffset= length() - 1;

        return findReplace(FIND_FIRST, startOffset, findString, null, forwardSearch, caseSensitive, wholeWord, regExSearch);
    }

    /**
     * Stateful findReplace executes a FIND, REPLACE, REPLACE_FIND or FIND_FIRST operation.
     * In case of REPLACE and REPLACE_FIND it sends a <code>DocumentEvent</code> to all
     * registered <code>IDocumentListener</code>.
     *
     * @param startOffset document offset at which search starts
     *          this value is only used in the FIND_FIRST operation and otherwise ignored
     * @param findString the string to find
     *          this value is only used in the FIND_FIRST operation and otherwise ignored
     * @param replaceText the string to replace the current match
     *          this value is only used in the REPLACE and REPLACE_FIND operations and otherwise ignored
     * @param forwardSearch the search direction
     * @param caseSensitive indicates whether lower and upper case should be distinguished
     * @param wholeWord indicates whether the findString should be limited by white spaces as
     *          defined by Character.isWhiteSpace. Must not be used in combination with <code>regExSearch</code>.
     * @param regExSearch if <code>true</code> this operation represents a regular expression
     *          Must not be used in combination with <code>wholeWord</code>.
     * @param operationCode specifies what kind of operation is executed
     * @return the find or replace region or <code>null</code> if there was no match
     * @throws BadLocationException if startOffset is an invalid document offset
     * @throws IllegalStateException if a REPLACE or REPLACE_FIND operation is not preceded by a successful FIND operation
     * @throws PatternSyntaxException if a regular expression has invalid syntax
     */
    private IRegion findReplace(FindReplaceOperationCode operationCode, int startOffset, String findString, String replaceText, bool forwardSearch, bool caseSensitive, bool wholeWord, bool regExSearch)  {

        // Validate option combinations
        Assert.isTrue(!(regExSearch && wholeWord));

        // Validate state
        if ((operationCode is REPLACE || operationCode is REPLACE_FIND_NEXT) && (fFindReplaceState !is FIND_FIRST && fFindReplaceState !is FIND_NEXT))
            throw new IllegalStateException("illegal findReplace state: cannot replace without preceding find"); //$NON-NLS-1$

        if (operationCode is FIND_FIRST) {
            // Reset

            if (findString is null || findString.length() is 0)
                return null;

            // Validate start offset
            if (startOffset < 0 || startOffset >= length())
                throw new BadLocationException();

            int patternFlags= 0;

            if (regExSearch) {
                patternFlags |= Pattern.MULTILINE;
                findString= substituteLinebreak(findString);
            }

            if (!caseSensitive)
                patternFlags |= Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE;

            if (wholeWord)
                findString= "\\b" + findString + "\\b"; //$NON-NLS-1$ //$NON-NLS-2$

            if (!regExSearch && !wholeWord)
                findString= asRegPattern(findString);

            fFindReplaceMatchOffset= startOffset;
            if (fFindReplaceMatcher !is null && fFindReplaceMatcher.pattern().pattern().equals(findString) && fFindReplaceMatcher.pattern().flags() is patternFlags) {
                /*
                 * Commented out for optimization:
                 * The call is not needed since FIND_FIRST uses find(int) which resets the matcher
                 */
                // fFindReplaceMatcher.reset();
            } else {
                Pattern pattern= Pattern.compile(findString, patternFlags);
                fFindReplaceMatcher= pattern.matcher(this);
            }
        }

        // Set state
        fFindReplaceState= operationCode;

        if (operationCode is REPLACE || operationCode is REPLACE_FIND_NEXT) {
            if (regExSearch) {
                Pattern pattern= fFindReplaceMatcher.pattern();
                String prevMatch= fFindReplaceMatcher.group();
                try {
                    replaceText= interpretReplaceEscapes(replaceText, prevMatch);
                    Matcher replaceTextMatcher= pattern.matcher(prevMatch);
                    replaceText= replaceTextMatcher.replaceFirst(replaceText);
                } catch (IndexOutOfBoundsException ex) {
                    throw new PatternSyntaxException(ex.getLocalizedMessage(), replaceText, -1);
                }
            }

            int offset= fFindReplaceMatcher.start();
            int length= fFindReplaceMatcher.group().length();

            if (cast(IRepairableDocumentExtension)fDocument
                    && (cast(IRepairableDocumentExtension)fDocument).isLineInformationRepairNeeded(offset, length, replaceText)) {
                String message= TextMessages.getString("FindReplaceDocumentAdapter.incompatibleLineDelimiter"); //$NON-NLS-1$
                throw new PatternSyntaxException(message, replaceText, offset);
            }

            fDocument.replace(offset, length, replaceText);

            if (operationCode is REPLACE) {
                return new Region(offset, replaceText.length());
            }
        }

        if (operationCode !is REPLACE) {
            if (forwardSearch) {

                bool found= false;
                if (operationCode is FIND_FIRST)
                    found= fFindReplaceMatcher.find(startOffset);
                else
                    found= fFindReplaceMatcher.find();

                if (operationCode is REPLACE_FIND_NEXT)
                    fFindReplaceState= FIND_NEXT;

                if (found && fFindReplaceMatcher.group().length() > 0)
                    return new Region(fFindReplaceMatcher.start(), fFindReplaceMatcher.group().length());
                return null;
            }

            // backward search
            bool found= fFindReplaceMatcher.find(0);
            int index= -1;
            int length= -1;
            while (found && fFindReplaceMatcher.start() + fFindReplaceMatcher.group().length() <= fFindReplaceMatchOffset + 1) {
                index= fFindReplaceMatcher.start();
                length= fFindReplaceMatcher.group().length();
                found= fFindReplaceMatcher.find(index + 1);
            }
            fFindReplaceMatchOffset= index;
            if (index > -1) {
                // must set matcher to correct position
                fFindReplaceMatcher.find(index);
                return new Region(index, length);
            }
            return null;
        }

        return null;
    }

    /**
     * Substitutes \R in a regex find pattern with (?>\r\n?|\n)
     *
     * @param findString the original find pattern
     * @return the transformed find pattern
     * @throws PatternSyntaxException if \R is added at an illegal position (e.g. in a character set)
     * @since 3.4
     */
    private String substituteLinebreak(String findString)  {
        int length= findString.length();
        StringBuffer buf= new StringBuffer(length);

        int inCharGroup= 0;
        int inBraces= 0;
        bool inQuote= false;
        for (int i= 0; i < length; i++) {
            char ch= findString.charAt(i);
            switch (ch) {
                case '[':
                    buf.append(ch);
                    if (! inQuote)
                        inCharGroup++;
                    break;

                case ']':
                    buf.append(ch);
                    if (! inQuote)
                        inCharGroup--;
                    break;

                case '{':
                    buf.append(ch);
                    if (! inQuote && inCharGroup is 0)
                        inBraces++;
                    break;

                case '}':
                    buf.append(ch);
                    if (! inQuote && inCharGroup is 0)
                        inBraces--;
                    break;

                case '\\':
                    if (i + 1 < length) {
                        char ch1= findString.charAt(i + 1);
                        if (inQuote) {
                            if (ch1 is 'E')
                                inQuote= false;
                            buf.append(ch).append(ch1);
                            i++;

                        } else if (ch1 is 'R') {
                            if (inCharGroup > 0 || inBraces > 0) {
                                String msg= TextMessages.getString("FindReplaceDocumentAdapter.illegalLinebreak"); //$NON-NLS-1$
                                throw new PatternSyntaxException(msg, findString, i);
                            }
                            buf.append("(?>\\r\\n?|\\n)"); //$NON-NLS-1$
                            i++;

                        } else {
                            if (ch1 is 'Q') {
                                inQuote= true;
                            }
                            buf.append(ch).append(ch1);
                            i++;
                        }
                    } else {
                        buf.append(ch);
                    }
                    break;

                default:
                    buf.append(ch);
                    break;
            }

        }
        return buf.toString();
    }

    /**
     * Interprets current Retain Case mode (all upper-case,all lower-case,capitalized or mixed)
     * and appends the character <code>ch</code> to <code>buf</code> after processing.
     *
     * @param buf the output buffer
     * @param ch the character to process
     * @since 3.4
     */
    private void interpretRetainCase(StringBuffer buf, char ch) {
        if (fRetainCaseMode is RC_UPPER)
            buf.append(Character.toUpperCase(ch));
        else if (fRetainCaseMode is RC_LOWER)
            buf.append(Character.toLowerCase(ch));
        else if (fRetainCaseMode is RC_FIRSTUPPER) {
            buf.append(Character.toUpperCase(ch));
            fRetainCaseMode= RC_MIXED;
        } else
            buf.append(ch);
    }

    /**
     * Interprets escaped characters in the given replace pattern.
     *
     * @param replaceText the replace pattern
     * @param foundText the found pattern to be replaced
     * @return a replace pattern with escaped characters substituted by the respective characters
     * @since 3.4
     */
    private String interpretReplaceEscapes(String replaceText, String foundText) {
        int length= replaceText.length();
        bool inEscape= false;
        StringBuffer buf= new StringBuffer(length);

        /* every string we did not check looks mixed at first
         * so initialize retain case mode with RC_MIXED
         */
        fRetainCaseMode= RC_MIXED;

        for (int i= 0; i < length; i++) {
            final char ch= replaceText.charAt(i);
            if (inEscape) {
                i= interpretReplaceEscape(ch, i, buf, replaceText, foundText);
                inEscape= false;

            } else if (ch is '\\') {
                inEscape= true;

            } else if (ch is '$') {
                buf.append(ch);

                /*
                 * Feature in java.util.regex.Matcher#replaceFirst(String):
                 * $00, $000, etc. are interpreted as $0 and
                 * $01, $001, etc. are interpreted as $1, etc. .
                 * If we support \0 as replacement pattern for capturing group 0,
                 * it would not be possible any more to write a replacement pattern
                 * that appends 0 to a capturing group (like $0\0).
                 * The fix is to interpret \00 and $00 as $0\0, and
                 * \01 and $01 as $0\1, etc.
                 */
                if (i + 2 < length) {
                    char ch1= replaceText.charAt(i + 1);
                    char ch2= replaceText.charAt(i + 2);
                    if (ch1 is '0' && '0' <= ch2 && ch2 <= '9') {
                        buf.append("0\\"); //$NON-NLS-1$
                        i++; // consume the 0
                    }
                }
            } else {
                interpretRetainCase(buf, ch);
            }
        }

        if (inEscape) {
            // '\' as last character is invalid, but we still add it to get an error message
            buf.append('\\');
        }
        return buf.toString();
    }

    /**
     * Interprets the escaped character <code>ch</code> at offset <code>i</code>
     * of the <code>replaceText</code> and appends the interpretation to <code>buf</code>.
     *
     * @param ch the escaped character
     * @param i the offset
     * @param buf the output buffer
     * @param replaceText the original replace pattern
     * @param foundText the found pattern to be replaced
     * @return the new offset
     * @since 3.4
     */
    private int interpretReplaceEscape(char ch, int i, StringBuffer buf, String replaceText, String foundText) {
        int length= replaceText.length();
        switch (ch) {
            case 'r':
                buf.append('\r');
                break;
            case 'n':
                buf.append('\n');
                break;
            case 't':
                buf.append('\t');
                break;
            case 'f':
                buf.append('\f');
                break;
            case 'a':
                buf.append('\u0007');
                break;
            case 'e':
                buf.append('\u001B');
                break;
            case 'R': //see http://www.unicode.org/unicode/reports/tr18/#Line_Boundaries
                buf.append(TextUtilities.getDefaultLineDelimiter(fDocument));
                break;
            /*
             * \0 for octal is not supported in replace string, since it
             * would conflict with capturing group \0, etc.
             */
            case '0':
                buf.append('$').append(ch);
                /*
                 * See explanation in "Feature in java.util.regex.Matcher#replaceFirst(String)"
                 * in interpretReplaceEscape(String) above.
                 */
                if (i + 1 < length) {
                    char ch1= replaceText.charAt(i + 1);
                    if ('0' <= ch1 && ch1 <= '9') {
                        buf.append('\\');
                    }
                }
                break;

            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                buf.append('$').append(ch);
                break;

            case 'c':
                if (i + 1 < length) {
                    char ch1= replaceText.charAt(i + 1);
                    interpretRetainCase(buf, cast(wchar)(ch1 ^ 64));
                    i++;
                } else {
                    String msg= TextMessages.getFormattedString("FindReplaceDocumentAdapter.illegalControlEscape", "\\c"); //$NON-NLS-1$ //$NON-NLS-2$
                    throw new PatternSyntaxException(msg, replaceText, i);
                }
                break;

            case 'x':
                if (i + 2 < length) {
                    int parsedInt;
                    try {
                        parsedInt= Integer.parseInt(replaceText.substring(i + 1, i + 3), 16);
                        if (parsedInt < 0)
                            throw new NumberFormatException();
                    } catch (NumberFormatException e) {
                        String msg= TextMessages.getFormattedString("FindReplaceDocumentAdapter.illegalHexEscape", replaceText.substring(i - 1, i + 3)); //$NON-NLS-1$
                        throw new PatternSyntaxException(msg, replaceText, i);
                    }
                    interpretRetainCase(buf, cast(wchar) parsedInt);
                    i+= 2;
                } else {
                    String msg= TextMessages.getFormattedString("FindReplaceDocumentAdapter.illegalHexEscape", replaceText.substring(i - 1, length)); //$NON-NLS-1$
                    throw new PatternSyntaxException(msg, replaceText, i);
                }
                break;

            case 'u':
                if (i + 4 < length) {
                    int parsedInt;
                    try {
                        parsedInt= Integer.parseInt(replaceText.substring(i + 1, i + 5), 16);
                        if (parsedInt < 0)
                            throw new NumberFormatException();
                    } catch (NumberFormatException e) {
                        String msg= TextMessages.getFormattedString("FindReplaceDocumentAdapter.illegalUnicodeEscape", replaceText.substring(i - 1, i + 5)); //$NON-NLS-1$
                        throw new PatternSyntaxException(msg, replaceText, i);
                    }
                    interpretRetainCase(buf, cast(wchar) parsedInt);
                    i+= 4;
                } else {
                    String msg= TextMessages.getFormattedString("FindReplaceDocumentAdapter.illegalUnicodeEscape", replaceText.substring(i - 1, length)); //$NON-NLS-1$
                    throw new PatternSyntaxException(msg, replaceText, i);
                }
                break;

            case 'C':
                if(foundText.toUpperCase().equals(foundText)) // is whole match upper-case?
                    fRetainCaseMode= RC_UPPER;
                else if (foundText.toLowerCase().equals(foundText)) // is whole match lower-case?
                    fRetainCaseMode= RC_LOWER;
                else if(Character.isUpperCase(foundText.charAt(0))) // is first character upper-case?
                    fRetainCaseMode= RC_FIRSTUPPER;
                else
                    fRetainCaseMode= RC_MIXED;
                break;

            default:
                // unknown escape k: append uninterpreted \k
                buf.append('\\').append(ch);
                break;
        }
        return i;
    }

    /**
     * Converts a non-regex string to a pattern
     * that can be used with the regex search engine.
     *
     * @param string the non-regex pattern
     * @return the string converted to a regex pattern
     */
    private String asRegPattern(String string) {
        StringBuffer out_= new StringBuffer(string.length());
        bool quoting= false;

        for (int i= 0, length= string.length(); i < length; i++) {
            char ch= string.charAt(i);
            if (ch is '\\') {
                if (quoting) {
                    out_.append("\\E"); //$NON-NLS-1$
                    quoting= false;
                }
                out_.append("\\\\"); //$NON-NLS-1$
                continue;
            }
            if (!quoting) {
                out_.append("\\Q"); //$NON-NLS-1$
                quoting= true;
            }
            out_.append(ch);
        }
        if (quoting)
            out_.append("\\E"); //$NON-NLS-1$

        return out_.toString();
    }

    /**
     * Substitutes the previous match with the given text.
     * Sends a <code>DocumentEvent</code> to all registered <code>IDocumentListener</code>.
     *
     * @param text the substitution text
     * @param regExReplace if <code>true</code> <code>text</code> represents a regular expression
     * @return the replace region or <code>null</code> if there was no match
     * @throws BadLocationException if startOffset is an invalid document offset
     * @throws IllegalStateException if a REPLACE or REPLACE_FIND operation is not preceded by a successful FIND operation
     * @throws PatternSyntaxException if a regular expression has invalid syntax
     *
     * @see DocumentEvent
     * @see IDocumentListener
     */
    public IRegion replace(String text, bool regExReplace)  {
        return findReplace(REPLACE, -1, null, text, false, false, false, regExReplace);
    }

    // ---------- CharSequence implementation ----------

    /*
     * @see java.lang.CharSequence#length()
     */
    public int length() {
        return fDocument.getLength();
    }

    /*
     * @see java.lang.CharSequence#charAt(int)
     */
    public char charAt(int index) {
        try {
            return fDocument.getChar(index);
        } catch (BadLocationException e) {
            throw new IndexOutOfBoundsException();
        }
    }

    /*
     * @see java.lang.CharSequence#subSequence(int, int)
     */
    public CharSequence subSequence(int start, int end) {
        try {
            return fDocument.get(start, end - start);
        } catch (BadLocationException e) {
            throw new IndexOutOfBoundsException();
        }
    }

    /*
     * @see java.lang.Object#toString()
     */
    public String toString() {
        return fDocument.get();
    }
}