org.eclipse.swt.custom.StyledText.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.swt.custom.StyledText.java

Source

/*******************************************************************************
 * Copyright (c) 2000, 2018 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Andrey Loskutov <loskutov@gmx.de> - bug 488172
 *     Stefan Xenos (Google) - bug 487254 - StyledText.getTopIndex() can return negative values
 *     Angelo Zerr <angelo.zerr@gmail.com> - Customize different line spacing of StyledText - Bug 522020
 *     Karsten Thoms <thoms@itemis.de> - bug 528746 add getOffsetAtPoint(Point)
 *******************************************************************************/
package org.eclipse.swt.custom;

import java.util.*;
import java.util.List;

import org.eclipse.swt.*;
import org.eclipse.swt.accessibility.*;
import org.eclipse.swt.dnd.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.internal.*;
import org.eclipse.swt.printing.*;
import org.eclipse.swt.widgets.*;

/**
 * A StyledText is an editable user interface object that displays lines
 * of text.  The following style attributes can be defined for the text:
 * <ul>
 * <li>foreground color
 * <li>background color
 * <li>font style (bold, italic, bold-italic, regular)
 * <li>underline
 * <li>strikeout
 * </ul>
 * <p>
 * In addition to text style attributes, the background color of a line may
 * be specified.
 * </p><p>
 * There are two ways to use this widget when specifying text style information.
 * You may use the API that is defined for StyledText or you may define your own
 * LineStyleListener.  If you define your own listener, you will be responsible
 * for maintaining the text style information for the widget.  IMPORTANT: You may
 * not define your own listener and use the StyledText API.  The following
 * StyledText API is not supported if you have defined a LineStyleListener:</p>
 * <ul>
 * <li>getStyleRangeAtOffset(int)
 * <li>getStyleRanges()
 * <li>replaceStyleRanges(int,int,StyleRange[])
 * <li>setStyleRange(StyleRange)
 * <li>setStyleRanges(StyleRange[])
 * </ul>
 * <p>
 * There are two ways to use this widget when specifying line background colors.
 * You may use the API that is defined for StyledText or you may define your own
 * LineBackgroundListener.  If you define your own listener, you will be responsible
 * for maintaining the line background color information for the widget.
 * IMPORTANT: You may not define your own listener and use the StyledText API.
 * The following StyledText API is not supported if you have defined a
 * LineBackgroundListener:</p>
 * <ul>
 * <li>getLineBackground(int)
 * <li>setLineBackground(int,int,Color)
 * </ul>
 * <p>
 * The content implementation for this widget may also be user-defined.  To do so,
 * you must implement the StyledTextContent interface and use the StyledText API
 * setContent(StyledTextContent) to initialize the widget.
 * </p>
 * <dl>
 * <dt><b>Styles:</b><dd>FULL_SELECTION, MULTI, READ_ONLY, SINGLE, WRAP
 * <dt><b>Events:</b><dd>ExtendedModify, LineGetBackground, LineGetSegments, LineGetStyle, Modify, Selection, Verify, VerifyKey, OrientationChange
 * </dl>
 * <p>
 * IMPORTANT: This class is <em>not</em> intended to be subclassed.
 * </p>
 *
 * @see <a href="http://www.eclipse.org/swt/snippets/#styledtext">StyledText snippets</a>
 * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Examples: CustomControlExample, TextEditor</a>
 * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
 * @noextend This class is not intended to be subclassed by clients.
 */
public class StyledText extends Canvas {
    static final char TAB = '\t';
    static final String PlatformLineDelimiter = System.getProperty("line.separator");
    static final int BIDI_CARET_WIDTH = 3;
    static final int DEFAULT_WIDTH = 64;
    static final int DEFAULT_HEIGHT = 64;
    static final int V_SCROLL_RATE = 50;
    static final int H_SCROLL_RATE = 10;
    static final int PREVIOUS_OFFSET_TRAILING = 0;
    static final int OFFSET_LEADING = 1;

    static final String STYLEDTEXT_KEY = "org.eclipse.swt.internal.cocoa.styledtext"; //$NON-NLS-1$

    Color selectionBackground; // selection background color
    Color selectionForeground; // selection foreground color
    StyledTextContent content; // native content (default or user specified)
    StyledTextRenderer renderer;
    Listener listener;
    TextChangeListener textChangeListener; // listener for TextChanging, TextChanged and TextSet events from StyledTextContent
    int verticalScrollOffset = 0; // pixel based
    int horizontalScrollOffset = 0; // pixel based
    boolean alwaysShowScroll = true;
    int ignoreResize = 0;
    int topIndex = 0; // top visible line
    int topIndexY;
    int clientAreaHeight = 0; // the client area height. Needed to calculate content width for new visible lines during Resize callback
    int clientAreaWidth = 0; // the client area width. Needed during Resize callback to determine if line wrap needs to be recalculated
    int tabLength = 4; // number of characters in a tab
    int[] tabs;
    int leftMargin;
    int topMargin;
    int rightMargin;
    int bottomMargin;
    Color marginColor;
    int columnX; // keep track of the horizontal caret position when changing lines/pages. Fixes bug 5935
    int caretOffset;
    int caretAlignment;
    Point selection = new Point(0, 0); // x and y are start and end caret offsets of selection (x <= y)
    Point clipboardSelection; // x and y are start and end caret offsets of previous selection
    int selectionAnchor; // position of selection anchor. 0 based offset from beginning of text
    Point doubleClickSelection; // selection after last mouse double click
    boolean editable = true;
    boolean wordWrap = false; // text is wrapped automatically
    boolean visualWrap = false; // process line breaks inside logical lines (inserted by BidiSegmentEvent)
    boolean doubleClickEnabled = true; // see getDoubleClickEnabled
    boolean overwrite = false; // insert/overwrite edit mode
    int textLimit = -1; // limits the number of characters the user can type in the widget. Unlimited by default.
    Map<Integer, Integer> keyActionMap = new HashMap<>();
    Color background = null; // workaround for bug 4791
    Color foreground = null; //
    Clipboard clipboard;
    int clickCount;
    int autoScrollDirection = SWT.NULL; // the direction of autoscrolling (up, down, right, left)
    int autoScrollDistance = 0;
    int lastTextChangeStart; // cache data of the
    int lastTextChangeNewLineCount; // last text changing
    int lastTextChangeNewCharCount; // event for use in the
    int lastTextChangeReplaceLineCount; // text changed handler
    int lastTextChangeReplaceCharCount;
    int lastCharCount = 0;
    int lastLineBottom; // the bottom pixel of the last line been replaced
    boolean bidiColoring = false; // apply the BIDI algorithm on text segments of the same color
    Image leftCaretBitmap = null;
    Image rightCaretBitmap = null;
    int caretDirection = SWT.NULL;
    int caretWidth = 0;
    Caret defaultCaret = null;
    boolean updateCaretDirection = true;
    boolean fixedLineHeight;
    boolean dragDetect = true;
    IME ime;
    Cursor cursor;
    int alignment;
    boolean justify;
    int indent, wrapIndent;
    int lineSpacing;
    int alignmentMargin;
    int newOrientation = SWT.NONE;
    int accCaretOffset;
    Accessible acc;
    AccessibleControlAdapter accControlAdapter;
    AccessibleAttributeAdapter accAttributeAdapter;
    AccessibleEditableTextListener accEditableTextListener;
    AccessibleTextExtendedAdapter accTextExtendedAdapter;
    AccessibleAdapter accAdapter;
    MouseNavigator mouseNavigator;
    boolean middleClickPressed;

    //block selection
    boolean blockSelection;
    int blockXAnchor = -1, blockYAnchor = -1;
    int blockXLocation = -1, blockYLocation = -1;

    final static boolean IS_MAC, IS_GTK;
    static {
        String platform = SWT.getPlatform();
        IS_MAC = "cocoa".equals(platform);
        IS_GTK = "gtk".equals(platform);
    }

    /**
     * The Printing class implements printing of a range of text.
     * An instance of <code>Printing</code> is returned in the
     * StyledText#print(Printer) API. The run() method may be
     * invoked from any thread.
     */
    static class Printing implements Runnable {
        final static int LEFT = 0; // left aligned header/footer segment
        final static int CENTER = 1; // centered header/footer segment
        final static int RIGHT = 2; // right aligned header/footer segment

        Printer printer;
        StyledTextRenderer printerRenderer;
        StyledTextPrintOptions printOptions;
        Rectangle clientArea;
        FontData fontData;
        Font printerFont;
        Map<Resource, Resource> resources;
        int tabLength;
        GC gc; // printer GC
        int pageWidth; // width of a printer page in pixels
        int startPage; // first page to print
        int endPage; // last page to print
        int scope; // scope of print job
        int startLine; // first (wrapped) line to print
        int endLine; // last (wrapped) line to print
        boolean singleLine; // widget single line mode
        Point selection = null; // selected text
        boolean mirrored; // indicates the printing gc should be mirrored
        int lineSpacing;
        int printMargin;

        /**
         * Creates an instance of <code>Printing</code>.
         * Copies the widget content and rendering data that needs
         * to be requested from listeners.
         * </p>
         * @param parent StyledText widget to print.
         * @param printer printer device to print on.
         * @param printOptions print options
         */
        Printing(StyledText styledText, Printer printer, StyledTextPrintOptions printOptions) {
            this.printer = printer;
            this.printOptions = printOptions;
            this.mirrored = (styledText.getStyle() & SWT.MIRRORED) != 0;
            singleLine = styledText.isSingleLine();
            startPage = 1;
            endPage = Integer.MAX_VALUE;
            PrinterData data = printer.getPrinterData();
            scope = data.scope;
            if (scope == PrinterData.PAGE_RANGE) {
                startPage = data.startPage;
                endPage = data.endPage;
                if (endPage < startPage) {
                    int temp = endPage;
                    endPage = startPage;
                    startPage = temp;
                }
            } else if (scope == PrinterData.SELECTION) {
                selection = styledText.getSelectionRange();
            }
            printerRenderer = new StyledTextRenderer(printer, null);
            printerRenderer.setContent(copyContent(styledText.getContent()));
            cacheLineData(styledText);
        }

        /**
         * Caches all line data that needs to be requested from a listener.
         * </p>
         * @param printerContent <code>StyledTextContent</code> to request
         *    line data for.
         */
        void cacheLineData(StyledText styledText) {
            StyledTextRenderer renderer = styledText.renderer;
            renderer.copyInto(printerRenderer);
            fontData = styledText.getFont().getFontData()[0];
            tabLength = styledText.tabLength;
            int lineCount = printerRenderer.lineCount;
            if (styledText.isListening(ST.LineGetBackground) || (styledText.isListening(ST.LineGetSegments))
                    || styledText.isListening(ST.LineGetStyle)) {
                StyledTextContent content = printerRenderer.content;
                for (int i = 0; i < lineCount; i++) {
                    String line = content.getLine(i);
                    int lineOffset = content.getOffsetAtLine(i);
                    StyledTextEvent event = styledText.getLineBackgroundData(lineOffset, line);
                    if (event != null && event.lineBackground != null) {
                        printerRenderer.setLineBackground(i, 1, event.lineBackground);
                    }
                    event = styledText.getBidiSegments(lineOffset, line);
                    if (event != null) {
                        printerRenderer.setLineSegments(i, 1, event.segments);
                        printerRenderer.setLineSegmentChars(i, 1, event.segmentsChars);
                    }
                    event = styledText.getLineStyleData(lineOffset, line);
                    if (event != null) {
                        printerRenderer.setLineIndent(i, 1, event.indent);
                        printerRenderer.setLineAlignment(i, 1, event.alignment);
                        printerRenderer.setLineJustify(i, 1, event.justify);
                        printerRenderer.setLineBullet(i, 1, event.bullet);
                        StyleRange[] styles = event.styles;
                        if (styles != null && styles.length > 0) {
                            printerRenderer.setStyleRanges(event.ranges, styles);
                        }
                    }
                }
            }
            Point screenDPI = styledText.getDisplay().getDPI();
            Point printerDPI = printer.getDPI();
            resources = new HashMap<>();
            for (int i = 0; i < lineCount; i++) {
                Color color = printerRenderer.getLineBackground(i, null);
                if (color != null) {
                    if (printOptions.printLineBackground) {
                        Color printerColor = (Color) resources.get(color);
                        if (printerColor == null) {
                            printerColor = new Color(printer, color.getRGB());
                            resources.put(color, printerColor);
                        }
                        printerRenderer.setLineBackground(i, 1, printerColor);
                    } else {
                        printerRenderer.setLineBackground(i, 1, null);
                    }
                }
                int indent = printerRenderer.getLineIndent(i, 0);
                if (indent != 0) {
                    printerRenderer.setLineIndent(i, 1, indent * printerDPI.x / screenDPI.x);
                }
            }
            StyleRange[] styles = printerRenderer.styles;
            for (int i = 0; i < printerRenderer.styleCount; i++) {
                StyleRange style = styles[i];
                Font font = style.font;
                if (style.font != null) {
                    Font printerFont = (Font) resources.get(font);
                    if (printerFont == null) {
                        printerFont = new Font(printer, font.getFontData());
                        resources.put(font, printerFont);
                    }
                    style.font = printerFont;
                }
                Color color = style.foreground;
                if (color != null) {
                    Color printerColor = (Color) resources.get(color);
                    if (printOptions.printTextForeground) {
                        if (printerColor == null) {
                            printerColor = new Color(printer, color.getRGB());
                            resources.put(color, printerColor);
                        }
                        style.foreground = printerColor;
                    } else {
                        style.foreground = null;
                    }
                }
                color = style.background;
                if (color != null) {
                    Color printerColor = (Color) resources.get(color);
                    if (printOptions.printTextBackground) {
                        if (printerColor == null) {
                            printerColor = new Color(printer, color.getRGB());
                            resources.put(color, printerColor);
                        }
                        style.background = printerColor;
                    } else {
                        style.background = null;
                    }
                }
                if (!printOptions.printTextFontStyle) {
                    style.fontStyle = SWT.NORMAL;
                }
                style.rise = style.rise * printerDPI.y / screenDPI.y;
                GlyphMetrics metrics = style.metrics;
                if (metrics != null) {
                    metrics.ascent = metrics.ascent * printerDPI.y / screenDPI.y;
                    metrics.descent = metrics.descent * printerDPI.y / screenDPI.y;
                    metrics.width = metrics.width * printerDPI.x / screenDPI.x;
                }
            }
            lineSpacing = styledText.lineSpacing * printerDPI.y / screenDPI.y;
            if (printOptions.printLineNumbers) {
                printMargin = 3 * printerDPI.x / screenDPI.x;
            }
        }

        /**
         * Copies the text of the specified <code>StyledTextContent</code>.
         * </p>
         * @param original the <code>StyledTextContent</code> to copy.
         */
        StyledTextContent copyContent(StyledTextContent original) {
            StyledTextContent printerContent = new DefaultContent();
            int insertOffset = 0;
            for (int i = 0; i < original.getLineCount(); i++) {
                int insertEndOffset;
                if (i < original.getLineCount() - 1) {
                    insertEndOffset = original.getOffsetAtLine(i + 1);
                } else {
                    insertEndOffset = original.getCharCount();
                }
                printerContent.replaceTextRange(insertOffset, 0,
                        original.getTextRange(insertOffset, insertEndOffset - insertOffset));
                insertOffset = insertEndOffset;
            }
            return printerContent;
        }

        /**
         * Disposes of the resources and the <code>PrintRenderer</code>.
         */
        void dispose() {
            if (gc != null) {
                gc.dispose();
                gc = null;
            }
            if (resources != null) {
                for (Resource resource : resources.values()) {
                    resource.dispose();
                }
                resources = null;
            }
            if (printerFont != null) {
                printerFont.dispose();
                printerFont = null;
            }
            if (printerRenderer != null) {
                printerRenderer.dispose();
                printerRenderer = null;
            }
        }

        void init() {
            Rectangle trim = printer.computeTrim(0, 0, 0, 0);
            Point dpi = printer.getDPI();

            printerFont = new Font(printer, fontData.getName(), fontData.getHeight(), SWT.NORMAL);
            clientArea = printer.getClientArea();
            pageWidth = clientArea.width;
            // one inch margin around text
            clientArea.x = dpi.x + trim.x;
            clientArea.y = dpi.y + trim.y;
            clientArea.width -= (clientArea.x + trim.width);
            clientArea.height -= (clientArea.y + trim.height);

            int style = mirrored ? SWT.RIGHT_TO_LEFT : SWT.LEFT_TO_RIGHT;
            gc = new GC(printer, style);
            gc.setFont(printerFont);
            printerRenderer.setFont(printerFont, tabLength);
            int lineHeight = printerRenderer.getLineHeight();
            if (printOptions.header != null) {
                clientArea.y += lineHeight * 2;
                clientArea.height -= lineHeight * 2;
            }
            if (printOptions.footer != null) {
                clientArea.height -= lineHeight * 2;
            }

            // TODO not wrapped
            StyledTextContent content = printerRenderer.content;
            startLine = 0;
            endLine = singleLine ? 0 : content.getLineCount() - 1;
            if (scope == PrinterData.PAGE_RANGE) {
                int pageSize = clientArea.height / lineHeight;//WRONG
                startLine = (startPage - 1) * pageSize;
            } else if (scope == PrinterData.SELECTION) {
                startLine = content.getLineAtOffset(selection.x);
                if (selection.y > 0) {
                    endLine = content.getLineAtOffset(selection.x + selection.y - 1);
                } else {
                    endLine = startLine - 1;
                }
            }
        }

        /**
         * Prints the lines in the specified page range.
         */
        void print() {
            Color background = gc.getBackground();
            Color foreground = gc.getForeground();
            int paintY = clientArea.y;
            int paintX = clientArea.x;
            int width = clientArea.width;
            int page = startPage;
            int pageBottom = clientArea.y + clientArea.height;
            int orientation = gc.getStyle() & (SWT.RIGHT_TO_LEFT | SWT.LEFT_TO_RIGHT);
            TextLayout printLayout = null;
            if (printOptions.printLineNumbers || printOptions.header != null || printOptions.footer != null) {
                printLayout = new TextLayout(printer);
                printLayout.setFont(printerFont);
            }
            if (printOptions.printLineNumbers) {
                int numberingWidth = 0;
                int count = endLine - startLine + 1;
                String[] lineLabels = printOptions.lineLabels;
                if (lineLabels != null) {
                    for (int i = startLine; i < Math.min(count, lineLabels.length); i++) {
                        if (lineLabels[i] != null) {
                            printLayout.setText(lineLabels[i]);
                            int lineWidth = printLayout.getBounds().width;
                            numberingWidth = Math.max(numberingWidth, lineWidth);
                        }
                    }
                } else {
                    StringBuilder buffer = new StringBuilder("0");
                    while ((count /= 10) > 0)
                        buffer.append("0");
                    printLayout.setText(buffer.toString());
                    numberingWidth = printLayout.getBounds().width;
                }
                numberingWidth += printMargin;
                if (numberingWidth > width)
                    numberingWidth = width;
                paintX += numberingWidth;
                width -= numberingWidth;
            }
            for (int i = startLine; i <= endLine && page <= endPage; i++) {
                if (paintY == clientArea.y) {
                    printer.startPage();
                    printDecoration(page, true, printLayout);
                }
                TextLayout layout = printerRenderer.getTextLayout(i, orientation, width, lineSpacing);
                Color lineBackground = printerRenderer.getLineBackground(i, background);
                int paragraphBottom = paintY + layout.getBounds().height;
                if (paragraphBottom <= pageBottom) {
                    //normal case, the whole paragraph fits in the current page
                    printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
                    paintY = paragraphBottom;
                } else {
                    int lineCount = layout.getLineCount();
                    while (paragraphBottom > pageBottom && lineCount > 0) {
                        lineCount--;
                        paragraphBottom -= layout.getLineBounds(lineCount).height + layout.getSpacing();
                    }
                    if (lineCount == 0) {
                        //the whole paragraph goes to the next page
                        printDecoration(page, false, printLayout);
                        printer.endPage();
                        page++;
                        if (page <= endPage) {
                            printer.startPage();
                            printDecoration(page, true, printLayout);
                            paintY = clientArea.y;
                            printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
                            paintY += layout.getBounds().height;
                        }
                    } else {
                        //draw paragraph top in the current page and paragraph bottom in the next
                        int height = paragraphBottom - paintY;
                        gc.setClipping(clientArea.x, paintY, clientArea.width, height);
                        printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
                        gc.setClipping((Rectangle) null);
                        printDecoration(page, false, printLayout);
                        printer.endPage();
                        page++;
                        if (page <= endPage) {
                            printer.startPage();
                            printDecoration(page, true, printLayout);
                            paintY = clientArea.y - height;
                            int layoutHeight = layout.getBounds().height;
                            gc.setClipping(clientArea.x, clientArea.y, clientArea.width, layoutHeight - height);
                            printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
                            gc.setClipping((Rectangle) null);
                            paintY += layoutHeight;
                        }
                    }
                }
                printerRenderer.disposeTextLayout(layout);
            }
            if (page <= endPage && paintY > clientArea.y) {
                // close partial page
                printDecoration(page, false, printLayout);
                printer.endPage();
            }
            if (printLayout != null)
                printLayout.dispose();
        }

        /**
         * Print header or footer decorations.
         *
         * @param page page number to print, if specified in the StyledTextPrintOptions header or footer.
         * @param header true = print the header, false = print the footer
         */
        void printDecoration(int page, boolean header, TextLayout layout) {
            String text = header ? printOptions.header : printOptions.footer;
            if (text == null)
                return;
            int lastSegmentIndex = 0;
            for (int i = 0; i < 3; i++) {
                int segmentIndex = text.indexOf(StyledTextPrintOptions.SEPARATOR, lastSegmentIndex);
                String segment;
                if (segmentIndex == -1) {
                    segment = text.substring(lastSegmentIndex);
                    printDecorationSegment(segment, i, page, header, layout);
                    break;
                } else {
                    segment = text.substring(lastSegmentIndex, segmentIndex);
                    printDecorationSegment(segment, i, page, header, layout);
                    lastSegmentIndex = segmentIndex + StyledTextPrintOptions.SEPARATOR.length();
                }
            }
        }

        /**
         * Print one segment of a header or footer decoration.
         * Headers and footers have three different segments.
         * One each for left aligned, centered, and right aligned text.
         *
         * @param segment decoration segment to print
         * @param alignment alignment of the segment. 0=left, 1=center, 2=right
         * @param page page number to print, if specified in the decoration segment.
         * @param header true = print the header, false = print the footer
         */
        void printDecorationSegment(String segment, int alignment, int page, boolean header, TextLayout layout) {
            int pageIndex = segment.indexOf(StyledTextPrintOptions.PAGE_TAG);
            if (pageIndex != -1) {
                int pageTagLength = StyledTextPrintOptions.PAGE_TAG.length();
                StringBuilder buffer = new StringBuilder(segment.substring(0, pageIndex));
                buffer.append(page);
                buffer.append(segment.substring(pageIndex + pageTagLength));
                segment = buffer.toString();
            }
            if (segment.length() > 0) {
                layout.setText(segment);
                int segmentWidth = layout.getBounds().width;
                int segmentHeight = printerRenderer.getLineHeight();
                int drawX = 0, drawY;
                if (alignment == LEFT) {
                    drawX = clientArea.x;
                } else if (alignment == CENTER) {
                    drawX = (pageWidth - segmentWidth) / 2;
                } else if (alignment == RIGHT) {
                    drawX = clientArea.x + clientArea.width - segmentWidth;
                }
                if (header) {
                    drawY = clientArea.y - segmentHeight * 2;
                } else {
                    drawY = clientArea.y + clientArea.height + segmentHeight;
                }
                layout.draw(gc, drawX, drawY);
            }
        }

        void printLine(int x, int y, GC gc, Color foreground, Color background, TextLayout layout,
                TextLayout printLayout, int index) {
            if (background != null) {
                Rectangle rect = layout.getBounds();
                gc.setBackground(background);
                gc.fillRectangle(x, y, rect.width, rect.height);

                //         int lineCount = layout.getLineCount();
                //         for (int i = 0; i < lineCount; i++) {
                //            Rectangle rect = layout.getLineBounds(i);
                //            rect.x += paintX;
                //            rect.y += paintY + layout.getSpacing();
                //            rect.width = width;//layout bounds
                //            gc.fillRectangle(rect);
                //         }
            }
            if (printOptions.printLineNumbers) {
                FontMetrics metrics = layout.getLineMetrics(0);
                printLayout.setAscent(metrics.getAscent() + metrics.getLeading());
                printLayout.setDescent(metrics.getDescent());
                String[] lineLabels = printOptions.lineLabels;
                if (lineLabels != null) {
                    if (0 <= index && index < lineLabels.length && lineLabels[index] != null) {
                        printLayout.setText(lineLabels[index]);
                    } else {
                        printLayout.setText("");
                    }
                } else {
                    printLayout.setText(String.valueOf(index));
                }
                int paintX = x - printMargin - printLayout.getBounds().width;
                printLayout.draw(gc, paintX, y);
                printLayout.setAscent(-1);
                printLayout.setDescent(-1);
            }
            gc.setForeground(foreground);
            layout.draw(gc, x, y);
        }

        /**
         * Starts a print job and prints the pages specified in the constructor.
         */
        @Override
        public void run() {
            String jobName = printOptions.jobName;
            if (jobName == null) {
                jobName = "Printing";
            }
            if (printer.startJob(jobName)) {
                init();
                print();
                dispose();
                printer.endJob();
            }
        }
    }

    /**
     * The <code>RTFWriter</code> class is used to write widget content as
     * rich text. The implementation complies with the RTF specification
     * version 1.5.
     * <p>
     * toString() is guaranteed to return a valid RTF string only after
     * close() has been called.
     * </p><p>
     * Whole and partial lines and line breaks can be written. Lines will be
     * formatted using the styles queried from the LineStyleListener, if
     * set, or those set directly in the widget. All styles are applied to
     * the RTF stream like they are rendered by the widget. In addition, the
     * widget font name and size is used for the whole text.
     * </p>
     */
    class RTFWriter extends TextWriter {
        static final int DEFAULT_FOREGROUND = 0;
        static final int DEFAULT_BACKGROUND = 1;
        List<Color> colorTable;
        List<Font> fontTable;

        /**
         * Creates a RTF writer that writes content starting at offset "start"
         * in the document.  <code>start</code> and <code>length</code>can be set to specify partial
         * lines.
         *
         * @param start start offset of content to write, 0 based from
         *    beginning of document
         * @param length length of content to write
         */
        public RTFWriter(int start, int length) {
            super(start, length);
            colorTable = new ArrayList<>();
            fontTable = new ArrayList<>();
            colorTable.add(getForeground());
            colorTable.add(getBackground());
            fontTable.add(getFont());
        }

        /**
         * Closes the RTF writer. Once closed no more content can be written.
         * <b>NOTE:</b>  <code>toString()</code> does not return a valid RTF string until
         * <code>close()</code> has been called.
         */
        @Override
        public void close() {
            if (!isClosed()) {
                writeHeader();
                write("\n}}\0");
                super.close();
            }
        }

        /**
         * Returns the index of the specified color in the RTF color table.
         *
         * @param color the color
         * @param defaultIndex return value if color is null
         * @return the index of the specified color in the RTF color table
         *    or "defaultIndex" if "color" is null.
         */
        int getColorIndex(Color color, int defaultIndex) {
            if (color == null)
                return defaultIndex;
            int index = colorTable.indexOf(color);
            if (index == -1) {
                index = colorTable.size();
                colorTable.add(color);
            }
            return index;
        }

        /**
         * Returns the index of the specified color in the RTF color table.
         *
         * @param color the color
         * @param defaultIndex return value if color is null
         * @return the index of the specified color in the RTF color table
         *    or "defaultIndex" if "color" is null.
         */
        int getFontIndex(Font font) {
            int index = fontTable.indexOf(font);
            if (index == -1) {
                index = fontTable.size();
                fontTable.add(font);
            }
            return index;
        }

        /**
         * Appends the specified segment of "string" to the RTF data.
         * Copy from <code>start</code> up to, but excluding, <code>end</code>.
         *
         * @param string string to copy a segment from. Must not contain
         *    line breaks. Line breaks should be written using writeLineDelimiter()
         * @param start start offset of segment. 0 based.
         * @param end end offset of segment
         */
        void write(String string, int start, int end) {
            for (int index = start; index < end; index++) {
                char ch = string.charAt(index);
                if (ch > 0x7F) {
                    // write the sub string from the last escaped character
                    // to the current one. Fixes bug 21698.
                    if (index > start) {
                        write(string.substring(start, index));
                    }
                    write("\\u");
                    write(Integer.toString((short) ch));
                    write('?'); // ANSI representation (1 byte long, \\uc1)
                    start = index + 1;
                } else if (ch == '}' || ch == '{' || ch == '\\') {
                    // write the sub string from the last escaped character
                    // to the current one. Fixes bug 21698.
                    if (index > start) {
                        write(string.substring(start, index));
                    }
                    write('\\');
                    write(ch);
                    start = index + 1;
                }
            }
            // write from the last escaped character to the end.
            // Fixes bug 21698.
            if (start < end) {
                write(string.substring(start, end));
            }
        }

        /**
         * Writes the RTF header including font table and color table.
         */
        void writeHeader() {
            StringBuilder header = new StringBuilder();
            FontData fontData = getFont().getFontData()[0];
            header.append("{\\rtf1\\ansi");
            // specify code page, necessary for copy to work in bidi
            // systems that don't support Unicode RTF.
            String cpg = System.getProperty("file.encoding").toLowerCase();
            if (cpg.startsWith("cp") || cpg.startsWith("ms")) {
                cpg = cpg.substring(2, cpg.length());
                header.append("\\ansicpg");
                header.append(cpg);
            }
            header.append("\\uc1\\deff0{\\fonttbl{\\f0\\fnil ");
            header.append(fontData.getName());
            header.append(";");
            for (int i = 1; i < fontTable.size(); i++) {
                header.append("\\f");
                header.append(i);
                header.append(" ");
                FontData fd = fontTable.get(i).getFontData()[0];
                header.append(fd.getName());
                header.append(";");
            }
            header.append("}}\n{\\colortbl");
            for (int i = 0; i < colorTable.size(); i++) {
                Color color = colorTable.get(i);
                header.append("\\red");
                header.append(color.getRed());
                header.append("\\green");
                header.append(color.getGreen());
                header.append("\\blue");
                header.append(color.getBlue());
                header.append(";");
            }
            // some RTF readers ignore the deff0 font tag. Explicitly
            // set the font for the whole document to work around this.
            header.append("}\n{\\f0\\fs");
            // font size is specified in half points
            header.append(fontData.getHeight() * 2);
            header.append(" ");
            write(header.toString(), 0);
        }

        /**
         * Appends the specified line text to the RTF data.  Lines will be formatted
         * using the styles queried from the LineStyleListener, if set, or those set
         * directly in the widget.
         *
         * @param line line text to write as RTF. Must not contain line breaks
         *    Line breaks should be written using writeLineDelimiter()
         * @param lineOffset offset of the line. 0 based from the start of the
         *    widget document. Any text occurring before the start offset or after the
         *    end offset specified during object creation is ignored.
         * @exception SWTException <ul>
         *   <li>ERROR_IO when the writer is closed.</li>
         * </ul>
         */
        @Override
        public void writeLine(String line, int lineOffset) {
            if (isClosed()) {
                SWT.error(SWT.ERROR_IO);
            }
            int lineIndex = content.getLineAtOffset(lineOffset);
            int lineAlignment, lineIndent;
            boolean lineJustify;
            int[] ranges;
            StyleRange[] styles;
            StyledTextEvent event = getLineStyleData(lineOffset, line);
            if (event != null) {
                lineAlignment = event.alignment;
                lineIndent = event.indent;
                lineJustify = event.justify;
                ranges = event.ranges;
                styles = event.styles;
            } else {
                lineAlignment = renderer.getLineAlignment(lineIndex, alignment);
                lineIndent = renderer.getLineIndent(lineIndex, indent);
                lineJustify = renderer.getLineJustify(lineIndex, justify);
                ranges = renderer.getRanges(lineOffset, line.length());
                styles = renderer.getStyleRanges(lineOffset, line.length(), false);
            }
            if (styles == null)
                styles = new StyleRange[0];
            Color lineBackground = renderer.getLineBackground(lineIndex, null);
            event = getLineBackgroundData(lineOffset, line);
            if (event != null && event.lineBackground != null)
                lineBackground = event.lineBackground;
            writeStyledLine(line, lineOffset, ranges, styles, lineBackground, lineIndent, lineAlignment,
                    lineJustify);
        }

        /**
         * Appends the specified line delimiter to the RTF data.
         *
         * @param lineDelimiter line delimiter to write as RTF.
         * @exception SWTException <ul>
         *   <li>ERROR_IO when the writer is closed.</li>
         * </ul>
         */
        @Override
        public void writeLineDelimiter(String lineDelimiter) {
            if (isClosed()) {
                SWT.error(SWT.ERROR_IO);
            }
            write(lineDelimiter, 0, lineDelimiter.length());
            write("\\par ");
        }

        /**
         * Appends the specified line text to the RTF data.
         * <p>
         * Use the colors and font styles specified in "styles" and "lineBackground".
         * Formatting is written to reflect the text rendering by the text widget.
         * Style background colors take precedence over the line background color.
         * Background colors are written using the \chshdng0\chcbpat tag (vs. the \cb tag).
         * </p>
         *
         * @param line line text to write as RTF. Must not contain line breaks
         *    Line breaks should be written using writeLineDelimiter()
         * @param lineOffset offset of the line. 0 based from the start of the
         *    widget document. Any text occurring before the start offset or after the
         *    end offset specified during object creation is ignored.
         * @param styles styles to use for formatting. Must not be null.
         * @param lineBackground line background color to use for formatting.
         *    May be null.
         */
        void writeStyledLine(String line, int lineOffset, int ranges[], StyleRange[] styles, Color lineBackground,
                int indent, int alignment, boolean justify) {
            int lineLength = line.length();
            int startOffset = getStart();
            int writeOffset = startOffset - lineOffset;
            if (writeOffset >= lineLength)
                return;
            int lineIndex = Math.max(0, writeOffset);

            write("\\fi");
            write(indent);
            switch (alignment) {
            case SWT.LEFT:
                write("\\ql");
                break;
            case SWT.CENTER:
                write("\\qc");
                break;
            case SWT.RIGHT:
                write("\\qr");
                break;
            }
            if (justify)
                write("\\qj");
            write(" ");

            if (lineBackground != null) {
                write("{\\chshdng0\\chcbpat");
                write(getColorIndex(lineBackground, DEFAULT_BACKGROUND));
                write(" ");
            }
            int endOffset = startOffset + super.getCharCount();
            int lineEndOffset = Math.min(lineLength, endOffset - lineOffset);
            for (int i = 0; i < styles.length; i++) {
                StyleRange style = styles[i];
                int start, end;
                if (ranges != null) {
                    start = ranges[i << 1] - lineOffset;
                    end = start + ranges[(i << 1) + 1];
                } else {
                    start = style.start - lineOffset;
                    end = start + style.length;
                }
                // skip over partial first line
                if (end < writeOffset) {
                    continue;
                }
                // style starts beyond line end or RTF write end
                if (start >= lineEndOffset) {
                    break;
                }
                // write any unstyled text
                if (lineIndex < start) {
                    // copy to start of style
                    // style starting beyond end of write range or end of line
                    // is guarded against above.
                    write(line, lineIndex, start);
                    lineIndex = start;
                }
                // write styled text
                write("{\\cf");
                write(getColorIndex(style.foreground, DEFAULT_FOREGROUND));
                int colorIndex = getColorIndex(style.background, DEFAULT_BACKGROUND);
                if (colorIndex != DEFAULT_BACKGROUND) {
                    write("\\chshdng0\\chcbpat");
                    write(colorIndex);
                }
                int fontStyle = style.fontStyle;
                Font font = style.font;
                if (font != null) {
                    int fontIndex = getFontIndex(font);
                    write("\\f");
                    write(fontIndex);
                    FontData fontData = font.getFontData()[0];
                    write("\\fs");
                    write(fontData.getHeight() * 2);
                    fontStyle = fontData.getStyle();
                }
                if ((fontStyle & SWT.BOLD) != 0) {
                    write("\\b");
                }
                if ((fontStyle & SWT.ITALIC) != 0) {
                    write("\\i");
                }
                if (style.underline) {
                    write("\\ul");
                }
                if (style.strikeout) {
                    write("\\strike");
                }
                write(" ");
                // copy to end of style or end of write range or end of line
                int copyEnd = Math.min(end, lineEndOffset);
                // guard against invalid styles and let style processing continue
                copyEnd = Math.max(copyEnd, lineIndex);
                write(line, lineIndex, copyEnd);
                if ((fontStyle & SWT.BOLD) != 0) {
                    write("\\b0");
                }
                if ((style.fontStyle & SWT.ITALIC) != 0) {
                    write("\\i0");
                }
                if (style.underline) {
                    write("\\ul0");
                }
                if (style.strikeout) {
                    write("\\strike0");
                }
                write("}");
                lineIndex = copyEnd;
            }
            // write unstyled text at the end of the line
            if (lineIndex < lineEndOffset) {
                write(line, lineIndex, lineEndOffset);
            }
            if (lineBackground != null)
                write("}");
        }
    }

    /**
     * The <code>TextWriter</code> class is used to write widget content to
     * a string.  Whole and partial lines and line breaks can be written. To write
     * partial lines, specify the start and length of the desired segment
     * during object creation.
     * <p>
     * </b>NOTE:</b> <code>toString()</code> is guaranteed to return a valid string only after close()
     * has been called.
     * </p>
     */
    class TextWriter {
        private StringBuilder buffer;
        private int startOffset; // offset of first character that will be written
        private int endOffset; // offset of last character that will be written.
        // 0 based from the beginning of the widget text.
        private boolean isClosed = false;

        /**
         * Creates a writer that writes content starting at offset "start"
         * in the document.  <code>start</code> and <code>length</code> can be set to specify partial lines.
         *
         * @param start start offset of content to write, 0 based from beginning of document
         * @param length length of content to write
         */
        public TextWriter(int start, int length) {
            buffer = new StringBuilder(length);
            startOffset = start;
            endOffset = start + length;
        }

        /**
         * Closes the writer. Once closed no more content can be written.
         * <b>NOTE:</b>  <code>toString()</code> is not guaranteed to return a valid string unless
         * the writer is closed.
         */
        public void close() {
            if (!isClosed) {
                isClosed = true;
            }
        }

        /**
         * Returns the number of characters to write.
         * @return the integer number of characters to write
         */
        public int getCharCount() {
            return endOffset - startOffset;
        }

        /**
         * Returns the offset where writing starts. 0 based from the start of
         * the widget text. Used to write partial lines.
         * @return the integer offset where writing starts
         */
        public int getStart() {
            return startOffset;
        }

        /**
         * Returns whether the writer is closed.
         * @return a boolean specifying whether or not the writer is closed
         */
        public boolean isClosed() {
            return isClosed;
        }

        /**
         * Returns the string.  <code>close()</code> must be called before <code>toString()</code>
         * is guaranteed to return a valid string.
         *
         * @return the string
         */
        @Override
        public String toString() {
            return buffer.toString();
        }

        /**
         * Appends the given string to the data.
         */
        void write(String string) {
            buffer.append(string);
        }

        /**
         * Inserts the given string to the data at the specified offset.
         * <p>
         * Do nothing if "offset" is < 0 or > getCharCount()
         * </p>
         *
         * @param string text to insert
         * @param offset offset in the existing data to insert "string" at.
         */
        void write(String string, int offset) {
            if (offset < 0 || offset > buffer.length()) {
                return;
            }
            buffer.insert(offset, string);
        }

        /**
         * Appends the given int to the data.
         */
        void write(int i) {
            buffer.append(i);
        }

        /**
         * Appends the given character to the data.
         */
        void write(char i) {
            buffer.append(i);
        }

        /**
         * Appends the specified line text to the data.
         *
         * @param line line text to write. Must not contain line breaks
         *    Line breaks should be written using writeLineDelimiter()
         * @param lineOffset offset of the line. 0 based from the start of the
         *    widget document. Any text occurring before the start offset or after the
         *   end offset specified during object creation is ignored.
         * @exception SWTException <ul>
         *   <li>ERROR_IO when the writer is closed.</li>
         * </ul>
         */
        public void writeLine(String line, int lineOffset) {
            if (isClosed) {
                SWT.error(SWT.ERROR_IO);
            }
            int writeOffset = startOffset - lineOffset;
            int lineLength = line.length();
            int lineIndex;
            if (writeOffset >= lineLength) {
                return; // whole line is outside write range
            } else if (writeOffset > 0) {
                lineIndex = writeOffset; // line starts before write start
            } else {
                lineIndex = 0;
            }
            int copyEnd = Math.min(lineLength, endOffset - lineOffset);
            if (lineIndex < copyEnd) {
                write(line.substring(lineIndex, copyEnd));
            }
        }

        /**
         * Appends the specified line delimiter to the data.
         *
         * @param lineDelimiter line delimiter to write
         * @exception SWTException <ul>
         *   <li>ERROR_IO when the writer is closed.</li>
         * </ul>
         */
        public void writeLineDelimiter(String lineDelimiter) {
            if (isClosed) {
                SWT.error(SWT.ERROR_IO);
            }
            write(lineDelimiter);
        }
    }

    /**
     * Constructs a new instance of this class given its parent
     * and a style value describing its behavior and appearance.
     * <p>
     * The style value is either one of the style constants defined in
     * class <code>SWT</code> which is applicable to instances of this
     * class, or must be built by <em>bitwise OR</em>'ing together
     * (that is, using the <code>int</code> "|" operator) two or more
     * of those <code>SWT</code> style constants. The class description
     * lists the style constants that are applicable to the class.
     * Style bits are also inherited from superclasses.
     * </p>
     *
     * @param parent a widget which will be the parent of the new instance (cannot be null)
     * @param style the style of widget to construct
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
     * </ul>
     *
     * @see SWT#FULL_SELECTION
     * @see SWT#MULTI
     * @see SWT#READ_ONLY
     * @see SWT#SINGLE
     * @see SWT#WRAP
     * @see #getStyle
     */
    public StyledText(Composite parent, int style) {
        super(parent, checkStyle(style));
        // set the fg in the OS to ensure that these are the same as StyledText, necessary
        // for ensuring that the bg/fg the IME box uses is the same as what StyledText uses
        super.setForeground(getForeground());
        super.setDragDetect(false);
        Display display = getDisplay();
        fixedLineHeight = true;
        if ((style & SWT.READ_ONLY) != 0) {
            setEditable(false);
        }
        leftMargin = rightMargin = isBidiCaret() ? BIDI_CARET_WIDTH - 1 : 0;
        if ((style & SWT.SINGLE) != 0 && (style & SWT.BORDER) != 0) {
            leftMargin = topMargin = rightMargin = bottomMargin = 2;
        }
        alignment = style & (SWT.LEFT | SWT.RIGHT | SWT.CENTER);
        if (alignment == 0)
            alignment = SWT.LEFT;
        clipboard = new Clipboard(display);
        installDefaultContent();
        renderer = new StyledTextRenderer(getDisplay(), this);
        renderer.setContent(content);
        renderer.setFont(getFont(), tabLength);
        ime = new IME(this, SWT.NONE);
        defaultCaret = new Caret(this, SWT.NONE);
        if ((style & SWT.WRAP) != 0) {
            setWordWrap(true);
        }
        if (isBidiCaret()) {
            createCaretBitmaps();
            Runnable runnable = () -> {
                int direction = BidiUtil.getKeyboardLanguage() == BidiUtil.KEYBOARD_BIDI ? SWT.RIGHT : SWT.LEFT;
                if (direction == caretDirection)
                    return;
                if (getCaret() != defaultCaret)
                    return;
                Point newCaretPos = getPointAtOffset(caretOffset);
                setCaretLocation(newCaretPos, direction);
            };
            BidiUtil.addLanguageListener(this, runnable);
        }
        setCaret(defaultCaret);
        calculateScrollBars();
        createKeyBindings();
        super.setCursor(display.getSystemCursor(SWT.CURSOR_IBEAM));
        installListeners();
        initializeAccessible();
        setData("DEFAULT_DROP_TARGET_EFFECT", new StyledTextDropTargetEffect(this));
        if (IS_MAC)
            setData(STYLEDTEXT_KEY);
    }

    /**
     * Adds an extended modify listener. An ExtendedModify event is sent by the
     * widget when the widget text has changed.
     *
     * @param extendedModifyListener the listener
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
     * </ul>
     */
    public void addExtendedModifyListener(ExtendedModifyListener extendedModifyListener) {
        checkWidget();
        if (extendedModifyListener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        StyledTextListener typedListener = new StyledTextListener(extendedModifyListener);
        addListener(ST.ExtendedModify, typedListener);
    }

    /**
     * Adds a bidirectional segment listener.
     * <p>
     * A BidiSegmentEvent is sent
     * whenever a line of text is measured or rendered. You can
     * specify text ranges in the line that should be treated as if they
     * had a different direction than the surrounding text.
     * This may be used when adjacent segments of right-to-left text should
     * not be reordered relative to each other.
     * E.g., multiple Java string literals in a right-to-left language
     * should generally remain in logical order to each other, that is, the
     * way they are stored.
     * </p>
     *
     * @param listener the listener
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
     * </ul>
     * @see BidiSegmentEvent
     * @since 2.0
     */
    public void addBidiSegmentListener(BidiSegmentListener listener) {
        checkWidget();
        if (listener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        addListener(ST.LineGetSegments, new StyledTextListener(listener));
        resetCache(0, content.getLineCount());
        setCaretLocation();
        super.redraw();
    }

    /**
     * Adds a caret listener. CaretEvent is sent when the caret offset changes.
     *
     * @param listener the listener
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
     * </ul>
     *
     * @since 3.5
     */
    public void addCaretListener(CaretListener listener) {
        checkWidget();
        if (listener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        addListener(ST.CaretMoved, new StyledTextListener(listener));
    }

    /**
     * Adds a line background listener. A LineGetBackground event is sent by the
     * widget to determine the background color for a line.
     *
     * @param listener the listener
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
     * </ul>
     */
    public void addLineBackgroundListener(LineBackgroundListener listener) {
        checkWidget();
        if (listener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        if (!isListening(ST.LineGetBackground)) {
            renderer.clearLineBackground(0, content.getLineCount());
        }
        addListener(ST.LineGetBackground, new StyledTextListener(listener));
    }

    /**
     * Adds a line style listener. A LineGetStyle event is sent by the widget to
     * determine the styles for a line.
     *
     * @param listener the listener
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
     * </ul>
     */
    public void addLineStyleListener(LineStyleListener listener) {
        checkWidget();
        if (listener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        if (!isListening(ST.LineGetStyle)) {
            setStyleRanges(0, 0, null, null, true);
            renderer.clearLineStyle(0, content.getLineCount());
        }
        addListener(ST.LineGetStyle, new StyledTextListener(listener));
        setCaretLocation();
    }

    /**
     * Adds a modify listener. A Modify event is sent by the widget when the widget text
     * has changed.
     *
     * @param modifyListener the listener
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
     * </ul>
     */
    public void addModifyListener(ModifyListener modifyListener) {
        checkWidget();
        if (modifyListener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        addListener(SWT.Modify, new TypedListener(modifyListener));
    }

    /**
     * Adds a paint object listener. A paint object event is sent by the widget when an object
     * needs to be drawn.
     *
     * @param listener the listener
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
     * </ul>
     *
     * @since 3.2
     *
     * @see PaintObjectListener
     * @see PaintObjectEvent
     */
    public void addPaintObjectListener(PaintObjectListener listener) {
        checkWidget();
        if (listener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        addListener(ST.PaintObject, new StyledTextListener(listener));
    }

    /**
     * Adds a selection listener. A Selection event is sent by the widget when the
     * user changes the selection.
     * <p>
     * When <code>widgetSelected</code> is called, the event x and y fields contain
     * the start and end caret indices of the selection. The selection values returned are visual
     * (i.e., x will always always be &lt;= y).
     * No event is sent when the caret is moved while the selection length is 0.
     * </p><p>
     * <code>widgetDefaultSelected</code> is not called for StyledTexts.
     * </p>
     *
     * @param listener the listener which should be notified when the user changes the receiver's selection
        
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see SelectionListener
     * @see #removeSelectionListener
     * @see SelectionEvent
     */
    public void addSelectionListener(SelectionListener listener) {
        checkWidget();
        if (listener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        addListener(SWT.Selection, new TypedListener(listener));
    }

    /**
     * Adds a verify key listener. A VerifyKey event is sent by the widget when a key
     * is pressed. The widget ignores the key press if the listener sets the doit field
     * of the event to false.
     *
     * @param listener the listener
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
     * </ul>
     */
    public void addVerifyKeyListener(VerifyKeyListener listener) {
        checkWidget();
        if (listener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        addListener(ST.VerifyKey, new StyledTextListener(listener));
    }

    /**
     * Adds a verify listener. A Verify event is sent by the widget when the widget text
     * is about to change. The listener can set the event text and the doit field to
     * change the text that is set in the widget or to force the widget to ignore the
     * text change.
     *
     * @param verifyListener the listener
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
     * </ul>
     */
    public void addVerifyListener(VerifyListener verifyListener) {
        checkWidget();
        if (verifyListener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        addListener(SWT.Verify, new TypedListener(verifyListener));
    }

    /**
     * Adds a word movement listener. A movement event is sent when the boundary
     * of a word is needed. For example, this occurs during word next and word
     * previous actions.
     *
     * @param movementListener the listener
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
     * </ul>
     *
     * @see MovementEvent
     * @see MovementListener
     * @see #removeWordMovementListener
     *
     * @since 3.3
     */
    public void addWordMovementListener(MovementListener movementListener) {
        checkWidget();
        if (movementListener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        addListener(ST.WordNext, new StyledTextListener(movementListener));
        addListener(ST.WordPrevious, new StyledTextListener(movementListener));
    }

    /**
     * Appends a string to the text at the end of the widget.
     *
     * @param string the string to be appended
     * @see #replaceTextRange(int,int,String)
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
     * </ul>
     */
    public void append(String string) {
        checkWidget();
        if (string == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        int lastChar = Math.max(getCharCount(), 0);
        replaceTextRange(lastChar, 0, string);
    }

    /**
     * Calculates the scroll bars
     */
    void calculateScrollBars() {
        ScrollBar horizontalBar = getHorizontalBar();
        ScrollBar verticalBar = getVerticalBar();
        setScrollBars(true);
        if (verticalBar != null) {
            verticalBar.setIncrement(getVerticalIncrement());
        }
        if (horizontalBar != null) {
            horizontalBar.setIncrement(getHorizontalIncrement());
        }
    }

    /**
     * Calculates the top index based on the current vertical scroll offset.
     * The top index is the index of the topmost fully visible line or the
     * topmost partially visible line if no line is fully visible.
     * The top index starts at 0.
     */
    void calculateTopIndex(int delta) {
        int oldDelta = delta;
        int oldTopIndex = topIndex;
        int oldTopIndexY = topIndexY;
        if (isFixedLineHeight()) {
            int verticalIncrement = getVerticalIncrement();
            if (verticalIncrement == 0) {
                return;
            }
            topIndex = Compatibility.ceil(getVerticalScrollOffset(), verticalIncrement);
            // Set top index to partially visible top line if no line is fully
            // visible but at least some of the widget client area is visible.
            // Fixes bug 15088.
            if (topIndex > 0) {
                if (clientAreaHeight > 0) {
                    int bottomPixel = getVerticalScrollOffset() + clientAreaHeight;
                    int fullLineTopPixel = topIndex * verticalIncrement;
                    int fullLineVisibleHeight = bottomPixel - fullLineTopPixel;
                    // set top index to partially visible line if no line fully fits in
                    // client area or if space is available but not used (the latter should
                    // never happen because we use claimBottomFreeSpace)
                    if (fullLineVisibleHeight < verticalIncrement) {
                        topIndex = getVerticalScrollOffset() / verticalIncrement;
                    }
                } else if (topIndex >= content.getLineCount()) {
                    topIndex = content.getLineCount() - 1;
                }
            }
        } else {
            if (delta >= 0) {
                delta -= topIndexY;
                int lineIndex = topIndex;
                int lineCount = content.getLineCount();
                while (lineIndex < lineCount) {
                    if (delta <= 0)
                        break;
                    delta -= renderer.getLineHeight(lineIndex++);
                }
                if (lineIndex < lineCount && -delta + renderer.getLineHeight(lineIndex) <= clientAreaHeight
                        - topMargin - bottomMargin) {
                    topIndex = lineIndex;
                    topIndexY = -delta;
                } else {
                    topIndex = lineIndex - 1;
                    topIndexY = -renderer.getLineHeight(topIndex) - delta;
                }
            } else {
                delta -= topIndexY;
                int lineIndex = topIndex;
                while (lineIndex > 0) {
                    int lineHeight = renderer.getLineHeight(lineIndex - 1);
                    if (delta + lineHeight > 0)
                        break;
                    delta += lineHeight;
                    lineIndex--;
                }
                if (lineIndex == 0 || -delta + renderer.getLineHeight(lineIndex) <= clientAreaHeight - topMargin
                        - bottomMargin) {
                    topIndex = lineIndex;
                    topIndexY = -delta;
                } else {
                    topIndex = lineIndex - 1;
                    topIndexY = -renderer.getLineHeight(topIndex) - delta;
                }
            }
        }
        if (topIndex < 0) {
            // TODO: This logging is in place to determine why topIndex is getting set to negative values.
            // It should be deleted once we fix the root cause of this issue. See bug 487254 for details.
            System.err.println("StyledText: topIndex was " + topIndex + ", isFixedLineHeight() = "
                    + isFixedLineHeight() + ", delta = " + delta + ", content.getLineCount() = "
                    + content.getLineCount() + ", clientAreaHeight = " + clientAreaHeight + ", oldTopIndex = "
                    + oldTopIndex + ", oldTopIndexY = " + oldTopIndexY + ", getVerticalScrollOffset = "
                    + getVerticalScrollOffset() + ", oldDelta = " + oldDelta + ", getVerticalIncrement() = "
                    + getVerticalIncrement());
            topIndex = 0;
        }
        if (topIndex != oldTopIndex || oldTopIndexY != topIndexY) {
            int width = renderer.getWidth();
            renderer.calculateClientArea();
            if (width != renderer.getWidth()) {
                setScrollBars(false);
            }
        }
    }

    /**
     * Hides the scroll bars if widget is created in single line mode.
     */
    static int checkStyle(int style) {
        if ((style & SWT.SINGLE) != 0) {
            style &= ~(SWT.H_SCROLL | SWT.V_SCROLL | SWT.WRAP | SWT.MULTI);
        } else {
            style |= SWT.MULTI;
            if ((style & SWT.WRAP) != 0) {
                style &= ~SWT.H_SCROLL;
            }
        }
        style |= SWT.NO_REDRAW_RESIZE | SWT.DOUBLE_BUFFERED | SWT.NO_BACKGROUND;
        /* Clear SWT.CENTER to avoid the conflict with SWT.EMBEDDED */
        return style & ~SWT.CENTER;
    }

    /**
     * Scrolls down the text to use new space made available by a resize or by
     * deleted lines.
     */
    void claimBottomFreeSpace() {
        if (ime.getCompositionOffset() != -1)
            return;
        if (isFixedLineHeight()) {
            int newVerticalOffset = Math.max(0, renderer.getHeight() - clientAreaHeight);
            if (newVerticalOffset < getVerticalScrollOffset()) {
                scrollVertical(newVerticalOffset - getVerticalScrollOffset(), true);
            }
        } else {
            int bottomIndex = getPartialBottomIndex();
            int height = getLinePixel(bottomIndex + 1);
            if (clientAreaHeight > height) {
                scrollVertical(-getAvailableHeightAbove(clientAreaHeight - height), true);
            }
        }
    }

    /**
     * Scrolls text to the right to use new space made available by a resize.
     */
    void claimRightFreeSpace() {
        int newHorizontalOffset = Math.max(0, renderer.getWidth() - clientAreaWidth);
        if (newHorizontalOffset < horizontalScrollOffset) {
            // item is no longer drawn past the right border of the client area
            // align the right end of the item with the right border of the
            // client area (window is scrolled right).
            scrollHorizontal(newHorizontalOffset - horizontalScrollOffset, true);
        }
    }

    void clearBlockSelection(boolean reset, boolean sendEvent) {
        if (reset)
            resetSelection();
        blockXAnchor = blockYAnchor = -1;
        blockXLocation = blockYLocation = -1;
        caretDirection = SWT.NULL;
        updateCaretVisibility();
        super.redraw();
        if (sendEvent)
            sendSelectionEvent();
    }

    /**
     * Removes the widget selection.
     *
     * @param sendEvent a Selection event is sent when set to true and when the selection is actually reset.
     */
    void clearSelection(boolean sendEvent) {
        int selectionStart = selection.x;
        int selectionEnd = selection.y;
        resetSelection();
        // redraw old selection, if any
        if (selectionEnd - selectionStart > 0) {
            int length = content.getCharCount();
            // called internally to remove selection after text is removed
            // therefore make sure redraw range is valid.
            int redrawStart = Math.min(selectionStart, length);
            int redrawEnd = Math.min(selectionEnd, length);
            if (redrawEnd - redrawStart > 0) {
                internalRedrawRange(redrawStart, redrawEnd - redrawStart);
            }
            if (sendEvent) {
                sendSelectionEvent();
            }
        }
    }

    @Override
    public Point computeSize(int wHint, int hHint, boolean changed) {
        checkWidget();
        int lineCount = (getStyle() & SWT.SINGLE) != 0 ? 1 : content.getLineCount();
        int width = 0;
        int height = 0;
        if (wHint == SWT.DEFAULT || hHint == SWT.DEFAULT) {
            Display display = getDisplay();
            int maxHeight = display.getClientArea().height;
            for (int lineIndex = 0; lineIndex < lineCount; lineIndex++) {
                TextLayout layout = renderer.getTextLayout(lineIndex);
                int wrapWidth = layout.getWidth();
                if (wordWrap)
                    layout.setWidth(wHint == 0 ? 1
                            : wHint == SWT.DEFAULT ? SWT.DEFAULT : Math.max(1, wHint - leftMargin - rightMargin));
                Rectangle rect = layout.getBounds();
                height += rect.height;
                width = Math.max(width, rect.width);
                layout.setWidth(wrapWidth);
                renderer.disposeTextLayout(layout);
                if (isFixedLineHeight() && height > maxHeight)
                    break;
            }
            if (isFixedLineHeight()) {
                height = lineCount * renderer.getLineHeight();
            }
        }
        // Use default values if no text is defined.
        if (width == 0)
            width = DEFAULT_WIDTH;
        if (height == 0)
            height = DEFAULT_HEIGHT;
        if (wHint != SWT.DEFAULT)
            width = wHint;
        if (hHint != SWT.DEFAULT)
            height = hHint;
        int wTrim = getLeftMargin() + rightMargin + getCaretWidth();
        int hTrim = topMargin + bottomMargin;
        Rectangle rect = computeTrim(0, 0, width + wTrim, height + hTrim);
        return new Point(rect.width, rect.height);
    }

    /**
     * Copies the selected text to the <code>DND.CLIPBOARD</code> clipboard.
     * <p>
     * The text will be put on the clipboard in plain text format and RTF format.
     * The <code>DND.CLIPBOARD</code> clipboard is used for data that is
     * transferred by keyboard accelerator (such as Ctrl+C/Ctrl+V) or
     * by menu action.
     * </p>
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void copy() {
        checkWidget();
        copySelection(DND.CLIPBOARD);
    }

    /**
     * Copies the selected text to the specified clipboard.  The text will be put in the
     * clipboard in plain text format and RTF format.
     * <p>
     * The clipboardType is  one of the clipboard constants defined in class
     * <code>DND</code>.  The <code>DND.CLIPBOARD</code>  clipboard is
     * used for data that is transferred by keyboard accelerator (such as Ctrl+C/Ctrl+V)
     * or by menu action.  The <code>DND.SELECTION_CLIPBOARD</code>
     * clipboard is used for data that is transferred by selecting text and pasting
     * with the middle mouse button.
     * </p>
     *
     * @param clipboardType indicates the type of clipboard
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.1
     */
    public void copy(int clipboardType) {
        checkWidget();
        copySelection(clipboardType);
    }

    boolean copySelection(int type) {
        if (type != DND.CLIPBOARD && type != DND.SELECTION_CLIPBOARD)
            return false;
        try {
            if (blockSelection && blockXLocation != -1) {
                String text = getBlockSelectionText(PlatformLineDelimiter);
                if (text.length() > 0) {
                    //TODO RTF support
                    TextTransfer plainTextTransfer = TextTransfer.getInstance();
                    Object[] data = new Object[] { text };
                    Transfer[] types = new Transfer[] { plainTextTransfer };
                    clipboard.setContents(data, types, type);
                    return true;
                }
            } else {
                int length = selection.y - selection.x;
                if (length > 0) {
                    setClipboardContent(selection.x, length, type);
                    return true;
                }
            }
        } catch (SWTError error) {
            // Copy to clipboard failed. This happens when another application
            // is accessing the clipboard while we copy. Ignore the error.
            // Rethrow all other errors. Fixes bug 17578.
            if (error.code != DND.ERROR_CANNOT_SET_CLIPBOARD) {
                throw error;
            }
        }
        return false;
    }

    /**
     * Returns the alignment of the widget.
     *
     * @return the alignment
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see #getLineAlignment(int)
     *
     * @since 3.2
     */
    public int getAlignment() {
        checkWidget();
        return alignment;
    }

    /**
     * Returns the Always Show Scrollbars flag.  True if the scrollbars are
     * always shown even if they are not required.  False if the scrollbars are only
     * visible when some part of the content needs to be scrolled to be seen.
     * The H_SCROLL and V_SCROLL style bits are also required to enable scrollbars in the
     * horizontal and vertical directions.
     *
     * @return the Always Show Scrollbars flag value
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.8
     */
    public boolean getAlwaysShowScrollBars() {
        checkWidget();
        return alwaysShowScroll;
    }

    int getAvailableHeightAbove(int height) {
        int maxHeight = verticalScrollOffset;
        if (maxHeight == -1) {
            int lineIndex = topIndex - 1;
            maxHeight = -topIndexY;
            if (topIndexY > 0) {
                maxHeight += renderer.getLineHeight(lineIndex--);
            }
            while (height > maxHeight && lineIndex >= 0) {
                maxHeight += renderer.getLineHeight(lineIndex--);
            }
        }
        return Math.min(height, maxHeight);
    }

    int getAvailableHeightBellow(int height) {
        int partialBottomIndex = getPartialBottomIndex();
        int topY = getLinePixel(partialBottomIndex);
        int lineHeight = renderer.getLineHeight(partialBottomIndex);
        int availableHeight = 0;
        int clientAreaHeight = this.clientAreaHeight - topMargin - bottomMargin;
        if (topY + lineHeight > clientAreaHeight) {
            availableHeight = lineHeight - (clientAreaHeight - topY);
        }
        int lineIndex = partialBottomIndex + 1;
        int lineCount = content.getLineCount();
        while (height > availableHeight && lineIndex < lineCount) {
            availableHeight += renderer.getLineHeight(lineIndex++);
        }
        return Math.min(height, availableHeight);
    }

    /**
     * Returns the color of the margins.
     *
     * @return the color of the margins.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.5
     */
    public Color getMarginColor() {
        checkWidget();
        return marginColor != null ? marginColor : getBackground();
    }

    /**
     * Returns a string that uses only the line delimiter specified by the
     * StyledTextContent implementation.
     * <p>
     * Returns only the first line if the widget has the SWT.SINGLE style.
     * </p>
     *
     * @param text the text that may have line delimiters that don't
     *    match the model line delimiter. Possible line delimiters
     *    are CR ('\r'), LF ('\n'), CR/LF ("\r\n")
     * @return the converted text that only uses the line delimiter
     *    specified by the model. Returns only the first line if the widget
     *    has the SWT.SINGLE style.
     */
    String getModelDelimitedText(String text) {
        int length = text.length();
        if (length == 0) {
            return text;
        }
        int crIndex = 0;
        int lfIndex = 0;
        int i = 0;
        StringBuilder convertedText = new StringBuilder(length);
        String delimiter = getLineDelimiter();
        while (i < length) {
            if (crIndex != -1) {
                crIndex = text.indexOf(SWT.CR, i);
            }
            if (lfIndex != -1) {
                lfIndex = text.indexOf(SWT.LF, i);
            }
            if (lfIndex == -1 && crIndex == -1) { // no more line breaks?
                break;
            } else if ((crIndex < lfIndex && crIndex != -1) || lfIndex == -1) {
                convertedText.append(text.substring(i, crIndex));
                if (lfIndex == crIndex + 1) { // CR/LF combination?
                    i = lfIndex + 1;
                } else {
                    i = crIndex + 1;
                }
            } else { // LF occurs before CR!
                convertedText.append(text.substring(i, lfIndex));
                i = lfIndex + 1;
            }
            if (isSingleLine()) {
                break;
            }
            convertedText.append(delimiter);
        }
        // copy remaining text if any and if not in single line mode or no
        // text copied thus far (because there only is one line)
        if (i < length && (!isSingleLine() || convertedText.length() == 0)) {
            convertedText.append(text.substring(i));
        }
        return convertedText.toString();
    }

    boolean checkDragDetect(Event event) {
        if (!isListening(SWT.DragDetect))
            return false;
        if (event.button != 1)
            return false;
        if (blockSelection && blockXLocation != -1) {
            Rectangle rect = getBlockSelectionRectangle();
            if (rect.contains(event.x, event.y)) {
                return dragDetect(event);
            }
        } else {
            if (selection.x == selection.y)
                return false;
            int offset = getOffsetAtPoint(event.x, event.y, null, true);
            if (selection.x <= offset && offset < selection.y) {
                return dragDetect(event);
            }

        }
        return false;
    }

    /**
     * Creates default key bindings.
     */
    void createKeyBindings() {
        int nextKey = isMirrored() ? SWT.ARROW_LEFT : SWT.ARROW_RIGHT;
        int previousKey = isMirrored() ? SWT.ARROW_RIGHT : SWT.ARROW_LEFT;

        // Navigation
        setKeyBinding(SWT.ARROW_UP, ST.LINE_UP);
        setKeyBinding(SWT.ARROW_DOWN, ST.LINE_DOWN);
        if (IS_MAC) {
            setKeyBinding(previousKey | SWT.MOD1, ST.LINE_START);
            setKeyBinding(nextKey | SWT.MOD1, ST.LINE_END);
            setKeyBinding(SWT.HOME, ST.TEXT_START);
            setKeyBinding(SWT.END, ST.TEXT_END);
            setKeyBinding(SWT.ARROW_UP | SWT.MOD1, ST.TEXT_START);
            setKeyBinding(SWT.ARROW_DOWN | SWT.MOD1, ST.TEXT_END);
            setKeyBinding(nextKey | SWT.MOD3, ST.WORD_NEXT);
            setKeyBinding(previousKey | SWT.MOD3, ST.WORD_PREVIOUS);
        } else {
            setKeyBinding(SWT.HOME, ST.LINE_START);
            setKeyBinding(SWT.END, ST.LINE_END);
            setKeyBinding(SWT.HOME | SWT.MOD1, ST.TEXT_START);
            setKeyBinding(SWT.END | SWT.MOD1, ST.TEXT_END);
            setKeyBinding(nextKey | SWT.MOD1, ST.WORD_NEXT);
            setKeyBinding(previousKey | SWT.MOD1, ST.WORD_PREVIOUS);
        }
        setKeyBinding(SWT.PAGE_UP, ST.PAGE_UP);
        setKeyBinding(SWT.PAGE_DOWN, ST.PAGE_DOWN);
        setKeyBinding(SWT.PAGE_UP | SWT.MOD1, ST.WINDOW_START);
        setKeyBinding(SWT.PAGE_DOWN | SWT.MOD1, ST.WINDOW_END);
        setKeyBinding(nextKey, ST.COLUMN_NEXT);
        setKeyBinding(previousKey, ST.COLUMN_PREVIOUS);

        // Selection
        setKeyBinding(SWT.ARROW_UP | SWT.MOD2, ST.SELECT_LINE_UP);
        setKeyBinding(SWT.ARROW_DOWN | SWT.MOD2, ST.SELECT_LINE_DOWN);
        if (IS_MAC) {
            setKeyBinding(previousKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_LINE_START);
            setKeyBinding(nextKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_LINE_END);
            setKeyBinding(SWT.HOME | SWT.MOD2, ST.SELECT_TEXT_START);
            setKeyBinding(SWT.END | SWT.MOD2, ST.SELECT_TEXT_END);
            setKeyBinding(SWT.ARROW_UP | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_START);
            setKeyBinding(SWT.ARROW_DOWN | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_END);
            setKeyBinding(nextKey | SWT.MOD2 | SWT.MOD3, ST.SELECT_WORD_NEXT);
            setKeyBinding(previousKey | SWT.MOD2 | SWT.MOD3, ST.SELECT_WORD_PREVIOUS);
        } else {
            setKeyBinding(SWT.HOME | SWT.MOD2, ST.SELECT_LINE_START);
            setKeyBinding(SWT.END | SWT.MOD2, ST.SELECT_LINE_END);
            setKeyBinding(SWT.HOME | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_START);
            setKeyBinding(SWT.END | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_END);
            setKeyBinding(nextKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_WORD_NEXT);
            setKeyBinding(previousKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_WORD_PREVIOUS);
        }
        setKeyBinding(SWT.PAGE_UP | SWT.MOD2, ST.SELECT_PAGE_UP);
        setKeyBinding(SWT.PAGE_DOWN | SWT.MOD2, ST.SELECT_PAGE_DOWN);
        setKeyBinding(SWT.PAGE_UP | SWT.MOD1 | SWT.MOD2, ST.SELECT_WINDOW_START);
        setKeyBinding(SWT.PAGE_DOWN | SWT.MOD1 | SWT.MOD2, ST.SELECT_WINDOW_END);
        setKeyBinding(nextKey | SWT.MOD2, ST.SELECT_COLUMN_NEXT);
        setKeyBinding(previousKey | SWT.MOD2, ST.SELECT_COLUMN_PREVIOUS);

        // Modification
        // Cut, Copy, Paste
        setKeyBinding('X' | SWT.MOD1, ST.CUT);
        setKeyBinding('C' | SWT.MOD1, ST.COPY);
        setKeyBinding('V' | SWT.MOD1, ST.PASTE);
        if (IS_MAC) {
            setKeyBinding(SWT.DEL | SWT.MOD2, ST.DELETE_NEXT);
            setKeyBinding(SWT.BS | SWT.MOD3, ST.DELETE_WORD_PREVIOUS);
            setKeyBinding(SWT.DEL | SWT.MOD3, ST.DELETE_WORD_NEXT);
        } else {
            // Cut, Copy, Paste Wordstar style
            setKeyBinding(SWT.DEL | SWT.MOD2, ST.CUT);
            setKeyBinding(SWT.INSERT | SWT.MOD1, ST.COPY);
            setKeyBinding(SWT.INSERT | SWT.MOD2, ST.PASTE);
        }
        setKeyBinding(SWT.BS | SWT.MOD2, ST.DELETE_PREVIOUS);
        setKeyBinding(SWT.BS, ST.DELETE_PREVIOUS);
        setKeyBinding(SWT.DEL, ST.DELETE_NEXT);
        setKeyBinding(SWT.BS | SWT.MOD1, ST.DELETE_WORD_PREVIOUS);
        setKeyBinding(SWT.DEL | SWT.MOD1, ST.DELETE_WORD_NEXT);

        // Miscellaneous
        setKeyBinding(SWT.INSERT, ST.TOGGLE_OVERWRITE);
    }

    /**
     * Create the bitmaps to use for the caret in bidi mode.  This
     * method only needs to be called upon widget creation and when the
     * font changes (the caret bitmap height needs to match font height).
     */
    void createCaretBitmaps() {
        int caretWidth = BIDI_CARET_WIDTH;
        Display display = getDisplay();
        if (leftCaretBitmap != null) {
            if (defaultCaret != null && leftCaretBitmap.equals(defaultCaret.getImage())) {
                defaultCaret.setImage(null);
            }
            leftCaretBitmap.dispose();
        }
        int lineHeight = renderer.getLineHeight();
        leftCaretBitmap = new Image(display, caretWidth, lineHeight);
        GC gc = new GC(leftCaretBitmap);
        gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
        gc.fillRectangle(0, 0, caretWidth, lineHeight);
        gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
        gc.drawLine(0, 0, 0, lineHeight);
        gc.drawLine(0, 0, caretWidth - 1, 0);
        gc.drawLine(0, 1, 1, 1);
        gc.dispose();

        if (rightCaretBitmap != null) {
            if (defaultCaret != null && rightCaretBitmap.equals(defaultCaret.getImage())) {
                defaultCaret.setImage(null);
            }
            rightCaretBitmap.dispose();
        }
        rightCaretBitmap = new Image(display, caretWidth, lineHeight);
        gc = new GC(rightCaretBitmap);
        gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
        gc.fillRectangle(0, 0, caretWidth, lineHeight);
        gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
        gc.drawLine(caretWidth - 1, 0, caretWidth - 1, lineHeight);
        gc.drawLine(0, 0, caretWidth - 1, 0);
        gc.drawLine(caretWidth - 1, 1, 1, 1);
        gc.dispose();
    }

    /**
     * Moves the selected text to the clipboard.  The text will be put in the
     * clipboard in plain text format and RTF format.
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void cut() {
        checkWidget();
        // Abort cut operation if copy to clipboard fails.
        // Fixes bug 21030.
        if (copySelection(DND.CLIPBOARD)) {
            if (blockSelection && blockXLocation != -1) {
                insertBlockSelectionText((char) 0, SWT.NULL);
            } else {
                doDelete();
            }
        }
    }

    /**
     * A mouse move event has occurred.  See if we should start autoscrolling.  If
     * the move position is outside of the client area, initiate autoscrolling.
     * Otherwise, we've moved back into the widget so end autoscrolling.
     */
    void doAutoScroll(Event event) {
        int caretLine = getCaretLine();
        if (event.y > clientAreaHeight - bottomMargin && caretLine != content.getLineCount() - 1) {
            doAutoScroll(SWT.DOWN, event.y - (clientAreaHeight - bottomMargin));
        } else if (event.y < topMargin && caretLine != 0) {
            doAutoScroll(SWT.UP, topMargin - event.y);
        } else if (event.x < leftMargin && !wordWrap) {
            doAutoScroll(ST.COLUMN_PREVIOUS, leftMargin - event.x);
        } else if (event.x > clientAreaWidth - rightMargin && !wordWrap) {
            doAutoScroll(ST.COLUMN_NEXT, event.x - (clientAreaWidth - rightMargin));
        } else {
            endAutoScroll();
        }
    }

    /**
     * Initiates autoscrolling.
     *
     * @param direction SWT.UP, SWT.DOWN, SWT.COLUMN_NEXT, SWT.COLUMN_PREVIOUS
     */
    void doAutoScroll(int direction, int distance) {
        autoScrollDistance = distance;
        // If we're already autoscrolling in the given direction do nothing
        if (autoScrollDirection == direction) {
            return;
        }

        Runnable timer = null;
        final Display display = getDisplay();
        // Set a timer that will simulate the user pressing and holding
        // down a cursor key (i.e., arrowUp, arrowDown).
        if (direction == SWT.UP) {
            timer = new Runnable() {
                @Override
                public void run() {
                    /* Bug 437357 - NPE in StyledText.getCaretLine
                     * StyledText.content is null at times, probably because the
                     * widget itself has been disposed.
                     */
                    if (isDisposed())
                        return;
                    if (autoScrollDirection == SWT.UP) {
                        if (blockSelection) {
                            int verticalScrollOffset = getVerticalScrollOffset();
                            int y = blockYLocation - verticalScrollOffset;
                            int pixels = Math.max(-autoScrollDistance, -verticalScrollOffset);
                            if (pixels != 0) {
                                setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y + pixels,
                                        true);
                                scrollVertical(pixels, true);
                            }
                        } else {
                            doSelectionPageUp(autoScrollDistance);
                        }
                        display.timerExec(V_SCROLL_RATE, this);
                    }
                }
            };
            autoScrollDirection = direction;
            display.timerExec(V_SCROLL_RATE, timer);
        } else if (direction == SWT.DOWN) {
            timer = new Runnable() {
                @Override
                public void run() {
                    /* Bug 437357 - NPE in StyledText.getCaretLine
                     * StyledText.content is null at times, probably because the
                     * widget itself has been disposed.
                     */
                    if (isDisposed())
                        return;
                    if (autoScrollDirection == SWT.DOWN) {
                        if (blockSelection) {
                            int verticalScrollOffset = getVerticalScrollOffset();
                            int y = blockYLocation - verticalScrollOffset;
                            int max = renderer.getHeight() - verticalScrollOffset - clientAreaHeight;
                            int pixels = Math.min(autoScrollDistance, Math.max(0, max));
                            if (pixels != 0) {
                                setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y + pixels,
                                        true);
                                scrollVertical(pixels, true);
                            }
                        } else {
                            doSelectionPageDown(autoScrollDistance);
                        }
                        display.timerExec(V_SCROLL_RATE, this);
                    }
                }
            };
            autoScrollDirection = direction;
            display.timerExec(V_SCROLL_RATE, timer);
        } else if (direction == ST.COLUMN_NEXT) {
            timer = new Runnable() {
                @Override
                public void run() {
                    /* Bug 437357 - NPE in StyledText.getCaretLine
                     * StyledText.content is null at times, probably because the
                     * widget itself has been disposed.
                     */
                    if (isDisposed())
                        return;
                    if (autoScrollDirection == ST.COLUMN_NEXT) {
                        if (blockSelection) {
                            int x = blockXLocation - horizontalScrollOffset;
                            int max = renderer.getWidth() - horizontalScrollOffset - clientAreaWidth;
                            int pixels = Math.min(autoScrollDistance, Math.max(0, max));
                            if (pixels != 0) {
                                setBlockSelectionLocation(x + pixels, blockYLocation - getVerticalScrollOffset(),
                                        true);
                                scrollHorizontal(pixels, true);
                            }
                        } else {
                            doVisualNext();
                            setMouseWordSelectionAnchor();
                            doMouseSelection();
                        }
                        display.timerExec(H_SCROLL_RATE, this);
                    }
                }
            };
            autoScrollDirection = direction;
            display.timerExec(H_SCROLL_RATE, timer);
        } else if (direction == ST.COLUMN_PREVIOUS) {
            timer = new Runnable() {
                @Override
                public void run() {
                    /* Bug 437357 - NPE in StyledText.getCaretLine
                     * StyledText.content is null at times, probably because the
                     * widget itself has been disposed.
                     */
                    if (isDisposed())
                        return;
                    if (autoScrollDirection == ST.COLUMN_PREVIOUS) {
                        if (blockSelection) {
                            int x = blockXLocation - horizontalScrollOffset;
                            int pixels = Math.max(-autoScrollDistance, -horizontalScrollOffset);
                            if (pixels != 0) {
                                setBlockSelectionLocation(x + pixels, blockYLocation - getVerticalScrollOffset(),
                                        true);
                                scrollHorizontal(pixels, true);
                            }
                        } else {
                            doVisualPrevious();
                            setMouseWordSelectionAnchor();
                            doMouseSelection();
                        }
                        display.timerExec(H_SCROLL_RATE, this);
                    }
                }
            };
            autoScrollDirection = direction;
            display.timerExec(H_SCROLL_RATE, timer);
        }
    }

    /**
     * Deletes the previous character. Delete the selected text if any.
     * Move the caret in front of the deleted text.
     */
    void doBackspace() {
        Event event = new Event();
        event.text = "";
        if (selection.x != selection.y) {
            event.start = selection.x;
            event.end = selection.y;
            sendKeyEvent(event);
        } else if (caretOffset > 0) {
            int lineIndex = content.getLineAtOffset(caretOffset);
            int lineOffset = content.getOffsetAtLine(lineIndex);
            if (caretOffset == lineOffset) {
                lineOffset = content.getOffsetAtLine(lineIndex - 1);
                event.start = lineOffset + content.getLine(lineIndex - 1).length();
                event.end = caretOffset;
            } else {
                boolean isSurrogate = false;
                String lineText = content.getLine(lineIndex);
                char ch = lineText.charAt(caretOffset - lineOffset - 1);
                if (0xDC00 <= ch && ch <= 0xDFFF) {
                    if (caretOffset - lineOffset - 2 >= 0) {
                        ch = lineText.charAt(caretOffset - lineOffset - 2);
                        isSurrogate = 0xD800 <= ch && ch <= 0xDBFF;
                    }
                }
                TextLayout layout = renderer.getTextLayout(lineIndex);
                int start = layout.getPreviousOffset(caretOffset - lineOffset,
                        isSurrogate ? SWT.MOVEMENT_CLUSTER : SWT.MOVEMENT_CHAR);
                renderer.disposeTextLayout(layout);
                event.start = start + lineOffset;
                event.end = caretOffset;
            }
            sendKeyEvent(event);
        }
    }

    void doBlockColumn(boolean next) {
        if (blockXLocation == -1)
            setBlockSelectionOffset(caretOffset, false);
        int x = blockXLocation - horizontalScrollOffset;
        int y = blockYLocation - getVerticalScrollOffset();
        int[] trailing = new int[1];
        int offset = getOffsetAtPoint(x, y, trailing, true);
        if (offset != -1) {
            offset += trailing[0];
            int lineIndex = content.getLineAtOffset(offset);
            int newOffset;
            if (next) {
                newOffset = getClusterNext(offset, lineIndex);
            } else {
                newOffset = getClusterPrevious(offset, lineIndex);
            }
            offset = newOffset != offset ? newOffset : -1;
        }
        if (offset != -1) {
            setBlockSelectionOffset(offset, true);
            showCaret();
        } else {
            int width = next ? renderer.averageCharWidth : -renderer.averageCharWidth;
            int maxWidth = Math.max(clientAreaWidth - rightMargin - leftMargin, renderer.getWidth());
            x = Math.max(0, Math.min(blockXLocation + width, maxWidth)) - horizontalScrollOffset;
            setBlockSelectionLocation(x, y, true);
            Rectangle rect = new Rectangle(x, y, 0, 0);
            showLocation(rect, true);
        }
    }

    void doBlockContentStartEnd(boolean end) {
        if (blockXLocation == -1)
            setBlockSelectionOffset(caretOffset, false);
        int offset = end ? content.getCharCount() : 0;
        setBlockSelectionOffset(offset, true);
        showCaret();
    }

    void doBlockWord(boolean next) {
        if (blockXLocation == -1)
            setBlockSelectionOffset(caretOffset, false);
        int x = blockXLocation - horizontalScrollOffset;
        int y = blockYLocation - getVerticalScrollOffset();
        int[] trailing = new int[1];
        int offset = getOffsetAtPoint(x, y, trailing, true);
        if (offset != -1) {
            offset += trailing[0];
            int lineIndex = content.getLineAtOffset(offset);
            int lineOffset = content.getOffsetAtLine(lineIndex);
            String lineText = content.getLine(lineIndex);
            int lineLength = lineText.length();
            int newOffset = offset;
            if (next) {
                if (offset < lineOffset + lineLength) {
                    newOffset = getWordNext(offset, SWT.MOVEMENT_WORD);
                }
            } else {
                if (offset > lineOffset) {
                    newOffset = getWordPrevious(offset, SWT.MOVEMENT_WORD);
                }
            }
            offset = newOffset != offset ? newOffset : -1;
        }
        if (offset != -1) {
            setBlockSelectionOffset(offset, true);
            showCaret();
        } else {
            int width = (next ? renderer.averageCharWidth : -renderer.averageCharWidth) * 6;
            int maxWidth = Math.max(clientAreaWidth - rightMargin - leftMargin, renderer.getWidth());
            x = Math.max(0, Math.min(blockXLocation + width, maxWidth)) - horizontalScrollOffset;
            setBlockSelectionLocation(x, y, true);
            Rectangle rect = new Rectangle(x, y, 0, 0);
            showLocation(rect, true);
        }
    }

    void doBlockLineVertical(boolean up) {
        if (blockXLocation == -1)
            setBlockSelectionOffset(caretOffset, false);
        int y = blockYLocation - getVerticalScrollOffset();
        int lineIndex = getLineIndex(y);
        if (up) {
            if (lineIndex > 0) {
                y = getLinePixel(lineIndex - 1);
                setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y, true);
                if (y < topMargin) {
                    scrollVertical(y - topMargin, true);
                }
            }
        } else {
            int lineCount = content.getLineCount();
            if (lineIndex + 1 < lineCount) {
                y = getLinePixel(lineIndex + 2) - 1;
                setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y, true);
                int bottom = clientAreaHeight - bottomMargin;
                if (y > bottom) {
                    scrollVertical(y - bottom, true);
                }
            }
        }
    }

    void doBlockLineHorizontal(boolean end) {
        if (blockXLocation == -1)
            setBlockSelectionOffset(caretOffset, false);
        int x = blockXLocation - horizontalScrollOffset;
        int y = blockYLocation - getVerticalScrollOffset();
        int lineIndex = getLineIndex(y);
        int lineOffset = content.getOffsetAtLine(lineIndex);
        String lineText = content.getLine(lineIndex);
        int lineLength = lineText.length();
        int[] trailing = new int[1];
        int offset = getOffsetAtPoint(x, y, trailing, true);
        if (offset != -1) {
            offset += trailing[0];
            int newOffset = offset;
            if (end) {
                if (offset < lineOffset + lineLength) {
                    newOffset = lineOffset + lineLength;
                }
            } else {
                if (offset > lineOffset) {
                    newOffset = lineOffset;
                }
            }
            offset = newOffset != offset ? newOffset : -1;
        } else {
            if (!end)
                offset = lineOffset + lineLength;
        }
        if (offset != -1) {
            setBlockSelectionOffset(offset, true);
            showCaret();
        } else {
            int maxWidth = Math.max(clientAreaWidth - rightMargin - leftMargin, renderer.getWidth());
            x = (end ? maxWidth : 0) - horizontalScrollOffset;
            setBlockSelectionLocation(x, y, true);
            Rectangle rect = new Rectangle(x, y, 0, 0);
            showLocation(rect, true);
        }
    }

    void doBlockSelection(boolean sendEvent) {
        if (caretOffset > selectionAnchor) {
            selection.x = selectionAnchor;
            selection.y = caretOffset;
        } else {
            selection.x = caretOffset;
            selection.y = selectionAnchor;
        }
        updateCaretVisibility();
        setCaretLocation();
        super.redraw();
        if (sendEvent) {
            sendSelectionEvent();
        }
        sendAccessibleTextCaretMoved();
    }

    /**
     * Replaces the selection with the character or insert the character at the
     * current caret position if no selection exists.
     * <p>
     * If a carriage return was typed replace it with the line break character
     * used by the widget on this platform.
     * </p>
     *
     * @param key the character typed by the user
     */
    void doContent(char key) {
        if (blockSelection && blockXLocation != -1) {
            insertBlockSelectionText(key, SWT.NULL);
            return;
        }

        Event event = new Event();
        event.start = selection.x;
        event.end = selection.y;
        // replace a CR line break with the widget line break
        // CR does not make sense on Windows since most (all?) applications
        // don't recognize CR as a line break.
        if (key == SWT.CR || key == SWT.LF) {
            if (!isSingleLine()) {
                event.text = getLineDelimiter();
            }
        } else if (selection.x == selection.y && overwrite && key != TAB) {
            // no selection and overwrite mode is on and the typed key is not a
            // tab character (tabs are always inserted without overwriting)?
            int lineIndex = content.getLineAtOffset(event.end);
            int lineOffset = content.getOffsetAtLine(lineIndex);
            String line = content.getLine(lineIndex);
            // replace character at caret offset if the caret is not at the
            // end of the line
            if (event.end < lineOffset + line.length()) {
                event.end++;
            }
            event.text = new String(new char[] { key });
        } else {
            event.text = new String(new char[] { key });
        }
        if (event.text != null) {
            if (textLimit > 0 && content.getCharCount() - (event.end - event.start) >= textLimit) {
                return;
            }
            sendKeyEvent(event);
        }
    }

    /**
     * Moves the caret after the last character of the widget content.
     */
    void doContentEnd() {
        // place caret at end of first line if receiver is in single
        // line mode. fixes 4820.
        if (isSingleLine()) {
            doLineEnd();
        } else {
            int length = content.getCharCount();
            setCaretOffset(length, SWT.DEFAULT);
            showCaret();
        }
    }

    /**
     * Moves the caret in front of the first character of the widget content.
     */
    void doContentStart() {
        setCaretOffset(0, SWT.DEFAULT);
        showCaret();
    }

    /**
     * Moves the caret to the start of the selection if a selection exists.
     * Otherwise, if no selection exists move the cursor according to the
     * cursor selection rules.
     *
     * @see #doSelectionCursorPrevious
     */
    void doCursorPrevious() {
        if (selection.y - selection.x > 0) {
            setCaretOffset(selection.x, OFFSET_LEADING);
            showCaret();
        } else {
            doSelectionCursorPrevious();
        }
    }

    /**
     * Moves the caret to the end of the selection if a selection exists.
     * Otherwise, if no selection exists move the cursor according to the
     * cursor selection rules.
     *
     * @see #doSelectionCursorNext
     */
    void doCursorNext() {
        if (selection.y - selection.x > 0) {
            setCaretOffset(selection.y, PREVIOUS_OFFSET_TRAILING);
            showCaret();
        } else {
            doSelectionCursorNext();
        }
    }

    /**
     * Deletes the next character. Delete the selected text if any.
     */
    void doDelete() {
        Event event = new Event();
        event.text = "";
        if (selection.x != selection.y) {
            event.start = selection.x;
            event.end = selection.y;
            sendKeyEvent(event);
        } else if (caretOffset < content.getCharCount()) {
            int line = content.getLineAtOffset(caretOffset);
            int lineOffset = content.getOffsetAtLine(line);
            int lineLength = content.getLine(line).length();
            if (caretOffset == lineOffset + lineLength) {
                event.start = caretOffset;
                event.end = content.getOffsetAtLine(line + 1);
            } else {
                event.start = caretOffset;
                event.end = getClusterNext(caretOffset, line);
            }
            sendKeyEvent(event);
        }
    }

    /**
     * Deletes the next word.
     */
    void doDeleteWordNext() {
        if (selection.x != selection.y) {
            // if a selection exists, treat the as if
            // only the delete key was pressed
            doDelete();
        } else {
            Event event = new Event();
            event.text = "";
            event.start = caretOffset;
            event.end = getWordNext(caretOffset, SWT.MOVEMENT_WORD);
            sendKeyEvent(event);
        }
    }

    /**
     * Deletes the previous word.
     */
    void doDeleteWordPrevious() {
        if (selection.x != selection.y) {
            // if a selection exists, treat as if
            // only the backspace key was pressed
            doBackspace();
        } else {
            Event event = new Event();
            event.text = "";
            event.start = getWordPrevious(caretOffset, SWT.MOVEMENT_WORD);
            event.end = caretOffset;
            sendKeyEvent(event);
        }
    }

    /**
     * Moves the caret one line down and to the same character offset relative
     * to the beginning of the line. Move the caret to the end of the new line
     * if the new line is shorter than the character offset. Moves the caret to
     * the end of the text if the caret already is on the last line.
     */
    void doLineDown(boolean select) {
        int caretLine = getCaretLine();
        int lineCount = content.getLineCount();
        int y = 0;
        boolean lastLine = false;
        if (isWordWrap()) {
            int lineOffset = content.getOffsetAtLine(caretLine);
            int offsetInLine = caretOffset - lineOffset;
            TextLayout layout = renderer.getTextLayout(caretLine);
            int lineIndex = getVisualLineIndex(layout, offsetInLine);
            int layoutLineCount = layout.getLineCount();
            if (lineIndex == layoutLineCount - 1) {
                lastLine = caretLine == lineCount - 1;
                caretLine++;
            } else {
                y = layout.getLineBounds(lineIndex + 1).y;
                y++; // bug 485722: workaround for fractional line heights
            }
            renderer.disposeTextLayout(layout);
        } else {
            lastLine = caretLine == lineCount - 1;
            caretLine++;
        }
        if (lastLine) {
            setCaretOffset(content.getCharCount(), SWT.DEFAULT);
        } else {
            int[] alignment = new int[1];
            int offset = getOffsetAtPoint(columnX, y, caretLine, alignment);
            setCaretOffset(offset, alignment[0]);
        }
        int oldColumnX = columnX;
        int oldHScrollOffset = horizontalScrollOffset;
        if (select) {
            setMouseWordSelectionAnchor();
            // select first and then scroll to reduce flash when key
            // repeat scrolls lots of lines
            doSelection(ST.COLUMN_NEXT);
        }
        showCaret();
        int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
        columnX = oldColumnX + hScrollChange;
    }

    /**
     * Moves the caret to the end of the line.
     */
    void doLineEnd() {
        int caretLine = getCaretLine();
        int lineOffset = content.getOffsetAtLine(caretLine);
        int lineEndOffset;
        if (isWordWrap()) {
            TextLayout layout = renderer.getTextLayout(caretLine);
            int offsetInLine = caretOffset - lineOffset;
            int lineIndex = getVisualLineIndex(layout, offsetInLine);
            int[] offsets = layout.getLineOffsets();
            lineEndOffset = lineOffset + offsets[lineIndex + 1];
            renderer.disposeTextLayout(layout);
        } else {
            int lineLength = content.getLine(caretLine).length();
            lineEndOffset = lineOffset + lineLength;
        }
        setCaretOffset(lineEndOffset, PREVIOUS_OFFSET_TRAILING);
        showCaret();
    }

    /**
     * Moves the caret to the beginning of the line.
     */
    void doLineStart() {
        int caretLine = getCaretLine();
        int lineOffset = content.getOffsetAtLine(caretLine);
        if (isWordWrap()) {
            TextLayout layout = renderer.getTextLayout(caretLine);
            int offsetInLine = caretOffset - lineOffset;
            int lineIndex = getVisualLineIndex(layout, offsetInLine);
            int[] offsets = layout.getLineOffsets();
            lineOffset += offsets[lineIndex];
            renderer.disposeTextLayout(layout);
        }
        setCaretOffset(lineOffset, OFFSET_LEADING);
        showCaret();
    }

    /**
     * Moves the caret one line up and to the same character offset relative
     * to the beginning of the line. Move the caret to the end of the new line
     * if the new line is shorter than the character offset. Moves the caret to
     * the beginning of the document if it is already on the first line.
     */
    void doLineUp(boolean select) {
        int caretLine = getCaretLine(), y = 0;
        boolean firstLine = false;
        if (isWordWrap()) {
            int lineOffset = content.getOffsetAtLine(caretLine);
            int offsetInLine = caretOffset - lineOffset;
            TextLayout layout = renderer.getTextLayout(caretLine);
            int lineIndex = getVisualLineIndex(layout, offsetInLine);
            if (lineIndex == 0) {
                firstLine = caretLine == 0;
                if (!firstLine) {
                    caretLine--;
                    y = renderer.getLineHeight(caretLine) - 1;
                    y--; // bug 485722: workaround for fractional line heights
                }
            } else {
                y = layout.getLineBounds(lineIndex - 1).y;
                y++; // bug 485722: workaround for fractional line heights
            }
            renderer.disposeTextLayout(layout);
        } else {
            firstLine = caretLine == 0;
            caretLine--;
        }
        if (firstLine) {
            setCaretOffset(0, SWT.DEFAULT);
        } else {
            int[] alignment = new int[1];
            int offset = getOffsetAtPoint(columnX, y, caretLine, alignment);
            setCaretOffset(offset, alignment[0]);
        }
        int oldColumnX = columnX;
        int oldHScrollOffset = horizontalScrollOffset;
        if (select)
            setMouseWordSelectionAnchor();
        showCaret();
        if (select)
            doSelection(ST.COLUMN_PREVIOUS);
        int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
        columnX = oldColumnX + hScrollChange;
    }

    void doMouseLinkCursor() {
        Display display = getDisplay();
        Point point = display.getCursorLocation();
        point = display.map(null, this, point);
        doMouseLinkCursor(point.x, point.y);
    }

    void doMouseLinkCursor(int x, int y) {
        int offset = getOffsetAtPoint(x, y, null, true);
        Display display = getDisplay();
        Cursor newCursor = cursor;
        if (renderer.hasLink(offset)) {
            newCursor = display.getSystemCursor(SWT.CURSOR_HAND);
        } else {
            if (cursor == null) {
                int type = blockSelection ? SWT.CURSOR_CROSS : SWT.CURSOR_IBEAM;
                newCursor = display.getSystemCursor(type);
            }
        }
        if (newCursor != getCursor())
            super.setCursor(newCursor);
    }

    /**
     * Moves the caret to the specified location.
     *
     * @param x x location of the new caret position
     * @param y y location of the new caret position
     * @param select the location change is a selection operation.
     *    include the line delimiter in the selection
     */
    void doMouseLocationChange(int x, int y, boolean select) {
        int line = getLineIndex(y);

        updateCaretDirection = true;

        if (blockSelection) {
            x = Math.max(leftMargin, Math.min(x, clientAreaWidth - rightMargin));
            y = Math.max(topMargin, Math.min(y, clientAreaHeight - bottomMargin));
            if (doubleClickEnabled && clickCount > 1) {
                boolean wordSelect = (clickCount & 1) == 0;
                if (wordSelect) {
                    Point left = getPointAtOffset(doubleClickSelection.x);
                    int[] trailing = new int[1];
                    int offset = getOffsetAtPoint(x, y, trailing, true);
                    if (offset != -1) {
                        if (x > left.x) {
                            offset = getWordNext(offset + trailing[0], SWT.MOVEMENT_WORD_END);
                            setBlockSelectionOffset(doubleClickSelection.x, offset, true);
                        } else {
                            offset = getWordPrevious(offset + trailing[0], SWT.MOVEMENT_WORD_START);
                            setBlockSelectionOffset(doubleClickSelection.y, offset, true);
                        }
                    } else {
                        if (x > left.x) {
                            setBlockSelectionLocation(left.x, left.y, x, y, true);
                        } else {
                            Point right = getPointAtOffset(doubleClickSelection.y);
                            setBlockSelectionLocation(right.x, right.y, x, y, true);
                        }
                    }
                } else {
                    setBlockSelectionLocation(blockXLocation, y, true);
                }
                return;
            } else {
                if (select) {
                    if (blockXLocation == -1) {
                        setBlockSelectionOffset(caretOffset, false);
                    }
                } else {
                    clearBlockSelection(true, false);
                }
                int[] trailing = new int[1];
                int offset = getOffsetAtPoint(x, y, trailing, true);
                if (offset != -1) {
                    if (select) {
                        setBlockSelectionOffset(offset + trailing[0], true);
                        return;
                    }
                } else {
                    if (isFixedLineHeight() && renderer.fixedPitch) {
                        int avg = renderer.averageCharWidth;
                        x = ((x + avg / 2 - leftMargin + horizontalScrollOffset) / avg * avg) + leftMargin
                                - horizontalScrollOffset;
                    }
                    setBlockSelectionLocation(x, y, true);
                    return;
                }
            }
        }

        // allow caret to be placed below first line only if receiver is
        // not in single line mode. fixes 4820.
        if (line < 0 || (isSingleLine() && line > 0)) {
            return;
        }
        int[] alignment = new int[1];
        int newCaretOffset = getOffsetAtPoint(x, y, alignment);
        int newCaretAlignemnt = alignment[0];

        if (doubleClickEnabled && clickCount > 1) {
            newCaretOffset = doMouseWordSelect(x, newCaretOffset, line);
        }

        int newCaretLine = content.getLineAtOffset(newCaretOffset);

        // Is the mouse within the left client area border or on
        // a different line? If not the autoscroll selection
        // could be incorrectly reset. Fixes 1GKM3XS
        boolean vchange = 0 <= y && y < clientAreaHeight || newCaretLine == 0
                || newCaretLine == content.getLineCount() - 1;
        boolean hchange = 0 <= x && x < clientAreaWidth || wordWrap
                || newCaretLine != content.getLineAtOffset(caretOffset);
        if (vchange && hchange && (newCaretOffset != caretOffset || newCaretAlignemnt != caretAlignment)) {
            setCaretOffset(newCaretOffset, newCaretAlignemnt);
            if (select)
                doMouseSelection();
            showCaret();
        }
        if (!select) {
            setCaretOffset(newCaretOffset, newCaretAlignemnt);
            clearSelection(true);
        }
    }

    /**
     * Updates the selection based on the caret position
     */
    void doMouseSelection() {
        if (caretOffset <= selection.x
                || (caretOffset > selection.x && caretOffset < selection.y && selectionAnchor == selection.x)) {
            doSelection(ST.COLUMN_PREVIOUS);
        } else {
            doSelection(ST.COLUMN_NEXT);
        }
    }

    /**
     * Returns the offset of the word at the specified offset.
     * If the current selection extends from high index to low index
     * (i.e., right to left, or caret is at left border of selection on
     * non-bidi platforms) the start offset of the word preceding the
     * selection is returned. If the current selection extends from
     * low index to high index the end offset of the word following
     * the selection is returned.
     *
     * @param x mouse x location
     * @param newCaretOffset caret offset of the mouse cursor location
     * @param line line index of the mouse cursor location
     */
    int doMouseWordSelect(int x, int newCaretOffset, int line) {
        // flip selection anchor based on word selection direction from
        // base double click. Always do this here (and don't rely on doAutoScroll)
        // because auto scroll only does not cover all possible mouse selections
        // (e.g., mouse x < 0 && mouse y > caret line y)
        if (newCaretOffset < selectionAnchor && selectionAnchor == selection.x) {
            selectionAnchor = doubleClickSelection.y;
        } else if (newCaretOffset > selectionAnchor && selectionAnchor == selection.y) {
            selectionAnchor = doubleClickSelection.x;
        }
        if (0 <= x && x < clientAreaWidth) {
            boolean wordSelect = (clickCount & 1) == 0;
            if (caretOffset == selection.x) {
                if (wordSelect) {
                    newCaretOffset = getWordPrevious(newCaretOffset, SWT.MOVEMENT_WORD_START);
                } else {
                    newCaretOffset = content.getOffsetAtLine(line);
                }
            } else {
                if (wordSelect) {
                    newCaretOffset = getWordNext(newCaretOffset, SWT.MOVEMENT_WORD_END);
                } else {
                    int lineEnd = content.getCharCount();
                    if (line + 1 < content.getLineCount()) {
                        lineEnd = content.getOffsetAtLine(line + 1);
                    }
                    newCaretOffset = lineEnd;
                }
            }
        }
        return newCaretOffset;
    }

    /**
     * Scrolls one page down so that the last line (truncated or whole)
     * of the current page becomes the fully visible top line.
     * <p>
     * The caret is scrolled the same number of lines so that its location
     * relative to the top line remains the same. The exception is the end
     * of the text where a full page scroll is not possible. In this case
     * the caret is moved after the last character.
     * </p>
     *
     * @param select whether or not to select the page
     */
    void doPageDown(boolean select, int height) {
        if (isSingleLine())
            return;
        int oldColumnX = columnX;
        int oldHScrollOffset = horizontalScrollOffset;
        if (isFixedLineHeight()) {
            int lineCount = content.getLineCount();
            int caretLine = getCaretLine();
            if (caretLine < lineCount - 1) {
                int lineHeight = renderer.getLineHeight();
                int lines = (height == -1 ? clientAreaHeight : height) / lineHeight;
                int scrollLines = Math.min(lineCount - caretLine - 1, lines);
                // ensure that scrollLines never gets negative and at least one
                // line is scrolled. fixes bug 5602.
                scrollLines = Math.max(1, scrollLines);
                int[] alignment = new int[1];
                int offset = getOffsetAtPoint(columnX, getLinePixel(caretLine + scrollLines), alignment);
                setCaretOffset(offset, alignment[0]);
                if (select) {
                    doSelection(ST.COLUMN_NEXT);
                }
                // scroll one page down or to the bottom
                int verticalMaximum = lineCount * getVerticalIncrement();
                int pageSize = clientAreaHeight;
                int verticalScrollOffset = getVerticalScrollOffset();
                int scrollOffset = verticalScrollOffset + scrollLines * getVerticalIncrement();
                if (scrollOffset + pageSize > verticalMaximum) {
                    scrollOffset = verticalMaximum - pageSize;
                }
                if (scrollOffset > verticalScrollOffset) {
                    scrollVertical(scrollOffset - verticalScrollOffset, true);
                }
            }
        } else {
            int lineCount = content.getLineCount();
            int caretLine = getCaretLine();
            int lineIndex, lineHeight;
            if (height == -1) {
                lineIndex = getPartialBottomIndex();
                int topY = getLinePixel(lineIndex);
                lineHeight = renderer.getLineHeight(lineIndex);
                height = topY;
                if (topY + lineHeight <= clientAreaHeight) {
                    height += lineHeight;
                } else {
                    if (isWordWrap()) {
                        TextLayout layout = renderer.getTextLayout(lineIndex);
                        int y = clientAreaHeight - topY;
                        for (int i = 0; i < layout.getLineCount(); i++) {
                            Rectangle bounds = layout.getLineBounds(i);
                            if (bounds.contains(bounds.x, y)) {
                                height += bounds.y;
                                break;
                            }
                        }
                        renderer.disposeTextLayout(layout);
                    }
                }
            } else {
                lineIndex = getLineIndex(height);
                int topLineY = getLinePixel(lineIndex);
                if (isWordWrap()) {
                    TextLayout layout = renderer.getTextLayout(lineIndex);
                    int y = height - topLineY;
                    for (int i = 0; i < layout.getLineCount(); i++) {
                        Rectangle bounds = layout.getLineBounds(i);
                        if (bounds.contains(bounds.x, y)) {
                            height = topLineY + bounds.y + bounds.height;
                            break;
                        }
                    }
                    renderer.disposeTextLayout(layout);
                } else {
                    height = topLineY + renderer.getLineHeight(lineIndex);
                }
            }
            int caretHeight = height;
            if (isWordWrap()) {
                TextLayout layout = renderer.getTextLayout(caretLine);
                int offsetInLine = caretOffset - content.getOffsetAtLine(caretLine);
                lineIndex = getVisualLineIndex(layout, offsetInLine);
                caretHeight += layout.getLineBounds(lineIndex).y;
                renderer.disposeTextLayout(layout);
            }
            lineIndex = caretLine;
            lineHeight = renderer.getLineHeight(lineIndex);
            while (caretHeight - lineHeight >= 0 && lineIndex < lineCount - 1) {
                caretHeight -= lineHeight;
                lineHeight = renderer.getLineHeight(++lineIndex);
            }
            int[] alignment = new int[1];
            int offset = getOffsetAtPoint(columnX, caretHeight, lineIndex, alignment);
            setCaretOffset(offset, alignment[0]);
            if (select)
                doSelection(ST.COLUMN_NEXT);
            height = getAvailableHeightBellow(height);
            scrollVertical(height, true);
            if (height == 0)
                setCaretLocation();
        }
        showCaret();
        int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
        columnX = oldColumnX + hScrollChange;
    }

    /**
     * Moves the cursor to the end of the last fully visible line.
     */
    void doPageEnd() {
        // go to end of line if in single line mode. fixes 5673
        if (isSingleLine()) {
            doLineEnd();
        } else {
            int bottomOffset;
            if (isWordWrap()) {
                int lineIndex = getPartialBottomIndex();
                TextLayout layout = renderer.getTextLayout(lineIndex);
                int y = (clientAreaHeight - bottomMargin) - getLinePixel(lineIndex);
                int index = layout.getLineCount() - 1;
                while (index >= 0) {
                    Rectangle bounds = layout.getLineBounds(index);
                    if (y >= bounds.y + bounds.height)
                        break;
                    index--;
                }
                if (index == -1 && lineIndex > 0) {
                    bottomOffset = content.getOffsetAtLine(lineIndex - 1) + content.getLine(lineIndex - 1).length();
                } else {
                    bottomOffset = content.getOffsetAtLine(lineIndex)
                            + Math.max(0, layout.getLineOffsets()[index + 1] - 1);
                }
                renderer.disposeTextLayout(layout);
            } else {
                int lineIndex = getBottomIndex();
                bottomOffset = content.getOffsetAtLine(lineIndex) + content.getLine(lineIndex).length();
            }
            if (caretOffset < bottomOffset) {
                setCaretOffset(bottomOffset, OFFSET_LEADING);
                showCaret();
            }
        }
    }

    /**
     * Moves the cursor to the beginning of the first fully visible line.
     */
    void doPageStart() {
        int topOffset;
        if (isWordWrap()) {
            int y, lineIndex;
            if (topIndexY > 0) {
                lineIndex = topIndex - 1;
                y = renderer.getLineHeight(lineIndex) - topIndexY;
            } else {
                lineIndex = topIndex;
                y = -topIndexY;
            }
            TextLayout layout = renderer.getTextLayout(lineIndex);
            int index = 0;
            int lineCount = layout.getLineCount();
            while (index < lineCount) {
                Rectangle bounds = layout.getLineBounds(index);
                if (y <= bounds.y)
                    break;
                index++;
            }
            if (index == lineCount) {
                topOffset = content.getOffsetAtLine(lineIndex + 1);
            } else {
                topOffset = content.getOffsetAtLine(lineIndex) + layout.getLineOffsets()[index];
            }
            renderer.disposeTextLayout(layout);
        } else {
            topOffset = content.getOffsetAtLine(topIndex);
        }
        if (caretOffset > topOffset) {
            setCaretOffset(topOffset, OFFSET_LEADING);
            showCaret();
        }
    }

    /**
     * Scrolls one page up so that the first line (truncated or whole)
     * of the current page becomes the fully visible last line.
     * The caret is scrolled the same number of lines so that its location
     * relative to the top line remains the same. The exception is the beginning
     * of the text where a full page scroll is not possible. In this case the
     * caret is moved in front of the first character.
     */
    void doPageUp(boolean select, int height) {
        if (isSingleLine())
            return;
        int oldHScrollOffset = horizontalScrollOffset;
        int oldColumnX = columnX;
        if (isFixedLineHeight()) {
            int caretLine = getCaretLine();
            if (caretLine > 0) {
                int lineHeight = renderer.getLineHeight();
                int lines = (height == -1 ? clientAreaHeight : height) / lineHeight;
                int scrollLines = Math.max(1, Math.min(caretLine, lines));
                caretLine -= scrollLines;
                int[] alignment = new int[1];
                int offset = getOffsetAtPoint(columnX, getLinePixel(caretLine), alignment);
                setCaretOffset(offset, alignment[0]);
                if (select) {
                    doSelection(ST.COLUMN_PREVIOUS);
                }
                int verticalScrollOffset = getVerticalScrollOffset();
                int scrollOffset = Math.max(0, verticalScrollOffset - scrollLines * getVerticalIncrement());
                if (scrollOffset < verticalScrollOffset) {
                    scrollVertical(scrollOffset - verticalScrollOffset, true);
                }
            }
        } else {
            int caretLine = getCaretLine();
            int lineHeight, lineIndex;
            if (height == -1) {
                if (topIndexY == 0) {
                    height = clientAreaHeight;
                } else {
                    int y;
                    if (topIndex > 0) {
                        lineIndex = topIndex - 1;
                        lineHeight = renderer.getLineHeight(lineIndex);
                        height = clientAreaHeight - topIndexY;
                        y = lineHeight - topIndexY;
                    } else {
                        lineIndex = topIndex;
                        lineHeight = renderer.getLineHeight(lineIndex);
                        height = clientAreaHeight - (lineHeight + topIndexY);
                        y = -topIndexY;
                    }
                    if (isWordWrap()) {
                        TextLayout layout = renderer.getTextLayout(lineIndex);
                        for (int i = 0; i < layout.getLineCount(); i++) {
                            Rectangle bounds = layout.getLineBounds(i);
                            if (bounds.contains(bounds.x, y)) {
                                height += lineHeight - (bounds.y + bounds.height);
                                break;
                            }
                        }
                        renderer.disposeTextLayout(layout);
                    }
                }
            } else {
                lineIndex = getLineIndex(clientAreaHeight - height);
                int topLineY = getLinePixel(lineIndex);
                if (isWordWrap()) {
                    TextLayout layout = renderer.getTextLayout(lineIndex);
                    int y = topLineY;
                    for (int i = 0; i < layout.getLineCount(); i++) {
                        Rectangle bounds = layout.getLineBounds(i);
                        if (bounds.contains(bounds.x, y)) {
                            height = clientAreaHeight - (topLineY + bounds.y);
                            break;
                        }
                    }
                    renderer.disposeTextLayout(layout);
                } else {
                    height = clientAreaHeight - topLineY;
                }
            }
            int caretHeight = height;
            if (isWordWrap()) {
                TextLayout layout = renderer.getTextLayout(caretLine);
                int offsetInLine = caretOffset - content.getOffsetAtLine(caretLine);
                lineIndex = getVisualLineIndex(layout, offsetInLine);
                caretHeight += layout.getBounds().height - layout.getLineBounds(lineIndex).y;
                renderer.disposeTextLayout(layout);
            }
            lineIndex = caretLine;
            lineHeight = renderer.getLineHeight(lineIndex);
            while (caretHeight - lineHeight >= 0 && lineIndex > 0) {
                caretHeight -= lineHeight;
                lineHeight = renderer.getLineHeight(--lineIndex);
            }
            lineHeight = renderer.getLineHeight(lineIndex);
            int[] alignment = new int[1];
            int offset = getOffsetAtPoint(columnX, lineHeight - caretHeight, lineIndex, alignment);
            setCaretOffset(offset, alignment[0]);
            if (select)
                doSelection(ST.COLUMN_PREVIOUS);
            height = getAvailableHeightAbove(height);
            scrollVertical(-height, true);
            if (height == 0)
                setCaretLocation();
        }
        showCaret();
        int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
        columnX = oldColumnX + hScrollChange;
    }

    /**
     * Updates the selection to extend to the current caret position.
     */
    void doSelection(int direction) {
        int redrawStart = -1;
        int redrawEnd = -1;
        if (selectionAnchor == -1) {
            selectionAnchor = selection.x;
        }
        if (direction == ST.COLUMN_PREVIOUS) {
            if (caretOffset < selection.x) {
                // grow selection
                redrawEnd = selection.x;
                redrawStart = selection.x = caretOffset;
                // check if selection has reversed direction
                if (selection.y != selectionAnchor) {
                    redrawEnd = selection.y;
                    selection.y = selectionAnchor;
                }
                // test whether selection actually changed. Fixes 1G71EO1
            } else if (selectionAnchor == selection.x && caretOffset < selection.y) {
                // caret moved towards selection anchor (left side of selection).
                // shrink selection
                redrawEnd = selection.y;
                redrawStart = selection.y = caretOffset;
            }
        } else {
            if (caretOffset > selection.y) {
                // grow selection
                redrawStart = selection.y;
                redrawEnd = selection.y = caretOffset;
                // check if selection has reversed direction
                if (selection.x != selectionAnchor) {
                    redrawStart = selection.x;
                    selection.x = selectionAnchor;
                }
                // test whether selection actually changed. Fixes 1G71EO1
            } else if (selectionAnchor == selection.y && caretOffset > selection.x) {
                // caret moved towards selection anchor (right side of selection).
                // shrink selection
                redrawStart = selection.x;
                redrawEnd = selection.x = caretOffset;
            }
        }
        if (redrawStart != -1 && redrawEnd != -1) {
            internalRedrawRange(redrawStart, redrawEnd - redrawStart);
            sendSelectionEvent();
        }
        sendAccessibleTextCaretMoved();
    }

    /**
     * Moves the caret to the next character or to the beginning of the
     * next line if the cursor is at the end of a line.
     */
    void doSelectionCursorNext() {
        int caretLine = getCaretLine();
        int lineOffset = content.getOffsetAtLine(caretLine);
        int offsetInLine = caretOffset - lineOffset;
        int offset, alignment;
        if (offsetInLine < content.getLine(caretLine).length()) {
            TextLayout layout = renderer.getTextLayout(caretLine);
            offsetInLine = layout.getNextOffset(offsetInLine, SWT.MOVEMENT_CLUSTER);
            int lineStart = layout.getLineOffsets()[layout.getLineIndex(offsetInLine)];
            renderer.disposeTextLayout(layout);
            offset = offsetInLine + lineOffset;
            alignment = offsetInLine == lineStart ? OFFSET_LEADING : PREVIOUS_OFFSET_TRAILING;
            setCaretOffset(offset, alignment);
            showCaret();
        } else if (caretLine < content.getLineCount() - 1 && !isSingleLine()) {
            caretLine++;
            offset = content.getOffsetAtLine(caretLine);
            alignment = PREVIOUS_OFFSET_TRAILING;
            setCaretOffset(offset, alignment);
            showCaret();
        }
    }

    /**
     * Moves the caret to the previous character or to the end of the previous
     * line if the cursor is at the beginning of a line.
     */
    void doSelectionCursorPrevious() {
        int caretLine = getCaretLine();
        int lineOffset = content.getOffsetAtLine(caretLine);
        int offsetInLine = caretOffset - lineOffset;
        if (offsetInLine > 0) {
            int offset = getClusterPrevious(caretOffset, caretLine);
            setCaretOffset(offset, OFFSET_LEADING);
            showCaret();
        } else if (caretLine > 0) {
            caretLine--;
            lineOffset = content.getOffsetAtLine(caretLine);
            int offset = lineOffset + content.getLine(caretLine).length();
            setCaretOffset(offset, OFFSET_LEADING);
            showCaret();
        }
    }

    /**
     * Moves the caret one line down and to the same character offset relative
     * to the beginning of the line. Moves the caret to the end of the new line
     * if the new line is shorter than the character offset.
     * Moves the caret to the end of the text if the caret already is on the
     * last line.
     * Adjusts the selection according to the caret change. This can either add
     * to or subtract from the old selection, depending on the previous selection
     * direction.
     */
    void doSelectionLineDown() {
        int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
        doLineDown(true);
        columnX = oldColumnX;
    }

    /**
     * Moves the caret one line up and to the same character offset relative
     * to the beginning of the line. Moves the caret to the end of the new line
     * if the new line is shorter than the character offset.
     * Moves the caret to the beginning of the document if it is already on the
     * first line.
     * Adjusts the selection according to the caret change. This can either add
     * to or subtract from the old selection, depending on the previous selection
     * direction.
     */
    void doSelectionLineUp() {
        int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
        doLineUp(true);
        columnX = oldColumnX;
    }

    /**
     * Scrolls one page down so that the last line (truncated or whole)
     * of the current page becomes the fully visible top line.
     * <p>
     * The caret is scrolled the same number of lines so that its location
     * relative to the top line remains the same. The exception is the end
     * of the text where a full page scroll is not possible. In this case
     * the caret is moved after the last character.
     * <p></p>
     * Adjusts the selection according to the caret change. This can either add
     * to or subtract from the old selection, depending on the previous selection
     * direction.
     * </p>
     */
    void doSelectionPageDown(int pixels) {
        int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
        doPageDown(true, pixels);
        columnX = oldColumnX;
    }

    /**
     * Scrolls one page up so that the first line (truncated or whole)
     * of the current page becomes the fully visible last line.
     * <p>
     * The caret is scrolled the same number of lines so that its location
     * relative to the top line remains the same. The exception is the beginning
     * of the text where a full page scroll is not possible. In this case the
     * caret is moved in front of the first character.
     * </p><p>
     * Adjusts the selection according to the caret change. This can either add
     * to or subtract from the old selection, depending on the previous selection
     * direction.
     * </p>
     */
    void doSelectionPageUp(int pixels) {
        int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
        doPageUp(true, pixels);
        columnX = oldColumnX;
    }

    /**
     * Moves the caret to the end of the next word .
     */
    void doSelectionWordNext() {
        int offset = getWordNext(caretOffset, SWT.MOVEMENT_WORD);
        // don't change caret position if in single line mode and the cursor
        // would be on a different line. fixes 5673
        if (!isSingleLine() || content.getLineAtOffset(caretOffset) == content.getLineAtOffset(offset)) {
            // Force symmetrical movement for word next and previous. Fixes 14536
            setCaretOffset(offset, OFFSET_LEADING);
            showCaret();
        }
    }

    /**
     * Moves the caret to the start of the previous word.
     */
    void doSelectionWordPrevious() {
        int offset = getWordPrevious(caretOffset, SWT.MOVEMENT_WORD);
        setCaretOffset(offset, OFFSET_LEADING);
        showCaret();
    }

    /**
     * Moves the caret one character to the left.  Do not go to the previous line.
     * When in a bidi locale and at a R2L character the caret is moved to the
     * beginning of the R2L segment (visually right) and then one character to the
     * left (visually left because it's now in a L2R segment).
     */
    void doVisualPrevious() {
        int offset = getClusterPrevious(caretOffset, getCaretLine());
        setCaretOffset(offset, SWT.DEFAULT);
        showCaret();
    }

    /**
     * Moves the caret one character to the right.  Do not go to the next line.
     * When in a bidi locale and at a R2L character the caret is moved to the
     * end of the R2L segment (visually left) and then one character to the
     * right (visually right because it's now in a L2R segment).
     */
    void doVisualNext() {
        int offset = getClusterNext(caretOffset, getCaretLine());
        setCaretOffset(offset, SWT.DEFAULT);
        showCaret();
    }

    /**
     * Moves the caret to the end of the next word.
     * If a selection exists, move the caret to the end of the selection
     * and remove the selection.
     */
    void doWordNext() {
        if (selection.y - selection.x > 0) {
            setCaretOffset(selection.y, SWT.DEFAULT);
            showCaret();
        } else {
            doSelectionWordNext();
        }
    }

    /**
     * Moves the caret to the start of the previous word.
     * If a selection exists, move the caret to the start of the selection
     * and remove the selection.
     */
    void doWordPrevious() {
        if (selection.y - selection.x > 0) {
            setCaretOffset(selection.x, SWT.DEFAULT);
            showCaret();
        } else {
            doSelectionWordPrevious();
        }
    }

    /**
     * Ends the autoscroll process.
     */
    void endAutoScroll() {
        autoScrollDirection = SWT.NULL;
    }

    @Override
    public Color getBackground() {
        checkWidget();
        if (background == null) {
            return getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND);
        }
        return background;
    }

    /**
     * Returns the baseline, in points.
     *
     * Note: this API should not be used if a StyleRange attribute causes lines to
     * have different heights (i.e. different fonts, rise, etc).
     *
     * @return baseline the baseline
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @since 3.0
     *
     * @see #getBaseline(int)
     */
    public int getBaseline() {
        checkWidget();
        return renderer.getBaseline();
    }

    /**
     * Returns the baseline at the given offset, in points.
     *
     * @param offset the offset
     *
     * @return baseline the baseline
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_RANGE when the offset is outside the valid range (&lt; 0 or &gt; getCharCount())</li>
     * </ul>
     *
     * @since 3.2
     */
    public int getBaseline(int offset) {
        checkWidget();
        if (!(0 <= offset && offset <= content.getCharCount())) {
            SWT.error(SWT.ERROR_INVALID_RANGE);
        }
        if (isFixedLineHeight()) {
            return renderer.getBaseline();
        }
        int lineIndex = content.getLineAtOffset(offset);
        int lineOffset = content.getOffsetAtLine(lineIndex);
        TextLayout layout = renderer.getTextLayout(lineIndex);
        int lineInParagraph = layout.getLineIndex(Math.min(offset - lineOffset, layout.getText().length()));
        FontMetrics metrics = layout.getLineMetrics(lineInParagraph);
        renderer.disposeTextLayout(layout);
        return metrics.getAscent() + metrics.getLeading();
    }

    /**
     * Gets the BIDI coloring mode.  When true the BIDI text display
     * algorithm is applied to segments of text that are the same
     * color.
     *
     * @return the current coloring mode
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @deprecated use BidiSegmentListener instead.
     */
    @Deprecated
    public boolean getBidiColoring() {
        checkWidget();
        return bidiColoring;
    }

    /**
     * Returns whether the widget is in block selection mode.
     *
     * @return true if widget is in block selection mode, false otherwise
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.5
     */
    public boolean getBlockSelection() {
        checkWidget();
        return blockSelection;
    }

    Rectangle getBlockSelectionPosition() {
        int firstLine = getLineIndex(blockYAnchor - getVerticalScrollOffset());
        int lastLine = getLineIndex(blockYLocation - getVerticalScrollOffset());
        if (firstLine > lastLine) {
            int temp = firstLine;
            firstLine = lastLine;
            lastLine = temp;
        }
        int left = blockXAnchor;
        int right = blockXLocation;
        if (left > right) {
            left = blockXLocation;
            right = blockXAnchor;
        }
        return new Rectangle(left - horizontalScrollOffset, firstLine, right - horizontalScrollOffset, lastLine);
    }

    /**
     * Returns the block selection bounds. The bounds is
     * relative to the upper left corner of the document.
     *
     * @return the block selection bounds
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.5
     */
    public Rectangle getBlockSelectionBounds() {
        Rectangle rect;
        if (blockSelection && blockXLocation != -1) {
            rect = getBlockSelectionRectangle();
        } else {
            Point startPoint = getPointAtOffset(selection.x);
            Point endPoint = getPointAtOffset(selection.y);
            int height = getLineHeight(selection.y);
            rect = new Rectangle(startPoint.x, startPoint.y, endPoint.x - startPoint.x,
                    endPoint.y + height - startPoint.y);
            if (selection.x == selection.y) {
                rect.width = getCaretWidth();
            }
        }
        rect.x += horizontalScrollOffset;
        rect.y += getVerticalScrollOffset();
        return rect;
    }

    Rectangle getBlockSelectionRectangle() {
        Rectangle rect = getBlockSelectionPosition();
        rect.y = getLinePixel(rect.y);
        rect.width = rect.width - rect.x;
        rect.height = getLinePixel(rect.height + 1) - rect.y;
        return rect;
    }

    String getBlockSelectionText(String delimiter) {
        Rectangle rect = getBlockSelectionPosition();
        int firstLine = rect.y;
        int lastLine = rect.height;
        int left = rect.x;
        int right = rect.width;
        StringBuilder buffer = new StringBuilder();
        for (int lineIndex = firstLine; lineIndex <= lastLine; lineIndex++) {
            int start = getOffsetAtPoint(left, 0, lineIndex, null);
            int end = getOffsetAtPoint(right, 0, lineIndex, null);
            if (start > end) {
                int temp = start;
                start = end;
                end = temp;
            }
            String text = content.getTextRange(start, end - start);
            buffer.append(text);
            if (lineIndex < lastLine)
                buffer.append(delimiter);
        }
        return buffer.toString();
    }

    /**
     * Returns the index of the last fully visible line.
     *
     * @return index of the last fully visible line.
     */
    int getBottomIndex() {
        int bottomIndex;
        if (isFixedLineHeight()) {
            int lineCount = 1;
            int lineHeight = renderer.getLineHeight();
            if (lineHeight != 0) {
                // calculate the number of lines that are fully visible
                int partialTopLineHeight = topIndex * lineHeight - getVerticalScrollOffset();
                lineCount = (clientAreaHeight - partialTopLineHeight) / lineHeight;
            }
            bottomIndex = Math.min(content.getLineCount() - 1, topIndex + Math.max(0, lineCount - 1));
        } else {
            int clientAreaHeight = this.clientAreaHeight - bottomMargin;
            bottomIndex = getLineIndex(clientAreaHeight);
            if (bottomIndex > 0) {
                int linePixel = getLinePixel(bottomIndex);
                int lineHeight = renderer.getLineHeight(bottomIndex);
                if (linePixel + lineHeight > clientAreaHeight) {
                    if (getLinePixel(bottomIndex - 1) >= topMargin) {
                        bottomIndex--;
                    }
                }
            }
        }
        return bottomIndex;
    }

    /**
     * Returns the bottom margin.
     *
     * @return the bottom margin.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.5
     */
    public int getBottomMargin() {
        checkWidget();
        return bottomMargin;
    }

    Rectangle getBoundsAtOffset(int offset) {
        int lineIndex = content.getLineAtOffset(offset);
        int lineOffset = content.getOffsetAtLine(lineIndex);
        String line = content.getLine(lineIndex);
        Rectangle bounds;
        if (line.length() != 0) {
            TextLayout layout = renderer.getTextLayout(lineIndex);
            int offsetInLine = Math.min(layout.getText().length(), Math.max(0, offset - lineOffset));
            bounds = layout.getBounds(offsetInLine, offsetInLine);
            if (getListeners(ST.LineGetSegments).length > 0 && caretAlignment == PREVIOUS_OFFSET_TRAILING
                    && offsetInLine != 0) {
                offsetInLine = layout.getPreviousOffset(offsetInLine, SWT.MOVEMENT_CLUSTER);
                Point point = layout.getLocation(offsetInLine, true);
                bounds = new Rectangle(point.x, point.y, 0, bounds.height);
            }
            renderer.disposeTextLayout(layout);
        } else {
            bounds = new Rectangle(0, 0, 0, renderer.getLineHeight());
        }
        if (offset == caretOffset && !isWordWrap()) {
            int lineEnd = lineOffset + line.length();
            if (offset == lineEnd) {
                bounds.width += getCaretWidth();
            }
        }
        bounds.x += leftMargin - horizontalScrollOffset;
        bounds.y += getLinePixel(lineIndex);
        return bounds;
    }

    /**
     * Returns the caret position relative to the start of the text.
     *
     * @return the caret position relative to the start of the text.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public int getCaretOffset() {
        checkWidget();
        return caretOffset;
    }

    /**
     * Returns the caret width.
     *
     * @return the caret width, 0 if caret is null.
     */
    int getCaretWidth() {
        Caret caret = getCaret();
        if (caret == null)
            return 0;
        return caret.getSize().x;
    }

    Object getClipboardContent(int clipboardType) {
        TextTransfer plainTextTransfer = TextTransfer.getInstance();
        return clipboard.getContents(plainTextTransfer, clipboardType);
    }

    int getClusterNext(int offset, int lineIndex) {
        int lineOffset = content.getOffsetAtLine(lineIndex);
        TextLayout layout = renderer.getTextLayout(lineIndex);
        offset -= lineOffset;
        offset = layout.getNextOffset(offset, SWT.MOVEMENT_CLUSTER);
        offset += lineOffset;
        renderer.disposeTextLayout(layout);
        return offset;
    }

    int getClusterPrevious(int offset, int lineIndex) {
        int lineOffset = content.getOffsetAtLine(lineIndex);
        TextLayout layout = renderer.getTextLayout(lineIndex);
        offset -= lineOffset;
        offset = layout.getPreviousOffset(offset, SWT.MOVEMENT_CLUSTER);
        offset += lineOffset;
        renderer.disposeTextLayout(layout);
        return offset;
    }

    /**
     * Returns the content implementation that is used for text storage.
     *
     * @return content the user defined content implementation that is used for
     * text storage or the default content implementation if no user defined
     * content implementation has been set.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public StyledTextContent getContent() {
        checkWidget();
        return content;
    }

    @Override
    public boolean getDragDetect() {
        checkWidget();
        return dragDetect;
    }

    /**
     * Returns whether the widget implements double click mouse behavior.
     *
     * @return true if double clicking a word selects the word, false if double clicks
     * have the same effect as regular mouse clicks
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public boolean getDoubleClickEnabled() {
        checkWidget();
        return doubleClickEnabled;
    }

    /**
     * Returns whether the widget content can be edited.
     *
     * @return true if content can be edited, false otherwise
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public boolean getEditable() {
        checkWidget();
        return editable;
    }

    @Override
    public Color getForeground() {
        checkWidget();
        if (foreground == null) {
            return getDisplay().getSystemColor(SWT.COLOR_LIST_FOREGROUND);
        }
        return foreground;
    }

    /**
     * Returns the horizontal scroll increment.
     *
     * @return horizontal scroll increment.
     */
    int getHorizontalIncrement() {
        return renderer.averageCharWidth;
    }

    /**
     * Returns the horizontal scroll offset relative to the start of the line.
     *
     * @return horizontal scroll offset relative to the start of the line,
     * measured in character increments starting at 0, if &gt; 0 the content is scrolled
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public int getHorizontalIndex() {
        checkWidget();
        return horizontalScrollOffset / getHorizontalIncrement();
    }

    /**
     * Returns the horizontal scroll offset relative to the start of the line.
     *
     * @return the horizontal scroll offset relative to the start of the line,
     * measured in SWT logical point starting at 0, if &gt; 0 the content is scrolled.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public int getHorizontalPixel() {
        checkWidget();
        return horizontalScrollOffset;
    }

    /**
     * Returns the line indentation of the widget.
     *
     * @return the line indentation
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see #getLineIndent(int)
     *
     * @since 3.2
     */
    public int getIndent() {
        checkWidget();
        return indent;
    }

    /**
     * Returns whether the widget justifies lines.
     *
     * @return whether lines are justified
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see #getLineJustify(int)
     *
     * @since 3.2
     */
    public boolean getJustify() {
        checkWidget();
        return justify;
    }

    /**
     * Returns the action assigned to the key.
     * Returns SWT.NULL if there is no action associated with the key.
     *
     * @param key a key code defined in SWT.java or a character.
     *    Optionally ORd with a state mask.  Preferred state masks are one or more of
     *  SWT.MOD1, SWT.MOD2, SWT.MOD3, since these masks account for modifier platform
     *  differences.  However, there may be cases where using the specific state masks
     *  (i.e., SWT.CTRL, SWT.SHIFT, SWT.ALT, SWT.COMMAND) makes sense.
     * @return one of the predefined actions defined in ST.java or SWT.NULL
     *    if there is no action associated with the key.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public int getKeyBinding(int key) {
        checkWidget();
        Integer action = keyActionMap.get(key);
        return action == null ? SWT.NULL : action.intValue();
    }

    /**
     * Gets the number of characters.
     *
     * @return number of characters in the widget
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public int getCharCount() {
        checkWidget();
        return content.getCharCount();
    }

    /**
     * Returns the line at the given line index without delimiters.
     * Index 0 is the first line of the content. When there are not
     * any lines, getLine(0) is a valid call that answers an empty string.
     * <p>
     *
     * @param lineIndex index of the line to return.
     * @return the line text without delimiters
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_RANGE when the line index is outside the valid range (&lt; 0 or &gt;= getLineCount())</li>
     * </ul>
     * @since 3.4
     */
    public String getLine(int lineIndex) {
        checkWidget();
        if (lineIndex < 0 || (lineIndex > 0 && lineIndex >= content.getLineCount())) {
            SWT.error(SWT.ERROR_INVALID_RANGE);
        }
        return content.getLine(lineIndex);
    }

    /**
     * Returns the alignment of the line at the given index.
     *
     * @param index the index of the line
     *
     * @return the line alignment
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
     * </ul>
     *
     * @see #getAlignment()
     *
     * @since 3.2
     */
    public int getLineAlignment(int index) {
        checkWidget();
        if (index < 0 || index > content.getLineCount()) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        return renderer.getLineAlignment(index, alignment);
    }

    /**
     * Returns the line at the specified offset in the text
     * where 0 &lt; offset &lt; getCharCount() so that getLineAtOffset(getCharCount())
     * returns the line of the insert location.
     *
     * @param offset offset relative to the start of the content.
     *    0 &lt;= offset &lt;= getCharCount()
     * @return line at the specified offset in the text
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_RANGE when the offset is outside the valid range (&lt; 0 or &gt; getCharCount())</li>
     * </ul>
     */
    public int getLineAtOffset(int offset) {
        checkWidget();
        if (offset < 0 || offset > getCharCount()) {
            SWT.error(SWT.ERROR_INVALID_RANGE);
        }
        return content.getLineAtOffset(offset);
    }

    /**
     * Returns the background color of the line at the given index.
     * Returns null if a LineBackgroundListener has been set or if no background
     * color has been specified for the line. Should not be called if a
     * LineBackgroundListener has been set since the listener maintains the
     * line background colors.
     *
     * @param index the index of the line
     * @return the background color of the line at the given index.
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
     * </ul>
     */
    public Color getLineBackground(int index) {
        checkWidget();
        if (index < 0 || index > content.getLineCount()) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        return isListening(ST.LineGetBackground) ? null : renderer.getLineBackground(index, null);
    }

    /**
     * Returns the bullet of the line at the given index.
     *
     * @param index the index of the line
     *
     * @return the line bullet
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
     * </ul>
     *
     * @since 3.2
     */
    public Bullet getLineBullet(int index) {
        checkWidget();
        if (index < 0 || index > content.getLineCount()) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        return isListening(ST.LineGetStyle) ? null : renderer.getLineBullet(index, null);
    }

    /**
     * Returns the line background data for the given line or null if
     * there is none.
     *
     * @param lineOffset offset of the line start relative to the start
     *    of the content.
     * @param line line to get line background data for
     * @return line background data for the given line.
     */
    StyledTextEvent getLineBackgroundData(int lineOffset, String line) {
        return sendLineEvent(ST.LineGetBackground, lineOffset, line);
    }

    /**
     * Gets the number of text lines.
     *
     * @return the number of lines in the widget
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public int getLineCount() {
        checkWidget();
        return content.getLineCount();
    }

    /**
     * Returns the number of lines that can be completely displayed in the
     * widget client area.
     *
     * @return number of lines that can be completely displayed in the widget
     *    client area.
     */
    int getLineCountWhole() {
        if (isFixedLineHeight()) {
            int lineHeight = renderer.getLineHeight();
            return lineHeight != 0 ? clientAreaHeight / lineHeight : 1;
        }
        return getBottomIndex() - topIndex + 1;
    }

    /**
     * Returns the line delimiter used for entering new lines by key down
     * or paste operation.
     *
     * @return line delimiter used for entering new lines by key down
     * or paste operation.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public String getLineDelimiter() {
        checkWidget();
        return content.getLineDelimiter();
    }

    /**
     * Returns the line height.
     * <p>
     * Note: this API should not be used if a StyleRange attribute causes lines to
     * have different heights (i.e. different fonts, rise, etc).
     * </p>
     *
     * @return line height in points.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @see #getLineHeight(int)
     */
    public int getLineHeight() {
        checkWidget();
        return renderer.getLineHeight();
    }

    /**
     * Returns the line height at the given offset.
     *
     * @param offset the offset
     *
     * @return line height in points
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_RANGE when the offset is outside the valid range (&lt; 0 or &gt; getCharCount())</li>
     * </ul>
     *
     * @since 3.2
     */
    public int getLineHeight(int offset) {
        checkWidget();
        if (!(0 <= offset && offset <= content.getCharCount())) {
            SWT.error(SWT.ERROR_INVALID_RANGE);
        }
        if (isFixedLineHeight()) {
            return renderer.getLineHeight();
        }
        int lineIndex = content.getLineAtOffset(offset);
        int lineOffset = content.getOffsetAtLine(lineIndex);
        TextLayout layout = renderer.getTextLayout(lineIndex);
        int lineInParagraph = layout.getLineIndex(Math.min(offset - lineOffset, layout.getText().length()));
        int height = layout.getLineBounds(lineInParagraph).height;
        renderer.disposeTextLayout(layout);
        return height;
    }

    /**
     * Returns the indentation of the line at the given index.
     *
     * @param index the index of the line
     *
     * @return the line indentation
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
     * </ul>
     *
     * @see #getIndent()
     *
     * @since 3.2
     */
    public int getLineIndent(int index) {
        checkWidget();
        if (index < 0 || index > content.getLineCount()) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        return isListening(ST.LineGetStyle) ? 0 : renderer.getLineIndent(index, indent);
    }

    /**
     * Returns the vertical indentation of the line at the given index.
     *
     * @param index the index of the line
     *
     * @return the line vertical indentation
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
     * </ul>
     *
     * @since 3.109
     */
    public int getLineVerticalIndent(int index) {
        checkWidget();
        if (index < 0 || index >= content.getLineCount()) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        return isListening(ST.LineGetStyle) ? 0 : renderer.getLineVerticalIndent(index);
    }

    /**
     * Returns whether the line at the given index is justified.
     *
     * @param index the index of the line
     *
     * @return whether the line is justified
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
     * </ul>
     *
     * @see #getJustify()
     *
     * @since 3.2
     */
    public boolean getLineJustify(int index) {
        checkWidget();
        if (index < 0 || index > content.getLineCount()) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        return isListening(ST.LineGetStyle) ? false : renderer.getLineJustify(index, justify);
    }

    /**
     * Returns the line spacing of the widget.
     *
     * @return the line spacing
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.2
     */
    public int getLineSpacing() {
        checkWidget();
        return lineSpacing;
    }

    /**
     * Returns the line style data for the given line or null if there is
     * none.
     * <p>
     * If there is a LineStyleListener but it does not set any styles,
     * the StyledTextEvent.styles field will be initialized to an empty
     * array.
     * </p>
     *
     * @param lineOffset offset of the line start relative to the start of
     *    the content.
     * @param line line to get line styles for
     * @return line style data for the given line. Styles may start before
     *    line start and end after line end
     */
    StyledTextEvent getLineStyleData(int lineOffset, String line) {
        return sendLineEvent(ST.LineGetStyle, lineOffset, line);
    }

    /**
     * Returns the top SWT logical point, relative to the client area, of a given line.
     * Clamps out of ranges index.
     *
     * @param lineIndex the line index, the max value is lineCount. If
     * lineIndex == lineCount it returns the bottom SWT logical point of the last line.
     * It means this function can be used to retrieve the bottom SWT logical point of any line.
     *
     * @return the top SWT logical point of a given line index
     *
     * @since 3.2
     */
    public int getLinePixel(int lineIndex) {
        checkWidget();
        int lineCount = content.getLineCount();
        lineIndex = Math.max(0, Math.min(lineCount, lineIndex));
        if (isFixedLineHeight()) {
            int lineHeight = renderer.getLineHeight();
            return lineIndex * lineHeight - getVerticalScrollOffset() + topMargin;
        }
        if (lineIndex == topIndex)
            return topIndexY + topMargin;
        int height = topIndexY;
        if (lineIndex > topIndex) {
            for (int i = topIndex; i < lineIndex; i++) {
                height += renderer.getLineHeight(i);
            }
        } else {
            for (int i = topIndex - 1; i >= lineIndex; i--) {
                height -= renderer.getLineHeight(i);
            }
        }
        return height + topMargin;
    }

    /**
     * Returns the line index for a y, relative to the client area.
     * The line index returned is always in the range 0..lineCount - 1.
     *
     * @param y the y-coordinate point
     *
     * @return the line index for a given y-coordinate point
     *
     * @since 3.2
     */
    public int getLineIndex(int y) {
        checkWidget();
        y -= topMargin;
        if (isFixedLineHeight()) {
            int lineHeight = renderer.getLineHeight();
            int lineIndex = (y + getVerticalScrollOffset()) / lineHeight;
            int lineCount = content.getLineCount();
            lineIndex = Math.max(0, Math.min(lineCount - 1, lineIndex));
            return lineIndex;
        }
        if (y == topIndexY)
            return topIndex;
        int line = topIndex;
        if (y < topIndexY) {
            while (y < topIndexY && line > 0) {
                y += renderer.getLineHeight(--line);
            }
        } else {
            int lineCount = content.getLineCount();
            int lineHeight = renderer.getLineHeight(line);
            while (y - lineHeight >= topIndexY && line < lineCount - 1) {
                y -= lineHeight;
                lineHeight = renderer.getLineHeight(++line);
            }
        }
        return line;
    }

    /**
     * Returns the tab stops of the line at the given <code>index</code>.
     *
     * @param index the index of the line
     *
     * @return the tab stops for the line
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
     * </ul>
     *
     * @see #getTabStops()
     *
     * @since 3.6
     */
    public int[] getLineTabStops(int index) {
        checkWidget();
        if (index < 0 || index > content.getLineCount()) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        if (isListening(ST.LineGetStyle))
            return null;
        int[] tabs = renderer.getLineTabStops(index, null);
        if (tabs == null)
            tabs = this.tabs;
        if (tabs == null)
            return new int[] { renderer.tabWidth };
        int[] result = new int[tabs.length];
        System.arraycopy(tabs, 0, result, 0, tabs.length);
        return result;
    }

    /**
     * Returns the wrap indentation of the line at the given <code>index</code>.
     *
     * @param index the index of the line
     *
     * @return the wrap indentation
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
     * </ul>
     *
     * @see #getWrapIndent()
     *
     * @since 3.6
     */
    public int getLineWrapIndent(int index) {
        checkWidget();
        if (index < 0 || index > content.getLineCount()) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        return isListening(ST.LineGetStyle) ? 0 : renderer.getLineWrapIndent(index, wrapIndent);
    }

    /**
     * Returns the left margin.
     *
     * @return the left margin.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.5
     */
    public int getLeftMargin() {
        checkWidget();
        return leftMargin - alignmentMargin;
    }

    /**
     * Returns the x, y location of the upper left corner of the character
     * bounding box at the specified offset in the text. The point is
     * relative to the upper left corner of the widget client area.
     *
     * @param offset offset relative to the start of the content.
     *    0 &lt;= offset &lt;= getCharCount()
     * @return x, y location of the upper left corner of the character
     *    bounding box at the specified offset in the text.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_RANGE when the offset is outside the valid range (&lt; 0 or &gt; getCharCount())</li>
     * </ul>
     */
    public Point getLocationAtOffset(int offset) {
        checkWidget();
        if (offset < 0 || offset > getCharCount()) {
            SWT.error(SWT.ERROR_INVALID_RANGE);
        }
        return getPointAtOffset(offset);
    }

    /**
     * Returns <code>true</code> if the mouse navigator is enabled.
     * When mouse navigator is enabled, the user can navigate through the widget by pressing the middle button and moving the cursor
     *
     * @return the mouse navigator's enabled state
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see #getEnabled
     * @since 3.110
     */
    public boolean getMouseNavigatorEnabled() {
        checkWidget();
        return mouseNavigator != null;
    }

    /**
     * Returns the character offset of the first character of the given line.
     *
     * @param lineIndex index of the line, 0 based relative to the first
     *    line in the content. 0 &lt;= lineIndex &lt; getLineCount(), except
     *    lineIndex may always be 0
     * @return offset offset of the first character of the line, relative to
     *    the beginning of the document. The first character of the document is
     *   at offset 0.
     *  When there are not any lines, getOffsetAtLine(0) is a valid call that
     *    answers 0.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_RANGE when the line index is outside the valid range (&lt; 0 or &gt;= getLineCount())</li>
     * </ul>
     * @since 2.0
     */
    public int getOffsetAtLine(int lineIndex) {
        checkWidget();
        if (lineIndex < 0 || (lineIndex > 0 && lineIndex >= content.getLineCount())) {
            SWT.error(SWT.ERROR_INVALID_RANGE);
        }
        return content.getOffsetAtLine(lineIndex);
    }

    /**
     * Returns the offset of the character at the given location relative
     * to the first character in the document.
     * <p>
     * The return value reflects the character offset that the caret will
     * be placed at if a mouse click occurred at the specified location.
     * If the x coordinate of the location is beyond the center of a character
     * the returned offset will be behind the character.
     * </p>
     *
     * @param point the origin of character bounding box relative to
     *  the origin of the widget client area.
     * @return offset of the character at the given location relative
     *  to the first character in the document.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_NULL_ARGUMENT when point is null</li>
     *   <li>ERROR_INVALID_ARGUMENT when there is no character at the specified location</li>
     * </ul>
     *
     * @deprecated Use {@link #getOffsetAtPoint(Point)} instead for better performance
     */
    @Deprecated
    public int getOffsetAtLocation(Point point) {
        checkWidget();
        if (point == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        int[] trailing = new int[1];
        int offset = getOffsetAtPoint(point.x, point.y, trailing, true);
        if (offset == -1) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        return offset + trailing[0];
    }

    /**
     * Returns the offset of the character at the given point relative
     * to the first character in the document.
     * <p>
     * The return value reflects the character offset that the caret will
     * be placed at if a mouse click occurred at the specified point.
     * If the x coordinate of the point is beyond the center of a character
     * the returned offset will be behind the character.
     * </p>
     * Note: This method is functionally similar to {@link #getOffsetAtLocation(Point)} except that
     * it does not throw an exception when no character is found and thus performs faster.
     *
     * @param point the origin of character bounding box relative to
     *  the origin of the widget client area.
     * @return offset of the character at the given point relative
     *  to the first character in the document.
     * -1 when there is no character at the specified location.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_NULL_ARGUMENT when point is <code>null</code></li>
     * </ul>
     *
     * @since 3.107
     */
    public int getOffsetAtPoint(Point point) {
        checkWidget();
        if (point == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        int[] trailing = new int[1];
        int offset = getOffsetAtPoint(point.x, point.y, trailing, true);
        return offset != -1 ? offset + trailing[0] : -1;
    }

    int getOffsetAtPoint(int x, int y, int[] alignment) {
        int lineIndex = getLineIndex(y);
        y -= getLinePixel(lineIndex);
        return getOffsetAtPoint(x, y, lineIndex, alignment);
    }

    int getOffsetAtPoint(int x, int y, int lineIndex, int[] alignment) {
        TextLayout layout = renderer.getTextLayout(lineIndex);
        x += horizontalScrollOffset - leftMargin;
        int[] trailing = new int[1];
        int offsetInLine = layout.getOffset(x, y, trailing);
        if (alignment != null)
            alignment[0] = OFFSET_LEADING;
        if (trailing[0] != 0) {
            int lineInParagraph = layout.getLineIndex(offsetInLine + trailing[0]);
            int lineStart = layout.getLineOffsets()[lineInParagraph];
            if (offsetInLine + trailing[0] == lineStart) {
                offsetInLine += trailing[0];
                if (alignment != null)
                    alignment[0] = PREVIOUS_OFFSET_TRAILING;
            } else {
                String line = content.getLine(lineIndex);
                int level = 0;
                if (alignment != null) {
                    int offset = offsetInLine;
                    while (offset > 0 && Character.isDigit(line.charAt(offset)))
                        offset--;
                    if (offset == 0 && Character.isDigit(line.charAt(offset))) {
                        level = isMirrored() ? 1 : 0;
                    } else {
                        level = layout.getLevel(offset) & 0x1;
                    }
                }
                offsetInLine += trailing[0];
                if (alignment != null) {
                    int trailingLevel = layout.getLevel(offsetInLine) & 0x1;
                    if ((level ^ trailingLevel) != 0) {
                        alignment[0] = PREVIOUS_OFFSET_TRAILING;
                    } else {
                        alignment[0] = OFFSET_LEADING;
                    }
                }
            }
        }
        renderer.disposeTextLayout(layout);
        return offsetInLine + content.getOffsetAtLine(lineIndex);
    }

    int getOffsetAtPoint(int x, int y, int[] trailing, boolean inTextOnly) {
        if (inTextOnly && y + getVerticalScrollOffset() < 0 || x + horizontalScrollOffset < 0) {
            return -1;
        }
        int bottomIndex = getPartialBottomIndex();
        int height = getLinePixel(bottomIndex + 1);
        if (inTextOnly && y > height) {
            return -1;
        }
        int lineIndex = getLineIndex(y);
        int lineOffset = content.getOffsetAtLine(lineIndex);
        TextLayout layout = renderer.getTextLayout(lineIndex);
        x += horizontalScrollOffset - leftMargin;
        y -= getLinePixel(lineIndex);
        int offset = layout.getOffset(x, y, trailing);
        Rectangle rect = layout.getLineBounds(layout.getLineIndex(offset));
        renderer.disposeTextLayout(layout);
        if (inTextOnly && !(rect.x <= x && x <= rect.x + rect.width)) {
            return -1;
        }
        return offset + lineOffset;
    }

    /**
     * Returns the orientation of the receiver.
     *
     * @return the orientation style
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 2.1.2
     */
    @Override
    public int getOrientation() {
        return super.getOrientation();
    }

    /**
     * Returns the index of the last partially visible line.
     *
     * @return index of the last partially visible line.
     */
    int getPartialBottomIndex() {
        if (isFixedLineHeight()) {
            int lineHeight = renderer.getLineHeight();
            int partialLineCount = Compatibility.ceil(clientAreaHeight, lineHeight);
            return Math.max(0, Math.min(content.getLineCount(), topIndex + partialLineCount) - 1);
        }
        return getLineIndex(clientAreaHeight - bottomMargin);
    }

    /**
     * Returns the index of the first partially visible line.
     *
     * @return index of the first partially visible line.
     */
    int getPartialTopIndex() {
        if (isFixedLineHeight()) {
            int lineHeight = renderer.getLineHeight();
            return getVerticalScrollOffset() / lineHeight;
        }
        return topIndexY <= 0 ? topIndex : topIndex - 1;
    }

    /**
     * Returns the content in the specified range using the platform line
     * delimiter to separate lines.
     *
     * @param writer the TextWriter to write line text into
     * @return the content in the specified range using the platform line
     *    delimiter to separate lines as written by the specified TextWriter.
     */
    String getPlatformDelimitedText(TextWriter writer) {
        int end = writer.getStart() + writer.getCharCount();
        int startLine = content.getLineAtOffset(writer.getStart());
        int endLine = content.getLineAtOffset(end);
        String endLineText = content.getLine(endLine);
        int endLineOffset = content.getOffsetAtLine(endLine);

        for (int i = startLine; i <= endLine; i++) {
            writer.writeLine(content.getLine(i), content.getOffsetAtLine(i));
            if (i < endLine) {
                writer.writeLineDelimiter(PlatformLineDelimiter);
            }
        }
        if (end > endLineOffset + endLineText.length()) {
            writer.writeLineDelimiter(PlatformLineDelimiter);
        }
        writer.close();
        return writer.toString();
    }

    /**
     * Returns all the ranges of text that have an associated StyleRange.
     * Returns an empty array if a LineStyleListener has been set.
     * Should not be called if a LineStyleListener has been set since the
     * listener maintains the styles.
     * <p>
     * The ranges array contains start and length pairs.  Each pair refers to
     * the corresponding style in the styles array.  For example, the pair
     * that starts at ranges[n] with length ranges[n+1] uses the style
     * at styles[n/2] returned by <code>getStyleRanges(int, int, boolean)</code>.
     * </p>
     *
     * @return the ranges or an empty array if a LineStyleListener has been set.
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.2
     *
     * @see #getStyleRanges(boolean)
     */
    public int[] getRanges() {
        checkWidget();
        if (!isListening(ST.LineGetStyle)) {
            int[] ranges = renderer.getRanges(0, content.getCharCount());
            if (ranges != null)
                return ranges;
        }
        return new int[0];
    }

    /**
     * Returns the ranges of text that have an associated StyleRange.
     * Returns an empty array if a LineStyleListener has been set.
     * Should not be called if a LineStyleListener has been set since the
     * listener maintains the styles.
     * <p>
     * The ranges array contains start and length pairs.  Each pair refers to
     * the corresponding style in the styles array.  For example, the pair
     * that starts at ranges[n] with length ranges[n+1] uses the style
     * at styles[n/2] returned by <code>getStyleRanges(int, int, boolean)</code>.
     * </p>
     *
     * @param start the start offset of the style ranges to return
     * @param length the number of style ranges to return
     *
     * @return the ranges or an empty array if a LineStyleListener has been set.
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_RANGE if start or length are outside the widget content</li>
     * </ul>
     *
     * @since 3.2
     *
     * @see #getStyleRanges(int, int, boolean)
     */
    public int[] getRanges(int start, int length) {
        checkWidget();
        int contentLength = getCharCount();
        int end = start + length;
        if (start > end || start < 0 || end > contentLength) {
            SWT.error(SWT.ERROR_INVALID_RANGE);
        }
        if (!isListening(ST.LineGetStyle)) {
            int[] ranges = renderer.getRanges(start, length);
            if (ranges != null)
                return ranges;
        }
        return new int[0];
    }

    /**
     * Returns the right margin.
     *
     * @return the right margin.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.5
     */
    public int getRightMargin() {
        checkWidget();
        return rightMargin;
    }

    /**
     * Returns the selection.
     * <p>
     * Text selections are specified in terms of caret positions.  In a text
     * widget that contains N characters, there are N+1 caret positions,
     * ranging from 0..N
     * </p>
     *
     * @return start and end of the selection, x is the offset of the first
     *    selected character, y is the offset after the last selected character.
     *  The selection values returned are visual (i.e., x will always always be
     *  &lt;= y).  To determine if a selection is right-to-left (RtoL) vs. left-to-right
     *  (LtoR), compare the caretOffset to the start and end of the selection
     *  (e.g., caretOffset == start of selection implies that the selection is RtoL).
     * @see #getSelectionRange
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public Point getSelection() {
        checkWidget();
        return new Point(selection.x, selection.y);
    }

    /**
     * Returns the selection.
     *
     * @return start and length of the selection, x is the offset of the
     *    first selected character, relative to the first character of the
     *    widget content. y is the length of the selection.
     *  The selection values returned are visual (i.e., length will always always be
     *  positive).  To determine if a selection is right-to-left (RtoL) vs. left-to-right
     *  (LtoR), compare the caretOffset to the start and end of the selection
     *  (e.g., caretOffset == start of selection implies that the selection is RtoL).
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public Point getSelectionRange() {
        checkWidget();
        return new Point(selection.x, selection.y - selection.x);
    }

    /**
     * Returns the ranges of text that are inside the block selection rectangle.
     * <p>
     * The ranges array contains start and length pairs. When the receiver is not
     * in block selection mode the return arrays contains the start and length of
     * the regular selection.
     *
     * @return the ranges array
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.5
     */
    public int[] getSelectionRanges() {
        checkWidget();
        if (blockSelection && blockXLocation != -1) {
            Rectangle rect = getBlockSelectionPosition();
            int firstLine = rect.y;
            int lastLine = rect.height;
            int left = rect.x;
            int right = rect.width;
            int[] ranges = new int[(lastLine - firstLine + 1) * 2];
            int index = 0;
            for (int lineIndex = firstLine; lineIndex <= lastLine; lineIndex++) {
                int start = getOffsetAtPoint(left, 0, lineIndex, null);
                int end = getOffsetAtPoint(right, 0, lineIndex, null);
                if (start > end) {
                    int temp = start;
                    start = end;
                    end = temp;
                }
                ranges[index++] = start;
                ranges[index++] = end - start;
            }
            return ranges;
        }
        return new int[] { selection.x, selection.y - selection.x };
    }

    /**
     * Returns the receiver's selection background color.
     *
     * @return the selection background color
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @since 2.1
     */
    public Color getSelectionBackground() {
        checkWidget();
        if (selectionBackground == null) {
            return getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION);
        }
        return selectionBackground;
    }

    /**
     * Gets the number of selected characters.
     *
     * @return the number of selected characters.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public int getSelectionCount() {
        checkWidget();
        if (blockSelection && blockXLocation != -1) {
            return getBlockSelectionText(content.getLineDelimiter()).length();
        }
        return getSelectionRange().y;
    }

    /**
     * Returns the receiver's selection foreground color.
     *
     * @return the selection foreground color
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @since 2.1
     */
    public Color getSelectionForeground() {
        checkWidget();
        if (selectionForeground == null) {
            return getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT);
        }
        return selectionForeground;
    }

    /**
     * Returns the selected text.
     *
     * @return selected text, or an empty String if there is no selection.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public String getSelectionText() {
        checkWidget();
        if (blockSelection && blockXLocation != -1) {
            return getBlockSelectionText(content.getLineDelimiter());
        }
        return content.getTextRange(selection.x, selection.y - selection.x);
    }

    StyledTextEvent getBidiSegments(int lineOffset, String line) {
        if (!isListening(ST.LineGetSegments)) {
            if (!bidiColoring)
                return null;
            StyledTextEvent event = new StyledTextEvent(content);
            event.segments = getBidiSegmentsCompatibility(line, lineOffset);
            return event;
        }
        StyledTextEvent event = sendLineEvent(ST.LineGetSegments, lineOffset, line);
        if (event == null || event.segments == null || event.segments.length == 0)
            return null;
        int lineLength = line.length();
        int[] segments = event.segments;
        if (segments[0] > lineLength) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        char[] segmentsChars = event.segmentsChars;
        boolean hasSegmentsChars = segmentsChars != null;
        for (int i = 1; i < segments.length; i++) {
            if ((hasSegmentsChars ? segments[i] < segments[i - 1] : segments[i] <= segments[i - 1])
                    || segments[i] > lineLength) {
                SWT.error(SWT.ERROR_INVALID_ARGUMENT);
            }
        }
        if (hasSegmentsChars && !visualWrap) {
            for (int i = 0; i < segmentsChars.length; i++) {
                if (segmentsChars[i] == '\n' || segmentsChars[i] == '\r') {
                    visualWrap = true;
                    setVariableLineHeight();
                    break;
                }
            }
        }
        return event;
    }

    /**
     * @see #getBidiSegments
     * Supports deprecated setBidiColoring API. Remove when API is removed.
     */
    int[] getBidiSegmentsCompatibility(String line, int lineOffset) {
        int lineLength = line.length();
        StyleRange[] styles = null;
        StyledTextEvent event = getLineStyleData(lineOffset, line);
        if (event != null) {
            styles = event.styles;
        } else {
            styles = renderer.getStyleRanges(lineOffset, lineLength, true);
        }
        if (styles == null || styles.length == 0) {
            return new int[] { 0, lineLength };
        }
        int k = 0, count = 1;
        while (k < styles.length && styles[k].start == 0 && styles[k].length == lineLength) {
            k++;
        }
        int[] offsets = new int[(styles.length - k) * 2 + 2];
        for (int i = k; i < styles.length; i++) {
            StyleRange style = styles[i];
            int styleLineStart = Math.max(style.start - lineOffset, 0);
            int styleLineEnd = Math.max(style.start + style.length - lineOffset, styleLineStart);
            styleLineEnd = Math.min(styleLineEnd, line.length());
            if (i > 0 && count > 1
                    && ((styleLineStart >= offsets[count - 2] && styleLineStart <= offsets[count - 1])
                            || (styleLineEnd >= offsets[count - 2] && styleLineEnd <= offsets[count - 1]))
                    && style.similarTo(styles[i - 1])) {
                offsets[count - 2] = Math.min(offsets[count - 2], styleLineStart);
                offsets[count - 1] = Math.max(offsets[count - 1], styleLineEnd);
            } else {
                if (styleLineStart > offsets[count - 1]) {
                    offsets[count] = styleLineStart;
                    count++;
                }
                offsets[count] = styleLineEnd;
                count++;
            }
        }
        // add offset for last non-colored segment in line, if any
        if (lineLength > offsets[count - 1]) {
            offsets[count] = lineLength;
            count++;
        }
        if (count == offsets.length) {
            return offsets;
        }
        int[] result = new int[count];
        System.arraycopy(offsets, 0, result, 0, count);
        return result;
    }

    /**
     * Returns the style range at the given offset.
     * <p>
     * Returns null if a LineStyleListener has been set or if a style is not set
     * for the offset.
     * Should not be called if a LineStyleListener has been set since the
     * listener maintains the styles.
     * </p>
     *
     * @param offset the offset to return the style for.
     *    0 &lt;= offset &lt; getCharCount() must be true.
     * @return a StyleRange with start == offset and length == 1, indicating
     *    the style at the given offset. null if a LineStyleListener has been set
     *    or if a style is not set for the given offset.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_ARGUMENT when the offset is invalid</li>
     * </ul>
     */
    public StyleRange getStyleRangeAtOffset(int offset) {
        checkWidget();
        if (offset < 0 || offset >= getCharCount()) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        if (!isListening(ST.LineGetStyle)) {
            StyleRange[] ranges = renderer.getStyleRanges(offset, 1, true);
            if (ranges != null)
                return ranges[0];
        }
        return null;
    }

    /**
     * Returns the styles.
     * <p>
     * Returns an empty array if a LineStyleListener has been set.
     * Should not be called if a LineStyleListener has been set since the
     * listener maintains the styles.
     * </p><p>
     * Note: Because a StyleRange includes the start and length, the
     * same instance cannot occur multiple times in the array of styles.
     * If the same style attributes, such as font and color, occur in
     * multiple StyleRanges, <code>getStyleRanges(boolean)</code>
     * can be used to get the styles without the ranges.
     * </p>
     *
     * @return the styles or an empty array if a LineStyleListener has been set.
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see #getStyleRanges(boolean)
     */
    public StyleRange[] getStyleRanges() {
        checkWidget();
        return getStyleRanges(0, content.getCharCount(), true);
    }

    /**
     * Returns the styles.
     * <p>
     * Returns an empty array if a LineStyleListener has been set.
     * Should not be called if a LineStyleListener has been set since the
     * listener maintains the styles.
     * </p><p>
     * Note: When <code>includeRanges</code> is true, the start and length
     * fields of each StyleRange will be valid, however the StyleRange
     * objects may need to be cloned. When <code>includeRanges</code> is
     * false, <code>getRanges(int, int)</code> can be used to get the
     * associated ranges.
     * </p>
     *
     * @param includeRanges whether the start and length field of the StyleRanges should be set.
     *
     * @return the styles or an empty array if a LineStyleListener has been set.
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.2
     *
     * @see #getRanges(int, int)
     * @see #setStyleRanges(int[], StyleRange[])
     */
    public StyleRange[] getStyleRanges(boolean includeRanges) {
        checkWidget();
        return getStyleRanges(0, content.getCharCount(), includeRanges);
    }

    /**
     * Returns the styles for the given text range.
     * <p>
     * Returns an empty array if a LineStyleListener has been set.
     * Should not be called if a LineStyleListener has been set since the
     * listener maintains the styles.
     * </p><p>
     * Note: Because the StyleRange includes the start and length, the
     * same instance cannot occur multiple times in the array of styles.
     * If the same style attributes, such as font and color, occur in
     * multiple StyleRanges, <code>getStyleRanges(int, int, boolean)</code>
     * can be used to get the styles without the ranges.
     * </p>
     * @param start the start offset of the style ranges to return
     * @param length the number of style ranges to return
     *
     * @return the styles or an empty array if a LineStyleListener has
     *  been set.  The returned styles will reflect the given range.  The first
     *  returned <code>StyleRange</code> will have a starting offset &gt;= start
     *  and the last returned <code>StyleRange</code> will have an ending
     *  offset &lt;= start + length - 1
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
     * </ul>
     *
     * @see #getStyleRanges(int, int, boolean)
     *
     * @since 3.0
     */
    public StyleRange[] getStyleRanges(int start, int length) {
        checkWidget();
        return getStyleRanges(start, length, true);
    }

    /**
     * Returns the styles for the given text range.
     * <p>
     * Returns an empty array if a LineStyleListener has been set.
     * Should not be called if a LineStyleListener has been set since the
     * listener maintains the styles.
     * </p><p>
     * Note: When <code>includeRanges</code> is true, the start and length
     * fields of each StyleRange will be valid, however the StyleRange
     * objects may need to be cloned. When <code>includeRanges</code> is
     * false, <code>getRanges(int, int)</code> can be used to get the
     * associated ranges.
     * </p>
     *
     * @param start the start offset of the style ranges to return
     * @param length the number of style ranges to return
     * @param includeRanges whether the start and length field of the StyleRanges should be set.
     *
     * @return the styles or an empty array if a LineStyleListener has
     *  been set.  The returned styles will reflect the given range.  The first
     *  returned <code>StyleRange</code> will have a starting offset &gt;= start
     *  and the last returned <code>StyleRange</code> will have an ending
     *  offset &gt;= start + length - 1
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
     * </ul>
     *
     * @since 3.2
     *
     * @see #getRanges(int, int)
     * @see #setStyleRanges(int[], StyleRange[])
     */
    public StyleRange[] getStyleRanges(int start, int length, boolean includeRanges) {
        checkWidget();
        int contentLength = getCharCount();
        int end = start + length;
        if (start > end || start < 0 || end > contentLength) {
            SWT.error(SWT.ERROR_INVALID_RANGE);
        }
        if (!isListening(ST.LineGetStyle)) {
            StyleRange[] ranges = renderer.getStyleRanges(start, length, includeRanges);
            if (ranges != null)
                return ranges;
        }
        return new StyleRange[0];
    }

    /**
     * Returns the tab width measured in characters.
     *
     * @return tab width measured in characters
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see #getTabStops()
     */
    public int getTabs() {
        checkWidget();
        return tabLength;
    }

    /**
     * Returns the tab list of the receiver.
     *
     * @return the tab list
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.6
     */
    public int[] getTabStops() {
        checkWidget();
        if (tabs == null)
            return new int[] { renderer.tabWidth };
        int[] result = new int[tabs.length];
        System.arraycopy(tabs, 0, result, 0, tabs.length);
        return result;
    }

    /**
     * Returns a copy of the widget content.
     *
     * @return copy of the widget content
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public String getText() {
        checkWidget();
        return content.getTextRange(0, getCharCount());
    }

    /**
     * Returns the widget content between the two offsets.
     *
     * @param start offset of the first character in the returned String
     * @param end offset of the last character in the returned String
     * @return widget content starting at start and ending at end
     * @see #getTextRange(int,int)
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
     * </ul>
     */
    public String getText(int start, int end) {
        checkWidget();
        int contentLength = getCharCount();
        if (start < 0 || start >= contentLength || end < 0 || end >= contentLength || start > end) {
            SWT.error(SWT.ERROR_INVALID_RANGE);
        }
        return content.getTextRange(start, end - start + 1);
    }

    /**
     * Returns the smallest bounding rectangle that includes the characters between two offsets.
     *
     * @param start offset of the first character included in the bounding box
     * @param end offset of the last character included in the bounding box
     * @return bounding box of the text between start and end
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
     * </ul>
     * @since 3.1
     */
    public Rectangle getTextBounds(int start, int end) {
        checkWidget();
        int contentLength = getCharCount();
        if (start < 0 || start >= contentLength || end < 0 || end >= contentLength || start > end) {
            SWT.error(SWT.ERROR_INVALID_RANGE);
        }
        int lineStart = content.getLineAtOffset(start);
        int lineEnd = content.getLineAtOffset(end);
        Rectangle rect;
        int y = getLinePixel(lineStart);
        int height = 0;
        int left = 0x7fffffff, right = 0;
        for (int i = lineStart; i <= lineEnd; i++) {
            int lineOffset = content.getOffsetAtLine(i);
            TextLayout layout = renderer.getTextLayout(i);
            int length = layout.getText().length();
            if (length > 0) {
                if (i == lineStart) {
                    if (i == lineEnd) {
                        rect = layout.getBounds(start - lineOffset, end - lineOffset);
                    } else {
                        rect = layout.getBounds(start - lineOffset, length);
                    }
                    y += rect.y;
                } else if (i == lineEnd) {
                    rect = layout.getBounds(0, end - lineOffset);
                } else {
                    rect = layout.getBounds();
                }
                left = Math.min(left, rect.x);
                right = Math.max(right, rect.x + rect.width);
                height += rect.height;
            } else {
                height += renderer.getLineHeight();
            }
            renderer.disposeTextLayout(layout);
        }
        rect = new Rectangle(left, y, right - left, height);
        rect.x += leftMargin - horizontalScrollOffset;
        return rect;
    }

    /**
     * Returns the widget content starting at start for length characters.
     *
     * @param start offset of the first character in the returned String
     * @param length number of characters to return
     * @return widget content starting at start and extending length characters.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_RANGE when start and/or length are outside the widget content</li>
     * </ul>
     */
    public String getTextRange(int start, int length) {
        checkWidget();
        int contentLength = getCharCount();
        int end = start + length;
        if (start > end || start < 0 || end > contentLength) {
            SWT.error(SWT.ERROR_INVALID_RANGE);
        }
        return content.getTextRange(start, length);
    }

    /**
     * Returns the maximum number of characters that the receiver is capable of holding.
     *
     * @return the text limit
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public int getTextLimit() {
        checkWidget();
        return textLimit;
    }

    /**
     * Gets the top index.
     * <p>
     * The top index is the index of the fully visible line that is currently
     * at the top of the widget or the topmost partially visible line if no line is fully visible.
     * The top index changes when the widget is scrolled. Indexing is zero based.
     * </p>
     *
     * @return the index of the top line
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public int getTopIndex() {
        checkWidget();
        return topIndex;
    }

    /**
     * Returns the top margin.
     *
     * @return the top margin.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.5
     */
    public int getTopMargin() {
        checkWidget();
        return topMargin;
    }

    /**
     * Gets the top SWT logical point.
     * <p>
     * The top point is the SWT logical point position of the line that is
     * currently at the top of the widget. The text widget can be scrolled by points
     * by dragging the scroll thumb so that a partial line may be displayed at the top
     * the widget.  The top point changes when the widget is scrolled.  The top point
     * does not include the widget trimming.
     * </p>
     *
     * @return SWT logical point position of the top line
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public int getTopPixel() {
        checkWidget();
        return getVerticalScrollOffset();
    }

    /**
     * Returns the vertical scroll increment.
     *
     * @return vertical scroll increment.
     */
    int getVerticalIncrement() {
        return renderer.getLineHeight();
    }

    int getVerticalScrollOffset() {
        if (verticalScrollOffset == -1) {
            renderer.calculate(0, topIndex);
            int height = 0;
            for (int i = 0; i < topIndex; i++) {
                height += renderer.getLineHeight(i);
            }
            height -= topIndexY;
            verticalScrollOffset = height;
        }
        return verticalScrollOffset;
    }

    int getVisualLineIndex(TextLayout layout, int offsetInLine) {
        int lineIndex = layout.getLineIndex(offsetInLine);
        int[] offsets = layout.getLineOffsets();
        Caret caret = getCaret();
        if (caret != null && lineIndex != 0 && offsetInLine == offsets[lineIndex]) {
            int lineY = layout.getLineBounds(lineIndex).y;
            int caretY = caret.getLocation().y - getLinePixel(getCaretLine());
            if (lineY > caretY)
                lineIndex--;
            caretAlignment = OFFSET_LEADING;
        }
        return lineIndex;
    }

    int getCaretDirection() {
        if (!isBidiCaret())
            return SWT.DEFAULT;
        if (ime.getCompositionOffset() != -1)
            return SWT.DEFAULT;
        if (!updateCaretDirection && caretDirection != SWT.NULL)
            return caretDirection;
        updateCaretDirection = false;
        int caretLine = getCaretLine();
        int lineOffset = content.getOffsetAtLine(caretLine);
        String line = content.getLine(caretLine);
        int offset = caretOffset - lineOffset;
        int lineLength = line.length();
        if (lineLength == 0)
            return isMirrored() ? SWT.RIGHT : SWT.LEFT;
        if (caretAlignment == PREVIOUS_OFFSET_TRAILING && offset > 0)
            offset--;
        if (offset == lineLength && offset > 0)
            offset--;
        while (offset > 0 && Character.isDigit(line.charAt(offset)))
            offset--;
        if (offset == 0 && Character.isDigit(line.charAt(offset))) {
            return isMirrored() ? SWT.RIGHT : SWT.LEFT;
        }
        TextLayout layout = renderer.getTextLayout(caretLine);
        int level = layout.getLevel(offset);
        renderer.disposeTextLayout(layout);
        return ((level & 1) != 0) ? SWT.RIGHT : SWT.LEFT;
    }

    /*
     * Returns the index of the line the caret is on.
     */
    int getCaretLine() {
        return content.getLineAtOffset(caretOffset);
    }

    int getWrapWidth() {
        if (wordWrap && !isSingleLine()) {
            int width = clientAreaWidth - leftMargin - rightMargin;
            return width > 0 ? width : 1;
        }
        return -1;
    }

    int getWordNext(int offset, int movement) {
        return getWordNext(offset, movement, false);
    }

    int getWordNext(int offset, int movement, boolean ignoreListener) {
        int newOffset, lineOffset;
        String lineText;
        if (offset >= getCharCount()) {
            newOffset = offset;
            int lineIndex = content.getLineCount() - 1;
            lineOffset = content.getOffsetAtLine(lineIndex);
            lineText = content.getLine(lineIndex);
        } else {
            int lineIndex = content.getLineAtOffset(offset);
            lineOffset = content.getOffsetAtLine(lineIndex);
            lineText = content.getLine(lineIndex);
            int lineLength = lineText.length();
            if (offset >= lineOffset + lineLength) {
                newOffset = content.getOffsetAtLine(lineIndex + 1);
            } else {
                TextLayout layout = renderer.getTextLayout(lineIndex);
                newOffset = lineOffset + layout.getNextOffset(offset - lineOffset, movement);
                renderer.disposeTextLayout(layout);
            }
        }
        if (ignoreListener)
            return newOffset;
        return sendWordBoundaryEvent(ST.WordNext, movement, offset, newOffset, lineText, lineOffset);
    }

    int getWordPrevious(int offset, int movement) {
        return getWordPrevious(offset, movement, false);
    }

    int getWordPrevious(int offset, int movement, boolean ignoreListener) {
        int newOffset, lineOffset;
        String lineText;
        if (offset <= 0) {
            newOffset = 0;
            int lineIndex = content.getLineAtOffset(newOffset);
            lineOffset = content.getOffsetAtLine(lineIndex);
            lineText = content.getLine(lineIndex);
        } else {
            int lineIndex = content.getLineAtOffset(offset);
            lineOffset = content.getOffsetAtLine(lineIndex);
            lineText = content.getLine(lineIndex);
            if (offset == lineOffset) {
                String nextLineText = content.getLine(lineIndex - 1);
                int nextLineOffset = content.getOffsetAtLine(lineIndex - 1);
                newOffset = nextLineOffset + nextLineText.length();
            } else {
                int layoutOffset = Math.min(offset - lineOffset, lineText.length());
                TextLayout layout = renderer.getTextLayout(lineIndex);
                newOffset = lineOffset + layout.getPreviousOffset(layoutOffset, movement);
                renderer.disposeTextLayout(layout);
            }
        }
        if (ignoreListener)
            return newOffset;
        return sendWordBoundaryEvent(ST.WordPrevious, movement, offset, newOffset, lineText, lineOffset);
    }

    /**
     * Returns whether the widget wraps lines.
     *
     * @return true if widget wraps lines, false otherwise
     * @since 2.0
     */
    public boolean getWordWrap() {
        checkWidget();
        return wordWrap;
    }

    /**
     * Returns the wrap indentation of the widget.
     *
     * @return the wrap indentation
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see #getLineWrapIndent(int)
     *
     * @since 3.6
     */
    public int getWrapIndent() {
        checkWidget();
        return wrapIndent;
    }

    /**
     * Returns the location of the given offset.
     * <p>
     * <b>NOTE:</b> Does not return correct values for true italic fonts (vs. slanted fonts).
     * </p>
     *
     * @return location of the character at the given offset in the line.
     */
    Point getPointAtOffset(int offset) {
        int lineIndex = content.getLineAtOffset(offset);
        String line = content.getLine(lineIndex);
        int lineOffset = content.getOffsetAtLine(lineIndex);
        int offsetInLine = Math.max(0, offset - lineOffset);
        int lineLength = line.length();
        if (lineIndex < content.getLineCount() - 1) {
            int endLineOffset = content.getOffsetAtLine(lineIndex + 1) - 1;
            if (lineLength < offsetInLine && offsetInLine <= endLineOffset) {
                offsetInLine = lineLength;
            }
        }
        Point point;
        TextLayout layout = renderer.getTextLayout(lineIndex);
        if (lineLength != 0 && offsetInLine <= lineLength) {
            if (offsetInLine == lineLength) {
                offsetInLine = layout.getPreviousOffset(offsetInLine, SWT.MOVEMENT_CLUSTER);
                point = layout.getLocation(offsetInLine, true);
            } else {
                switch (caretAlignment) {
                case OFFSET_LEADING:
                    point = layout.getLocation(offsetInLine, false);
                    break;
                case PREVIOUS_OFFSET_TRAILING:
                default:
                    boolean lineBegin = offsetInLine == 0;
                    // If word wrap is enabled, we should also consider offsets
                    // of wrapped line parts as line begin and do NOT go back.
                    // This prevents clients to jump one line higher than
                    // expected, see bug 488172.
                    // Respect caretAlignment at the caretOffset, unless there's
                    // a non-empty selection, see bug 488172 comment 6.
                    if (wordWrap && !lineBegin && (offset != caretOffset || selection.x != selection.y)) {
                        int[] offsets = layout.getLineOffsets();
                        for (int i : offsets) {
                            if (i == offsetInLine) {
                                lineBegin = true;
                                break;
                            }
                        }
                    }
                    if (lineBegin) {
                        point = layout.getLocation(offsetInLine, false);
                    } else {
                        offsetInLine = layout.getPreviousOffset(offsetInLine, SWT.MOVEMENT_CLUSTER);
                        point = layout.getLocation(offsetInLine, true);
                    }
                    break;
                }
            }
        } else {
            point = new Point(layout.getIndent(), layout.getVerticalIndent());
        }
        renderer.disposeTextLayout(layout);
        point.x += leftMargin - horizontalScrollOffset;
        point.y += getLinePixel(lineIndex);
        return point;
    }

    /**
     * Inserts a string.  The old selection is replaced with the new text.
     *
     * @param string the string
     * @see #replaceTextRange(int,int,String)
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when string is null</li>
     * </ul>
     */
    public void insert(String string) {
        checkWidget();
        if (string == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        if (blockSelection) {
            insertBlockSelectionText(string, false);
        } else {
            Point sel = getSelectionRange();
            replaceTextRange(sel.x, sel.y, string);
        }
    }

    int insertBlockSelectionText(String text, boolean fillWithSpaces) {
        int lineCount = 1;
        for (int i = 0; i < text.length(); i++) {
            char ch = text.charAt(i);
            if (ch == '\n' || ch == '\r') {
                lineCount++;
                if (ch == '\r' && i + 1 < text.length() && text.charAt(i + 1) == '\n') {
                    i++;
                }
            }
        }
        String[] lines = new String[lineCount];
        int start = 0;
        lineCount = 0;
        for (int i = 0; i < text.length(); i++) {
            char ch = text.charAt(i);
            if (ch == '\n' || ch == '\r') {
                lines[lineCount++] = text.substring(start, i);
                if (ch == '\r' && i + 1 < text.length() && text.charAt(i + 1) == '\n') {
                    i++;
                }
                start = i + 1;
            }
        }
        lines[lineCount++] = text.substring(start);
        if (fillWithSpaces) {
            int maxLength = 0;
            for (int i = 0; i < lines.length; i++) {
                int length = lines[i].length();
                maxLength = Math.max(maxLength, length);
            }
            for (int i = 0; i < lines.length; i++) {
                String line = lines[i];
                int length = line.length();
                if (length < maxLength) {
                    int numSpaces = maxLength - length;
                    StringBuilder buffer = new StringBuilder(length + numSpaces);
                    buffer.append(line);
                    for (int j = 0; j < numSpaces; j++)
                        buffer.append(' ');
                    lines[i] = buffer.toString();
                }
            }
        }
        int firstLine, lastLine, left, right;
        if (blockXLocation != -1) {
            Rectangle rect = getBlockSelectionPosition();
            firstLine = rect.y;
            lastLine = rect.height;
            left = rect.x;
            right = rect.width;
        } else {
            firstLine = lastLine = getCaretLine();
            left = right = getPointAtOffset(caretOffset).x;
        }
        start = caretOffset;
        int caretLine = getCaretLine();
        int index = 0, lineIndex = firstLine;
        while (lineIndex <= lastLine) {
            String string = index < lineCount ? lines[index++] : "";
            int lineStart = sendTextEvent(left, right, lineIndex, string, fillWithSpaces);
            if (lineIndex == caretLine)
                start = lineStart;
            lineIndex++;
        }
        while (index < lineCount) {
            int lineStart = sendTextEvent(left, left, lineIndex, lines[index++], fillWithSpaces);
            if (lineIndex == caretLine)
                start = lineStart;
            lineIndex++;
        }
        return start;
    }

    void insertBlockSelectionText(char key, int action) {
        if (key == SWT.CR || key == SWT.LF)
            return;
        Rectangle rect = getBlockSelectionPosition();
        int firstLine = rect.y;
        int lastLine = rect.height;
        int left = rect.x;
        int right = rect.width;
        int[] trailing = new int[1];
        int offset = 0, delta = 0;
        String text = key != 0 ? new String(new char[] { key }) : "";
        int length = text.length();
        for (int lineIndex = firstLine; lineIndex <= lastLine; lineIndex++) {
            String line = content.getLine(lineIndex);
            int lineOffset = content.getOffsetAtLine(lineIndex);
            int lineEndOffset = lineOffset + line.length();
            int linePixel = getLinePixel(lineIndex);
            int start = getOffsetAtPoint(left, linePixel, trailing, true);
            boolean outOfLine = start == -1;
            if (outOfLine) {
                start = left < leftMargin ? lineOffset : lineEndOffset;
            } else {
                start += trailing[0];
            }
            int end = getOffsetAtPoint(right, linePixel, trailing, true);
            if (end == -1) {
                end = right < leftMargin ? lineOffset : lineEndOffset;
            } else {
                end += trailing[0];
            }
            if (start > end) {
                int temp = start;
                start = end;
                end = temp;
            }
            if (start == end && !outOfLine) {
                switch (action) {
                case ST.DELETE_PREVIOUS:
                    if (start > lineOffset)
                        start = getClusterPrevious(start, lineIndex);
                    break;
                case ST.DELETE_NEXT:
                    if (end < lineEndOffset)
                        end = getClusterNext(end, lineIndex);
                    break;
                }
            }
            if (outOfLine) {
                if (line.length() >= delta) {
                    delta = line.length();
                    offset = lineEndOffset + length;
                }
            } else {
                offset = start + length;
                delta = content.getCharCount();
            }
            Event event = new Event();
            event.text = text;
            event.start = start;
            event.end = end;
            sendKeyEvent(event);
        }
        int x = getPointAtOffset(offset).x;
        int verticalScrollOffset = getVerticalScrollOffset();
        setBlockSelectionLocation(x, blockYAnchor - verticalScrollOffset, x, blockYLocation - verticalScrollOffset,
                false);
    }

    /**
     * Creates content change listeners and set the default content model.
     */
    void installDefaultContent() {
        textChangeListener = new TextChangeListener() {
            @Override
            public void textChanging(TextChangingEvent event) {
                handleTextChanging(event);
            }

            @Override
            public void textChanged(TextChangedEvent event) {
                handleTextChanged(event);
            }

            @Override
            public void textSet(TextChangedEvent event) {
                handleTextSet(event);
            }
        };
        content = new DefaultContent();
        content.addTextChangeListener(textChangeListener);
    }

    /**
     * Adds event listeners
     */
    void installListeners() {
        ScrollBar verticalBar = getVerticalBar();
        ScrollBar horizontalBar = getHorizontalBar();

        listener = event -> {
            switch (event.type) {
            case SWT.Dispose:
                handleDispose(event);
                break;
            case SWT.KeyDown:
                handleKeyDown(event);
                break;
            case SWT.KeyUp:
                handleKeyUp(event);
                break;
            case SWT.MenuDetect:
                handleMenuDetect(event);
                break;
            case SWT.MouseDown:
                handleMouseDown(event);
                break;
            case SWT.MouseUp:
                handleMouseUp(event);
                break;
            case SWT.MouseMove:
                handleMouseMove(event);
                break;
            case SWT.Paint:
                handlePaint(event);
                break;
            case SWT.Resize:
                handleResize(event);
                break;
            case SWT.Traverse:
                handleTraverse(event);
                break;
            }
        };
        addListener(SWT.Dispose, listener);
        addListener(SWT.KeyDown, listener);
        addListener(SWT.KeyUp, listener);
        addListener(SWT.MenuDetect, listener);
        addListener(SWT.MouseDown, listener);
        addListener(SWT.MouseUp, listener);
        addListener(SWT.MouseMove, listener);
        addListener(SWT.Paint, listener);
        addListener(SWT.Resize, listener);
        addListener(SWT.Traverse, listener);
        ime.addListener(SWT.ImeComposition, event -> {
            if (!editable) {
                event.doit = false;
                event.start = 0;
                event.end = 0;
                event.text = "";
                return;
            }

            switch (event.detail) {
            case SWT.COMPOSITION_SELECTION:
                handleCompositionSelection(event);
                break;
            case SWT.COMPOSITION_CHANGED:
                handleCompositionChanged(event);
                break;
            case SWT.COMPOSITION_OFFSET:
                handleCompositionOffset(event);
                break;
            }
        });
        if (verticalBar != null) {
            verticalBar.addListener(SWT.Selection, event -> handleVerticalScroll(event));
        }
        if (horizontalBar != null) {
            horizontalBar.addListener(SWT.Selection, event -> handleHorizontalScroll(event));
        }
    }

    void internalRedrawRange(int start, int length) {
        if (length <= 0)
            return;
        int end = start + length;
        int startLine = content.getLineAtOffset(start);
        int endLine = content.getLineAtOffset(end);
        int partialBottomIndex = getPartialBottomIndex();
        int partialTopIndex = getPartialTopIndex();
        if (startLine > partialBottomIndex || endLine < partialTopIndex) {
            return;
        }
        if (partialTopIndex > startLine) {
            startLine = partialTopIndex;
            start = 0;
        } else {
            start -= content.getOffsetAtLine(startLine);
        }
        if (partialBottomIndex < endLine) {
            endLine = partialBottomIndex + 1;
            end = 0;
        } else {
            end -= content.getOffsetAtLine(endLine);
        }

        TextLayout layout = renderer.getTextLayout(startLine);
        int lineX = leftMargin - horizontalScrollOffset, startLineY = getLinePixel(startLine);
        int[] offsets = layout.getLineOffsets();
        int startIndex = layout.getLineIndex(Math.min(start, layout.getText().length()));

        /* Redraw end of line before start line if wrapped and start offset is first char */
        if (isWordWrap() && startIndex > 0 && offsets[startIndex] == start) {
            Rectangle rect = layout.getLineBounds(startIndex - 1);
            rect.x = rect.width;
            rect.width = clientAreaWidth - rightMargin - rect.x;
            rect.x += lineX;
            rect.y += startLineY;
            super.redraw(rect.x, rect.y, rect.width, rect.height, false);
        }

        if (startLine == endLine) {
            int endIndex = layout.getLineIndex(Math.min(end, layout.getText().length()));
            if (startIndex == endIndex) {
                /* Redraw rect between start and end offset if start and end offsets are in same wrapped line */
                Rectangle rect = layout.getBounds(start, end - 1);
                rect.x += lineX;
                rect.y += startLineY;
                super.redraw(rect.x, rect.y, rect.width, rect.height, false);
                renderer.disposeTextLayout(layout);
                return;
            }
        }

        /* Redraw start line from the start offset to the end of client area */
        Rectangle startRect = layout.getBounds(start, offsets[startIndex + 1] - 1);
        if (startRect.height == 0) {
            Rectangle bounds = layout.getLineBounds(startIndex);
            startRect.x = bounds.width;
            startRect.y = bounds.y;
            startRect.height = bounds.height;
        }
        startRect.x += lineX;
        startRect.y += startLineY;
        startRect.width = clientAreaWidth - rightMargin - startRect.x;
        super.redraw(startRect.x, startRect.y, startRect.width, startRect.height, false);

        /* Redraw end line from the beginning of the line to the end offset */
        if (startLine != endLine) {
            renderer.disposeTextLayout(layout);
            layout = renderer.getTextLayout(endLine);
            offsets = layout.getLineOffsets();
        }
        int endIndex = layout.getLineIndex(Math.min(end, layout.getText().length()));
        Rectangle endRect = layout.getBounds(offsets[endIndex], end - 1);
        if (endRect.height == 0) {
            Rectangle bounds = layout.getLineBounds(endIndex);
            endRect.y = bounds.y;
            endRect.height = bounds.height;
        }
        endRect.x += lineX;
        endRect.y += getLinePixel(endLine);
        super.redraw(endRect.x, endRect.y, endRect.width, endRect.height, false);
        renderer.disposeTextLayout(layout);

        /* Redraw all lines in between start and end line */
        int y = startRect.y + startRect.height;
        if (endRect.y > y) {
            super.redraw(leftMargin, y, clientAreaWidth - rightMargin - leftMargin, endRect.y - y, false);
        }
    }

    void handleCompositionOffset(Event event) {
        int[] trailing = new int[1];
        event.index = getOffsetAtPoint(event.x, event.y, trailing, true);
        event.count = trailing[0];
    }

    void handleCompositionSelection(Event event) {
        if (event.start != event.end) {
            int charCount = getCharCount();
            event.start = Math.max(0, Math.min(event.start, charCount));
            event.end = Math.max(0, Math.min(event.end, charCount));
            if (event.text != null) {
                setSelection(event.start, event.end);
            } else {
                event.text = getTextRange(event.start, event.end - event.start);
            }
        } else {
            event.start = selection.x;
            event.end = selection.y;
            event.text = getSelectionText();
        }
    }

    void handleCompositionChanged(Event event) {
        String text = event.text;
        int start = event.start;
        int end = event.end;
        int charCount = content.getCharCount();
        start = Math.min(start, charCount);
        end = Math.min(end, charCount);
        int length = text.length();
        if (length == ime.getCommitCount()) {
            content.replaceTextRange(start, end - start, "");
            setCaretOffset(ime.getCompositionOffset(), SWT.DEFAULT);
            caretWidth = 0;
            caretDirection = SWT.NULL;
        } else {
            content.replaceTextRange(start, end - start, text);
            int alignment = SWT.DEFAULT;
            if (ime.getWideCaret()) {
                start = ime.getCompositionOffset();
                int lineIndex = getCaretLine();
                int lineOffset = content.getOffsetAtLine(lineIndex);
                TextLayout layout = renderer.getTextLayout(lineIndex);
                caretWidth = layout.getBounds(start - lineOffset, start + length - 1 - lineOffset).width;
                renderer.disposeTextLayout(layout);
                alignment = OFFSET_LEADING;
            }
            setCaretOffset(ime.getCaretOffset(), alignment);
        }
        resetSelection();
        showCaret();
    }

    /**
     * Frees resources.
     */
    void handleDispose(Event event) {
        removeListener(SWT.Dispose, listener);
        notifyListeners(SWT.Dispose, event);
        event.type = SWT.None;

        clipboard.dispose();
        if (renderer != null) {
            renderer.dispose();
            renderer = null;
        }
        if (content != null) {
            content.removeTextChangeListener(textChangeListener);
            content = null;
        }
        if (defaultCaret != null) {
            defaultCaret.dispose();
            defaultCaret = null;
        }
        if (leftCaretBitmap != null) {
            leftCaretBitmap.dispose();
            leftCaretBitmap = null;
        }
        if (rightCaretBitmap != null) {
            rightCaretBitmap.dispose();
            rightCaretBitmap = null;
        }
        if (isBidiCaret()) {
            BidiUtil.removeLanguageListener(this);
        }
        selectionBackground = null;
        selectionForeground = null;
        marginColor = null;
        textChangeListener = null;
        selection = null;
        doubleClickSelection = null;
        keyActionMap = null;
        background = null;
        foreground = null;
        clipboard = null;
        tabs = null;
    }

    /**
     * Scrolls the widget horizontally.
     */
    void handleHorizontalScroll(Event event) {
        int scrollPixel = getHorizontalBar().getSelection() - horizontalScrollOffset;
        scrollHorizontal(scrollPixel, false);
    }

    /**
     * If an action has been registered for the key stroke execute the action.
     * Otherwise, if a character has been entered treat it as new content.
     *
     * @param event keyboard event
     */
    void handleKey(Event event) {
        int action;
        caretAlignment = PREVIOUS_OFFSET_TRAILING;
        if (event.keyCode != 0) {
            // special key pressed (e.g., F1)
            action = getKeyBinding(event.keyCode | event.stateMask);
        } else {
            // character key pressed
            action = getKeyBinding(event.character | event.stateMask);
            if (action == SWT.NULL) {
                // see if we have a control character
                if ((event.stateMask & SWT.CTRL) != 0 && event.character <= 31) {
                    // get the character from the CTRL+char sequence, the control
                    // key subtracts 64 from the value of the key that it modifies
                    int c = event.character + 64;
                    action = getKeyBinding(c | event.stateMask);
                }
            }
        }
        if (action == SWT.NULL) {
            boolean ignore = false;

            if (IS_MAC) {
                // Ignore accelerator key combinations (we do not want to
                // insert a character in the text in this instance).
                ignore = (event.stateMask & (SWT.COMMAND | SWT.CTRL)) != 0;
            } else {
                // Ignore accelerator key combinations (we do not want to
                // insert a character in the text in this instance). Don't
                // ignore CTRL+ALT combinations since that is the Alt Gr
                // key on some keyboards.  See bug 20953.
                ignore = (event.stateMask ^ SWT.ALT) == 0 || (event.stateMask ^ SWT.CTRL) == 0
                        || (event.stateMask ^ (SWT.ALT | SWT.SHIFT)) == 0
                        || (event.stateMask ^ (SWT.CTRL | SWT.SHIFT)) == 0;
            }
            // -ignore anything below SPACE except for line delimiter keys and tab.
            // -ignore DEL
            if (!ignore && event.character > 31 && event.character != SWT.DEL || event.character == SWT.CR
                    || event.character == SWT.LF || event.character == TAB) {
                doContent(event.character);
                update();
            }
        } else {
            invokeAction(action);
        }
    }

    /**
     * If a VerifyKey listener exists, verify that the key that was entered
     * should be processed.
     *
     * @param event keyboard event
     */
    void handleKeyDown(Event event) {
        if (clipboardSelection == null) {
            clipboardSelection = new Point(selection.x, selection.y);
        }
        newOrientation = SWT.NONE;
        event.stateMask &= SWT.MODIFIER_MASK;

        Event verifyEvent = new Event();
        verifyEvent.character = event.character;
        verifyEvent.keyCode = event.keyCode;
        verifyEvent.keyLocation = event.keyLocation;
        verifyEvent.stateMask = event.stateMask;
        verifyEvent.doit = event.doit;
        notifyListeners(ST.VerifyKey, verifyEvent);
        if (verifyEvent.doit) {
            if ((event.stateMask & SWT.MODIFIER_MASK) == SWT.CTRL && event.keyCode == SWT.SHIFT && isBidiCaret()) {
                newOrientation = event.keyLocation == SWT.LEFT ? SWT.LEFT_TO_RIGHT : SWT.RIGHT_TO_LEFT;
            }
            handleKey(event);
        }
    }

    /**
     * Update the Selection Clipboard.
     *
     * @param event keyboard event
     */
    void handleKeyUp(Event event) {
        if (clipboardSelection != null) {
            if (clipboardSelection.x != selection.x || clipboardSelection.y != selection.y) {
                copySelection(DND.SELECTION_CLIPBOARD);
            }
        }
        clipboardSelection = null;

        if (newOrientation != SWT.NONE) {
            if (newOrientation != getOrientation()) {
                Event e = new Event();
                e.doit = true;
                notifyListeners(SWT.OrientationChange, e);
                if (e.doit) {
                    setOrientation(newOrientation);
                }
            }
            newOrientation = SWT.NONE;
        }
    }

    /**
     * Update the event location for focus-based context menu triggers.
     *
     * @param event menu detect event
     */
    void handleMenuDetect(Event event) {
        if (event.detail == SWT.MENU_KEYBOARD) {
            Point point = getDisplay().map(this, null, getPointAtOffset(caretOffset));
            event.x = point.x;
            event.y = point.y + getLineHeight(caretOffset);
        }
    }

    /**
     * Updates the caret location and selection if mouse button 1 has been
     * pressed.
     */
    void handleMouseDown(Event event) {
        //force focus (object support)
        forceFocus();

        //drag detect
        if (dragDetect && checkDragDetect(event))
            return;

        //paste clipboard selection
        if (event.button == 2) {
            // On GTK, if mouseNavigator is enabled we have to distinguish a short middle-click (to paste content) from
            // a long middle-click (mouse navigation started)
            if (IS_GTK && mouseNavigator != null) {
                middleClickPressed = true;
                getDisplay().timerExec(200, () -> {
                    boolean click = middleClickPressed;
                    middleClickPressed = false;
                    if (click && mouseNavigator != null) {
                        mouseNavigator.onMouseDown(event);
                    } else {
                        pasteOnMiddleClick(event);
                    }
                });
                return;
            } else {
                pasteOnMiddleClick(event);
            }
        }

        //set selection
        if ((event.button != 1) || (IS_MAC && (event.stateMask & SWT.MOD4) != 0)) {
            return;
        }
        clickCount = event.count;
        if (clickCount == 1) {
            boolean select = (event.stateMask & SWT.MOD2) != 0;
            doMouseLocationChange(event.x, event.y, select);
        } else {
            if (doubleClickEnabled) {
                boolean wordSelect = (clickCount & 1) == 0;
                int offset = getOffsetAtPoint(event.x, event.y, null);
                int lineIndex = content.getLineAtOffset(offset);
                int lineOffset = content.getOffsetAtLine(lineIndex);
                if (wordSelect) {
                    int min = blockSelection ? lineOffset : 0;
                    int max = blockSelection ? lineOffset + content.getLine(lineIndex).length()
                            : content.getCharCount();
                    int start = Math.max(min, getWordPrevious(offset, SWT.MOVEMENT_WORD_START));
                    int end = Math.min(max, getWordNext(start, SWT.MOVEMENT_WORD_END));
                    setSelection(start, end - start, false, true);
                    sendSelectionEvent();
                } else {
                    if (blockSelection) {
                        setBlockSelectionLocation(leftMargin, event.y, clientAreaWidth - rightMargin, event.y,
                                true);
                    } else {
                        int lineEnd = content.getCharCount();
                        if (lineIndex + 1 < content.getLineCount()) {
                            lineEnd = content.getOffsetAtLine(lineIndex + 1);
                        }
                        setSelection(lineOffset, lineEnd - lineOffset, false, false);
                        sendSelectionEvent();
                    }
                }
                doubleClickSelection = new Point(selection.x, selection.y);
                showCaret();
            }
        }
    }

    /**
     * Updates the caret location and selection if mouse button 1 is pressed
     * during the mouse move.
     */
    void handleMouseMove(Event event) {
        if (clickCount > 0) {
            update();
            doAutoScroll(event);
            doMouseLocationChange(event.x, event.y, true);
        }
        if (renderer.hasLinks) {
            doMouseLinkCursor(event.x, event.y);
        }
    }

    /**
     * Autoscrolling ends when the mouse button is released.
     */
    void handleMouseUp(Event event) {
        middleClickPressed = false;
        clickCount = 0;
        endAutoScroll();
        if (event.button == 1) {
            copySelection(DND.SELECTION_CLIPBOARD);
        }
    }

    /**
     * Renders the invalidated area specified in the paint event.
     *
     * @param event paint event
     */
    void handlePaint(Event event) {
        if (event.width == 0 || event.height == 0)
            return;
        if (clientAreaWidth == 0 || clientAreaHeight == 0)
            return;

        int startLine = getLineIndex(event.y);
        int y = getLinePixel(startLine);
        int endY = event.y + event.height;
        GC gc = event.gc;
        Color background = getBackground();
        Color foreground = getForeground();
        if (endY > 0) {
            int lineCount = isSingleLine() ? 1 : content.getLineCount();
            int x = leftMargin - horizontalScrollOffset;
            for (int i = startLine; y < endY && i < lineCount; i++) {
                y += renderer.drawLine(i, x, y, gc, background, foreground);
            }
            if (y < endY) {
                gc.setBackground(background);
                drawBackground(gc, 0, y, clientAreaWidth, endY - y);
            }
        }
        if (blockSelection && blockXLocation != -1) {
            gc.setBackground(getSelectionBackground());
            Rectangle rect = getBlockSelectionRectangle();
            gc.drawRectangle(rect.x, rect.y, Math.max(1, rect.width - 1), Math.max(1, rect.height - 1));
            gc.setAdvanced(true);
            if (gc.getAdvanced()) {
                gc.setAlpha(100);
                gc.fillRectangle(rect);
                gc.setAdvanced(false);
            }
        }

        // fill the margin background
        gc.setBackground(marginColor != null ? marginColor : background);
        if (topMargin > 0) {
            drawBackground(gc, 0, 0, clientAreaWidth, topMargin);
        }
        if (bottomMargin > 0) {
            drawBackground(gc, 0, clientAreaHeight - bottomMargin, clientAreaWidth, bottomMargin);
        }
        if (leftMargin - alignmentMargin > 0) {
            drawBackground(gc, 0, 0, leftMargin - alignmentMargin, clientAreaHeight);
        }
        if (rightMargin > 0) {
            drawBackground(gc, clientAreaWidth - rightMargin, 0, rightMargin, clientAreaHeight);
        }
    }

    /**
     * Recalculates the scroll bars. Rewraps all lines when in word
     * wrap mode.
     *
     * @param event resize event
     */
    void handleResize(Event event) {
        int oldHeight = clientAreaHeight;
        int oldWidth = clientAreaWidth;
        Rectangle clientArea = getClientArea();
        clientAreaHeight = clientArea.height;
        clientAreaWidth = clientArea.width;
        if (!alwaysShowScroll && ignoreResize != 0)
            return;

        redrawMargins(oldHeight, oldWidth);
        if (wordWrap) {
            if (oldWidth != clientAreaWidth) {
                renderer.reset(0, content.getLineCount());
                verticalScrollOffset = -1;
                renderer.calculateIdle();
                super.redraw();
            }
            if (oldHeight != clientAreaHeight) {
                if (oldHeight == 0)
                    topIndexY = 0;
                setScrollBars(true);
            }
            setCaretLocation();
        } else {
            renderer.calculateClientArea();
            setScrollBars(true);
            claimRightFreeSpace();
            // StyledText allows any value for horizontalScrollOffset when clientArea is zero
            // in setHorizontalPixel() and setHorisontalOffset(). Fixes bug 168429.
            if (clientAreaWidth != 0) {
                ScrollBar horizontalBar = getHorizontalBar();
                if (horizontalBar != null && horizontalBar.getVisible()) {
                    if (horizontalScrollOffset != horizontalBar.getSelection()) {
                        horizontalBar.setSelection(horizontalScrollOffset);
                        horizontalScrollOffset = horizontalBar.getSelection();
                    }
                }
            }
        }
        updateCaretVisibility();
        claimBottomFreeSpace();
        setAlignment();
        //TODO FIX TOP INDEX DURING RESIZE
        //   if (oldHeight != clientAreaHeight || wordWrap) {
        //      calculateTopIndex(0);
        //   }
    }

    /**
     * Updates the caret position and selection and the scroll bars to reflect
     * the content change.
     */
    void handleTextChanged(TextChangedEvent event) {
        int offset = ime.getCompositionOffset();
        if (offset != -1 && lastTextChangeStart < offset) {
            ime.setCompositionOffset(offset + lastTextChangeNewCharCount - lastTextChangeReplaceCharCount);
        }
        int firstLine = content.getLineAtOffset(lastTextChangeStart);
        resetCache(firstLine, 0);
        if (!isFixedLineHeight() && topIndex > firstLine) {
            topIndex = firstLine;
            if (topIndex < 0) {
                // TODO: This logging is in place to determine why topIndex is getting set to negative values.
                // It should be deleted once we fix the root cause of this issue. See bug 487254 for details.
                System.err.println("StyledText: topIndex was " + topIndex + ", lastTextChangeStart = "
                        + lastTextChangeStart + ", content.getClass() = " + content.getClass());
                topIndex = 0;
            }
            topIndexY = 0;
            super.redraw();
        } else {
            int lastLine = firstLine + lastTextChangeNewLineCount;
            int firstLineTop = getLinePixel(firstLine);
            int newLastLineBottom = getLinePixel(lastLine + 1);
            if (lastLineBottom != newLastLineBottom) {
                super.redraw();
            } else {
                super.redraw(0, firstLineTop, clientAreaWidth, newLastLineBottom - firstLineTop, false);
                redrawLinesBullet(renderer.redrawLines);
            }
        }
        renderer.redrawLines = null;
        // update selection/caret location after styles have been changed.
        // otherwise any text measuring could be incorrect
        //
        // also, this needs to be done after all scrolling. Otherwise,
        // selection redraw would be flushed during scroll which is wrong.
        // in some cases new text would be drawn in scroll source area even
        // though the intent is to scroll it.
        if (!(blockSelection && blockXLocation != -1)) {
            updateSelection(lastTextChangeStart, lastTextChangeReplaceCharCount, lastTextChangeNewCharCount);
        }
        if (lastTextChangeReplaceLineCount > 0 || wordWrap || visualWrap) {
            claimBottomFreeSpace();
        }
        if (lastTextChangeReplaceCharCount > 0) {
            claimRightFreeSpace();
        }

        sendAccessibleTextChanged(lastTextChangeStart, lastTextChangeNewCharCount, 0);
        lastCharCount += lastTextChangeNewCharCount;
        lastCharCount -= lastTextChangeReplaceCharCount;
        setAlignment();
    }

    /**
     * Updates the screen to reflect a pending content change.
     *
     * @param event .start the start offset of the change
     * @param event .newText text that is going to be inserted or empty String
     *   if no text will be inserted
     * @param event .replaceCharCount length of text that is going to be replaced
     * @param event .newCharCount length of text that is going to be inserted
     * @param event .replaceLineCount number of lines that are going to be replaced
     * @param event .newLineCount number of new lines that are going to be inserted
     */
    void handleTextChanging(TextChangingEvent event) {
        if (event.replaceCharCount < 0) {
            event.start += event.replaceCharCount;
            event.replaceCharCount *= -1;
        }
        lastTextChangeStart = event.start;
        lastTextChangeNewLineCount = event.newLineCount;
        lastTextChangeNewCharCount = event.newCharCount;
        lastTextChangeReplaceLineCount = event.replaceLineCount;
        lastTextChangeReplaceCharCount = event.replaceCharCount;
        int lineIndex = content.getLineAtOffset(event.start);
        int srcY = getLinePixel(lineIndex + event.replaceLineCount + 1);
        int destY = getLinePixel(lineIndex + 1) + event.newLineCount * renderer.getLineHeight();
        lastLineBottom = destY;
        if (srcY < 0 && destY < 0) {
            lastLineBottom += srcY - destY;
            verticalScrollOffset += destY - srcY;
            calculateTopIndex(destY - srcY);
            setScrollBars(true);
        } else {
            scrollText(srcY, destY);
        }
        sendAccessibleTextChanged(lastTextChangeStart, 0, lastTextChangeReplaceCharCount);
        renderer.textChanging(event);

        // Update the caret offset if it is greater than the length of the content.
        // This is necessary since style range API may be called between the
        // handleTextChanging and handleTextChanged events and this API sets the
        // caretOffset.
        int newEndOfText = content.getCharCount() - event.replaceCharCount + event.newCharCount;
        if (caretOffset > newEndOfText)
            setCaretOffset(newEndOfText, SWT.DEFAULT);
    }

    /**
     * Called when the widget content is set programmatically, overwriting
     * the old content. Resets the caret position, selection and scroll offsets.
     * Recalculates the content width and scroll bars. Redraws the widget.
     *
     * @param event text change event.
     */
    void handleTextSet(TextChangedEvent event) {
        reset();
        int newCharCount = getCharCount();
        sendAccessibleTextChanged(0, newCharCount, lastCharCount);
        lastCharCount = newCharCount;
        setAlignment();
    }

    /**
     * Called when a traversal key is pressed.
     * Allow tab next traversal to occur when the widget is in single
     * line mode or in multi line and non-editable mode .
     * When in editable multi line mode we want to prevent the tab
     * traversal and receive the tab key event instead.
     *
     * @param event the event
     */
    void handleTraverse(Event event) {
        switch (event.detail) {
        case SWT.TRAVERSE_ESCAPE:
        case SWT.TRAVERSE_PAGE_NEXT:
        case SWT.TRAVERSE_PAGE_PREVIOUS:
            event.doit = true;
            break;
        case SWT.TRAVERSE_RETURN:
        case SWT.TRAVERSE_TAB_NEXT:
        case SWT.TRAVERSE_TAB_PREVIOUS:
            if ((getStyle() & SWT.SINGLE) != 0) {
                event.doit = true;
            } else {
                if (!editable || (event.stateMask & SWT.MODIFIER_MASK) != 0) {
                    event.doit = true;
                }
            }
            break;
        }
    }

    /**
     * Scrolls the widget vertically.
     */
    void handleVerticalScroll(Event event) {
        int scrollPixel = getVerticalBar().getSelection() - getVerticalScrollOffset();
        scrollVertical(scrollPixel, false);
    }

    /**
     * Add accessibility support for the widget.
     */
    void initializeAccessible() {
        acc = getAccessible();

        accAdapter = new AccessibleAdapter() {
            @Override
            public void getName(AccessibleEvent e) {
                String name = null;
                String text = getAssociatedLabel();
                if (text != null) {
                    name = stripMnemonic(text);
                }
                e.result = name;
            }

            @Override
            public void getHelp(AccessibleEvent e) {
                e.result = getToolTipText();
            }

            @Override
            public void getKeyboardShortcut(AccessibleEvent e) {
                String shortcut = null;
                String text = getAssociatedLabel();
                if (text != null) {
                    char mnemonic = _findMnemonic(text);
                    if (mnemonic != '\0') {
                        shortcut = "Alt+" + mnemonic; //$NON-NLS-1$
                    }
                }
                e.result = shortcut;
            }
        };
        acc.addAccessibleListener(accAdapter);

        accTextExtendedAdapter = new AccessibleTextExtendedAdapter() {
            @Override
            public void getCaretOffset(AccessibleTextEvent e) {
                e.offset = StyledText.this.getCaretOffset();
            }

            @Override
            public void setCaretOffset(AccessibleTextEvent e) {
                StyledText.this.setCaretOffset(e.offset);
                e.result = ACC.OK;
            }

            @Override
            public void getSelectionRange(AccessibleTextEvent e) {
                Point selection = StyledText.this.getSelectionRange();
                e.offset = selection.x;
                e.length = selection.y;
            }

            @Override
            public void addSelection(AccessibleTextEvent e) {
                StyledText st = StyledText.this;
                Point point = st.getSelection();
                if (point.x == point.y) {
                    int end = e.end;
                    if (end == -1)
                        end = st.getCharCount();
                    st.setSelection(e.start, end);
                    e.result = ACC.OK;
                }
            }

            @Override
            public void getSelection(AccessibleTextEvent e) {
                StyledText st = StyledText.this;
                if (st.blockSelection && st.blockXLocation != -1) {
                    Rectangle rect = st.getBlockSelectionPosition();
                    int lineIndex = rect.y + e.index;
                    int linePixel = st.getLinePixel(lineIndex);
                    e.ranges = getRanges(rect.x, linePixel, rect.width, linePixel);
                    if (e.ranges.length > 0) {
                        e.start = e.ranges[0];
                        e.end = e.ranges[e.ranges.length - 1];
                    }
                } else {
                    if (e.index == 0) {
                        Point point = st.getSelection();
                        e.start = point.x;
                        e.end = point.y;
                        if (e.start > e.end) {
                            int temp = e.start;
                            e.start = e.end;
                            e.end = temp;
                        }
                    }
                }
            }

            @Override
            public void getSelectionCount(AccessibleTextEvent e) {
                StyledText st = StyledText.this;
                if (st.blockSelection && st.blockXLocation != -1) {
                    Rectangle rect = st.getBlockSelectionPosition();
                    e.count = rect.height - rect.y + 1;
                } else {
                    Point point = st.getSelection();
                    e.count = point.x == point.y ? 0 : 1;
                }
            }

            @Override
            public void removeSelection(AccessibleTextEvent e) {
                StyledText st = StyledText.this;
                if (e.index == 0) {
                    if (st.blockSelection) {
                        st.clearBlockSelection(true, false);
                    } else {
                        st.clearSelection(false);
                    }
                    e.result = ACC.OK;
                }
            }

            @Override
            public void setSelection(AccessibleTextEvent e) {
                if (e.index != 0)
                    return;
                StyledText st = StyledText.this;
                Point point = st.getSelection();
                if (point.x == point.y)
                    return;
                int end = e.end;
                if (end == -1)
                    end = st.getCharCount();
                st.setSelection(e.start, end);
                e.result = ACC.OK;
            }

            @Override
            public void getCharacterCount(AccessibleTextEvent e) {
                e.count = StyledText.this.getCharCount();
            }

            @Override
            public void getOffsetAtPoint(AccessibleTextEvent e) {
                StyledText st = StyledText.this;
                Point point = new Point(e.x, e.y);
                Display display = st.getDisplay();
                point = display.map(null, st, point);
                e.offset = st.getOffsetAtPoint(point.x, point.y, null, true);
            }

            @Override
            public void getTextBounds(AccessibleTextEvent e) {
                StyledText st = StyledText.this;
                int start = e.start;
                int end = e.end;
                int contentLength = st.getCharCount();
                start = Math.max(0, Math.min(start, contentLength));
                end = Math.max(0, Math.min(end, contentLength));
                if (start > end) {
                    int temp = start;
                    start = end;
                    end = temp;
                }
                int startLine = st.getLineAtOffset(start);
                int endLine = st.getLineAtOffset(end);
                Rectangle[] rects = new Rectangle[endLine - startLine + 1];
                Rectangle bounds = null;
                int index = 0;
                Display display = st.getDisplay();
                for (int lineIndex = startLine; lineIndex <= endLine; lineIndex++) {
                    Rectangle rect = new Rectangle(0, 0, 0, 0);
                    rect.y = st.getLinePixel(lineIndex);
                    rect.height = st.renderer.getLineHeight(lineIndex);
                    if (lineIndex == startLine) {
                        rect.x = st.getPointAtOffset(start).x;
                    } else {
                        rect.x = st.leftMargin - st.horizontalScrollOffset;
                    }
                    if (lineIndex == endLine) {
                        rect.width = st.getPointAtOffset(end).x - rect.x;
                    } else {
                        TextLayout layout = st.renderer.getTextLayout(lineIndex);
                        rect.width = layout.getBounds().width - rect.x;
                        st.renderer.disposeTextLayout(layout);
                    }
                    rects[index++] = rect = display.map(st, null, rect);
                    if (bounds == null) {
                        bounds = new Rectangle(rect.x, rect.y, rect.width, rect.height);
                    } else {
                        bounds.add(rect);
                    }
                }
                e.rectangles = rects;
                if (bounds != null) {
                    e.x = bounds.x;
                    e.y = bounds.y;
                    e.width = bounds.width;
                    e.height = bounds.height;
                }
            }

            int[] getRanges(int left, int top, int right, int bottom) {
                StyledText st = StyledText.this;
                int lineStart = st.getLineIndex(top);
                int lineEnd = st.getLineIndex(bottom);
                int count = lineEnd - lineStart + 1;
                int[] ranges = new int[count * 2];
                int index = 0;
                for (int lineIndex = lineStart; lineIndex <= lineEnd; lineIndex++) {
                    String line = st.content.getLine(lineIndex);
                    int lineOffset = st.content.getOffsetAtLine(lineIndex);
                    int lineEndOffset = lineOffset + line.length();
                    int linePixel = st.getLinePixel(lineIndex);
                    int start = st.getOffsetAtPoint(left, linePixel, null, true);
                    if (start == -1) {
                        start = left < st.leftMargin ? lineOffset : lineEndOffset;
                    }
                    int[] trailing = new int[1];
                    int end = st.getOffsetAtPoint(right, linePixel, trailing, true);
                    if (end == -1) {
                        end = right < st.leftMargin ? lineOffset : lineEndOffset;
                    } else {
                        end += trailing[0];
                    }
                    if (start > end) {
                        int temp = start;
                        start = end;
                        end = temp;
                    }
                    ranges[index++] = start;
                    ranges[index++] = end;
                }
                return ranges;
            }

            @Override
            public void getRanges(AccessibleTextEvent e) {
                StyledText st = StyledText.this;
                Point point = new Point(e.x, e.y);
                Display display = st.getDisplay();
                point = display.map(null, st, point);
                e.ranges = getRanges(point.x, point.y, point.x + e.width, point.y + e.height);
                if (e.ranges.length > 0) {
                    e.start = e.ranges[0];
                    e.end = e.ranges[e.ranges.length - 1];
                }
            }

            @Override
            public void getText(AccessibleTextEvent e) {
                StyledText st = StyledText.this;
                int start = e.start;
                int end = e.end;
                int contentLength = st.getCharCount();
                if (end == -1)
                    end = contentLength;
                start = Math.max(0, Math.min(start, contentLength));
                end = Math.max(0, Math.min(end, contentLength));
                if (start > end) {
                    int temp = start;
                    start = end;
                    end = temp;
                }
                int count = e.count;
                switch (e.type) {
                case ACC.TEXT_BOUNDARY_ALL:
                    //nothing to do
                    break;
                case ACC.TEXT_BOUNDARY_CHAR: {
                    int newCount = 0;
                    if (count > 0) {
                        while (count-- > 0) {
                            int newEnd = st.getWordNext(end, SWT.MOVEMENT_CLUSTER);
                            if (newEnd == contentLength)
                                break;
                            if (newEnd == end)
                                break;
                            end = newEnd;
                            newCount++;
                        }
                        start = end;
                        end = st.getWordNext(end, SWT.MOVEMENT_CLUSTER);
                    } else {
                        while (count++ < 0) {
                            int newStart = st.getWordPrevious(start, SWT.MOVEMENT_CLUSTER);
                            if (newStart == start)
                                break;
                            start = newStart;
                            newCount--;
                        }
                        end = st.getWordNext(start, SWT.MOVEMENT_CLUSTER);
                    }
                    count = newCount;
                    break;
                }
                case ACC.TEXT_BOUNDARY_WORD: {
                    int newCount = 0;
                    if (count > 0) {
                        while (count-- > 0) {
                            int newEnd = st.getWordNext(end, SWT.MOVEMENT_WORD_START, true);
                            if (newEnd == end)
                                break;
                            newCount++;
                            end = newEnd;
                        }
                        start = end;
                        end = st.getWordNext(start, SWT.MOVEMENT_WORD_END, true);
                    } else {
                        if (st.getWordPrevious(Math.min(start + 1, contentLength), SWT.MOVEMENT_WORD_START,
                                true) == start) {
                            //start is a word start already
                            count++;
                        }
                        while (count <= 0) {
                            int newStart = st.getWordPrevious(start, SWT.MOVEMENT_WORD_START, true);
                            if (newStart == start)
                                break;
                            count++;
                            start = newStart;
                            if (count != 0)
                                newCount--;
                        }
                        if (count <= 0 && start == 0) {
                            end = start;
                        } else {
                            end = st.getWordNext(start, SWT.MOVEMENT_WORD_END, true);
                        }
                    }
                    count = newCount;
                    break;
                }
                case ACC.TEXT_BOUNDARY_LINE:
                    //TODO implement line
                case ACC.TEXT_BOUNDARY_PARAGRAPH:
                case ACC.TEXT_BOUNDARY_SENTENCE: {
                    int offset = count > 0 ? end : start;
                    int lineIndex = st.getLineAtOffset(offset) + count;
                    lineIndex = Math.max(0, Math.min(lineIndex, st.getLineCount() - 1));
                    start = st.getOffsetAtLine(lineIndex);
                    String line = st.getLine(lineIndex);
                    end = start + line.length();
                    count = lineIndex - st.getLineAtOffset(offset);
                    break;
                }
                }
                e.start = start;
                e.end = end;
                e.count = count;
                e.result = st.content.getTextRange(start, end - start);
            }

            @Override
            public void getVisibleRanges(AccessibleTextEvent e) {
                e.ranges = getRanges(leftMargin, topMargin, clientAreaWidth - rightMargin,
                        clientAreaHeight - bottomMargin);
                if (e.ranges.length > 0) {
                    e.start = e.ranges[0];
                    e.end = e.ranges[e.ranges.length - 1];
                }
            }

            @Override
            public void scrollText(AccessibleTextEvent e) {
                StyledText st = StyledText.this;
                int topPixel = getTopPixel(), horizontalPixel = st.getHorizontalPixel();
                switch (e.type) {
                case ACC.SCROLL_TYPE_ANYWHERE:
                case ACC.SCROLL_TYPE_TOP_LEFT:
                case ACC.SCROLL_TYPE_LEFT_EDGE:
                case ACC.SCROLL_TYPE_TOP_EDGE: {
                    Rectangle rect = st.getBoundsAtOffset(e.start);
                    if (e.type != ACC.SCROLL_TYPE_TOP_EDGE) {
                        horizontalPixel = horizontalPixel + rect.x - st.leftMargin;
                    }
                    if (e.type != ACC.SCROLL_TYPE_LEFT_EDGE) {
                        topPixel = topPixel + rect.y - st.topMargin;
                    }
                    break;
                }
                case ACC.SCROLL_TYPE_BOTTOM_RIGHT:
                case ACC.SCROLL_TYPE_BOTTOM_EDGE:
                case ACC.SCROLL_TYPE_RIGHT_EDGE: {
                    Rectangle rect = st.getBoundsAtOffset(e.end - 1);
                    if (e.type != ACC.SCROLL_TYPE_BOTTOM_EDGE) {
                        horizontalPixel = horizontalPixel - st.clientAreaWidth + rect.x + rect.width
                                + st.rightMargin;
                    }
                    if (e.type != ACC.SCROLL_TYPE_RIGHT_EDGE) {
                        topPixel = topPixel - st.clientAreaHeight + rect.y + rect.height + st.bottomMargin;
                    }
                    break;
                }
                case ACC.SCROLL_TYPE_POINT: {
                    Point point = new Point(e.x, e.y);
                    Display display = st.getDisplay();
                    point = display.map(null, st, point);
                    Rectangle rect = st.getBoundsAtOffset(e.start);
                    topPixel = topPixel - point.y + rect.y;
                    horizontalPixel = horizontalPixel - point.x + rect.x;
                    break;
                }
                }
                st.setTopPixel(topPixel);
                st.setHorizontalPixel(horizontalPixel);
                e.result = ACC.OK;
            }
        };
        acc.addAccessibleTextListener(accTextExtendedAdapter);

        accEditableTextListener = new AccessibleEditableTextListener() {
            @Override
            public void setTextAttributes(AccessibleTextAttributeEvent e) {
                // This method must be implemented by the application
                e.result = ACC.OK;
            }

            @Override
            public void replaceText(AccessibleEditableTextEvent e) {
                StyledText st = StyledText.this;
                st.replaceTextRange(e.start, e.end - e.start, e.string);
                e.result = ACC.OK;
            }

            @Override
            public void pasteText(AccessibleEditableTextEvent e) {
                StyledText st = StyledText.this;
                st.setSelection(e.start);
                st.paste();
                e.result = ACC.OK;
            }

            @Override
            public void cutText(AccessibleEditableTextEvent e) {
                StyledText st = StyledText.this;
                st.setSelection(e.start, e.end);
                st.cut();
                e.result = ACC.OK;
            }

            @Override
            public void copyText(AccessibleEditableTextEvent e) {
                StyledText st = StyledText.this;
                st.setSelection(e.start, e.end);
                st.copy();
                e.result = ACC.OK;
            }
        };
        acc.addAccessibleEditableTextListener(accEditableTextListener);

        accAttributeAdapter = new AccessibleAttributeAdapter() {
            @Override
            public void getAttributes(AccessibleAttributeEvent e) {
                StyledText st = StyledText.this;
                e.leftMargin = st.getLeftMargin();
                e.topMargin = st.getTopMargin();
                e.rightMargin = st.getRightMargin();
                e.bottomMargin = st.getBottomMargin();
                e.tabStops = st.getTabStops();
                e.justify = st.getJustify();
                e.alignment = st.getAlignment();
                e.indent = st.getIndent();
            }

            @Override
            public void getTextAttributes(AccessibleTextAttributeEvent e) {
                StyledText st = StyledText.this;
                int contentLength = st.getCharCount();
                if (!isListening(ST.LineGetStyle) && st.renderer.styleCount == 0) {
                    e.start = 0;
                    e.end = contentLength;
                    e.textStyle = new TextStyle(st.getFont(), st.foreground, st.background);
                    return;
                }
                int offset = Math.max(0, Math.min(e.offset, contentLength - 1));
                int lineIndex = st.getLineAtOffset(offset);
                int lineOffset = st.getOffsetAtLine(lineIndex);
                int lineCount = st.getLineCount();
                offset = offset - lineOffset;

                TextLayout layout = st.renderer.getTextLayout(lineIndex);
                int lineLength = layout.getText().length();
                if (lineLength > 0) {
                    e.textStyle = layout.getStyle(Math.max(0, Math.min(offset, lineLength - 1)));
                }

                // If no override info available, use defaults. Don't supply default colors, though.
                if (e.textStyle == null) {
                    e.textStyle = new TextStyle(st.getFont(), st.foreground, st.background);
                } else {
                    if (e.textStyle.foreground == null || e.textStyle.background == null
                            || e.textStyle.font == null) {
                        TextStyle textStyle = new TextStyle(e.textStyle);
                        if (textStyle.foreground == null)
                            textStyle.foreground = st.foreground;
                        if (textStyle.background == null)
                            textStyle.background = st.background;
                        if (textStyle.font == null)
                            textStyle.font = st.getFont();
                        e.textStyle = textStyle;
                    }
                }

                //offset at line delimiter case
                if (offset >= lineLength) {
                    e.start = lineOffset + lineLength;
                    if (lineIndex + 1 < lineCount) {
                        e.end = st.getOffsetAtLine(lineIndex + 1);
                    } else {
                        e.end = contentLength;
                    }
                    return;
                }

                int[] ranges = layout.getRanges();
                st.renderer.disposeTextLayout(layout);
                int index = 0;
                int end = 0;
                while (index < ranges.length) {
                    int styleStart = ranges[index++];
                    int styleEnd = ranges[index++];
                    if (styleStart <= offset && offset <= styleEnd) {
                        e.start = lineOffset + styleStart;
                        e.end = lineOffset + styleEnd + 1;
                        return;
                    }
                    if (styleStart > offset) {
                        e.start = lineOffset + end;
                        e.end = lineOffset + styleStart;
                        return;
                    }
                    end = styleEnd + 1;
                }
                if (index == ranges.length) {
                    e.start = lineOffset + end;
                    if (lineIndex + 1 < lineCount) {
                        e.end = st.getOffsetAtLine(lineIndex + 1);
                    } else {
                        e.end = contentLength;
                    }
                }
            }
        };
        acc.addAccessibleAttributeListener(accAttributeAdapter);

        accControlAdapter = new AccessibleControlAdapter() {
            @Override
            public void getRole(AccessibleControlEvent e) {
                e.detail = ACC.ROLE_TEXT;
            }

            @Override
            public void getState(AccessibleControlEvent e) {
                int state = 0;
                if (isEnabled())
                    state |= ACC.STATE_FOCUSABLE;
                if (isFocusControl())
                    state |= ACC.STATE_FOCUSED;
                if (!isVisible())
                    state |= ACC.STATE_INVISIBLE;
                if (!getEditable())
                    state |= ACC.STATE_READONLY;
                if (isSingleLine())
                    state |= ACC.STATE_SINGLELINE;
                else
                    state |= ACC.STATE_MULTILINE;
                e.detail = state;
            }

            @Override
            public void getValue(AccessibleControlEvent e) {
                e.result = StyledText.this.getText();
            }
        };
        acc.addAccessibleControlListener(accControlAdapter);

        addListener(SWT.FocusIn, event -> acc.setFocus(ACC.CHILDID_SELF));
    }

    @Override
    public void dispose() {
        /*
         * Note: It is valid to attempt to dispose a widget more than once.
         * Added check for this.
         */
        if (!isDisposed()) {
            acc.removeAccessibleControlListener(accControlAdapter);
            acc.removeAccessibleAttributeListener(accAttributeAdapter);
            acc.removeAccessibleEditableTextListener(accEditableTextListener);
            acc.removeAccessibleTextListener(accTextExtendedAdapter);
            acc.removeAccessibleListener(accAdapter);
        }
        super.dispose();
    }

    /*
     * Return the Label immediately preceding the receiver in the z-order,
     * or null if none.
     */
    String getAssociatedLabel() {
        Control[] siblings = getParent().getChildren();
        for (int i = 0; i < siblings.length; i++) {
            if (siblings[i] == StyledText.this) {
                if (i > 0) {
                    Control sibling = siblings[i - 1];
                    if (sibling instanceof Label)
                        return ((Label) sibling).getText();
                    if (sibling instanceof CLabel)
                        return ((CLabel) sibling).getText();
                }
                break;
            }
        }
        return null;
    }

    String stripMnemonic(String string) {
        int index = 0;
        int length = string.length();
        do {
            while ((index < length) && (string.charAt(index) != '&'))
                index++;
            if (++index >= length)
                return string;
            if (string.charAt(index) != '&') {
                return string.substring(0, index - 1) + string.substring(index, length);
            }
            index++;
        } while (index < length);
        return string;
    }

    /*
     * Return the lowercase of the first non-'&' character following
     * an '&' character in the given string. If there are no '&'
     * characters in the given string, return '\0'.
     */
    char _findMnemonic(String string) {
        if (string == null)
            return '\0';
        int index = 0;
        int length = string.length();
        do {
            while (index < length && string.charAt(index) != '&')
                index++;
            if (++index >= length)
                return '\0';
            if (string.charAt(index) != '&')
                return Character.toLowerCase(string.charAt(index));
            index++;
        } while (index < length);
        return '\0';
    }

    /**
     * Executes the action.
     *
     * @param action one of the actions defined in ST.java
     */
    public void invokeAction(int action) {
        checkWidget();
        if (blockSelection && invokeBlockAction(action))
            return;
        updateCaretDirection = true;
        switch (action) {
        // Navigation
        case ST.LINE_UP:
            doLineUp(false);
            clearSelection(true);
            break;
        case ST.LINE_DOWN:
            doLineDown(false);
            clearSelection(true);
            break;
        case ST.LINE_START:
            doLineStart();
            clearSelection(true);
            break;
        case ST.LINE_END:
            doLineEnd();
            clearSelection(true);
            break;
        case ST.COLUMN_PREVIOUS:
            doCursorPrevious();
            clearSelection(true);
            break;
        case ST.COLUMN_NEXT:
            doCursorNext();
            clearSelection(true);
            break;
        case ST.PAGE_UP:
            doPageUp(false, -1);
            clearSelection(true);
            break;
        case ST.PAGE_DOWN:
            doPageDown(false, -1);
            clearSelection(true);
            break;
        case ST.WORD_PREVIOUS:
            doWordPrevious();
            clearSelection(true);
            break;
        case ST.WORD_NEXT:
            doWordNext();
            clearSelection(true);
            break;
        case ST.TEXT_START:
            doContentStart();
            clearSelection(true);
            break;
        case ST.TEXT_END:
            doContentEnd();
            clearSelection(true);
            break;
        case ST.WINDOW_START:
            doPageStart();
            clearSelection(true);
            break;
        case ST.WINDOW_END:
            doPageEnd();
            clearSelection(true);
            break;
        // Selection
        case ST.SELECT_LINE_UP:
            doSelectionLineUp();
            break;
        case ST.SELECT_ALL:
            selectAll();
            break;
        case ST.SELECT_LINE_DOWN:
            doSelectionLineDown();
            break;
        case ST.SELECT_LINE_START:
            doLineStart();
            doSelection(ST.COLUMN_PREVIOUS);
            break;
        case ST.SELECT_LINE_END:
            doLineEnd();
            doSelection(ST.COLUMN_NEXT);
            break;
        case ST.SELECT_COLUMN_PREVIOUS:
            doSelectionCursorPrevious();
            doSelection(ST.COLUMN_PREVIOUS);
            break;
        case ST.SELECT_COLUMN_NEXT:
            doSelectionCursorNext();
            doSelection(ST.COLUMN_NEXT);
            break;
        case ST.SELECT_PAGE_UP:
            doSelectionPageUp(-1);
            break;
        case ST.SELECT_PAGE_DOWN:
            doSelectionPageDown(-1);
            break;
        case ST.SELECT_WORD_PREVIOUS:
            doSelectionWordPrevious();
            doSelection(ST.COLUMN_PREVIOUS);
            break;
        case ST.SELECT_WORD_NEXT:
            doSelectionWordNext();
            doSelection(ST.COLUMN_NEXT);
            break;
        case ST.SELECT_TEXT_START:
            doContentStart();
            doSelection(ST.COLUMN_PREVIOUS);
            break;
        case ST.SELECT_TEXT_END:
            doContentEnd();
            doSelection(ST.COLUMN_NEXT);
            break;
        case ST.SELECT_WINDOW_START:
            doPageStart();
            doSelection(ST.COLUMN_PREVIOUS);
            break;
        case ST.SELECT_WINDOW_END:
            doPageEnd();
            doSelection(ST.COLUMN_NEXT);
            break;
        // Modification
        case ST.CUT:
            cut();
            break;
        case ST.COPY:
            copy();
            break;
        case ST.PASTE:
            paste();
            break;
        case ST.DELETE_PREVIOUS:
            doBackspace();
            break;
        case ST.DELETE_NEXT:
            doDelete();
            break;
        case ST.DELETE_WORD_PREVIOUS:
            doDeleteWordPrevious();
            break;
        case ST.DELETE_WORD_NEXT:
            doDeleteWordNext();
            break;
        // Miscellaneous
        case ST.TOGGLE_OVERWRITE:
            overwrite = !overwrite; // toggle insert/overwrite mode
            break;
        case ST.TOGGLE_BLOCKSELECTION:
            setBlockSelection(!blockSelection);
            break;
        }
    }

    /**
    * Returns true if an action should not be performed when block
    * selection in active
    */
    boolean invokeBlockAction(int action) {
        switch (action) {
        // Navigation
        case ST.LINE_UP:
        case ST.LINE_DOWN:
        case ST.LINE_START:
        case ST.LINE_END:
        case ST.COLUMN_PREVIOUS:
        case ST.COLUMN_NEXT:
        case ST.PAGE_UP:
        case ST.PAGE_DOWN:
        case ST.WORD_PREVIOUS:
        case ST.WORD_NEXT:
        case ST.TEXT_START:
        case ST.TEXT_END:
        case ST.WINDOW_START:
        case ST.WINDOW_END:
            clearBlockSelection(false, false);
            return false;
        // Selection
        case ST.SELECT_LINE_UP:
            doBlockLineVertical(true);
            return true;
        case ST.SELECT_LINE_DOWN:
            doBlockLineVertical(false);
            return true;
        case ST.SELECT_LINE_START:
            doBlockLineHorizontal(false);
            return true;
        case ST.SELECT_LINE_END:
            doBlockLineHorizontal(true);
            return false;
        case ST.SELECT_COLUMN_PREVIOUS:
            doBlockColumn(false);
            return true;
        case ST.SELECT_COLUMN_NEXT:
            doBlockColumn(true);
            return true;
        case ST.SELECT_WORD_PREVIOUS:
            doBlockWord(false);
            return true;
        case ST.SELECT_WORD_NEXT:
            doBlockWord(true);
            return true;
        case ST.SELECT_ALL:
            return false;
        case ST.SELECT_TEXT_START:
            doBlockContentStartEnd(false);
            break;
        case ST.SELECT_TEXT_END:
            doBlockContentStartEnd(true);
            break;
        case ST.SELECT_PAGE_UP:
        case ST.SELECT_PAGE_DOWN:
        case ST.SELECT_WINDOW_START:
        case ST.SELECT_WINDOW_END:
            //blocked actions
            return true;
        // Modification
        case ST.CUT:
        case ST.COPY:
        case ST.PASTE:
            return false;
        case ST.DELETE_PREVIOUS:
        case ST.DELETE_NEXT:
            if (blockXLocation != -1) {
                insertBlockSelectionText((char) 0, action);
                return true;
            }
            return false;
        case ST.DELETE_WORD_PREVIOUS:
        case ST.DELETE_WORD_NEXT:
            //blocked actions
            return blockXLocation != -1;
        }
        return false;
    }

    boolean isBidiCaret() {
        return BidiUtil.isBidiPlatform();
    }

    boolean isFixedLineHeight() {
        return fixedLineHeight;
    }

    /**
     * Returns whether the given offset is inside a multi byte line delimiter.
     * Example:
     * "Line1\r\n" isLineDelimiter(5) == false but isLineDelimiter(6) == true
     *
     * @return true if the given offset is inside a multi byte line delimiter.
     * false if the given offset is before or after a line delimiter.
     */
    boolean isLineDelimiter(int offset) {
        int line = content.getLineAtOffset(offset);
        int lineOffset = content.getOffsetAtLine(line);
        int offsetInLine = offset - lineOffset;
        // offsetInLine will be greater than line length if the line
        // delimiter is longer than one character and the offset is set
        // in between parts of the line delimiter.
        return offsetInLine > content.getLine(line).length();
    }

    /**
     * Returns whether the widget is mirrored (right oriented/right to left
     * writing order).
     *
     * @return isMirrored true=the widget is right oriented, false=the widget
     *    is left oriented
     */
    boolean isMirrored() {
        return (getStyle() & SWT.MIRRORED) != 0;
    }

    /**
     * Returns <code>true</code> if any text in the widget is selected,
     * and <code>false</code> otherwise.
     *
     * @return the text selection state
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.103
     */
    public boolean isTextSelected() {
        checkWidget();
        if (blockSelection && blockXLocation != -1) {
            Rectangle rect = getBlockSelectionPosition();
            return !rect.isEmpty();
        }
        return selection.y != selection.x;
    }

    /**
     * Returns whether the widget can have only one line.
     *
     * @return true if widget can have only one line, false if widget can have
     *    multiple lines
     */
    boolean isSingleLine() {
        return (getStyle() & SWT.SINGLE) != 0;
    }

    /**
     * Sends the specified verify event, replace/insert text as defined by
     * the event and send a modify event.
     *
     * @param event   the text change event.
     *   <ul>
     *   <li>event.start - the replace start offset</li>
     *    <li>event.end - the replace end offset</li>
     *    <li>event.text - the new text</li>
     *   </ul>
     * @param updateCaret whether or not he caret should be set behind
     *   the new text
     */
    void modifyContent(Event event, boolean updateCaret) {
        event.doit = true;
        notifyListeners(SWT.Verify, event);
        if (event.doit) {
            StyledTextEvent styledTextEvent = null;
            int replacedLength = event.end - event.start;
            if (isListening(ST.ExtendedModify)) {
                styledTextEvent = new StyledTextEvent(content);
                styledTextEvent.start = event.start;
                styledTextEvent.end = event.start + event.text.length();
                styledTextEvent.text = content.getTextRange(event.start, replacedLength);
            }
            if (updateCaret) {
                //Fix advancing flag for delete/backspace key on direction boundary
                if (event.text.length() == 0) {
                    int lineIndex = content.getLineAtOffset(event.start);
                    int lineOffset = content.getOffsetAtLine(lineIndex);
                    TextLayout layout = renderer.getTextLayout(lineIndex);
                    int levelStart = layout.getLevel(event.start - lineOffset);
                    int lineIndexEnd = content.getLineAtOffset(event.end);
                    if (lineIndex != lineIndexEnd) {
                        renderer.disposeTextLayout(layout);
                        lineOffset = content.getOffsetAtLine(lineIndexEnd);
                        layout = renderer.getTextLayout(lineIndexEnd);
                    }
                    int levelEnd = layout.getLevel(event.end - lineOffset);
                    renderer.disposeTextLayout(layout);
                    if (levelStart != levelEnd) {
                        caretAlignment = PREVIOUS_OFFSET_TRAILING;
                    } else {
                        caretAlignment = OFFSET_LEADING;
                    }
                }
            }
            content.replaceTextRange(event.start, replacedLength, event.text);
            // set the caret position prior to sending the modify event.
            // fixes 1GBB8NJ
            if (updateCaret && !(blockSelection && blockXLocation != -1)) {
                // always update the caret location. fixes 1G8FODP
                setSelection(event.start + event.text.length(), 0, true, false);
                showCaret();
            }
            notifyListeners(SWT.Modify, event);
            if (isListening(ST.ExtendedModify)) {
                notifyListeners(ST.ExtendedModify, styledTextEvent);
            }
        }
    }

    void paintObject(GC gc, int x, int y, int ascent, int descent, StyleRange style, Bullet bullet,
            int bulletIndex) {
        if (isListening(ST.PaintObject)) {
            StyledTextEvent event = new StyledTextEvent(content);
            event.gc = gc;
            event.x = x;
            event.y = y;
            event.ascent = ascent;
            event.descent = descent;
            event.style = style;
            event.bullet = bullet;
            event.bulletIndex = bulletIndex;
            notifyListeners(ST.PaintObject, event);
        }
    }

    /**
     * Replaces the selection with the text on the <code>DND.CLIPBOARD</code>
     * clipboard  or, if there is no selection,  inserts the text at the current
     * caret offset.   If the widget has the SWT.SINGLE style and the
     * clipboard text contains more than one line, only the first line without
     * line delimiters is  inserted in the widget.
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void paste() {
        checkWidget();
        String text = (String) getClipboardContent(DND.CLIPBOARD);
        if (text != null && text.length() > 0) {
            if (blockSelection) {
                boolean fillWithSpaces = isFixedLineHeight() && renderer.fixedPitch;
                int offset = insertBlockSelectionText(text, fillWithSpaces);
                setCaretOffset(offset, SWT.DEFAULT);
                clearBlockSelection(true, true);
                setCaretLocation();
                return;
            }
            Event event = new Event();
            event.start = selection.x;
            event.end = selection.y;
            String delimitedText = getModelDelimitedText(text);
            if (textLimit > 0) {
                int uneditedTextLength = getCharCount() - (selection.y - selection.x);
                if ((uneditedTextLength + delimitedText.length()) > textLimit) {
                    int endIndex = textLimit - uneditedTextLength;
                    delimitedText = delimitedText.substring(0, Math.max(endIndex, 0));
                }
            }
            event.text = delimitedText;
            sendKeyEvent(event);
        }
    }

    private void pasteOnMiddleClick(Event event) {
        String text = (String) getClipboardContent(DND.SELECTION_CLIPBOARD);
        if (text != null && text.length() > 0) {
            // position cursor
            doMouseLocationChange(event.x, event.y, false);
            // insert text
            Event e = new Event();
            e.start = selection.x;
            e.end = selection.y;
            e.text = getModelDelimitedText(text);
            sendKeyEvent(e);
        }
    }

    /**
     * Prints the widget's text to the default printer.
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void print() {
        checkWidget();
        Printer printer = new Printer();
        StyledTextPrintOptions options = new StyledTextPrintOptions();
        options.printTextForeground = true;
        options.printTextBackground = true;
        options.printTextFontStyle = true;
        options.printLineBackground = true;
        new Printing(this, printer, options).run();
        printer.dispose();
    }

    /**
     * Returns a runnable that will print the widget's text
     * to the specified printer.
     * <p>
     * The runnable may be run in a non-UI thread.
     * </p>
     *
     * @param printer the printer to print to
     *
     * @return a <code>Runnable</code> for printing the receiver's text
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when printer is null</li>
     * </ul>
     */
    public Runnable print(Printer printer) {
        checkWidget();
        if (printer == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        StyledTextPrintOptions options = new StyledTextPrintOptions();
        options.printTextForeground = true;
        options.printTextBackground = true;
        options.printTextFontStyle = true;
        options.printLineBackground = true;
        return print(printer, options);
    }

    /**
     * Returns a runnable that will print the widget's text
     * to the specified printer.
     * <p>
     * The runnable may be run in a non-UI thread.
     * </p>
     *
     * @param printer the printer to print to
     * @param options print options to use during printing
     *
     * @return a <code>Runnable</code> for printing the receiver's text
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when printer or options is null</li>
     * </ul>
     * @since 2.1
     */
    public Runnable print(Printer printer, StyledTextPrintOptions options) {
        checkWidget();
        if (printer == null || options == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        return new Printing(this, printer, options);
    }

    /**
     * Causes the entire bounds of the receiver to be marked
     * as needing to be redrawn. The next time a paint request
     * is processed, the control will be completely painted.
     * <p>
     * Recalculates the content width for all lines in the bounds.
     * When a <code>LineStyleListener</code> is used a redraw call
     * is the only notification to the widget that styles have changed
     * and that the content width may have changed.
     * </p>
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see Control#update()
     */
    @Override
    public void redraw() {
        super.redraw();
        int itemCount = getPartialBottomIndex() - topIndex + 1;
        renderer.reset(topIndex, itemCount);
        renderer.calculate(topIndex, itemCount);
        setScrollBars(false);
        doMouseLinkCursor();
    }

    /**
     * Causes the rectangular area of the receiver specified by
     * the arguments to be marked as needing to be redrawn.
     * The next time a paint request is processed, that area of
     * the receiver will be painted. If the <code>all</code> flag
     * is <code>true</code>, any children of the receiver which
     * intersect with the specified area will also paint their
     * intersecting areas. If the <code>all</code> flag is
     * <code>false</code>, the children will not be painted.
     * <p>
     * Marks the content width of all lines in the specified rectangle
     * as unknown. Recalculates the content width of all visible lines.
     * When a <code>LineStyleListener</code> is used a redraw call
     * is the only notification to the widget that styles have changed
     * and that the content width may have changed.
     * </p>
     *
     * @param x the x coordinate of the area to draw
     * @param y the y coordinate of the area to draw
     * @param width the width of the area to draw
     * @param height the height of the area to draw
     * @param all <code>true</code> if children should redraw, and <code>false</code> otherwise
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see Control#update()
     */
    @Override
    public void redraw(int x, int y, int width, int height, boolean all) {
        super.redraw(x, y, width, height, all);
        if (height > 0) {
            int firstLine = getLineIndex(y);
            int lastLine = getLineIndex(y + height);
            resetCache(firstLine, lastLine - firstLine + 1);
            doMouseLinkCursor();
        }
    }

    void redrawLines(int startLine, int lineCount, boolean bottomChanged) {
        // do nothing if redraw range is completely invisible
        int endLine = startLine + lineCount - 1;
        int partialBottomIndex = getPartialBottomIndex();
        int partialTopIndex = getPartialTopIndex();
        if (startLine > partialBottomIndex || endLine < partialTopIndex) {
            return;
        }
        // only redraw visible lines
        if (startLine < partialTopIndex) {
            startLine = partialTopIndex;
        }
        if (endLine > partialBottomIndex) {
            endLine = partialBottomIndex;
        }
        int redrawTop = getLinePixel(startLine);
        int redrawBottom = getLinePixel(endLine + 1);
        if (bottomChanged)
            redrawBottom = clientAreaHeight - bottomMargin;
        int redrawWidth = clientAreaWidth - leftMargin - rightMargin;
        super.redraw(leftMargin, redrawTop, redrawWidth, redrawBottom - redrawTop, true);
    }

    void redrawLinesBullet(int[] redrawLines) {
        if (redrawLines == null)
            return;
        int topIndex = getPartialTopIndex();
        int bottomIndex = getPartialBottomIndex();
        for (int i = 0; i < redrawLines.length; i++) {
            int lineIndex = redrawLines[i];
            if (!(topIndex <= lineIndex && lineIndex <= bottomIndex))
                continue;
            int width = -1;
            Bullet bullet = renderer.getLineBullet(lineIndex, null);
            if (bullet != null) {
                StyleRange style = bullet.style;
                GlyphMetrics metrics = style.metrics;
                width = metrics.width;
            }
            if (width == -1)
                width = getClientArea().width;
            int height = renderer.getLineHeight(lineIndex);
            int y = getLinePixel(lineIndex);
            super.redraw(0, y, width, height, false);
        }
    }

    void redrawMargins(int oldHeight, int oldWidth) {
        /* Redraw the old or new right/bottom margin if needed */
        if (oldWidth != clientAreaWidth) {
            if (rightMargin > 0) {
                int x = (oldWidth < clientAreaWidth ? oldWidth : clientAreaWidth) - rightMargin;
                super.redraw(x, 0, rightMargin, oldHeight, false);
            }
        }
        if (oldHeight != clientAreaHeight) {
            if (bottomMargin > 0) {
                int y = (oldHeight < clientAreaHeight ? oldHeight : clientAreaHeight) - bottomMargin;
                super.redraw(0, y, oldWidth, bottomMargin, false);
            }
        }
    }

    /**
     * Redraws the specified text range.
     *
     * @param start offset of the first character to redraw
     * @param length number of characters to redraw
     * @param clearBackground true if the background should be cleared as
     *  part of the redraw operation.  If true, the entire redraw range will
     *  be cleared before anything is redrawn.  If the redraw range includes
     *   the last character of a line (i.e., the entire line is redrawn) the
     *    line is cleared all the way to the right border of the widget.
     *    The redraw operation will be faster and smoother if clearBackground
     *    is set to false.  Whether or not the flag can be set to false depends
     *    on the type of change that has taken place.  If font styles or
     *    background colors for the redraw range have changed, clearBackground
     *    should be set to true.  If only foreground colors have changed for
     *    the redraw range, clearBackground can be set to false.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
     * </ul>
     */
    public void redrawRange(int start, int length, boolean clearBackground) {
        checkWidget();
        int end = start + length;
        int contentLength = content.getCharCount();
        if (start > end || start < 0 || end > contentLength) {
            SWT.error(SWT.ERROR_INVALID_RANGE);
        }
        int firstLine = content.getLineAtOffset(start);
        int lastLine = content.getLineAtOffset(end);
        resetCache(firstLine, lastLine - firstLine + 1);
        internalRedrawRange(start, length);
        doMouseLinkCursor();
    }

    /**
     * Removes the specified bidirectional segment listener.
     *
     * @param listener the listener which should no longer be notified
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
     * </ul>
     *
     * @since 2.0
     */
    public void removeBidiSegmentListener(BidiSegmentListener listener) {
        checkWidget();
        if (listener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        removeListener(ST.LineGetSegments, listener);
        resetCache(0, content.getLineCount());
        setCaretLocation();
        super.redraw();
    }

    /**
     * Removes the specified caret listener.
     *
     * @param listener the listener which should no longer be notified
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
     * </ul>
     *
     * @since 3.5
     */
    public void removeCaretListener(CaretListener listener) {
        checkWidget();
        if (listener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        removeListener(ST.CaretMoved, listener);
    }

    /**
     * Removes the specified extended modify listener.
     *
     * @param extendedModifyListener the listener which should no longer be notified
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
     * </ul>
     */
    public void removeExtendedModifyListener(ExtendedModifyListener extendedModifyListener) {
        checkWidget();
        if (extendedModifyListener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        removeListener(ST.ExtendedModify, extendedModifyListener);
    }

    /**
     * Removes the specified line background listener.
     *
     * @param listener the listener which should no longer be notified
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
     * </ul>
     */
    public void removeLineBackgroundListener(LineBackgroundListener listener) {
        checkWidget();
        if (listener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        removeListener(ST.LineGetBackground, listener);
    }

    /**
     * Removes the specified line style listener.
     *
     * @param listener the listener which should no longer be notified
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
     * </ul>
     */
    public void removeLineStyleListener(LineStyleListener listener) {
        checkWidget();
        if (listener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        removeListener(ST.LineGetStyle, listener);
        setCaretLocation();
    }

    /**
     * Removes the specified modify listener.
     *
     * @param modifyListener the listener which should no longer be notified
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
     * </ul>
     */
    public void removeModifyListener(ModifyListener modifyListener) {
        checkWidget();
        if (modifyListener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        removeListener(SWT.Modify, modifyListener);
    }

    /**
     * Removes the specified listener.
     *
     * @param listener the listener which should no longer be notified
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
     * </ul>
     * @since 3.2
     */
    public void removePaintObjectListener(PaintObjectListener listener) {
        checkWidget();
        if (listener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        removeListener(ST.PaintObject, listener);
    }

    /**
     * Removes the listener from the collection of listeners who will
     * be notified when the user changes the receiver's selection.
     *
     * @param listener the listener which should no longer be notified
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see SelectionListener
     * @see #addSelectionListener
     */
    public void removeSelectionListener(SelectionListener listener) {
        checkWidget();
        if (listener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        removeListener(SWT.Selection, listener);
    }

    /**
     * Removes the specified verify listener.
     *
     * @param verifyListener the listener which should no longer be notified
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
     * </ul>
     */
    public void removeVerifyListener(VerifyListener verifyListener) {
        checkWidget();
        if (verifyListener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        removeListener(SWT.Verify, verifyListener);
    }

    /**
     * Removes the specified key verify listener.
     *
     * @param listener the listener which should no longer be notified
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
     * </ul>
     */
    public void removeVerifyKeyListener(VerifyKeyListener listener) {
        if (listener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        removeListener(ST.VerifyKey, listener);
    }

    /**
     * Removes the specified word movement listener.
     *
     * @param listener the listener which should no longer be notified
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
     * </ul>
     *
     * @see MovementEvent
     * @see MovementListener
     * @see #addWordMovementListener
     *
     * @since 3.3
     */

    public void removeWordMovementListener(MovementListener listener) {
        checkWidget();
        if (listener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        removeListener(ST.WordNext, listener);
        removeListener(ST.WordPrevious, listener);
    }

    /**
     * Replaces the styles in the given range with new styles.  This method
     * effectively deletes the styles in the given range and then adds the
     * the new styles.
     * <p>
     * Note: Because a StyleRange includes the start and length, the
     * same instance cannot occur multiple times in the array of styles.
     * If the same style attributes, such as font and color, occur in
     * multiple StyleRanges, <code>setStyleRanges(int, int, int[], StyleRange[])</code>
     * can be used to share styles and reduce memory usage.
     * </p><p>
     * Should not be called if a LineStyleListener has been set since the
     * listener maintains the styles.
     * </p>
     *
     * @param start offset of first character where styles will be deleted
     * @param length length of the range to delete styles in
     * @param ranges StyleRange objects containing the new style information.
     * The ranges should not overlap and should be within the specified start
     * and length. The style rendering is undefined if the ranges do overlap
     * or are ill-defined. Must not be null.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_RANGE when either start or end is outside the valid range (0 &lt;= offset &lt;= getCharCount())</li>
     *   <li>ERROR_NULL_ARGUMENT when ranges is null</li>
     * </ul>
     *
     * @since 2.0
     *
     * @see #setStyleRanges(int, int, int[], StyleRange[])
     */
    public void replaceStyleRanges(int start, int length, StyleRange[] ranges) {
        checkWidget();
        if (isListening(ST.LineGetStyle))
            return;
        if (ranges == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        setStyleRanges(start, length, null, ranges, false);
    }

    /**
     * Replaces the given text range with new text.
     * If the widget has the SWT.SINGLE style and "text" contains more than
     * one line, only the first line is rendered but the text is stored
     * unchanged. A subsequent call to getText will return the same text
     * that was set. Note that only a single line of text should be set when
     * the SWT.SINGLE style is used.
     * <p>
     * <b>NOTE:</b> During the replace operation the current selection is
     * changed as follows:
     * </p>
     * <ul>
     * <li>selection before replaced text: selection unchanged
     * <li>selection after replaced text: adjust the selection so that same text
     * remains selected
     * <li>selection intersects replaced text: selection is cleared and caret
     * is placed after inserted text
     * </ul>
     *
     * @param start offset of first character to replace
     * @param length number of characters to replace. Use 0 to insert text
     * @param text new text. May be empty to delete text.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_RANGE when either start or end is outside the valid range (0 &lt;= offset &lt;= getCharCount())</li>
     *   <li>ERROR_INVALID_ARGUMENT when either start or end is inside a multi byte line delimiter.
     *       Splitting a line delimiter for example by inserting text in between the CR and LF and deleting part of a line delimiter is not supported</li>
     *   <li>ERROR_NULL_ARGUMENT when string is null</li>
     * </ul>
     */
    public void replaceTextRange(int start, int length, String text) {
        checkWidget();
        if (text == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        int contentLength = getCharCount();
        int end = start + length;
        if (start > end || start < 0 || end > contentLength) {
            SWT.error(SWT.ERROR_INVALID_RANGE);
        }
        Event event = new Event();
        event.start = start;
        event.end = end;
        event.text = text;
        modifyContent(event, false);
    }

    /**
     * Resets the caret position, selection and scroll offsets. Recalculate
     * the content width and scroll bars. Redraw the widget.
     */
    void reset() {
        ScrollBar verticalBar = getVerticalBar();
        ScrollBar horizontalBar = getHorizontalBar();
        setCaretOffset(0, SWT.DEFAULT);
        topIndex = 0;
        topIndexY = 0;
        verticalScrollOffset = 0;
        horizontalScrollOffset = 0;
        resetSelection();
        renderer.setContent(content);
        if (verticalBar != null) {
            verticalBar.setSelection(0);
        }
        if (horizontalBar != null) {
            horizontalBar.setSelection(0);
        }
        resetCache(0, 0);
        setCaretLocation();
        super.redraw();
    }

    void resetBidiData() {
        caretDirection = SWT.NULL;
        resetCache(0, content.getLineCount());
        setCaretLocation();
        keyActionMap.clear();
        createKeyBindings();
        super.redraw();
    }

    void resetCache(SortedSet<Integer> lines) {
        if (lines == null || lines.isEmpty())
            return;
        int maxLineIndex = renderer.maxWidthLineIndex;
        renderer.reset(lines);
        renderer.calculateClientArea();
        if (0 <= maxLineIndex && maxLineIndex < content.getLineCount()) {
            renderer.calculate(maxLineIndex, 1);
        }
        setScrollBars(true);
        if (!isFixedLineHeight()) {
            if (topIndex > lines.iterator().next()) {
                verticalScrollOffset = -1;
            }
            renderer.calculateIdle();
        }
    }

    void resetCache(int firstLine, int count) {
        int maxLineIndex = renderer.maxWidthLineIndex;
        renderer.reset(firstLine, count);
        renderer.calculateClientArea();
        if (0 <= maxLineIndex && maxLineIndex < content.getLineCount()) {
            renderer.calculate(maxLineIndex, 1);
        }
        setScrollBars(true);
        if (!isFixedLineHeight()) {
            if (topIndex > firstLine) {
                verticalScrollOffset = -1;
            }
            renderer.calculateIdle();
        }
    }

    /**
     * Resets the selection.
     */
    void resetSelection() {
        selection.x = selection.y = caretOffset;
        selectionAnchor = -1;
        sendAccessibleTextCaretMoved();
    }

    @Override
    public void scroll(int destX, int destY, int x, int y, int width, int height, boolean all) {
        super.scroll(destX, destY, x, y, width, height, false);
        if (all) {
            int deltaX = destX - x, deltaY = destY - y;
            Control[] children = getChildren();
            for (int i = 0; i < children.length; i++) {
                Control child = children[i];
                Rectangle rect = child.getBounds();
                child.setLocation(rect.x + deltaX, rect.y + deltaY);
            }
        }
    }

    /**
     * Scrolls the widget horizontally.
     *
     * @param pixels number of SWT logical points to scroll, > 0 = scroll left,
     *    < 0 scroll right
     * @param adjustScrollBar
     *    true= the scroll thumb will be moved to reflect the new scroll offset.
     *    false = the scroll thumb will not be moved
     * @return
     *   true=the widget was scrolled
     *   false=the widget was not scrolled, the given offset is not valid.
     */
    boolean scrollHorizontal(int pixels, boolean adjustScrollBar) {
        if (pixels == 0)
            return false;
        if (wordWrap)
            return false;
        ScrollBar horizontalBar = getHorizontalBar();
        if (horizontalBar != null && adjustScrollBar) {
            horizontalBar.setSelection(horizontalScrollOffset + pixels);
        }
        int scrollHeight = clientAreaHeight - topMargin - bottomMargin;
        if (pixels > 0) {
            int sourceX = leftMargin + pixels;
            int scrollWidth = clientAreaWidth - sourceX - rightMargin;
            if (scrollWidth > 0) {
                scroll(leftMargin, topMargin, sourceX, topMargin, scrollWidth, scrollHeight, true);
            }
            if (sourceX > scrollWidth) {
                super.redraw(leftMargin + scrollWidth, topMargin, pixels - scrollWidth, scrollHeight, true);
            }
        } else {
            int destinationX = leftMargin - pixels;
            int scrollWidth = clientAreaWidth - destinationX - rightMargin;
            if (scrollWidth > 0) {
                scroll(destinationX, topMargin, leftMargin, topMargin, scrollWidth, scrollHeight, true);
            }
            if (destinationX > scrollWidth) {
                super.redraw(leftMargin + scrollWidth, topMargin, -pixels - scrollWidth, scrollHeight, true);
            }
        }
        horizontalScrollOffset += pixels;
        setCaretLocation();
        return true;
    }

    /**
     * Scrolls the widget vertically.
     *
     * @param pixel the new vertical scroll offset
     * @param adjustScrollBar
     *    true= the scroll thumb will be moved to reflect the new scroll offset.
     *    false = the scroll thumb will not be moved
     * @return
     *   true=the widget was scrolled
     *   false=the widget was not scrolled
     */
    boolean scrollVertical(int pixels, boolean adjustScrollBar) {
        if (pixels == 0) {
            return false;
        }
        if (verticalScrollOffset != -1) {
            ScrollBar verticalBar = getVerticalBar();
            if (verticalBar != null && adjustScrollBar) {
                verticalBar.setSelection(verticalScrollOffset + pixels);
            }
            int deltaY = 0;
            if (pixels > 0) {
                int sourceY = topMargin + pixels;
                int scrollHeight = clientAreaHeight - sourceY - bottomMargin;
                if (scrollHeight > 0) {
                    deltaY = -pixels;
                }
            } else {
                int destinationY = topMargin - pixels;
                int scrollHeight = clientAreaHeight - destinationY - bottomMargin;
                if (scrollHeight > 0) {
                    deltaY = -pixels;
                }
            }
            Control[] children = getChildren();
            for (int i = 0; i < children.length; i++) {
                Control child = children[i];
                Rectangle rect = child.getBounds();
                child.setLocation(rect.x, rect.y + deltaY);
            }
            verticalScrollOffset += pixels;
            calculateTopIndex(pixels);
            super.redraw();
        } else {
            calculateTopIndex(pixels);
            super.redraw();
        }
        setCaretLocation();
        return true;
    }

    void scrollText(int srcY, int destY) {
        if (srcY == destY)
            return;
        int deltaY = destY - srcY;
        int scrollWidth = clientAreaWidth - leftMargin - rightMargin, scrollHeight;
        if (deltaY > 0) {
            scrollHeight = clientAreaHeight - srcY - bottomMargin;
        } else {
            scrollHeight = clientAreaHeight - destY - bottomMargin;
        }
        scroll(leftMargin, destY, leftMargin, srcY, scrollWidth, scrollHeight, true);
        if ((0 < srcY + scrollHeight) && (topMargin > srcY)) {
            super.redraw(leftMargin, deltaY, scrollWidth, topMargin, false);
        }
        if ((0 < destY + scrollHeight) && (topMargin > destY)) {
            super.redraw(leftMargin, 0, scrollWidth, topMargin, false);
        }
        if ((clientAreaHeight - bottomMargin < srcY + scrollHeight) && (clientAreaHeight > srcY)) {
            super.redraw(leftMargin, clientAreaHeight - bottomMargin + deltaY, scrollWidth, bottomMargin, false);
        }
        if ((clientAreaHeight - bottomMargin < destY + scrollHeight) && (clientAreaHeight > destY)) {
            super.redraw(leftMargin, clientAreaHeight - bottomMargin, scrollWidth, bottomMargin, false);
        }
    }

    void sendAccessibleTextCaretMoved() {
        if (caretOffset != accCaretOffset) {
            accCaretOffset = caretOffset;
            getAccessible().textCaretMoved(caretOffset);
        }
    }

    void sendAccessibleTextChanged(int start, int newCharCount, int replaceCharCount) {
        Accessible accessible = getAccessible();
        if (replaceCharCount != 0) {
            accessible.textChanged(ACC.TEXT_DELETE, start, replaceCharCount);
        }
        if (newCharCount != 0) {
            accessible.textChanged(ACC.TEXT_INSERT, start, newCharCount);
        }
    }

    /**
     * Selects all the text.
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void selectAll() {
        checkWidget();
        if (blockSelection) {
            renderer.calculate(0, content.getLineCount());
            setScrollBars(false);
            int verticalScrollOffset = getVerticalScrollOffset();
            int left = leftMargin - horizontalScrollOffset;
            int top = topMargin - verticalScrollOffset;
            int right = renderer.getWidth() - rightMargin - horizontalScrollOffset;
            int bottom = renderer.getHeight() - bottomMargin - verticalScrollOffset;
            setBlockSelectionLocation(left, top, right, bottom, false);
            return;
        }
        setSelection(0, Math.max(getCharCount(), 0));
    }

    /**
     * Replaces/inserts text as defined by the event.
     *
     * @param event the text change event.
     *   <ul>
     *   <li>event.start - the replace start offset</li>
     *    <li>event.end - the replace end offset</li>
     *    <li>event.text - the new text</li>
     *   </ul>
     */
    void sendKeyEvent(Event event) {
        if (editable) {
            modifyContent(event, true);
        }
    }

    /**
     * Returns a StyledTextEvent that can be used to request data such
     * as styles and background color for a line.
     * <p>
     * The specified line may be a visual (wrapped) line if in word
     * wrap mode. The returned object will always be for a logical
     * (unwrapped) line.
     * </p>
     *
     * @param lineOffset offset of the line. This may be the offset of
     *    a visual line if the widget is in word wrap mode.
     * @param line line text. This may be the text of a visual line if
     *    the widget is in word wrap mode.
     * @return StyledTextEvent that can be used to request line data
     *    for the given line.
     */
    StyledTextEvent sendLineEvent(int eventType, int lineOffset, String line) {
        StyledTextEvent event = null;
        if (isListening(eventType)) {
            event = new StyledTextEvent(content);
            event.detail = lineOffset;
            event.text = line;
            event.alignment = alignment;
            event.indent = indent;
            event.wrapIndent = wrapIndent;
            event.justify = justify;
            notifyListeners(eventType, event);
        }
        return event;
    }

    /**
     * Sends the specified selection event.
     */
    void sendSelectionEvent() {
        getAccessible().textSelectionChanged();
        Event event = new Event();
        event.x = selection.x;
        event.y = selection.y;
        notifyListeners(SWT.Selection, event);
    }

    int sendTextEvent(int left, int right, int lineIndex, String text, boolean fillWithSpaces) {
        int lineWidth = 0, start, end;
        StringBuilder buffer = new StringBuilder();
        if (lineIndex < content.getLineCount()) {
            int[] trailing = new int[1];
            start = getOffsetAtPoint(left, getLinePixel(lineIndex), trailing, true);
            if (start == -1) {
                int lineOffset = content.getOffsetAtLine(lineIndex);
                int lineLegth = content.getLine(lineIndex).length();
                start = end = lineOffset + lineLegth;
                if (fillWithSpaces) {
                    TextLayout layout = renderer.getTextLayout(lineIndex);
                    lineWidth = layout.getBounds().width;
                    renderer.disposeTextLayout(layout);
                }
            } else {
                start += trailing[0];
                end = left == right ? start : getOffsetAtPoint(right, 0, lineIndex, null);
                fillWithSpaces = false;
            }
        } else {
            start = end = content.getCharCount();
            buffer.append(content.getLineDelimiter());
        }
        if (start > end) {
            int temp = start;
            start = end;
            end = temp;
        }
        if (fillWithSpaces) {
            int spacesWidth = left - lineWidth + horizontalScrollOffset - leftMargin;
            int spacesCount = spacesWidth / renderer.averageCharWidth;
            for (int i = 0; i < spacesCount; i++) {
                buffer.append(' ');
            }
        }
        buffer.append(text);
        Event event = new Event();
        event.start = start;
        event.end = end;
        event.text = buffer.toString();
        sendKeyEvent(event);
        return event.start + event.text.length();
    }

    int sendWordBoundaryEvent(int eventType, int movement, int offset, int newOffset, String lineText,
            int lineOffset) {
        if (isListening(eventType)) {
            StyledTextEvent event = new StyledTextEvent(content);
            event.detail = lineOffset;
            event.text = lineText;
            event.count = movement;
            event.start = offset;
            event.end = newOffset;
            notifyListeners(eventType, event);
            offset = event.end;
            if (offset != newOffset) {
                int length = getCharCount();
                if (offset < 0) {
                    offset = 0;
                } else if (offset > length) {
                    offset = length;
                } else {
                    if (isLineDelimiter(offset)) {
                        SWT.error(SWT.ERROR_INVALID_ARGUMENT);
                    }
                }
            }
            return offset;
        }
        return newOffset;
    }

    void setAlignment() {
        if ((getStyle() & SWT.SINGLE) == 0)
            return;
        int alignment = renderer.getLineAlignment(0, this.alignment);
        int newAlignmentMargin = 0;
        if (alignment != SWT.LEFT) {
            renderer.calculate(0, 1);
            int width = renderer.getWidth() - alignmentMargin;
            newAlignmentMargin = clientAreaWidth - width;
            if (newAlignmentMargin < 0)
                newAlignmentMargin = 0;
            if (alignment == SWT.CENTER)
                newAlignmentMargin /= 2;
        }
        if (alignmentMargin != newAlignmentMargin) {
            leftMargin -= alignmentMargin;
            leftMargin += newAlignmentMargin;
            alignmentMargin = newAlignmentMargin;
            resetCache(0, 1);
            setCaretLocation();
            super.redraw();
        }
    }

    /**
     * Sets the alignment of the widget. The argument should be one of <code>SWT.LEFT</code>,
     * <code>SWT.CENTER</code> or <code>SWT.RIGHT</code>. The alignment applies for all lines.
     * <p>
     * Note that if <code>SWT.MULTI</code> is set, then <code>SWT.WRAP</code> must also be set
     * in order to stabilize the right edge before setting alignment.
     * </p>
     *
     * @param alignment the new alignment
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see #setLineAlignment(int, int, int)
     *
     * @since 3.2
     */
    public void setAlignment(int alignment) {
        checkWidget();
        alignment &= (SWT.LEFT | SWT.RIGHT | SWT.CENTER);
        if (alignment == 0 || this.alignment == alignment)
            return;
        this.alignment = alignment;
        resetCache(0, content.getLineCount());
        setCaretLocation();
        setAlignment();
        super.redraw();
    }

    /**
     * Set the Always Show Scrollbars flag.  True if the scrollbars are
     * always shown even if they are not required (default value).  False if the scrollbars are only
     * visible when some part of the content needs to be scrolled to be seen.
     * The H_SCROLL and V_SCROLL style bits are also required to enable scrollbars in the
     * horizontal and vertical directions.
     *
     * @param show true to show the scrollbars even when not required, false to show scrollbars only when required
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.8
     */
    public void setAlwaysShowScrollBars(boolean show) {
        checkWidget();
        if (show == alwaysShowScroll)
            return;
        alwaysShowScroll = show;
        setScrollBars(true);
    }

    /**
     * @see Control#setBackground(Color)
     */
    @Override
    public void setBackground(Color color) {
        checkWidget();
        background = color;
        super.setBackground(color);
        resetCache(0, content.getLineCount());
        setCaretLocation();
        super.redraw();
    }

    /**
     * Sets the block selection mode.
     *
     * @param blockSelection true=enable block selection, false=disable block selection
     *
     * @since 3.5
     */
    public void setBlockSelection(boolean blockSelection) {
        checkWidget();
        if ((getStyle() & SWT.SINGLE) != 0)
            return;
        if (blockSelection == this.blockSelection)
            return;
        if (wordWrap)
            return;
        this.blockSelection = blockSelection;
        if (cursor == null) {
            Display display = getDisplay();
            int type = blockSelection ? SWT.CURSOR_CROSS : SWT.CURSOR_IBEAM;
            super.setCursor(display.getSystemCursor(type));
        }
        if (blockSelection) {
            int start = selection.x;
            int end = selection.y;
            if (start != end) {
                setBlockSelectionOffset(start, end, false);
            }
        } else {
            clearBlockSelection(false, false);
        }
    }

    /**
     * Sets the block selection bounds. The bounds is
     * relative to the upper left corner of the document.
     *
     * @param rect the new bounds for the block selection
     *
     * @see #setBlockSelectionBounds(int, int, int, int)
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_NULL_ARGUMENT when point is null</li>
     * </ul>
     *
     * @since 3.5
     */
    public void setBlockSelectionBounds(Rectangle rect) {
        checkWidget();
        if (rect == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        setBlockSelectionBounds(rect.x, rect.y, rect.width, rect.height);
    }

    /**
     * Sets the block selection bounds. The bounds is
     * relative to the upper left corner of the document.
     *
     * @param x the new x coordinate for the block selection
     * @param y the new y coordinate for the block selection
     * @param width the new width for the block selection
     * @param height the new height for the block selection
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.5
     */
    public void setBlockSelectionBounds(int x, int y, int width, int height) {
        checkWidget();
        int verticalScrollOffset = getVerticalScrollOffset();
        if (!blockSelection) {
            x -= horizontalScrollOffset;
            y -= verticalScrollOffset;
            int start = getOffsetAtPoint(x, y, null);
            int end = getOffsetAtPoint(x + width - 1, y + height - 1, null);
            setSelection(start, end - start, false, false);
            setCaretLocation();
            return;
        }
        int minY = topMargin;
        int minX = leftMargin;
        int maxY = renderer.getHeight() - bottomMargin;
        int maxX = Math.max(clientAreaWidth, renderer.getWidth()) - rightMargin;
        int anchorX = Math.max(minX, Math.min(maxX, x)) - horizontalScrollOffset;
        int anchorY = Math.max(minY, Math.min(maxY, y)) - verticalScrollOffset;
        int locationX = Math.max(minX, Math.min(maxX, x + width)) - horizontalScrollOffset;
        int locationY = Math.max(minY, Math.min(maxY, y + height - 1)) - verticalScrollOffset;
        if (isFixedLineHeight() && renderer.fixedPitch) {
            int avg = renderer.averageCharWidth;
            anchorX = ((anchorX - leftMargin + horizontalScrollOffset) / avg * avg) + leftMargin
                    - horizontalScrollOffset;
            locationX = ((locationX + avg / 2 - leftMargin + horizontalScrollOffset) / avg * avg) + leftMargin
                    - horizontalScrollOffset;
        }
        setBlockSelectionLocation(anchorX, anchorY, locationX, locationY, false);
    }

    void setBlockSelectionLocation(int x, int y, boolean sendEvent) {
        int verticalScrollOffset = getVerticalScrollOffset();
        blockXLocation = x + horizontalScrollOffset;
        blockYLocation = y + verticalScrollOffset;
        int[] alignment = new int[1];
        int offset = getOffsetAtPoint(x, y, alignment);
        setCaretOffset(offset, alignment[0]);
        if (blockXAnchor == -1) {
            blockXAnchor = blockXLocation;
            blockYAnchor = blockYLocation;
            selectionAnchor = caretOffset;
        }
        doBlockSelection(sendEvent);
    }

    void setBlockSelectionLocation(int anchorX, int anchorY, int x, int y, boolean sendEvent) {
        int verticalScrollOffset = getVerticalScrollOffset();
        blockXAnchor = anchorX + horizontalScrollOffset;
        blockYAnchor = anchorY + verticalScrollOffset;
        selectionAnchor = getOffsetAtPoint(anchorX, anchorY, null);
        setBlockSelectionLocation(x, y, sendEvent);
    }

    void setBlockSelectionOffset(int offset, boolean sendEvent) {
        Point point = getPointAtOffset(offset);
        int verticalScrollOffset = getVerticalScrollOffset();
        blockXLocation = point.x + horizontalScrollOffset;
        blockYLocation = point.y + verticalScrollOffset;
        setCaretOffset(offset, SWT.DEFAULT);
        if (blockXAnchor == -1) {
            blockXAnchor = blockXLocation;
            blockYAnchor = blockYLocation;
            selectionAnchor = caretOffset;
        }
        doBlockSelection(sendEvent);
    }

    void setBlockSelectionOffset(int anchorOffset, int offset, boolean sendEvent) {
        int verticalScrollOffset = getVerticalScrollOffset();
        Point anchorPoint = getPointAtOffset(anchorOffset);
        blockXAnchor = anchorPoint.x + horizontalScrollOffset;
        blockYAnchor = anchorPoint.y + verticalScrollOffset;
        selectionAnchor = anchorOffset;
        setBlockSelectionOffset(offset, sendEvent);
    }

    /**
     * Sets the receiver's caret.  Set the caret's height and location.
     *
     * @param caret the new caret for the receiver
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    @Override
    public void setCaret(Caret caret) {
        checkWidget();
        super.setCaret(caret);
        caretDirection = SWT.NULL;
        if (caret != null) {
            setCaretLocation();
        }
    }

    /**
     * Sets the BIDI coloring mode.  When true the BIDI text display
     * algorithm is applied to segments of text that are the same
     * color.
     *
     * @param mode the new coloring mode
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @deprecated use BidiSegmentListener instead.
     */
    @Deprecated
    public void setBidiColoring(boolean mode) {
        checkWidget();
        bidiColoring = mode;
    }

    /**
     * Sets the bottom margin.
     *
     * @param bottomMargin the bottom margin.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.5
     */
    public void setBottomMargin(int bottomMargin) {
        checkWidget();
        setMargins(getLeftMargin(), topMargin, rightMargin, bottomMargin);
    }

    /**
     * Moves the Caret to the current caret offset.
     */
    void setCaretLocation() {
        Point newCaretPos = getPointAtOffset(caretOffset);
        setCaretLocation(newCaretPos, getCaretDirection());
    }

    void setCaretLocation(final Point location, int direction) {
        Caret caret = getCaret();
        if (caret != null) {
            final boolean isDefaultCaret = caret == defaultCaret;
            final StyleRange styleAtOffset = content.getCharCount() > 0
                    ? (caretOffset < content.getCharCount() ? getStyleRangeAtOffset(caretOffset)
                            : getStyleRangeAtOffset(content.getCharCount() - 1))
                    : // caret after last char: use last char style
                    null;
            final int caretLine = getCaretLine();

            int graphicalLineHeight = getLineHeight();
            final int lineStartOffset = getOffsetAtLine(caretLine);
            int graphicalLineFirstOffset = lineStartOffset;
            final int lineEndOffset = lineStartOffset + getLine(caretLine).length();
            int graphicalLineLastOffset = lineEndOffset;
            if (caretLine < getLineCount() && renderer.getLineHeight(caretLine) != getLineHeight()) { // word wrap, metrics, styles...
                graphicalLineHeight = getLineHeight(caretOffset);
                final Rectangle characterBounds = getBoundsAtOffset(caretOffset);
                graphicalLineFirstOffset = getOffsetAtPoint(new Point(leftMargin, characterBounds.y));
                graphicalLineLastOffset = getOffsetAtPoint(
                        new Point(leftMargin, characterBounds.y + graphicalLineHeight)) - 1;
                if (graphicalLineLastOffset < graphicalLineFirstOffset) {
                    graphicalLineLastOffset = getCharCount();
                }
            }

            int caretHeight = getLineHeight();
            boolean isTextAlignedAtBottom = true;
            if (graphicalLineFirstOffset >= 0) {
                for (StyleRange style : getStyleRanges(graphicalLineFirstOffset,
                        graphicalLineLastOffset - graphicalLineFirstOffset)) {
                    isTextAlignedAtBottom &= ((style.font == null || Objects.equals(style.font, getFont()))
                            && style.rise >= 0 && (style.metrics == null || style.metrics.descent <= 0));
                }
            }
            if (!isTextAlignedAtBottom || (styleAtOffset != null && styleAtOffset.isVariableHeight())) {
                if (isDefaultCaret) {
                    direction = SWT.DEFAULT;
                    caretHeight = graphicalLineHeight;
                } else {
                    caretHeight = caret.getSize().y;
                }
            }
            if (isTextAlignedAtBottom && caretHeight < graphicalLineHeight) {
                location.y += (graphicalLineHeight - caretHeight);
            }

            int imageDirection = direction;
            if (isMirrored()) {
                if (imageDirection == SWT.LEFT) {
                    imageDirection = SWT.RIGHT;
                } else if (imageDirection == SWT.RIGHT) {
                    imageDirection = SWT.LEFT;
                }
            }
            if (isDefaultCaret && imageDirection == SWT.RIGHT) {
                location.x -= (caret.getSize().x - 1);
            }
            if (isDefaultCaret) {
                caret.setBounds(location.x, location.y, caretWidth, caretHeight);
            } else {
                caret.setLocation(location);
            }
            if (direction != caretDirection) {
                caretDirection = direction;
                if (isDefaultCaret) {
                    if (imageDirection == SWT.DEFAULT) {
                        defaultCaret.setImage(null);
                    } else if (imageDirection == SWT.LEFT) {
                        defaultCaret.setImage(leftCaretBitmap);
                    } else if (imageDirection == SWT.RIGHT) {
                        defaultCaret.setImage(rightCaretBitmap);
                    }
                }
                if (caretDirection == SWT.LEFT) {
                    BidiUtil.setKeyboardLanguage(BidiUtil.KEYBOARD_NON_BIDI);
                } else if (caretDirection == SWT.RIGHT) {
                    BidiUtil.setKeyboardLanguage(BidiUtil.KEYBOARD_BIDI);
                }
            }
            updateCaretVisibility();
        }
        columnX = location.x;
    }

    /**
     * Sets the caret offset.
     *
     * @param offset caret offset, relative to the first character in the text.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_ARGUMENT when the offset is inside a multi byte line
     *   delimiter (and thus neither clearly in front of or after the line delimiter)
     * </ul>
     */
    public void setCaretOffset(int offset) {
        checkWidget();
        int length = getCharCount();
        if (length > 0 && offset != caretOffset) {
            if (offset < 0) {
                offset = 0;
            } else if (offset > length) {
                offset = length;
            } else {
                if (isLineDelimiter(offset)) {
                    // offset is inside a multi byte line delimiter. This is an
                    // illegal operation and an exception is thrown. Fixes 1GDKK3R
                    SWT.error(SWT.ERROR_INVALID_ARGUMENT);
                }
            }
            setCaretOffset(offset, PREVIOUS_OFFSET_TRAILING);
            // clear the selection if the caret is moved.
            // don't notify listeners about the selection change.
            if (blockSelection) {
                clearBlockSelection(true, false);
            } else {
                clearSelection(false);
            }
        }
        setCaretLocation();
    }

    void setCaretOffset(int offset, int alignment) {
        if (caretOffset != offset) {
            caretOffset = offset;
            if (isListening(ST.CaretMoved)) {
                StyledTextEvent event = new StyledTextEvent(content);
                event.end = caretOffset;
                notifyListeners(ST.CaretMoved, event);
            }
        }
        if (alignment != SWT.DEFAULT) {
            caretAlignment = alignment;
        }
    }

    /**
     * Copies the specified text range to the clipboard.  The text will be placed
     * in the clipboard in plain text format and RTF format.
     *
     * @param start start index of the text
     * @param length length of text to place in clipboard
     *
     * @exception SWTError, see Clipboard.setContents
     * @see org.eclipse.swt.dnd.Clipboard#setContents
     */
    void setClipboardContent(int start, int length, int clipboardType) throws SWTError {
        if (clipboardType == DND.SELECTION_CLIPBOARD && !IS_GTK)
            return;
        TextTransfer plainTextTransfer = TextTransfer.getInstance();
        TextWriter plainTextWriter = new TextWriter(start, length);
        String plainText = getPlatformDelimitedText(plainTextWriter);
        Object[] data;
        Transfer[] types;
        if (clipboardType == DND.SELECTION_CLIPBOARD) {
            data = new Object[] { plainText };
            types = new Transfer[] { plainTextTransfer };
        } else {
            RTFTransfer rtfTransfer = RTFTransfer.getInstance();
            RTFWriter rtfWriter = new RTFWriter(start, length);
            String rtfText = getPlatformDelimitedText(rtfWriter);
            data = new Object[] { rtfText, plainText };
            types = new Transfer[] { rtfTransfer, plainTextTransfer };
        }
        clipboard.setContents(data, types, clipboardType);
    }

    /**
     * Sets the content implementation to use for text storage.
     *
     * @param newContent StyledTextContent implementation to use for text storage.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
     * </ul>
     */
    public void setContent(StyledTextContent newContent) {
        checkWidget();
        if (newContent == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        if (content != null) {
            content.removeTextChangeListener(textChangeListener);
        }
        content = newContent;
        content.addTextChangeListener(textChangeListener);
        reset();
    }

    /**
     * Sets the receiver's cursor to the cursor specified by the
     * argument.  Overridden to handle the null case since the
     * StyledText widget uses an ibeam as its default cursor.
     *
     * @see Control#setCursor(Cursor)
     */
    @Override
    public void setCursor(Cursor cursor) {
        checkWidget();
        if (cursor != null && cursor.isDisposed())
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        this.cursor = cursor;
        if (cursor == null) {
            Display display = getDisplay();
            int type = blockSelection ? SWT.CURSOR_CROSS : SWT.CURSOR_IBEAM;
            super.setCursor(display.getSystemCursor(type));
        } else {
            super.setCursor(cursor);
        }
    }

    /**
     * Sets whether the widget implements double click mouse behavior.
     *
     * @param enable if true double clicking a word selects the word, if false
     *    double clicks have the same effect as regular mouse clicks.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setDoubleClickEnabled(boolean enable) {
        checkWidget();
        doubleClickEnabled = enable;
    }

    @Override
    public void setDragDetect(boolean dragDetect) {
        checkWidget();
        this.dragDetect = dragDetect;
    }

    /**
     * Sets whether the widget content can be edited.
     *
     * @param editable if true content can be edited, if false content can not be
     *    edited
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setEditable(boolean editable) {
        checkWidget();
        this.editable = editable;
    }

    /**
     * Sets a new font to render text with.
     * <p>
     * <b>NOTE:</b> Italic fonts are not supported unless they have no overhang
     * and the same baseline as regular fonts.
     * </p>
     *
     * @param font new font
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    @Override
    public void setFont(Font font) {
        checkWidget();
        int oldLineHeight = renderer.getLineHeight();
        super.setFont(font);
        renderer.setFont(getFont(), tabLength);
        // keep the same top line visible. fixes 5815
        if (isFixedLineHeight()) {
            int lineHeight = renderer.getLineHeight();
            if (lineHeight != oldLineHeight) {
                int vscroll = (getVerticalScrollOffset() * lineHeight / oldLineHeight) - getVerticalScrollOffset();
                scrollVertical(vscroll, true);
            }
        }
        resetCache(0, content.getLineCount());
        claimBottomFreeSpace();
        calculateScrollBars();
        if (isBidiCaret())
            createCaretBitmaps();
        caretDirection = SWT.NULL;
        setCaretLocation();
        super.redraw();
    }

    @Override
    public void setForeground(Color color) {
        checkWidget();
        foreground = color;
        super.setForeground(getForeground());
        resetCache(0, content.getLineCount());
        setCaretLocation();
        super.redraw();
    }

    /**
     * Sets the horizontal scroll offset relative to the start of the line.
     * Do nothing if there is no text set.
     * <p>
     * <b>NOTE:</b> The horizontal index is reset to 0 when new text is set in the
     * widget.
     * </p>
     *
     * @param offset horizontal scroll offset relative to the start
     *    of the line, measured in character increments starting at 0, if
     *    equal to 0 the content is not scrolled, if &gt; 0 = the content is scrolled.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setHorizontalIndex(int offset) {
        checkWidget();
        if (getCharCount() == 0) {
            return;
        }
        if (offset < 0) {
            offset = 0;
        }
        offset *= getHorizontalIncrement();
        // allow any value if client area width is unknown or 0.
        // offset will be checked in resize handler.
        // don't use isVisible since width is known even if widget
        // is temporarily invisible
        if (clientAreaWidth > 0) {
            int width = renderer.getWidth();
            // prevent scrolling if the content fits in the client area.
            // align end of longest line with right border of client area
            // if offset is out of range.
            if (offset > width - clientAreaWidth) {
                offset = Math.max(0, width - clientAreaWidth);
            }
        }
        scrollHorizontal(offset - horizontalScrollOffset, true);
    }

    /**
     * Sets the horizontal SWT logical point offset relative to the start of the line.
     * Do nothing if there is no text set.
     * <p>
     * <b>NOTE:</b> The horizontal SWT logical point offset is reset to 0 when new text
     * is set in the widget.
     * </p>
     *
     * @param pixel horizontal SWT logical point offset relative to the start
     *    of the line.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @since 2.0
     */
    public void setHorizontalPixel(int pixel) {
        checkWidget();
        if (getCharCount() == 0) {
            return;
        }
        if (pixel < 0) {
            pixel = 0;
        }
        // allow any value if client area width is unknown or 0.
        // offset will be checked in resize handler.
        // don't use isVisible since width is known even if widget
        // is temporarily invisible
        if (clientAreaWidth > 0) {
            int width = renderer.getWidth();
            // prevent scrolling if the content fits in the client area.
            // align end of longest line with right border of client area
            // if offset is out of range.
            if (pixel > width - clientAreaWidth) {
                pixel = Math.max(0, width - clientAreaWidth);
            }
        }
        scrollHorizontal(pixel - horizontalScrollOffset, true);
    }

    /**
     * Sets the line indentation of the widget.
     * <p>
     * It is the amount of blank space, in points, at the beginning of each line.
     * When a line wraps in several lines only the first one is indented.
     * </p>
     *
     * @param indent the new indent
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see #setLineIndent(int, int, int)
     *
     * @since 3.2
     */
    public void setIndent(int indent) {
        checkWidget();
        if (this.indent == indent || indent < 0)
            return;
        this.indent = indent;
        resetCache(0, content.getLineCount());
        setCaretLocation();
        super.redraw();
    }

    /**
     * Sets whether the widget should justify lines.
     *
     * @param justify whether lines should be justified
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see #setLineJustify(int, int, boolean)
     *
     * @since 3.2
     */
    public void setJustify(boolean justify) {
        checkWidget();
        if (this.justify == justify)
            return;
        this.justify = justify;
        resetCache(0, content.getLineCount());
        setCaretLocation();
        super.redraw();
    }

    /**
     * Maps a key to an action.
     * <p>
     * One action can be associated with N keys. However, each key can only
     * have one action (key:action is N:1 relation).
     * </p>
     *
     * @param key a key code defined in SWT.java or a character.
     *    Optionally ORd with a state mask.  Preferred state masks are one or more of
     *  SWT.MOD1, SWT.MOD2, SWT.MOD3, since these masks account for modifier platform
     *  differences.  However, there may be cases where using the specific state masks
     *  (i.e., SWT.CTRL, SWT.SHIFT, SWT.ALT, SWT.COMMAND) makes sense.
     * @param action one of the predefined actions defined in ST.java.
     *    Use SWT.NULL to remove a key binding.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setKeyBinding(int key, int action) {
        checkWidget();
        int modifierValue = key & SWT.MODIFIER_MASK;
        int keyInt = key & SWT.KEY_MASK;
        char keyChar = (char) keyInt;
        /**
         * Bug 440535: Make sure the key getting mapped to letter is in defiened
         * character range and filter out incorrect int to char typecasting. For
         * Example: SWT.KEYPAD_CR int gets wrongly type-cast to char letter 'p'
         */
        if (Character.isDefined(keyInt) && Character.isLetter(keyChar)) {
            // make the keybinding case insensitive by adding it
            // in its upper and lower case form
            char ch = Character.toUpperCase(keyChar);
            int newKey = ch | modifierValue;
            if (action == SWT.NULL) {
                keyActionMap.remove(newKey);
            } else {
                keyActionMap.put(newKey, action);
            }
            ch = Character.toLowerCase(keyChar);
            newKey = ch | modifierValue;
            if (action == SWT.NULL) {
                keyActionMap.remove(newKey);
            } else {
                keyActionMap.put(newKey, action);
            }
        } else {
            if (action == SWT.NULL) {
                keyActionMap.remove(key);
            } else {
                keyActionMap.put(key, action);
            }
        }
    }

    /**
     * Sets the left margin.
     *
     * @param leftMargin the left margin.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.5
     */
    public void setLeftMargin(int leftMargin) {
        checkWidget();
        setMargins(leftMargin, topMargin, rightMargin, bottomMargin);
    }

    /**
     * Sets the alignment of the specified lines. The argument should be one of <code>SWT.LEFT</code>,
     * <code>SWT.CENTER</code> or <code>SWT.RIGHT</code>.
     * <p>
     * Note that if <code>SWT.MULTI</code> is set, then <code>SWT.WRAP</code> must also be set
     * in order to stabilize the right edge before setting alignment.
     * </p><p>
     * Should not be called if a LineStyleListener has been set since the listener
     * maintains the line attributes.
     * </p><p>
     * All line attributes are maintained relative to the line text, not the
     * line index that is specified in this method call.
     * During text changes, when entire lines are inserted or removed, the line
     * attributes that are associated with the lines after the change
     * will "move" with their respective text. An entire line is defined as
     * extending from the first character on a line to the last and including the
     * line delimiter.
     * </p>
     * When two lines are joined by deleting a line delimiter, the top line
     * attributes take precedence and the attributes of the bottom line are deleted.
     * For all other text changes line attributes will remain unchanged.
     *
     * @param startLine first line the alignment is applied to, 0 based
     * @param lineCount number of lines the alignment applies to.
     * @param alignment line alignment
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
     * </ul>
     * @see #setAlignment(int)
     * @since 3.2
     */
    public void setLineAlignment(int startLine, int lineCount, int alignment) {
        checkWidget();
        if (isListening(ST.LineGetStyle))
            return;
        if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }

        renderer.setLineAlignment(startLine, lineCount, alignment);
        resetCache(startLine, lineCount);
        redrawLines(startLine, lineCount, false);
        int caretLine = getCaretLine();
        if (startLine <= caretLine && caretLine < startLine + lineCount) {
            setCaretLocation();
        }
        setAlignment();
    }

    /**
     * Sets the background color of the specified lines.
     * <p>
     * The background color is drawn for the width of the widget. All
     * line background colors are discarded when setText is called.
     * The text background color if defined in a StyleRange overlays the
     * line background color.
     * </p><p>
     * Should not be called if a LineBackgroundListener has been set since the
     * listener maintains the line backgrounds.
     * </p><p>
     * All line attributes are maintained relative to the line text, not the
     * line index that is specified in this method call.
     * During text changes, when entire lines are inserted or removed, the line
     * attributes that are associated with the lines after the change
     * will "move" with their respective text. An entire line is defined as
     * extending from the first character on a line to the last and including the
     * line delimiter.
     * </p><p>
     * When two lines are joined by deleting a line delimiter, the top line
     * attributes take precedence and the attributes of the bottom line are deleted.
     * For all other text changes line attributes will remain unchanged.
     * </p>
     *
     * @param startLine first line the color is applied to, 0 based
     * @param lineCount number of lines the color applies to.
     * @param background line background color
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
     * </ul>
     */
    public void setLineBackground(int startLine, int lineCount, Color background) {
        checkWidget();
        if (isListening(ST.LineGetBackground))
            return;
        if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        if (background != null) {
            renderer.setLineBackground(startLine, lineCount, background);
        } else {
            renderer.clearLineBackground(startLine, lineCount);
        }
        redrawLines(startLine, lineCount, false);
    }

    /**
     * Sets the bullet of the specified lines.
     * <p>
     * Should not be called if a LineStyleListener has been set since the listener
     * maintains the line attributes.
     * </p><p>
     * All line attributes are maintained relative to the line text, not the
     * line index that is specified in this method call.
     * During text changes, when entire lines are inserted or removed, the line
     * attributes that are associated with the lines after the change
     * will "move" with their respective text. An entire line is defined as
     * extending from the first character on a line to the last and including the
     * line delimiter.
     * </p><p>
     * When two lines are joined by deleting a line delimiter, the top line
     * attributes take precedence and the attributes of the bottom line are deleted.
     * For all other text changes line attributes will remain unchanged.
     * </p>
     *
     * @param startLine first line the bullet is applied to, 0 based
     * @param lineCount number of lines the bullet applies to.
     * @param bullet line bullet
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
     * </ul>
     * @since 3.2
     */
    public void setLineBullet(int startLine, int lineCount, Bullet bullet) {
        checkWidget();
        if (isListening(ST.LineGetStyle))
            return;
        if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        int oldBottom = getLinePixel(startLine + lineCount);
        renderer.setLineBullet(startLine, lineCount, bullet);
        resetCache(startLine, lineCount);
        int newBottom = getLinePixel(startLine + lineCount);
        redrawLines(startLine, lineCount, oldBottom != newBottom);
        int caretLine = getCaretLine();
        if (startLine <= caretLine && caretLine < startLine + lineCount) {
            setCaretLocation();
        }
    }

    void setVariableLineHeight() {
        if (!fixedLineHeight)
            return;
        fixedLineHeight = false;
        renderer.calculateIdle();
    }

    /**
     * Returns true if StyledText is in word wrap mode and false otherwise.
     *
     * @return true if StyledText is in word wrap mode and false otherwise.
     */
    boolean isWordWrap() {
        return wordWrap || visualWrap;
    }

    /**
     * Sets the indent of the specified lines.
     * <p>
     * Should not be called if a LineStyleListener has been set since the listener
     * maintains the line attributes.
     * </p><p>
     * All line attributes are maintained relative to the line text, not the
     * line index that is specified in this method call.
     * During text changes, when entire lines are inserted or removed, the line
     * attributes that are associated with the lines after the change
     * will "move" with their respective text. An entire line is defined as
     * extending from the first character on a line to the last and including the
     * line delimiter.
     * </p><p>
     * When two lines are joined by deleting a line delimiter, the top line
     * attributes take precedence and the attributes of the bottom line are deleted.
     * For all other text changes line attributes will remain unchanged.
     * </p>
     *
     * @param startLine first line the indent is applied to, 0 based
     * @param lineCount number of lines the indent applies to.
     * @param indent line indent
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
     * </ul>
     * @see #setIndent(int)
     * @since 3.2
     */
    public void setLineIndent(int startLine, int lineCount, int indent) {
        checkWidget();
        if (isListening(ST.LineGetStyle))
            return;
        if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        int oldBottom = getLinePixel(startLine + lineCount);
        renderer.setLineIndent(startLine, lineCount, indent);
        resetCache(startLine, lineCount);
        int newBottom = getLinePixel(startLine + lineCount);
        redrawLines(startLine, lineCount, oldBottom != newBottom);
        int caretLine = getCaretLine();
        if (startLine <= caretLine && caretLine < startLine + lineCount) {
            setCaretLocation();
        }
    }

    /**
     * Sets the vertical indent of the specified lines.
     * <p>
     * Should not be called if a LineStyleListener has been set since the listener
     * maintains the line attributes.
     * </p><p>
     * All line attributes are maintained relative to the line text, not the
     * line index that is specified in this method call.
     * During text changes, when entire lines are inserted or removed, the line
     * attributes that are associated with the lines after the change
     * will "move" with their respective text. An entire line is defined as
     * extending from the first character on a line to the last and including the
     * line delimiter.
     * </p><p>
     * When two lines are joined by deleting a line delimiter, the top line
     * attributes take precedence and the attributes of the bottom line are deleted.
     * For all other text changes line attributes will remain unchanged.
     * </p><p>
     * Setting both line spacing and vertical indent on a line would result in the
     * spacing and indent add up for the line.
     * </p>
     *
     * @param lineIndex line index the vertical indent is applied to, 0 based
     * @param verticalLineIndent vertical line indent
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_ARGUMENT when the specified line index is invalid</li>
     * </ul>
     * @since 3.109
     */
    public void setLineVerticalIndent(int lineIndex, int verticalLineIndent) {
        checkWidget();
        if (isListening(ST.LineGetStyle))
            return;
        if (lineIndex < 0 || lineIndex >= content.getLineCount()) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        if (verticalLineIndent == renderer.getLineVerticalIndent(lineIndex)) {
            return;
        }
        setVariableLineHeight();
        int oldBottom = getLinePixel(lineIndex + 1);
        if (oldBottom <= getClientArea().height) {
            verticalScrollOffset = -1;
        }
        renderer.setLineVerticalIndent(lineIndex, verticalLineIndent);
        resetCache(lineIndex, 1);
        int newBottom = getLinePixel(lineIndex + 1);
        redrawLines(lineIndex, 1, oldBottom != newBottom);
        int caretLine = getCaretLine();
        if (lineIndex <= caretLine && caretLine < lineIndex + 1) {
            setCaretLocation();
        }
    }

    /**
     * Sets the justify of the specified lines.
     * <p>
     * Should not be called if a LineStyleListener has been set since the listener
     * maintains the line attributes.
     * </p><p>
     * All line attributes are maintained relative to the line text, not the
     * line index that is specified in this method call.
     * During text changes, when entire lines are inserted or removed, the line
     * attributes that are associated with the lines after the change
     * will "move" with their respective text. An entire line is defined as
     * extending from the first character on a line to the last and including the
     * line delimiter.
     * </p><p>
     * When two lines are joined by deleting a line delimiter, the top line
     * attributes take precedence and the attributes of the bottom line are deleted.
     * For all other text changes line attributes will remain unchanged.
     * </p>
     *
     * @param startLine first line the justify is applied to, 0 based
     * @param lineCount number of lines the justify applies to.
     * @param justify true if lines should be justified
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
     * </ul>
     * @see #setJustify(boolean)
     * @since 3.2
     */
    public void setLineJustify(int startLine, int lineCount, boolean justify) {
        checkWidget();
        if (isListening(ST.LineGetStyle))
            return;
        if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }

        renderer.setLineJustify(startLine, lineCount, justify);
        resetCache(startLine, lineCount);
        redrawLines(startLine, lineCount, false);
        int caretLine = getCaretLine();
        if (startLine <= caretLine && caretLine < startLine + lineCount) {
            setCaretLocation();
        }
    }

    /**
     * Sets the line spacing of the widget. The line spacing applies for all lines.
     * In the case of #setLineSpacingProvider(StyledTextLineSpacingProvider) is customized,
     * the line spacing are applied only for the lines which are not managed by {@link StyledTextLineSpacingProvider}.
     *
     * @param lineSpacing the line spacing
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @see #setLineSpacingProvider(StyledTextLineSpacingProvider)
     * @since 3.2
     */
    public void setLineSpacing(int lineSpacing) {
        checkWidget();
        if (this.lineSpacing == lineSpacing || lineSpacing < 0)
            return;
        this.lineSpacing = lineSpacing;
        setVariableLineHeight();
        resetCache(0, content.getLineCount());
        setCaretLocation();
        super.redraw();
    }

    /**
     * Sets the line spacing provider of the widget. The line spacing applies for some lines with customized spacing
     * or reset the customized spacing if the argument is null.
     *
     * @param lineSpacingProvider the line spacing provider (or null)
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @see #setLineSpacingProvider(StyledTextLineSpacingProvider)
     * @since 3.107
     */
    public void setLineSpacingProvider(StyledTextLineSpacingProvider lineSpacingProvider) {
        checkWidget();
        if (renderer.getLineSpacingProvider() == null && lineSpacingProvider == null
                || (renderer.getLineSpacingProvider() != null
                        && renderer.getLineSpacingProvider().equals(lineSpacingProvider)))
            return;
        renderer.setLineSpacingProvider(lineSpacingProvider);
        // reset lines cache if needed
        if (lineSpacingProvider == null) {
            if (!isFixedLineHeight()) {
                resetCache(0, content.getLineCount());
            }
        } else {
            if (isFixedLineHeight()) {
                int firstLine = -1;
                for (int i = 0; i < content.getLineCount(); i++) {
                    Integer lineSpacing = lineSpacingProvider.getLineSpacing(i);
                    if (lineSpacing != null && lineSpacing > 0) {
                        // there is a custom line spacing, set StyledText as variable line height mode
                        setVariableLineHeight();
                        // reset only the line size
                        renderer.reset(i, 1);
                        if (firstLine == -1) {
                            firstLine = i;
                        }
                    }
                }
                if (firstLine != -1) {
                    // call reset cache for the first line which have changed to recompute scrollbars
                    resetCache(firstLine, 0);
                }
            }
        }
        setCaretLocation();
        super.redraw();
    }

    /**
     * Sets the tab stops of the specified lines.
     * <p>
     * Should not be called if a <code>LineStyleListener</code> has been set since the listener
     * maintains the line attributes.
     * </p><p>
     * All line attributes are maintained relative to the line text, not the
     * line index that is specified in this method call.
     * During text changes, when entire lines are inserted or removed, the line
     * attributes that are associated with the lines after the change
     * will "move" with their respective text. An entire line is defined as
     * extending from the first character on a line to the last and including the
     * line delimiter.
     * </p><p>
     * When two lines are joined by deleting a line delimiter, the top line
     * attributes take precedence and the attributes of the bottom line are deleted.
     * For all other text changes line attributes will remain unchanged.
     * </p>
     *
     * @param startLine first line the justify is applied to, 0 based
     * @param lineCount number of lines the justify applies to.
     * @param tabStops tab stops
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
     * </ul>
     * @see #setTabStops(int[])
     * @since 3.6
     */
    public void setLineTabStops(int startLine, int lineCount, int[] tabStops) {
        checkWidget();
        if (isListening(ST.LineGetStyle))
            return;
        if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        if (tabStops != null) {
            int pos = 0;
            int[] newTabs = new int[tabStops.length];
            for (int i = 0; i < tabStops.length; i++) {
                if (tabStops[i] < pos)
                    SWT.error(SWT.ERROR_INVALID_ARGUMENT);
                newTabs[i] = pos = tabStops[i];
            }
            renderer.setLineTabStops(startLine, lineCount, newTabs);
        } else {
            renderer.setLineTabStops(startLine, lineCount, null);
        }
        resetCache(startLine, lineCount);
        redrawLines(startLine, lineCount, false);
        int caretLine = getCaretLine();
        if (startLine <= caretLine && caretLine < startLine + lineCount) {
            setCaretLocation();
        }
    }

    /**
     * Sets the wrap indent of the specified lines.
     * <p>
     * Should not be called if a <code>LineStyleListener</code> has been set since the listener
     * maintains the line attributes.
     * </p><p>
     * All line attributes are maintained relative to the line text, not the
     * line index that is specified in this method call.
     * During text changes, when entire lines are inserted or removed, the line
     * attributes that are associated with the lines after the change
     * will "move" with their respective text. An entire line is defined as
     * extending from the first character on a line to the last and including the
     * line delimiter.
     * </p><p>
     * When two lines are joined by deleting a line delimiter, the top line
     * attributes take precedence and the attributes of the bottom line are deleted.
     * For all other text changes line attributes will remain unchanged.
     * </p>
     *
     * @param startLine first line the wrap indent is applied to, 0 based
     * @param lineCount number of lines the wrap indent applies to.
     * @param wrapIndent line wrap indent
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
     * </ul>
     * @see #setWrapIndent(int)
     * @since 3.6
     */
    public void setLineWrapIndent(int startLine, int lineCount, int wrapIndent) {
        checkWidget();
        if (isListening(ST.LineGetStyle))
            return;
        if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        int oldBottom = getLinePixel(startLine + lineCount);
        renderer.setLineWrapIndent(startLine, lineCount, wrapIndent);
        resetCache(startLine, lineCount);
        int newBottom = getLinePixel(startLine + lineCount);
        redrawLines(startLine, lineCount, oldBottom != newBottom);
        int caretLine = getCaretLine();
        if (startLine <= caretLine && caretLine < startLine + lineCount) {
            setCaretLocation();
        }
    }

    /**
     * Sets the color of the margins.
     *
     * @param color the new color (or null)
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.5
     */
    public void setMarginColor(Color color) {
        checkWidget();
        if (color != null && color.isDisposed())
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        marginColor = color;
        super.redraw();
    }

    /**
     * Sets the margins.
     *
     * @param leftMargin the left margin.
     * @param topMargin the top margin.
     * @param rightMargin the right margin.
     * @param bottomMargin the bottom margin.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.5
     */
    public void setMargins(int leftMargin, int topMargin, int rightMargin, int bottomMargin) {
        checkWidget();
        this.leftMargin = Math.max(0, leftMargin) + alignmentMargin;
        this.topMargin = Math.max(0, topMargin);
        this.rightMargin = Math.max(0, rightMargin);
        this.bottomMargin = Math.max(0, bottomMargin);
        resetCache(0, content.getLineCount());
        setScrollBars(true);
        setCaretLocation();
        setAlignment();
        super.redraw();
    }

    /**
     * Sets the enabled state of the mouse navigator. When the mouse navigator is enabled, the user can navigate through the widget
     * by pressing the middle button and moving the cursor.
     *
     * @param enabled if true, the mouse navigator is enabled, if false the mouse navigator is deactivated
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @since 3.110
     */
    public void setMouseNavigatorEnabled(boolean enabled) {
        checkWidget();
        if ((enabled && mouseNavigator != null) || (!enabled && mouseNavigator == null)) {
            return;
        }
        if (enabled) {
            mouseNavigator = new MouseNavigator(this);
        } else {
            mouseNavigator.dispose();
            mouseNavigator = null;
        }
    }

    /**
     * Flips selection anchor based on word selection direction.
     */
    void setMouseWordSelectionAnchor() {
        if (doubleClickEnabled && clickCount > 1) {
            if (caretOffset < doubleClickSelection.x) {
                selectionAnchor = doubleClickSelection.y;
            } else if (caretOffset > doubleClickSelection.y) {
                selectionAnchor = doubleClickSelection.x;
            }
        }
    }

    /**
     * Sets the orientation of the receiver, which must be one
     * of the constants <code>SWT.LEFT_TO_RIGHT</code> or <code>SWT.RIGHT_TO_LEFT</code>.
     *
     * @param orientation new orientation style
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 2.1.2
     */
    @Override
    public void setOrientation(int orientation) {
        int oldOrientation = getOrientation();
        super.setOrientation(orientation);
        int newOrientation = getOrientation();
        if (oldOrientation != newOrientation) {
            resetBidiData();
        }
    }

    /**
     * Sets the right margin.
     *
     * @param rightMargin the right margin.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.5
     */
    public void setRightMargin(int rightMargin) {
        checkWidget();
        setMargins(getLeftMargin(), topMargin, rightMargin, bottomMargin);
    }

    void setScrollBar(ScrollBar bar, int clientArea, int maximum, int margin) {
        int inactive = 1;
        if (clientArea < maximum) {
            bar.setMaximum(maximum - margin);
            bar.setThumb(clientArea - margin);
            bar.setPageIncrement(clientArea - margin);
            if (!alwaysShowScroll)
                bar.setVisible(true);
        } else if (bar.getThumb() != inactive || bar.getMaximum() != inactive) {
            bar.setValues(bar.getSelection(), bar.getMinimum(), inactive, inactive, bar.getIncrement(), inactive);
        }
    }

    /**
     * Adjusts the maximum and the page size of the scroll bars to
     * reflect content width/length changes.
     *
     * @param vertical indicates if the vertical scrollbar also needs to be set
     */
    void setScrollBars(boolean vertical) {
        ignoreResize++;
        if (!isFixedLineHeight() || !alwaysShowScroll)
            vertical = true;
        ScrollBar verticalBar = vertical ? getVerticalBar() : null;
        ScrollBar horizontalBar = getHorizontalBar();
        int oldHeight = clientAreaHeight;
        int oldWidth = clientAreaWidth;
        if (!alwaysShowScroll) {
            if (verticalBar != null)
                verticalBar.setVisible(false);
            if (horizontalBar != null)
                horizontalBar.setVisible(false);
        }
        if (verticalBar != null) {
            setScrollBar(verticalBar, clientAreaHeight, renderer.getHeight(), topMargin + bottomMargin);
        }
        if (horizontalBar != null && !wordWrap) {
            setScrollBar(horizontalBar, clientAreaWidth, renderer.getWidth(), leftMargin + rightMargin);
            if (!alwaysShowScroll && horizontalBar.getVisible() && verticalBar != null) {
                setScrollBar(verticalBar, clientAreaHeight, renderer.getHeight(), topMargin + bottomMargin);
                if (verticalBar.getVisible()) {
                    setScrollBar(horizontalBar, clientAreaWidth, renderer.getWidth(), leftMargin + rightMargin);
                }
            }
        }
        if (!alwaysShowScroll) {
            redrawMargins(oldHeight, oldWidth);
        }
        ignoreResize--;
    }

    /**
     * Sets the selection to the given position and scrolls it into view.  Equivalent to setSelection(start,start).
     *
     * @param start new caret position
     * @see #setSelection(int,int)
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
     * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
     * </ul>
     */
    public void setSelection(int start) {
        // checkWidget test done in setSelectionRange
        setSelection(start, start);
    }

    /**
     * Sets the selection and scrolls it into view.
     * <p>
     * Indexing is zero based.  Text selections are specified in terms of
     * caret positions.  In a text widget that contains N characters, there are
     * N+1 caret positions, ranging from 0..N
     * </p>
     *
     * @param point x=selection start offset, y=selection end offset
     *    The caret will be placed at the selection start when x &gt; y.
     * @see #setSelection(int,int)
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_NULL_ARGUMENT when point is null</li>
     *   <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
     * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
     * </ul>
     */
    public void setSelection(Point point) {
        checkWidget();
        if (point == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        setSelection(point.x, point.y);
    }

    /**
     * Sets the receiver's selection background color to the color specified
     * by the argument, or to the default system color for the control
     * if the argument is null.
     *
     * @param color the new color (or null)
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @since 2.1
     */
    public void setSelectionBackground(Color color) {
        checkWidget();
        if (color != null) {
            if (color.isDisposed())
                SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        selectionBackground = color;
        resetCache(0, content.getLineCount());
        setCaretLocation();
        super.redraw();
    }

    /**
     * Sets the receiver's selection foreground color to the color specified
     * by the argument, or to the default system color for the control
     * if the argument is null.
     * <p>
     * Note that this is a <em>HINT</em>. Some platforms do not allow the application
     * to change the selection foreground color.
     * </p>
     * @param color the new color (or null)
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @since 2.1
     */
    public void setSelectionForeground(Color color) {
        checkWidget();
        if (color != null) {
            if (color.isDisposed())
                SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        selectionForeground = color;
        resetCache(0, content.getLineCount());
        setCaretLocation();
        super.redraw();
    }

    /**
     * Sets the selection and scrolls it into view.
     * <p>
     * Indexing is zero based.  Text selections are specified in terms of
     * caret positions.  In a text widget that contains N characters, there are
     * N+1 caret positions, ranging from 0..N
     * </p>
     *
     * @param start selection start offset. The caret will be placed at the
     *    selection start when start &gt; end.
     * @param end selection end offset
     * @see #setSelectionRange(int,int)
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
     * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
     * </ul>
     */
    public void setSelection(int start, int end) {
        setSelectionRange(start, end - start);
        showSelection();
    }

    /**
     * Sets the selection.
     * <p>
     * The new selection may not be visible. Call showSelection to scroll
     * the selection into view.
     * </p>
     *
     * @param start offset of the first selected character, start >= 0 must be true.
     * @param length number of characters to select, 0 <= start + length
     *    <= getCharCount() must be true.
     *    A negative length places the caret at the selection start.
     * @param sendEvent a Selection event is sent when set to true and when
     *    the selection is reset.
     */
    void setSelection(int start, int length, boolean sendEvent, boolean doBlock) {
        int end = start + length;
        if (start > end) {
            int temp = end;
            end = start;
            start = temp;
        }
        // is the selection range different or is the selection direction
        // different?
        if (selection.x != start || selection.y != end || (length > 0 && selectionAnchor != selection.x)
                || (length < 0 && selectionAnchor != selection.y)) {
            if (blockSelection && doBlock) {
                if (length < 0) {
                    setBlockSelectionOffset(end, start, sendEvent);
                } else {
                    setBlockSelectionOffset(start, end, sendEvent);
                }
            } else {
                int oldStart = selection.x;
                int oldLength = selection.y - selection.x;
                int charCount = content.getCharCount();
                // called internally to remove selection after text is removed
                // therefore make sure redraw range is valid.
                int redrawX = Math.min(selection.x, charCount);
                int redrawY = Math.min(selection.y, charCount);
                if (length < 0) {
                    selectionAnchor = selection.y = end;
                    selection.x = start;
                    setCaretOffset(start, PREVIOUS_OFFSET_TRAILING);
                } else {
                    selectionAnchor = selection.x = start;
                    selection.y = end;
                    setCaretOffset(end, PREVIOUS_OFFSET_TRAILING);
                }
                redrawX = Math.min(redrawX, selection.x);
                redrawY = Math.max(redrawY, selection.y);
                if (redrawY - redrawX > 0) {
                    internalRedrawRange(redrawX, redrawY - redrawX);
                }
                if (sendEvent && (oldLength != end - start || (oldLength != 0 && oldStart != start))) {
                    sendSelectionEvent();
                }
                sendAccessibleTextCaretMoved();
            }
        }
    }

    /**
     * Sets the selection.
     * <p>
     * The new selection may not be visible. Call showSelection to scroll the selection
     * into view. A negative length places the caret at the visual start of the selection.
     * </p>
     *
     * @param start offset of the first selected character
     * @param length number of characters to select
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
     * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
     * </ul>
     */
    public void setSelectionRange(int start, int length) {
        checkWidget();
        int contentLength = getCharCount();
        start = Math.max(0, Math.min(start, contentLength));
        int end = start + length;
        if (end < 0) {
            length = -start;
        } else {
            if (end > contentLength)
                length = contentLength - start;
        }
        if (isLineDelimiter(start) || isLineDelimiter(start + length)) {
            // the start offset or end offset of the selection range is inside a
            // multi byte line delimiter. This is an illegal operation and an exception
            // is thrown. Fixes 1GDKK3R
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        setSelection(start, length, false, true);
        setCaretLocation();
    }

    /**
     * Adds the specified style.
     * <p>
     * The new style overwrites existing styles for the specified range.
     * Existing style ranges are adjusted if they partially overlap with
     * the new style. To clear an individual style, call setStyleRange
     * with a StyleRange that has null attributes.
     * </p><p>
     * Should not be called if a LineStyleListener has been set since the
     * listener maintains the styles.
     * </p>
     *
     * @param range StyleRange object containing the style information.
     * Overwrites the old style in the given range. May be null to delete
     * all styles.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_INVALID_RANGE when the style range is outside the valid range (&gt; getCharCount())</li>
     * </ul>
     */
    public void setStyleRange(StyleRange range) {
        checkWidget();
        if (isListening(ST.LineGetStyle))
            return;
        if (range != null) {
            if (range.isUnstyled()) {
                setStyleRanges(range.start, range.length, null, null, false);
            } else {
                setStyleRanges(range.start, 0, null, new StyleRange[] { range }, false);
            }
        } else {
            setStyleRanges(0, 0, null, null, true);
        }
    }

    /**
     * Clears the styles in the range specified by <code>start</code> and
     * <code>length</code> and adds the new styles.
     * <p>
     * The ranges array contains start and length pairs.  Each pair refers to
     * the corresponding style in the styles array.  For example, the pair
     * that starts at ranges[n] with length ranges[n+1] uses the style
     * at styles[n/2].  The range fields within each StyleRange are ignored.
     * If ranges or styles is null, the specified range is cleared.
     * </p><p>
     * Note: It is expected that the same instance of a StyleRange will occur
     * multiple times within the styles array, reducing memory usage.
     * </p><p>
     * Should not be called if a LineStyleListener has been set since the
     * listener maintains the styles.
     * </p>
     *
     * @param start offset of first character where styles will be deleted
     * @param length length of the range to delete styles in
     * @param ranges the array of ranges.  The ranges must not overlap and must be in order.
     * @param styles the array of StyleRanges.  The range fields within the StyleRange are unused.
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when an element in the styles array is null</li>
     *    <li>ERROR_INVALID_RANGE when the number of ranges and style do not match (ranges.length * 2 == styles.length)</li>
     *    <li>ERROR_INVALID_RANGE when a range is outside the valid range (&gt; getCharCount() or less than zero)</li>
     *    <li>ERROR_INVALID_RANGE when a range overlaps</li>
     * </ul>
     *
     * @since 3.2
     */
    public void setStyleRanges(int start, int length, int[] ranges, StyleRange[] styles) {
        checkWidget();
        if (isListening(ST.LineGetStyle))
            return;
        if (ranges == null || styles == null) {
            setStyleRanges(start, length, null, null, false);
        } else {
            setStyleRanges(start, length, ranges, styles, false);
        }
    }

    /**
     * Sets styles to be used for rendering the widget content.
     * <p>
     * All styles in the widget will be replaced with the given set of ranges and styles.
     * The ranges array contains start and length pairs.  Each pair refers to
     * the corresponding style in the styles array.  For example, the pair
     * that starts at ranges[n] with length ranges[n+1] uses the style
     * at styles[n/2].  The range fields within each StyleRange are ignored.
     * If either argument is null, the styles are cleared.
     * </p><p>
     * Note: It is expected that the same instance of a StyleRange will occur
     * multiple times within the styles array, reducing memory usage.
     * </p><p>
     * Should not be called if a LineStyleListener has been set since the
     * listener maintains the styles.
     * </p>
     *
     * @param ranges the array of ranges.  The ranges must not overlap and must be in order.
     * @param styles the array of StyleRanges.  The range fields within the StyleRange are unused.
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when an element in the styles array is null</li>
     *    <li>ERROR_INVALID_RANGE when the number of ranges and style do not match (ranges.length * 2 == styles.length)</li>
     *    <li>ERROR_INVALID_RANGE when a range is outside the valid range (&gt; getCharCount() or less than zero)</li>
     *    <li>ERROR_INVALID_RANGE when a range overlaps</li>
     * </ul>
     *
     * @since 3.2
     */
    public void setStyleRanges(int[] ranges, StyleRange[] styles) {
        checkWidget();
        if (isListening(ST.LineGetStyle))
            return;
        if (ranges == null || styles == null) {
            setStyleRanges(0, 0, null, null, true);
        } else {
            setStyleRanges(0, 0, ranges, styles, true);
        }
    }

    void setStyleRanges(int start, int length, int[] ranges, StyleRange[] styles, boolean reset) {
        int[] formerRanges = getRanges(start, length);
        StyleRange[] formerStyles = getStyleRanges(start, length);
        int charCount = content.getCharCount();
        int end = start + length;
        if (start > end || start < 0) {
            SWT.error(SWT.ERROR_INVALID_RANGE);
        }
        if (styles != null) {
            if (end > charCount) {
                SWT.error(SWT.ERROR_INVALID_RANGE);
            }
            if (ranges != null) {
                if (ranges.length != styles.length << 1)
                    SWT.error(SWT.ERROR_INVALID_ARGUMENT);
            }
            int lastOffset = 0;
            boolean variableHeight = false;
            for (int i = 0; i < styles.length; i++) {
                if (styles[i] == null)
                    SWT.error(SWT.ERROR_INVALID_ARGUMENT);
                int rangeStart, rangeLength;
                if (ranges != null) {
                    rangeStart = ranges[i << 1];
                    rangeLength = ranges[(i << 1) + 1];
                } else {
                    rangeStart = styles[i].start;
                    rangeLength = styles[i].length;
                }
                if (rangeLength < 0)
                    SWT.error(SWT.ERROR_INVALID_ARGUMENT);
                if (!(0 <= rangeStart && rangeStart + rangeLength <= charCount))
                    SWT.error(SWT.ERROR_INVALID_ARGUMENT);
                if (lastOffset > rangeStart)
                    SWT.error(SWT.ERROR_INVALID_ARGUMENT);
                variableHeight |= styles[i].isVariableHeight();
                lastOffset = rangeStart + rangeLength;
            }
            if (variableHeight)
                setVariableLineHeight();
        }
        int rangeStart = start, rangeEnd = end;
        if (styles != null && styles.length > 0) {
            if (ranges != null) {
                rangeStart = ranges[0];
                rangeEnd = ranges[ranges.length - 2] + ranges[ranges.length - 1];
            } else {
                rangeStart = styles[0].start;
                rangeEnd = styles[styles.length - 1].start + styles[styles.length - 1].length;
            }
        }
        int expectedBottom = 0;
        if (!isFixedLineHeight() && !reset) {
            int lineEnd = content.getLineAtOffset(Math.max(end, rangeEnd));
            int partialTopIndex = getPartialTopIndex();
            int partialBottomIndex = getPartialBottomIndex();
            if (partialTopIndex <= lineEnd && lineEnd <= partialBottomIndex) {
                expectedBottom = getLinePixel(lineEnd + 1);
            }
        }
        if (reset) {
            renderer.setStyleRanges(null, null);
        } else {
            renderer.updateRanges(start, length, length);
        }
        if (styles != null && styles.length > 0) {
            renderer.setStyleRanges(ranges, styles);
        }

        SortedSet<Integer> modifiedLines = computeModifiedLines(formerRanges, formerStyles, ranges, styles);
        resetCache(modifiedLines);
        if (reset) {
            super.redraw();
        } else {
            int lineStart = content.getLineAtOffset(Math.min(start, rangeStart));
            int lineEnd = content.getLineAtOffset(Math.max(end, rangeEnd));
            int partialTopIndex = getPartialTopIndex();
            int partialBottomIndex = getPartialBottomIndex();
            if (!(lineStart > partialBottomIndex || lineEnd < partialTopIndex)) {
                int top = 0;
                int bottom = clientAreaHeight;
                if (partialTopIndex <= lineStart && lineStart <= partialBottomIndex) {
                    top = Math.max(0, getLinePixel(lineStart));
                }
                if (partialTopIndex <= lineEnd && lineEnd <= partialBottomIndex) {
                    bottom = getLinePixel(lineEnd + 1);
                }
                if (!isFixedLineHeight() && bottom != expectedBottom) {
                    bottom = clientAreaHeight;
                }
                super.redraw(0, top, clientAreaWidth, bottom - top, false);
            }
        }
        int oldColumnX = columnX;
        setCaretLocation();
        columnX = oldColumnX;
        doMouseLinkCursor();
    }

    /**
     *
     * @param referenceRanges former ranges, sorted by order and without overlapping, typically returned {@link #getRanges(int, int)}
     * @param referenceStyles
     * @param newRanges former ranges, sorted by order and without overlappingz
     * @param newStyles
     * @return
     */
    private SortedSet<Integer> computeModifiedLines(int[] referenceRanges, StyleRange[] referenceStyles,
            int[] newRanges, StyleRange[] newStyles) {
        if (referenceStyles == null) {
            referenceStyles = new StyleRange[0];
        }
        if (referenceRanges == null) {
            referenceRanges = createRanges(referenceStyles);
        }
        if (newStyles == null) {
            newStyles = new StyleRange[0];
        }
        if (newRanges == null) {
            newRanges = createRanges(newStyles);
        }
        if (referenceRanges.length != 2 * referenceStyles.length) {
            throw new IllegalArgumentException();
        }
        if (newRanges.length != 2 * newStyles.length) {
            throw new IllegalArgumentException();
        }
        SortedSet<Integer> res = new TreeSet<>();
        int referenceRangeIndex = 0;
        int newRangeIndex = 0;
        StyleRange defaultStyle = new StyleRange();
        defaultStyle.foreground = this.foreground;
        defaultStyle.background = this.background;
        defaultStyle.font = getFont();
        int currentOffset = referenceRanges.length > 0 ? referenceRanges[0] : Integer.MAX_VALUE;
        if (newRanges.length > 0) {
            currentOffset = Math.min(currentOffset, newRanges[0]);
        }
        while (currentOffset < content.getCharCount()
                && (referenceRangeIndex < referenceStyles.length || newRangeIndex < newRanges.length)) {
            int nextMilestoneOffset = Integer.MAX_VALUE; // next new range start/end after current offset

            while (referenceRangeIndex < referenceStyles.length
                    && endRangeOffset(referenceRanges, referenceRangeIndex) <= currentOffset) {
                referenceRangeIndex++;
            }
            StyleRange referenceStyleAtCurrentOffset = defaultStyle;
            if (isInRange(referenceRanges, referenceRangeIndex, currentOffset)) { // has styling
                referenceStyleAtCurrentOffset = referenceStyles[referenceRangeIndex];
                nextMilestoneOffset = Math.min(nextMilestoneOffset,
                        endRangeOffset(referenceRanges, referenceRangeIndex));
            } else if (referenceRangeIndex + 1 < referenceStyles.length) { // no range, default styling
                nextMilestoneOffset = referenceRanges[2 * (referenceRangeIndex + 1)]; // beginning of next range
            }

            while (newRangeIndex < newStyles.length && endRangeOffset(newRanges, newRangeIndex) <= currentOffset) {
                newRangeIndex++;
            }
            StyleRange newStyleAtCurrentOffset = defaultStyle;
            if (isInRange(newRanges, newRangeIndex, currentOffset)) {
                newStyleAtCurrentOffset = newStyles[newRangeIndex];
                nextMilestoneOffset = Math.min(nextMilestoneOffset, endRangeOffset(newRanges, newRangeIndex));
            } else if (newRangeIndex + 1 < newStyles.length) {
                nextMilestoneOffset = newRanges[2 * (newRangeIndex + 1)];
            }

            if (!referenceStyleAtCurrentOffset.similarTo(newStyleAtCurrentOffset)) {
                int fromLine = getLineAtOffset(currentOffset);
                int toLine = getLineAtOffset(nextMilestoneOffset - 1);
                for (int line = fromLine; line <= toLine; line++) {
                    res.add(line);
                }
                currentOffset = toLine + 1 < getLineCount() ? getOffsetAtLine(toLine + 1) : content.getCharCount();
            } else {
                currentOffset = nextMilestoneOffset;
            }
        }
        return res;
    }

    private int[] createRanges(StyleRange[] referenceStyles) {
        int[] referenceRanges;
        referenceRanges = new int[2 * referenceStyles.length];
        for (int i = 0; i < referenceStyles.length; i++) {
            referenceRanges[2 * i] = referenceStyles[i].start;
            referenceRanges[2 * i + 1] = referenceStyles[i].length;
        }
        return referenceRanges;
    }

    private boolean isInRange(int[] ranges, int styleIndex, int offset) {
        if (ranges == null || ranges.length == 0 || styleIndex < 0 || 2 * styleIndex + 1 > ranges.length) {
            return false;
        }
        int start = ranges[2 * styleIndex];
        int length = ranges[2 * styleIndex + 1];
        return offset >= start && offset < start + length;
    }

    /**
     * The offset on which the range ends (excluded)
     * @param ranges
     * @param styleIndex
     * @return
     */
    private int endRangeOffset(int[] ranges, int styleIndex) {
        if (styleIndex < 0 || 2 * styleIndex > ranges.length) {
            throw new IllegalArgumentException();
        }
        int start = ranges[2 * styleIndex];
        int length = ranges[2 * styleIndex + 1];
        return start + length;
    }

    /**
     * Sets styles to be used for rendering the widget content. All styles
     * in the widget will be replaced with the given set of styles.
     * <p>
     * Note: Because a StyleRange includes the start and length, the
     * same instance cannot occur multiple times in the array of styles.
     * If the same style attributes, such as font and color, occur in
     * multiple StyleRanges, <code>setStyleRanges(int[], StyleRange[])</code>
     * can be used to share styles and reduce memory usage.
     * </p><p>
     * Should not be called if a LineStyleListener has been set since the
     * listener maintains the styles.
     * </p>
     *
     * @param ranges StyleRange objects containing the style information.
     * The ranges should not overlap. The style rendering is undefined if
     * the ranges do overlap. Must not be null. The styles need to be in order.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when the list of ranges is null</li>
     *    <li>ERROR_INVALID_RANGE when the last of the style ranges is outside the valid range (&gt; getCharCount())</li>
     * </ul>
     *
     * @see #setStyleRanges(int[], StyleRange[])
     */
    public void setStyleRanges(StyleRange[] ranges) {
        checkWidget();
        if (isListening(ST.LineGetStyle))
            return;
        if (ranges == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        setStyleRanges(0, 0, null, ranges, true);
    }

    /**
     * Sets the tab width.
     *
     * @param tabs tab width measured in characters.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see #setTabStops(int[])
     */
    public void setTabs(int tabs) {
        checkWidget();
        tabLength = tabs;
        renderer.setFont(null, tabs);
        resetCache(0, content.getLineCount());
        setCaretLocation();
        super.redraw();
    }

    /**
     * Sets the receiver's tab list. Each value in the tab list specifies
     * the space in points from the origin of the document to the respective
     * tab stop.  The last tab stop width is repeated continuously.
     *
     * @param tabs the new tab list (or null)
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_INVALID_ARGUMENT - if a tab stop is negavite or less than the previous stop in the list</li>
     * </ul>
     *
     * @see StyledText#getTabStops()
     *
     * @since 3.6
     */
    public void setTabStops(int[] tabs) {
        checkWidget();
        if (tabs != null) {
            int pos = 0;
            int[] newTabs = new int[tabs.length];
            for (int i = 0; i < tabs.length; i++) {
                if (tabs[i] < pos)
                    SWT.error(SWT.ERROR_INVALID_ARGUMENT);
                newTabs[i] = pos = tabs[i];
            }
            this.tabs = newTabs;
        } else {
            this.tabs = null;
        }
        resetCache(0, content.getLineCount());
        setCaretLocation();
        super.redraw();
    }

    /**
     * Sets the widget content.
     * If the widget has the SWT.SINGLE style and "text" contains more than
     * one line, only the first line is rendered but the text is stored
     * unchanged. A subsequent call to getText will return the same text
     * that was set.
     * <p>
     * <b>Note:</b> Only a single line of text should be set when the SWT.SINGLE
     * style is used.
     * </p>
     *
     * @param text new widget content. Replaces existing content. Line styles
     *    that were set using StyledText API are discarded.  The
     *    current selection is also discarded.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT when string is null</li>
     * </ul>
     */
    public void setText(String text) {
        checkWidget();
        if (text == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        Event event = new Event();
        event.start = 0;
        event.end = getCharCount();
        event.text = text;
        event.doit = true;
        notifyListeners(SWT.Verify, event);
        if (event.doit) {
            StyledTextEvent styledTextEvent = null;
            if (isListening(ST.ExtendedModify)) {
                styledTextEvent = new StyledTextEvent(content);
                styledTextEvent.start = event.start;
                styledTextEvent.end = event.start + event.text.length();
                styledTextEvent.text = content.getTextRange(event.start, event.end - event.start);
            }
            content.setText(event.text);
            notifyListeners(SWT.Modify, event);
            if (styledTextEvent != null) {
                notifyListeners(ST.ExtendedModify, styledTextEvent);
            }
        }
    }

    /**
     * Sets the base text direction (a.k.a. "paragraph direction") of the receiver,
     * which must be one of the constants <code>SWT.LEFT_TO_RIGHT</code> or
     * <code>SWT.RIGHT_TO_LEFT</code>.
     * <p>
     * <code>setOrientation</code> would override this value with the text direction
     * that is consistent with the new orientation.
     * </p>
     * <p>
     * <b>Warning</b>: This API is currently only implemented on Windows.
     * It doesn't set the base text direction on GTK and Cocoa.
     * </p>
     *
     * @param textDirection the base text direction style
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see SWT#FLIP_TEXT_DIRECTION
     */
    @Override
    public void setTextDirection(int textDirection) {
        checkWidget();
        int oldStyle = getStyle();
        super.setTextDirection(textDirection);
        if (isAutoDirection() || oldStyle != getStyle()) {
            resetBidiData();
        }
    }

    /**
     * Sets the text limit to the specified number of characters.
     * <p>
     * The text limit specifies the amount of text that
     * the user can type into the widget.
     * </p>
     *
     * @param limit the new text limit.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @exception IllegalArgumentException <ul>
     *   <li>ERROR_CANNOT_BE_ZERO when limit is 0</li>
     * </ul>
     */
    public void setTextLimit(int limit) {
        checkWidget();
        if (limit == 0) {
            SWT.error(SWT.ERROR_CANNOT_BE_ZERO);
        }
        textLimit = limit;
    }

    /**
     * Sets the top index. Do nothing if there is no text set.
     * <p>
     * The top index is the index of the line that is currently at the top
     * of the widget. The top index changes when the widget is scrolled.
     * Indexing starts from zero.
     * Note: The top index is reset to 0 when new text is set in the widget.
     * </p>
     *
     * @param topIndex new top index. Must be between 0 and
     *    getLineCount() - fully visible lines per page. If no lines are fully
     *    visible the maximum value is getLineCount() - 1. An out of range
     *    index will be adjusted accordingly.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setTopIndex(int topIndex) {
        checkWidget();
        if (getCharCount() == 0) {
            return;
        }
        int lineCount = content.getLineCount(), pixel;
        if (isFixedLineHeight()) {
            int pageSize = Math.max(1, Math.min(lineCount, getLineCountWhole()));
            if (topIndex < 0) {
                topIndex = 0;
            } else if (topIndex > lineCount - pageSize) {
                topIndex = lineCount - pageSize;
            }
            pixel = getLinePixel(topIndex);
        } else {
            topIndex = Math.max(0, Math.min(lineCount - 1, topIndex));
            pixel = getLinePixel(topIndex);
            if (pixel > 0) {
                pixel = getAvailableHeightBellow(pixel);
            } else {
                pixel = getAvailableHeightAbove(pixel);
            }
        }
        scrollVertical(pixel, true);
    }

    /**
     * Sets the top margin.
     *
     * @param topMargin the top margin.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.5
     */
    public void setTopMargin(int topMargin) {
        checkWidget();
        setMargins(getLeftMargin(), topMargin, rightMargin, bottomMargin);
    }

    /**
     * Sets the top SWT logical point offset. Do nothing if there is no text set.
     * <p>
     * The top point offset is the vertical SWT logical point offset of the widget. The
     * widget is scrolled so that the given SWT logical point position is at the top.
     * The top index is adjusted to the corresponding top line.
     * Note: The top point is reset to 0 when new text is set in the widget.
     * </p>
     *
     * @param pixel new top point offset. Must be between 0 and
     *    (getLineCount() - visible lines per page) / getLineHeight()). An out
     *    of range offset will be adjusted accordingly.
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     * @since 2.0
     */
    public void setTopPixel(int pixel) {
        checkWidget();
        if (getCharCount() == 0) {
            return;
        }
        if (pixel < 0)
            pixel = 0;
        int lineCount = content.getLineCount();
        int height = clientAreaHeight - topMargin - bottomMargin;
        int verticalOffset = getVerticalScrollOffset();
        if (isFixedLineHeight()) {
            int maxTopPixel = Math.max(0, lineCount * getVerticalIncrement() - height);
            if (pixel > maxTopPixel)
                pixel = maxTopPixel;
            pixel -= verticalOffset;
        } else {
            pixel -= verticalOffset;
            if (pixel > 0) {
                pixel = getAvailableHeightBellow(pixel);
            }
        }
        scrollVertical(pixel, true);
    }

    /**
     * Sets whether the widget wraps lines.
     * <p>
     * This overrides the creation style bit SWT.WRAP.
     * </p>
     *
     * @param wrap true=widget wraps lines, false=widget does not wrap lines
     * @since 2.0
     */
    public void setWordWrap(boolean wrap) {
        checkWidget();
        if ((getStyle() & SWT.SINGLE) != 0)
            return;
        if (wordWrap == wrap)
            return;
        if (wordWrap && blockSelection)
            setBlockSelection(false);
        wordWrap = wrap;
        setVariableLineHeight();
        resetCache(0, content.getLineCount());
        horizontalScrollOffset = 0;
        ScrollBar horizontalBar = getHorizontalBar();
        if (horizontalBar != null) {
            horizontalBar.setVisible(!wordWrap);
        }
        setScrollBars(true);
        setCaretLocation();
        super.redraw();
    }

    /**
     * Sets the wrap line indentation of the widget.
     * <p>
     * It is the amount of blank space, in points, at the beginning of each wrapped line.
     * When a line wraps in several lines all the lines but the first one is indented
     * by this amount.
     * </p>
     *
     * @param wrapIndent the new wrap indent
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see #setLineWrapIndent(int, int, int)
     *
     * @since 3.6
     */
    public void setWrapIndent(int wrapIndent) {
        checkWidget();
        if (this.wrapIndent == wrapIndent || wrapIndent < 0)
            return;
        this.wrapIndent = wrapIndent;
        resetCache(0, content.getLineCount());
        setCaretLocation();
        super.redraw();
    }

    boolean showLocation(Rectangle rect, boolean scrollPage) {
        boolean scrolled = false;
        if (rect.y < topMargin) {
            scrolled = scrollVertical(rect.y - topMargin, true);
        } else if (rect.y + rect.height > clientAreaHeight - bottomMargin) {
            if (clientAreaHeight - topMargin - bottomMargin <= 0) {
                scrolled = scrollVertical(rect.y - topMargin, true);
            } else {
                scrolled = scrollVertical(rect.y + rect.height - (clientAreaHeight - bottomMargin), true);
            }
        }
        int width = clientAreaWidth - rightMargin - leftMargin;
        if (width > 0) {
            int minScroll = scrollPage ? width / 4 : 0;
            if (rect.x < leftMargin) {
                int scrollWidth = Math.max(leftMargin - rect.x, minScroll);
                int maxScroll = horizontalScrollOffset;
                scrolled = scrollHorizontal(-Math.min(maxScroll, scrollWidth), true);
            } else if (rect.x + rect.width > (clientAreaWidth - rightMargin)) {
                int scrollWidth = Math.max(rect.x + rect.width - (clientAreaWidth - rightMargin), minScroll);
                int maxScroll = renderer.getWidth() - horizontalScrollOffset - clientAreaWidth;
                scrolled = scrollHorizontal(Math.min(maxScroll, scrollWidth), true);
            }
        }
        return scrolled;
    }

    /**
     * Sets the caret location and scrolls the caret offset into view.
     */
    void showCaret() {
        Rectangle bounds = getBoundsAtOffset(caretOffset);
        if (!showLocation(bounds, true)) {
            setCaretLocation();
        }
    }

    /**
     * Scrolls the selection into view.
     * <p>
     * The end of the selection will be scrolled into view.
     * Note that if a right-to-left selection exists, the end of the selection is
     * the visual beginning of the selection (i.e., where the caret is located).
     * </p>
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void showSelection() {
        checkWidget();
        // is selection from right-to-left?
        boolean rightToLeft = caretOffset == selection.x;
        int startOffset, endOffset;
        if (rightToLeft) {
            startOffset = selection.y;
            endOffset = selection.x;
        } else {
            startOffset = selection.x;
            endOffset = selection.y;
        }

        Rectangle startBounds = getBoundsAtOffset(startOffset);
        Rectangle endBounds = getBoundsAtOffset(endOffset);

        // can the selection be fully displayed within the widget's visible width?
        int w = clientAreaWidth - leftMargin - rightMargin;
        boolean selectionFits = rightToLeft ? startBounds.x - endBounds.x <= w : endBounds.x - startBounds.x <= w;
        if (selectionFits) {
            // show as much of the selection as possible by first showing
            // the start of the selection
            if (showLocation(startBounds, false)) {
                // endX value could change if showing startX caused a scroll to occur
                endBounds = getBoundsAtOffset(endOffset);
            }
            // the character at endOffset is not part of the selection
            endBounds.width = endOffset == caretOffset ? getCaretWidth() : 0;
            showLocation(endBounds, false);
        } else {
            // just show the end of the selection since the selection start
            // will not be visible
            showLocation(endBounds, true);
        }
    }

    void updateCaretVisibility() {
        Caret caret = getCaret();
        if (caret != null) {
            if (blockSelection && blockXLocation != -1) {
                caret.setVisible(false);
            } else {
                Point location = caret.getLocation();
                Point size = caret.getSize();
                boolean visible = topMargin <= location.y + size.y && location.y <= clientAreaHeight - bottomMargin
                        && leftMargin <= location.x + size.x && location.x <= clientAreaWidth - rightMargin;
                caret.setVisible(visible);
            }
        }
    }

    /**
     * Updates the selection and caret position depending on the text change.
     * <p>
     * If the selection intersects with the replaced text, the selection is
     * reset and the caret moved to the end of the new text.
     * If the selection is behind the replaced text it is moved so that the
     * same text remains selected.  If the selection is before the replaced text
     * it is left unchanged.
     * </p>
     *
     * @param startOffset offset of the text change
     * @param replacedLength length of text being replaced
     * @param newLength length of new text
     */
    void updateSelection(int startOffset, int replacedLength, int newLength) {
        if (selection.y <= startOffset) {
            // selection ends before text change
            if (isWordWrap())
                setCaretLocation();
            return;
        }
        if (selection.x < startOffset) {
            // clear selection fragment before text change
            internalRedrawRange(selection.x, startOffset - selection.x);
        }
        if (selection.y > startOffset + replacedLength && selection.x < startOffset + replacedLength) {
            // clear selection fragment after text change.
            // do this only when the selection is actually affected by the
            // change. Selection is only affected if it intersects the change (1GDY217).
            int netNewLength = newLength - replacedLength;
            int redrawStart = startOffset + newLength;
            internalRedrawRange(redrawStart, selection.y + netNewLength - redrawStart);
        }
        if (selection.y > startOffset && selection.x < startOffset + replacedLength) {
            // selection intersects replaced text. set caret behind text change
            setSelection(startOffset + newLength, 0, true, false);
        } else {
            // move selection to keep same text selected
            setSelection(selection.x + newLength - replacedLength, selection.y - selection.x, true, false);
        }
        setCaretLocation();
    }
}