/*******************************************************************************
 * Copyright (c) 2000, 2021 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 java.util.stream.*;

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.lineSeparator();
	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$
	private static int getX(Point p) { return p.x; } // workaround p -> p.x lose typing and cannot be chained in comparing...
	static final Comparator<Point> SELECTION_COMPARATOR = Comparator.comparingInt(StyledText::getX).thenComparingInt(p -> p.y);

	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
	Caret[] carets;
	int[] caretOffsets = {0};
	int caretAlignment;
	Point[] selection = { new Point(0, 0) }; // x and y are start and end caret offsets of selection (x <= y)
	int[] selectionAnchors = {0};				// position of selection anchor for the selection ranges. 0 based offset from beginning of text
	Point clipboardSelection;           // x and y are start and end caret offsets of previous selection
	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 hasStyleWithVariableHeight = false;
	boolean hasVerticalIndent = false;
	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;			//
	/** True if a non-default background color is set */
	boolean customBackground;
	/** True if a non-default foreground color is set */
	boolean customForeground;
	/** False iff the widget is disabled */
	boolean enabled = true;
	/** True iff the widget is in the midst of being enabled or disabled */
	boolean insideSetEnableCall;
	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 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 = { new Point(0, 0) }; // x and y are start and end caret offsets of selection (x <= y)
		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.
	 *
	 * @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 = Arrays.copyOf(styledText.selection, styledText.selection.length);
		}
		printerRenderer = new StyledTextRenderer(printer, null);
		printerRenderer.setContent(copyContent(styledText.getContent()));
		cacheLineData(styledText);
	}
	/**
	 * Caches all line data that needs to be requested from a listener.
	 *
	 * @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 (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 (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 (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>.
	 *
	 * @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[0].x);
			if (selection[0].y > 0) {
				endLine = content.getLineAtOffset(selection[0].x + selection[0].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 (Color color : colorTable) {
			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>
	 */
	static 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 &lt; 0 or &gt; 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();
	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;
			setCaretLocations(Arrays.stream(caretOffsets).mapToObj(this::getPointAtOffset).toArray(Point[]::new), 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());
	setCaretLocations();
	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));
	setCaretLocations();
}
/**
 * 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[0]. 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;
				topIndexY = getLinePixel(topIndex);
				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.getCachedLineHeight(lineIndex++);
			}
			if (lineIndex < lineCount && -delta + renderer.getCachedLineHeight(lineIndex) <= clientAreaHeight - topMargin - bottomMargin) {
				topIndex = lineIndex;
				topIndexY = -delta;
			} else {
				topIndex = lineIndex - 1;
				topIndexY = -renderer.getCachedLineHeight(topIndex) - delta;
			}
		} else {
			delta -= topIndexY;
			int lineIndex = topIndex;
			while (lineIndex > 0) {
				int lineHeight = renderer.getCachedLineHeight(lineIndex - 1);
				if (delta + lineHeight > 0) break;
				delta += lineHeight;
				lineIndex--;
			}
			if (lineIndex == 0 || -delta + renderer.getCachedLineHeight(lineIndex) <= clientAreaHeight - topMargin - bottomMargin) {
				topIndex = lineIndex;
				topIndexY = - delta;
			} else {
				topIndex = lineIndex - 1;
				topIndexY = - renderer.getCachedLineHeight(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[0].x;
	int selectionEnd = selection[0].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 if (getSelectionRanges().length > 2) {
			StringBuilder text = new StringBuilder();
			int[] ranges = getSelectionRanges();
			for (int i = 0; i < ranges.length; i += 2) {
				int offset = ranges[i];
				int length = ranges[i + 1];
				text.append(length == 0 ? "" : getText(offset, offset + length - 1));
				text.append(PlatformLineDelimiter);
			}
			text.delete(text.length() - PlatformLineDelimiter.length(), text.length());
			if (text.length() > 0) {
				//TODO RTF support
				clipboard.setContents(new Object[]{text.toString()},  new Transfer[]{TextTransfer.getInstance()}, type);
				return true;
			}
		} else {
			int length = selection[0].y - selection[0].x;
			if (length > 0) {
				setClipboardContent(selection[0].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>
 *R
 * @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[0].x == selection[0].y) return false;
		int offset = getOffsetAtPoint(event.x, event.y, null, true);
		if (selection[0].x <= offset && offset < selection[0].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 = getFirstCaretLine();
	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 (Arrays.stream(selection).anyMatch(p -> p.x != p.y)) { // at least one range to delete
		for (int i = selection.length - 1; i >= 0; i--) { // from bottom to top to avoid moving ranges
			Point sel = selection[i];
			event.start = sel.x;
			event.end = sel.y;
			sendKeyEvent(event);
		}
	} else {
		for (int i = caretOffsets.length - 1; i >= 0; i--) { // reverse loop, process from bottom to top
			int caretOffset = caretOffsets[i];
			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(caretOffsets[0], 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(caretOffsets[0], false);
	int offset = end ? content.getCharCount() : 0;
	setBlockSelectionOffset(offset, true);
	showCaret();
}
void doBlockWord(boolean next) {
	if (blockXLocation == -1) setBlockSelectionOffset(caretOffsets[0], 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(caretOffsets[0], 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(caretOffsets[0], 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 (caretOffsets[0] > selectionAnchors[0]) {
		selection[0].x = selectionAnchors[0];
		selection[0].y = caretOffsets[0];
	} else {
		selection[0].x = caretOffsets[0];
		selection[0].y = selectionAnchors[0];
	}
	updateCaretVisibility();
	setCaretLocations();
	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(final char key) {
	if (blockSelection && blockXLocation != -1) {
		insertBlockSelectionText(key, SWT.NULL);
		return;
	}

	for (int i = selection.length - 1; i >= 0; i--) {
		Point sel = selection[i];
		Event event = new Event();
		event.start = sel.x;
		event.end = sel.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 (sel.x == sel.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();
		setCaretOffsets(new int[] {length}, SWT.DEFAULT);
		showCaret();
	}
}
/**
 * Moves the caret in front of the first character of the widget content.
 */
void doContentStart() {
	setCaretOffsets(new int[] {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 (Arrays.stream(selection).anyMatch(p -> p.x != p.y)) {
		setCaretOffsets(Arrays.stream(selection).mapToInt(sel -> sel.x).toArray(), 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 (Arrays.stream(selection).anyMatch(p -> p.x != p.y)) {
		setCaretOffsets(Arrays.stream(selection).mapToInt(sel -> sel.y).toArray(), 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 (Arrays.stream(selection).anyMatch(sel -> sel.x != sel.y)) {
		for (Point sel : selection) {
			event.start = sel.x;
			event.end = sel.y;
			sendKeyEvent(event);
		}
	} else {
		for (int i = caretOffsets.length - 1; i >= 0; i--) {
			int caretOffset = caretOffsets[i];
			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 (Arrays.stream(selection).anyMatch(sel -> sel.x != sel.y)) {
		// if a selection exists, treat the as if
		// only the delete key was pressed
		doDelete();
	} else {
		for (int i = caretOffsets.length - 1; i >= 0; i--) {
			int caretOffset = caretOffsets[i];
			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 (Arrays.stream(selection).anyMatch(sel -> sel.x != sel.y)) {
		// if a selection exists, treat as if
		// only the backspace key was pressed
		doBackspace();
	} else {
		for (int i = caretOffsets.length - 1; i >= 0; i--) {
			int caretOffset = caretOffsets[i];
			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[] newCaretOffsets = new int[caretOffsets.length];
	int lineCount = content.getLineCount();
	int[] alignment = new int[1];
	for (int i = 0; i < caretOffsets.length; i++) {
		int caretOffset = caretOffsets[i];
		int caretLine = content.getLineAtOffset(caretOffset);
		int x = caretOffsets.length == 1 ? columnX : getPointAtOffset(caretOffset).x;
		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) {
			newCaretOffsets[i] = content.getCharCount();
		} else {
			newCaretOffsets[i] = getOffsetAtPoint(x, y, caretLine, alignment);
		}
	}
	boolean hitLastLine = content.getLineAtOffset(newCaretOffsets[newCaretOffsets.length - 1]) == lineCount - 1;
	setCaretOffsets(newCaretOffsets, hitLastLine ? SWT.DEFAULT : 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[] newOffsets = new int[caretOffsets.length];
	for (int i = 0; i < caretOffsets.length; i++) {
		int caretOffset = caretOffsets[i];
		int caretLine = content.getLineAtOffset(caretOffset);
		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;
		}
		newOffsets[i] = lineEndOffset;
	}
	setCaretOffsets(newOffsets, PREVIOUS_OFFSET_TRAILING);
	showCaret();
}
/**
 * Moves the caret to the beginning of the line.
 */
void doLineStart() {
	int[] newCaretOffsets = new int[caretOffsets.length];
	for (int i = 0; i < caretOffsets.length; i++) {
		int caretOffset = caretOffsets[i];
		int caretLine = content.getLineAtOffset(caretOffset);
		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);
		}
		newCaretOffsets[i] = lineOffset;
	}
	setCaretOffsets(newCaretOffsets, 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[] newCaretOffsets = new int[caretOffsets.length];
	int[] alignment = new int[1];
	for (int i = 0; i < caretOffsets.length; i++) {
		int caretOffset = caretOffsets[i];
		int caretLine = content.getLineAtOffset(caretOffset);
		int x = caretOffsets.length == 1 ? columnX : getPointAtOffset(caretOffset).x;
		int 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) {
			newCaretOffsets[i] = 0;
		} else {
			newCaretOffsets[i] = getOffsetAtPoint(x, y, caretLine, alignment);
		}
	}
	setCaretOffsets(newCaretOffsets, newCaretOffsets[0] == 0 ? SWT.DEFAULT : 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(caretOffsets[0], 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(caretOffsets[0]);
	if (vchange && hchange && (newCaretOffset != caretOffsets[0] || newCaretAlignemnt != caretAlignment)) {
		setCaretOffsets(new int[] {newCaretOffset}, newCaretAlignemnt);
		if (select) doMouseSelection();
		showCaret();
	}
	if (!select) {
		setCaretOffsets(new int[] {newCaretOffset}, newCaretAlignemnt);
		clearSelection(true);
	}
}
/**
 * Updates the selection based on the caret position
 */
void doMouseSelection() {
	if (caretOffsets[0] <= selection[0].x ||
		(caretOffsets[0] > selection[0].x &&
		 caretOffsets[0] < selection[0].y && selectionAnchors[0] == selection[0].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 < selectionAnchors[0] && selectionAnchors[0] == selection[0].x) {
		selectionAnchors[0] = doubleClickSelection.y;
	} else if (newCaretOffset > selectionAnchors[0] && selectionAnchors[0] == selection[0].y) {
		selectionAnchors[0] = doubleClickSelection.x;
	}
	if (0 <= x && x < clientAreaWidth) {
		boolean wordSelect = (clickCount & 1) == 0;
		if (caretOffsets[0] == selection[0].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 = getFirstCaretLine();
		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);
			setCaretOffsets(new int[] {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 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()) {
			for (int caretOffset : caretOffsets) {
				int caretLine = content.getLineAtOffset(caretOffset);
				TextLayout layout = renderer.getTextLayout(caretLine);
				int offsetInLine = caretOffset - content.getOffsetAtLine(caretLine);
				lineIndex = getVisualLineIndex(layout, offsetInLine);
				caretHeight += layout.getLineBounds(lineIndex).y;
				renderer.disposeTextLayout(layout);
			}
		}
		lineIndex = getFirstCaretLine();
		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);
		setCaretOffsets(new int[] {offset}, alignment[0]);
		if (select) doSelection(ST.COLUMN_NEXT);
		height = getAvailableHeightBellow(height);
		scrollVertical(height, true);
		if (height == 0) setCaretLocations();
	}
	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 if (caretOffsets.length == 1) { // pageEnd doesn't make sense with multi-carets
		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 (caretOffsets[0] < bottomOffset) {
			setCaretOffsets(new int[] {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 (caretOffsets[0] > topOffset) {
		setCaretOffsets(new int[] {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 = getFirstCaretLine();
		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);
			setCaretOffsets(new int[] {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 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()) {
			for (int caretOffset : caretOffsets) {
				int caretLine = content.getLineAtOffset(caretOffset);
				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 = getFirstCaretLine();
		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);
		setCaretOffsets(new int[] {offset}, alignment[0]);
		if (select) doSelection(ST.COLUMN_PREVIOUS);
		height = getAvailableHeightAbove(height);
		scrollVertical(-height, true);
		if (height == 0) setCaretLocations();
	}
	showCaret();
	int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
	columnX = oldColumnX + hScrollChange;
}
/**
 * Updates the selection to extend to the current caret position.
 */
void doSelection(int direction) {
	if (caretOffsets.length != selection.length) {
		return;
	}
	if (selectionAnchors.length != selection.length) {
		selectionAnchors = new int[selection.length];
		Arrays.fill(selectionAnchors, -1);
	}
	boolean selectionChanged = false;
	Point[] newSelection = Arrays.stream(selection).map(p -> new Point(p.x, p.y)).toArray(Point[]::new);
	boolean[] caretAtBeginning = new boolean[newSelection.length];
	for (int i = 0; i < caretOffsets.length; i++) {
		int caretOffset = caretOffsets[i];
		Point currentSelection = newSelection[i];
		int selectionAnchor = selectionAnchors[i];
		if (selectionAnchor == -1) {
			selectionAnchor = selectionAnchors[i] = currentSelection.x;
		}
		int redrawStart = -1;
		int redrawEnd = -1;
		if (direction == ST.COLUMN_PREVIOUS) {
			if (caretOffset < currentSelection.x) {
				caretAtBeginning[i] = true;
				// grow selection
				redrawEnd = currentSelection.x;
				redrawStart = currentSelection.x = caretOffset;
				// check if selection has reversed direction
				if (currentSelection.y != selectionAnchor) {
					redrawEnd = currentSelection.y;
					currentSelection.y = selectionAnchor;
				}
			// test whether selection actually changed. Fixes 1G71EO1
			} else if (selectionAnchor == currentSelection.x && caretOffset < currentSelection.y) {
				// caret moved towards selection anchor (left side of selection).
				// shrink selection
				redrawEnd = currentSelection.y;
				redrawStart = currentSelection.y = caretOffset;
			}
		} else {
			if (caretOffset > currentSelection.y) {
				// grow selection
				redrawStart = currentSelection.y;
				redrawEnd = currentSelection.y = caretOffset;
				// check if selection has reversed direction
				if (currentSelection.x != selectionAnchor) {
					redrawStart = currentSelection.x;
					currentSelection.x = selectionAnchor;
				}
			// test whether selection actually changed. Fixes 1G71EO1
			} else if (selectionAnchor == currentSelection.y && caretOffset > currentSelection.x) {
				// caret moved towards selection anchor (right side of selection).
				// shrink selection
				caretAtBeginning[i] = true;
				redrawStart = currentSelection.x;
				redrawEnd = currentSelection.x = caretOffset;
			}
		}
		if (redrawStart != -1 && redrawEnd != -1) {
			internalRedrawRange(redrawStart, redrawEnd - redrawStart);
			selectionChanged = true;
		}
	}
	if (selectionChanged) {
		int[] regions = new int[newSelection.length * 2];
		for (int i = 0; i < newSelection.length; i++) {
			Point p = newSelection[i];
			if (caretAtBeginning[i]) {
				regions[2 * i] = p.y;
				regions[2 * i + 1] = p.x - p.y;
			} else {
				regions[2 * i] = p.x;
				regions[2 * i + 1] = p.y - p.x;
			}
		}
		setSelection(regions, false, blockSelection);
		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[] newCarets = Arrays.copyOf(caretOffsets, caretOffsets.length);
	int newAlignment = Integer.MIN_VALUE;
	for (int i = 0; i < caretOffsets.length; i++) {
		int caretOffset = caretOffsets[i];
		int caretLine = content.getLineAtOffset(caretOffset);
		int lineOffset = content.getOffsetAtLine(caretLine);
		int offsetInLine = caretOffset - lineOffset;
		int offset;
		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;
			newAlignment = offsetInLine == lineStart ? OFFSET_LEADING : PREVIOUS_OFFSET_TRAILING;
			newCarets[i] = offset;
		} else if (caretLine < content.getLineCount() - 1 && !isSingleLine()) {
			caretLine++;
			offset = content.getOffsetAtLine(caretLine);
			newAlignment = PREVIOUS_OFFSET_TRAILING;
			newCarets[i] = offset;
		}
	}
	if (newAlignment > Integer.MIN_VALUE) {
		setCaretOffsets(newCarets, newAlignment);
		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[] newCarets = Arrays.copyOf(caretOffsets, caretOffsets.length);
	for (int i = 0; i < caretOffsets.length; i++) {
		int caretOffset = caretOffsets[i];
		int caretLine = content.getLineAtOffset(caretOffset);
		int lineOffset = content.getOffsetAtLine(caretLine);
		int offsetInLine = caretOffset - lineOffset;
		if (offsetInLine > 0) {
			newCarets[i] = getClusterPrevious(caretOffset, caretLine);
		} else if (caretLine > 0) {
			caretLine--;
			lineOffset = content.getOffsetAtLine(caretLine);
			newCarets[i] = lineOffset + content.getLine(caretLine).length();
		}
	}
	if (!Arrays.equals(caretOffsets, newCarets)) {
		setCaretOffsets(newCarets, 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(caretOffsets[0]).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(caretOffsets[0]).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(caretOffsets[0]).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) {
	if (caretOffsets.length > 1) { // operation doesn't make sense for multi-carets
		return;
	}
	int oldColumnX = columnX = getPointAtOffset(caretOffsets[0]).x;
	doPageUp(true, pixels);
	columnX = oldColumnX;
}
/**
 * Moves the caret to the end of the next word .
 */
void doSelectionWordNext() {
	int[] offsets = Arrays.stream(caretOffsets).map(offset -> getWordNext(offset, SWT.MOVEMENT_WORD)).toArray();
	// don't change caret position if in single line mode and the cursor
	// would be on a different line. fixes 5673
	if (!isSingleLine()) {
		// Force symmetrical movement for word next and previous. Fixes 14536
		setCaretOffsets(offsets, OFFSET_LEADING);
		showCaret();
	} else {
		int[] linesForCurrentCarets = Arrays.stream(caretOffsets).map(offset -> content.getLineAtOffset(offset)).toArray();
		int[] linesForNewCarets = Arrays.stream(offsets).map(offset -> content.getLineAtOffset(offset)).toArray();
		if (Arrays.equals(linesForCurrentCarets, linesForNewCarets)) {
			// Force symmetrical movement for word next and previous. Fixes 14536
			setCaretOffsets(offsets, OFFSET_LEADING);
			showCaret();
		}
	}
}
/**
 * Moves the caret to the start of the previous word.
 */
void doSelectionWordPrevious() {
	setCaretOffsets(Arrays.stream(caretOffsets).map(offset -> getWordPrevious(offset, SWT.MOVEMENT_WORD)).toArray(), 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() {
	setCaretOffsets(Arrays.stream(caretOffsets).map(offset -> getClusterPrevious(offset, content.getLineAtOffset(offset))).toArray(), 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() {
	setCaretOffsets(Arrays.stream(caretOffsets).map(offset -> getClusterNext(offset, content.getLineAtOffset(offset))).toArray(), 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 (Arrays.stream(selection).anyMatch(p -> p.x != p.y)) {
		setCaretOffsets(Arrays.stream(selection).mapToInt(sel -> sel.y).toArray(), 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 (Arrays.stream(selection).anyMatch(p -> p.x != p.y)) {
		setCaretOffsets(Arrays.stream(selection).mapToInt(sel -> sel.x).toArray(), 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[0].x);
		Point endPoint = getPointAtOffset(selection[0].y);
		int height = getLineHeight(selection[0].y);
		rect = new Rectangle(startPoint.x, startPoint.y, endPoint.x - startPoint.x, endPoint.y + height - startPoint.y);
		if (selection[0].x == selection[0].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 (Arrays.binarySearch(caretOffsets, offset) >= 0 && !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 caretOffsets[0];
}
/**
 * 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) {
					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>
 * <p>
 * It is usually better to use {@link #getSelectionRanges()} which better
 * support multiple selection and carets and block selection.
 * </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 #getSelectionRanges
 * @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[0].x, selection[0].y);
}
/**
 * Returns the selection.
 * <p>
 * It is usually better to use {@link #getSelectionRanges()} which better
 * support multiple selection and carets and block selection.
 * </p>
 *
 * @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>
 * @see #getSelectionRanges
 */
public Point getSelectionRange() {
	checkWidget();
	return new Point(selection[0].x, selection[0].y - selection[0].x);
}
/**
 * Returns the selected ranges of text.
 * If block is enabled, return the ranges that are inside the block selection rectangle.
 * <p>
 * The ranges array contains start and length pairs.
 * <p>
 * When the receiver is not
 * in block selection mode the return arrays contains the start and length of
 * the regular selections.
 *
 * @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
 * @see #setSelectionRanges(int[])
 */
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;
	}
	int[] res = new int[2 * selection.length];
	int index = 0;
	for (Point p : selection) {
		res[index++] = p.x;
		res[index++] = p.y - p.x;
	}
	return res;
}
/**
 * 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 Arrays.stream(selection).collect(Collectors.summingInt(sel -> sel.y - sel.x));
}
/**
 * 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 Arrays.stream(selection).map(sel -> content.getTextRange(sel.x, sel.y - sel.x)).collect(Collectors.joining());
}
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 (char segmentsChar : segmentsChars) {
			if (segmentsChar == '\n' || segmentsChar == '\r') {
				visualWrap = true;
				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.getCachedLineHeight(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(getFirstCaretLine());
		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 = getFirstCaretLine();
	int lineOffset = content.getOffsetAtLine(caretLine);
	String line = content.getLine(caretLine);
	int offset = caretOffsets[0] - 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 first/top caret is on.
 */
int getFirstCaretLine() {
	return content.getLineAtOffset(caretOffsets[0]);
}
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 && (Arrays.binarySearch(caretOffsets, offset) < 0 || Arrays.stream(selection).allMatch(p -> p.x == p.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 (String line : lines) {
			int length = line.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 = getFirstCaretLine();
		left = right = getPointAtOffset(caretOffsets[0]).x;
	}
	start = caretOffsets[0];
	int caretLine = getFirstCaretLine();
	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, this::handleVerticalScroll);
	}
	if (horizontalBar != null) {
		horizontalBar.addListener(SWT.Selection, this::handleHorizontalScroll);
	}
}
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[0].x;
		event.end = selection[0].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, "");
		setCaretOffsets(new int[] {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();
			for (int caretOffset : caretOffsets) {
				int lineIndex = content.getLineAtOffset(caretOffset);
				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;
		}
		setCaretOffsets(new int[] {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 (carets != null) {
		for (Caret caret : carets) {
			if (caret != null) {
				caret.dispose();
			}
		}
		carets = 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 ||
					event.stateMask == SWT.CTRL ||
					event.stateMask == (SWT.ALT | SWT.SHIFT) ||
					event.stateMask == (SWT.CTRL | SWT.SHIFT);
		}
		// -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[0].x, selection[0].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[0].x || clipboardSelection.y != selection[0].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(caretOffsets[0]));
		event.x = point.x;
		event.y = point.y + getLineHeight(caretOffsets[0]);
	}
}
/**
 * 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;
	boolean addSelection = (event.stateMask & SWT.MOD3) != 0;
	if (clickCount == 1) {
		if (addSelection && !blockSelection) {
			int offset = getOffsetAtPoint(event.x, event.y, null);
			addSelection(offset, 0);
			sendSelectionEvent();
		} else {
			boolean expandSelection = (event.stateMask & SWT.MOD2) != 0;
			doMouseLocationChange(event.x, event.y, expandSelection);
		}
	} 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) {
				String line = content.getLine(lineIndex);
				int lineLength = line.length();
				int min = blockSelection ? lineOffset : 0;
				int max = blockSelection ? lineOffset + lineLength : content.getCharCount();
				final Point offsetPoint = getPointAtOffset(offset);
				if (event.x > offsetPoint.x
						&& offset < Math.min(max, lineOffset + lineLength) // Not beyond EOL
						&& !Character.isWhitespace(line.charAt(offset - lineOffset))) { // Not on whitespace
					offset++;
				}
				int start = Math.max(min, getWordPrevious(offset, SWT.MOVEMENT_WORD_START));
				int end = Math.min(max, getWordNext(start, SWT.MOVEMENT_WORD_END));
				int[] regions = new int[2];
				if (addSelection) {
					int[] current = getSelectionRanges();
					regions = Arrays.copyOf(current, current.length + 2);
				}
				regions[regions.length - 2] = start;
				regions[regions.length - 1] = end - start;
				setSelection(regions, 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);
					}
					int[] regions = new int[2];
					if (addSelection) {
						int[] current = getSelectionRanges();
						regions = Arrays.copyOf(current, current.length + 2);
					}
					regions[regions.length - 2] = lineOffset;
					regions[regions.length - 1] = lineEnd - lineOffset;
					setSelection(regions, false, false);
					sendSelectionEvent();
				}
			}
			doubleClickSelection = new Point(selection[0].x, selection[0].y);
			showCaret();
		}
	}
}

void addSelection(int offset, int length) {
	int[] ranges = getSelectionRanges();
	ranges = Arrays.copyOf(ranges, ranges.length + 2);
	ranges[ranges.length - 2] = offset;
	ranges[ranges.length - 1] = length;
	setSelection(ranges, true, true);
}

/**
 * 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);
		}
	}
	if(carets != null) {
		for (int i = 1; i < carets.length; i++) { //skip 1st caret that's already drawn
			Caret caret = carets[i];
			if (caret.isVisible()) {
				if (caret.getImage() != null) {
					gc.drawImage(caret.getImage(), caret.getBounds().x, caret.getBounds().y);
				} else {
					gc.drawRectangle(caret.getBounds().x, caret.getBounds().y, caret.getBounds().width, getLineHeight(caretOffsets[i]));
				}
			}
		}
	}

	// 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);
		}
		setCaretLocations();
	} 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;
	int tooBigOffsets = 0;
	while (tooBigOffsets < caretOffsets.length && caretOffsets[caretOffsets.length - 1 - tooBigOffsets] > newEndOfText) {
		tooBigOffsets++;
	}
	if (tooBigOffsets != 0) {
		int[] newCaretOffsets = Arrays.copyOf(caretOffsets, caretOffsets.length - tooBigOffsets + 1);
		newCaretOffsets[newCaretOffsets.length - 1] = newEndOfText;
		setCaretOffsets(newCaretOffsets, 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 !isWordWrap() && lineSpacing == 0 && renderer.lineSpacingProvider == null && !hasStyleWithVariableHeight && !hasVerticalIndent;
}
/**
 * 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 Arrays.stream(selection).anyMatch(range -> range.x != range.y);
}
/**
 * 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(Arrays.stream(selection).map(sel -> {
				if (sel.y < event.start || sel.x > event.end) {
					return sel;
				} else { // current range edited
					return new Point(event.start + event.text.length(), event.start + event.text.length());
				}
			}).flatMapToInt(p -> IntStream.of(p.x, p.y - p.x)).toArray(), 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);
			setCaretOffsets(new int[] {offset}, SWT.DEFAULT);
			clearBlockSelection(true, true);
			setCaretLocations();
			return;
		} else if (getSelectionRanges().length / 2 > 1) { // multi selection
			insertMultiSelectionText(text);
			setCaretLocations();
			return;
		}
		Event event = new Event();
		event.start = selection[0].x;
		event.end = selection[0].y;
		String delimitedText = getModelDelimitedText(text);
		if (textLimit > 0) {
			int uneditedTextLength = getCharCount() - (selection[0].y - selection[0].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 insertMultiSelectionText(String text) {
	String[] blocks = text.split(PlatformLineDelimiter);
	int[] ranges = getSelectionRanges();
	for (int i = ranges.length / 2 - 1; i >= 0; i --) {
		int offset = ranges[2 * i];
		int length = ranges[2 * i + 1];
		String toPaste = blocks.length > i ? blocks[i] : blocks[blocks.length - 1];
		Event event = new Event();
		event.start = offset;
		event.end = offset + length;
		event.text = toPaste;
		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[0].x;
		e.end = selection[0].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 redrawLine : redrawLines) {
		int lineIndex = redrawLine;
		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());
	setCaretLocations();
	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);
	setCaretLocations();
}
/**
 * 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();
	setCaretOffsets(new int[] {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);
	setCaretLocations();
	super.redraw();
}
void resetBidiData() {
	caretDirection = SWT.NULL;
	resetCache(0, content.getLineCount());
	setCaretLocations();
	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 = Arrays.stream(caretOffsets).mapToObj(offset -> new Point(offset, offset)).toArray(Point[]::new);
	selectionAnchors = Arrays.copyOf(caretOffsets, caretOffsets.length);
	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;
		for (Control child : getChildren()) {
			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, &gt; 0 = scroll left,
 * 	&lt; 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;
	setCaretLocations();
	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 (Control child : children) {
			Rectangle rect = child.getBounds();
			child.setLocation(rect.x, rect.y + deltaY);
		}
		verticalScrollOffset += pixels;
		calculateTopIndex(pixels);
		super.redraw();
	} else {
		calculateTopIndex(pixels);
		super.redraw();
	}
	setCaretLocations();
	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 (Arrays.stream(caretOffsets).noneMatch(caretOffset -> caretOffset == accCaretOffset)) {
		accCaretOffset = caretOffsets[0];
		getAccessible().textCaretMoved(caretOffsets[0]);
	}
}
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[0].x;
	event.y = selection[selection.length - 1].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);
		setCaretLocations();
		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());
	setCaretLocations();
	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();
	boolean backgroundDisabled = false;
	if (!this.enabled && color == null) {
		if (background != null) {
			Color disabledBg = getDisplay().getSystemColor(SWT.COLOR_TEXT_DISABLED_BACKGROUND);
			if (background.equals(disabledBg)) {
				return;
			} else {
				color = new Color (disabledBg.getRGBA());
				backgroundDisabled = true;
			}
		}
	}
	customBackground = color != null && !this.insideSetEnableCall && !backgroundDisabled;
	background = color;
	super.setBackground(color);
	resetCache(0, content.getLineCount());
	setCaretLocations();
	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[0].x;
		int end = selection[0].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(new int[] {start, end - start}, false, false);
		setCaretLocations();
		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);
	setCaretOffsets(new int[] {offset}, alignment[0]);
	if (blockXAnchor == -1) {
		blockXAnchor = blockXLocation;
		blockYAnchor = blockYLocation;
		selectionAnchors[0] = caretOffsets[0];
	}
	doBlockSelection(sendEvent);
}
void setBlockSelectionLocation (int anchorX, int anchorY, int x, int y, boolean sendEvent) {
	int verticalScrollOffset = getVerticalScrollOffset();
	blockXAnchor = anchorX + horizontalScrollOffset;
	blockYAnchor = anchorY + verticalScrollOffset;
	selectionAnchors[0] = 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;
	setCaretOffsets(new int[] {offset}, SWT.DEFAULT);
	if (blockXAnchor == -1) {
		blockXAnchor = blockXLocation;
		blockYAnchor = blockYLocation;
		selectionAnchors[0] = caretOffsets[0];
	}
	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;
	selectionAnchors[0] = 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) {
		setCaretLocations();
		if(carets != null) {
			for (int i = 1; i < carets.length; i++) {
				carets[i].dispose();
			}
		}
		carets = new Caret[] {caret};
	} else {
		carets = null;
	}
}
/**
 * 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 setCaretLocations() {
	Point[] newCaretPos = Arrays.stream(caretOffsets).mapToObj(this::getPointAtOffset).toArray(Point[]::new);
	setCaretLocations(newCaretPos, getCaretDirection());
}
void setCaretLocations(final Point[] locations, int direction) {
	Caret firstCaret = getCaret();
	if (firstCaret != null) {
		if (carets == null || carets.length == 0) {
			carets = new Caret[] { firstCaret };
		}
		final boolean isDefaultCaret = firstCaret == defaultCaret;
		if (locations.length > carets.length) {
			int formerCaretCount = carets.length;
			carets = Arrays.copyOf(carets, locations.length);
			for (int i = formerCaretCount; i < carets.length; i++) {
				carets[i] = new Caret(this, firstCaret.getStyle());
				carets[i].setImage(firstCaret.getImage());
				carets[i].setFont(firstCaret.getFont());
			}
		} else if (locations.length < carets.length) {
			for (int i = locations.length; i < carets.length; i++) {
				carets[i].dispose();
			}
			carets = Arrays.copyOf(carets, locations.length);
		}
		for (int i = 0; i < Math.min(caretOffsets.length, locations.length); i++) {
			final Caret caret = carets[i];
			final int caretOffset = caretOffsets[i];
			final Point location = locations[i];
			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 = content.getLineAtOffset(caretOffset);

			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 = locations[0].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 && !Arrays.equals(caretOffsets, new int[] {offset})) {
		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);
			}
		}
		setCaretOffsets(new int[] {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);
		}
	}
	setCaretLocations();
}
void setCaretOffsets(int[] newOffsets, int alignment) {
	if (newOffsets.length > 1) {
		newOffsets = Arrays.stream(newOffsets).distinct().sorted().toArray();
	}
	if (!Arrays.equals(caretOffsets, newOffsets)) {
		caretOffsets = newOffsets;
		if (isListening(ST.CaretMoved)) {
			StyledTextEvent event = new StyledTextEvent(content);
			event.end = caretOffsets[caretOffsets.length - 1];
			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 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;
}
@Override
public void setEnabled(boolean enabled) {
	super.setEnabled(enabled);
	Display display = getDisplay();
	this.enabled = enabled;
	this.insideSetEnableCall = true;
	try {
		if (enabled && editable) {
			if (!customBackground) setBackground(display.getSystemColor(SWT.COLOR_LIST_BACKGROUND));
			if (!customForeground) setForeground(display.getSystemColor(SWT.COLOR_LIST_FOREGROUND));
		} else if(!enabled) {
			if (!customBackground) setBackground(display.getSystemColor(SWT.COLOR_TEXT_DISABLED_BACKGROUND));
			if (!customForeground) setForeground(display.getSystemColor(SWT.COLOR_WIDGET_DISABLED_FOREGROUND));
		} else if(!editable) {
			if (!customBackground) setBackground(display.getSystemColor(SWT.COLOR_TEXT_DISABLED_BACKGROUND));
			if (!customForeground) setForeground(display.getSystemColor(SWT.COLOR_LIST_FOREGROUND));
		}
	}
	finally {
		this.insideSetEnableCall = false;
	}
}
/**
 * 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;
	setCaretLocations();
	super.redraw();
}
@Override
public void setForeground(Color color) {
	checkWidget();
	boolean foregroundDisabled = false;
	if (!this.enabled && color == null) {
		if (foreground != null) {
			Color disabledFg = getDisplay().getSystemColor(SWT.COLOR_WIDGET_DISABLED_FOREGROUND);
			if (foreground.equals(disabledFg)) {
				return;
			} else {
				color = new Color (disabledFg.getRGBA());
				foregroundDisabled = true;
			}
		}
	}
	customForeground = color != null && !this.insideSetEnableCall && !foregroundDisabled;
	foreground = color;
	super.setForeground(color);
	resetCache(0, content.getLineCount());
	setCaretLocations();
	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());
	setCaretLocations();
	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());
	setCaretLocations();
	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);
	if (Arrays.stream(caretOffsets).map(content::getLineAtOffset).anyMatch(caretLine -> startLine <= caretLine && caretLine < startLine + lineCount)) {
		setCaretLocations();
	}
	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);
	if (Arrays.stream(caretOffsets).map(content::getLineAtOffset).anyMatch(caretLine -> startLine <= caretLine && caretLine < startLine + lineCount)) {
		setCaretLocations();
	}
}
/**
 * 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);
	if (Arrays.stream(caretOffsets).map(content::getLineAtOffset).anyMatch(caretLine -> startLine <= caretLine && caretLine < startLine + lineCount)) {
		setCaretLocations();
	}
}

/**
 * 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);
	}
	int previousVerticalIndent = renderer.getLineVerticalIndent(lineIndex);
	if (verticalLineIndent == previousVerticalIndent) {
		return;
	}
	int initialTopPixel = getTopPixel();
	int initialTopIndex = getPartialTopIndex();
	int initialBottomIndex = getPartialBottomIndex();
	int verticalIndentDiff = verticalLineIndent - previousVerticalIndent;
	renderer.setLineVerticalIndent(lineIndex, verticalLineIndent);
	this.hasVerticalIndent = verticalLineIndent != 0 || renderer.hasVerticalIndent();
	ScrollBar verticalScrollbar = getVerticalBar();
	if (lineIndex < initialTopIndex) {
		verticalScrollOffset += verticalIndentDiff; // just change value, don't actually scroll/redraw
		if (verticalScrollbar != null) {
			verticalScrollbar.setSelection(verticalScrollOffset);
			verticalScrollbar.setMaximum(verticalScrollbar.getMaximum() + verticalIndentDiff);
		}
	} else if (lineIndex > initialBottomIndex) {
		if (verticalScrollbar != null) {
			verticalScrollbar.setMaximum(verticalScrollbar.getMaximum() + verticalIndentDiff);
		}
	} else {
		resetCache(lineIndex, 1);
		if((initialTopIndex == 0) && (initialBottomIndex == (content.getLineCount() - 1))) { // not scrollable editor
		    setCaretLocations();
			redrawLines(lineIndex, getBottomIndex() - lineIndex + 1, true);
		} else if (getFirstCaretLine() >= initialTopIndex && getFirstCaretLine() <= initialBottomIndex) { // caret line with caret mustn't move
			if (getFirstCaretLine() < lineIndex) {
				redrawLines(lineIndex, getPartialBottomIndex() - lineIndex + 1, true);
			} else {
				setTopPixel(initialTopPixel + verticalIndentDiff);
			}
		} else { // move as few lines as possible
			if (lineIndex - getTopIndex() < getBottomIndex() - lineIndex) {
				setTopPixel(initialTopPixel + verticalIndentDiff);
			} else {
				// redraw below
				redrawLines(lineIndex, getPartialBottomIndex() - lineIndex + 1, true);
			}
		}
		setScrollBars(true);
	}
}

/**
 * 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);
	if (Arrays.stream(caretOffsets).map(content::getLineAtOffset).anyMatch(caretLine -> startLine <= caretLine && caretLine < startLine + lineCount)) {
		setCaretLocations();
	}
}
/**
 * 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;
	resetCache(0, content.getLineCount());
	setCaretLocations();
	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();
	boolean wasFixedLineHeight = isFixedLineHeight();
	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 (!wasFixedLineHeight) {
			resetCache(0, content.getLineCount());
		}
	} else {
		if (wasFixedLineHeight) {
			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
					// 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);
			}
		}
	}
	setCaretLocations();
	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);
	if (Arrays.stream(caretOffsets).map(content::getLineAtOffset).anyMatch(caretLine -> startLine <= caretLine && caretLine < startLine + lineCount)) {
		setCaretLocations();
	}
}
/**
 * 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);
	if (Arrays.stream(caretOffsets).map(content::getLineAtOffset).anyMatch(caretLine -> startLine <= caretLine && caretLine < startLine + lineCount)) {
		setCaretLocations();
	}
}

/**
 * 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);
	setCaretLocations();
	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 (caretOffsets[0] < doubleClickSelection.x) {
			selectionAnchors[0] = doubleClickSelection.y;
		} else if (caretOffsets[0] > doubleClickSelection.y) {
			selectionAnchors[0] = 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());
	setCaretLocations();
	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());
	setCaretLocations();
	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 &gt;= 0 must be true.
 * @param length number of characters to select, 0 &lt;= start + length
 * 	&lt;= 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[] regions, boolean sendEvent, boolean doBlock) {
	if (regions.length == 2 && selection.length == 1) { // single range before/after
		int start = regions[0];
		int length = regions[1];
		int end = start + length;
		if (start > end) {
			int temp = end;
			end = start;
			start = temp;
		}
		int selectionAnchor = selectionAnchors[0];
		// is the selection range different or is the selection direction
		// different?
		if (selection[0].x != start || selection[0].y != end ||
			(length > 0 && selectionAnchor != selection[0].x) ||
			(length < 0 && selectionAnchor != selection[0].y)) {
			if (blockSelection && doBlock) {
				if (length < 0) {
					setBlockSelectionOffset(end, start, sendEvent);
				} else {
					setBlockSelectionOffset(start, end, sendEvent);
				}
			} else {
				int oldStart = selection[0].x;
				int oldLength = selection[0].y - selection[0].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[0].x, charCount);
				int redrawY = Math.min(selection[0].y, charCount);
				if (length < 0) {
					selectionAnchors[0] = selectionAnchor = selection[0].y = end;
					selection[0].x = start;
					setCaretOffsets(new int[] {start}, PREVIOUS_OFFSET_TRAILING);
				} else {
					selectionAnchors[0] = selectionAnchor = selection[0].x = start;
					selection[0].y = end;
					setCaretOffsets(new int[] {end}, PREVIOUS_OFFSET_TRAILING);
				}
				redrawX = Math.min(redrawX, selection[0].x);
				redrawY = Math.max(redrawY, selection[0].y);
				if (redrawY - redrawX > 0) {
					internalRedrawRange(redrawX, redrawY - redrawX);
				}
				if (sendEvent && (oldLength != end - start || (oldLength != 0 && oldStart != start))) {
					sendSelectionEvent();
				}
				sendAccessibleTextCaretMoved();
			}
		}
	} else if (!blockSelection || !doBlock) {
		boolean caretAtEndOfSelection = regions[1] > 0;
		int charCount = content.getCharCount();
		Point[] newRanges = new Point[regions.length / 2];
		for (int i = 0; i < regions.length; i += 2) {
			int start = regions[i];
			int length = regions[i + 1];
			int end = start + length;
			if (start > end) {
				int temp = end;
				end = start;
				start = temp;
			}
			newRanges[i / 2] = new Point(start, end);
		}
		Arrays.sort(newRanges, SELECTION_COMPARATOR);
		// merge contiguous ranges
		int newRangeIndex = 0;
		for (Point range : newRanges) {
			if (newRangeIndex > 0) {
				Point previousRange = newRanges[newRangeIndex - 1];
				if (previousRange.y >= range.x) {
					previousRange.y = Math.max(previousRange.y, range.y);
				} else {
					newRanges[newRangeIndex] = range;
					newRangeIndex++;
				}
			} else {
				newRanges[newRangeIndex] = range;
				newRangeIndex++;
			}
		}
		Point[] toRedraw = new Point[newRangeIndex + selection.length];
		System.arraycopy(newRanges, 0, toRedraw, 0, newRangeIndex);
		System.arraycopy(selection, 0, toRedraw, newRangeIndex, selection.length);
		Arrays.sort(toRedraw, SELECTION_COMPARATOR);
		Point[] formerSelection = selection;
		selection = Arrays.copyOf(newRanges, newRangeIndex);
		Point currentToRedraw = null;
		for (Point p : toRedraw) {
			if (currentToRedraw == null) {
				currentToRedraw = new Point(p.x, p.y);
			} else if (currentToRedraw.y >= p.x - 1) {
				// expand if necessary
				currentToRedraw = new Point(currentToRedraw.x, Math.max(p.y, currentToRedraw.y));
			} else {
				currentToRedraw = new Point(Math.max(0, currentToRedraw.x), Math.min(charCount, currentToRedraw.y));
				internalRedrawRange(currentToRedraw.x, currentToRedraw.y - currentToRedraw.x);
				currentToRedraw = null;
			}
		}
		if (currentToRedraw != null) {
			currentToRedraw = new Point(Math.max(0, currentToRedraw.x), Math.min(charCount, currentToRedraw.y));
			internalRedrawRange(currentToRedraw.x, currentToRedraw.y - currentToRedraw.x);
		}
		if (!caretAtEndOfSelection) {
			selectionAnchors = Arrays.stream(selection).mapToInt(p -> p.y).toArray();
			setCaretOffsets(Arrays.stream(selection).mapToInt(p -> p.x).toArray(), PREVIOUS_OFFSET_TRAILING);
		} else {
			selectionAnchors = Arrays.stream(selection).mapToInt(p -> p.x).toArray();
			setCaretOffsets(Arrays.stream(selection).mapToInt(p -> p.y).toArray(), PREVIOUS_OFFSET_TRAILING);
		}
		setCaretLocations();
		if (sendEvent && !Arrays.equals(formerSelection, selection)) {
			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) {
	setSelectionRanges(new int[] { start, length });
}

/**
 * Sets the selected locations/ranges.
 * <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 ranges an array of offset/length pairs.
 * @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 one selection range is inside a
 * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)</li>
 *   <li>ERROR_INVALID_ARGUMENT when ranges are null or size isn't valid (not pair)</li>
 * </ul>
 * @see #getSelectionRanges()
 * @since 3.117
 */
public void setSelectionRanges(int[] ranges) {
	checkWidget();
	int contentLength = getCharCount();
	if (ranges.length % 2 != 0) {
		SWT.error(SWT.ERROR_INVALID_ARGUMENT);
	}
	int[] fixedRanges = Arrays.copyOf(ranges, ranges.length);
	for (int i = 0; i < ranges.length; i+=2) {
		int start = ranges[i];
		start = Math.max(0, Math.min(start, contentLength));
		int length = ranges[i + 1];
		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);
		}
		fixedRanges[i] = start;
		fixedRanges[i + 1] = length;
	}
	setSelection(fixedRanges, false, true);
	setCaretLocations();
}

/**
 * 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 charCount = content.getCharCount();
	if (reset) {
		start = 0;
		length = charCount;
	}
	int[] formerRanges = getRanges(start, length);
	StyleRange[] formerStyles = getStyleRanges(start, length);
	int end = start + length;
	final boolean wasFixedLineHeight = isFixedLineHeight();
	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;
		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);
			hasStyleWithVariableHeight |= styles[i].isVariableHeight();
			lastOffset = rangeStart + rangeLength;
		}
	}
	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;
		}
	}

	// This needs to happen before new styles are applied
	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);
	}

	// re-evaluate variable height with all styles (including new ones)
	hasStyleWithVariableHeight = false;
	for (StyleRange style : getStyleRanges(false)) {
		hasStyleWithVariableHeight = style.isVariableHeight();
		if (hasStyleWithVariableHeight) break;
	}

	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 (!(wasFixedLineHeight && isFixedLineHeight()) && bottom != expectedBottom) {
				bottom = clientAreaHeight;
			}
			super.redraw(0, top, clientAreaWidth, bottom - top, false);
		}
	}
	int oldColumnX = columnX;
	setCaretLocations();
	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 overlapping
 * @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 =  endRangeOffset(referenceRanges, referenceRangeIndex);
		} else if (referenceRangeIndex < referenceStyles.length) { // no range, default styling
			nextMilestoneOffset = referenceRanges[2 * referenceRangeIndex]; // 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 < newStyles.length) {
			nextMilestoneOffset = Math.min(nextMilestoneOffset, newRanges[2 * newRangeIndex]);
		}

		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());
	setCaretLocations();
	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 negative 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());
	setCaretLocations();
	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;
	resetCache(0, content.getLineCount());
	horizontalScrollOffset = 0;
	ScrollBar horizontalBar = getHorizontalBar();
	if (horizontalBar != null) {
		horizontalBar.setVisible(!wordWrap);
	}
	setScrollBars(true);
	setCaretLocations();
	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());
	setCaretLocations();
	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(caretOffsets[0]);
	if (!showLocation(bounds, true) || (carets != null && caretOffsets.length != carets.length)) {
		setCaretLocations();
	}
}
/**
 * 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 = caretOffsets[0] == selection[0].x;
	int startOffset, endOffset;
	if (rightToLeft) {
		startOffset = selection[0].y;
		endOffset = selection[0].x;
	} else {
		startOffset = selection[0].x;
		endOffset = selection[0].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 == caretOffsets[0] ? 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 (carets == null || carets.length == 0) {
			carets = new Caret[] { caret };
		}
		if (blockSelection && blockXLocation != -1) {
			Arrays.stream(carets).forEach(c -> c.setVisible(false));
		} else {
			Arrays.stream(carets).forEach(c -> {
				Point location = c.getLocation();
				Point size = c.getSize();
				boolean visible =
					topMargin <= location.y + size.y && location.y <= clientAreaHeight - bottomMargin &&
					leftMargin <= location.x + size.x && location.x <= clientAreaWidth - rightMargin;
				c.setVisible(visible);
			});
		}
		redraw();
	}
}
/**
 * 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[selection.length - 1].y <= startOffset) {
		// selection ends before text change
		if (isWordWrap()) setCaretLocations();
		return;
	}
	// clear selection fragment before text change
	Arrays.stream(selection)
		.filter(sel -> sel.y > startOffset)
		.filter(sel -> sel.x < startOffset)
		.forEach(sel -> internalRedrawRange(sel.x, startOffset - sel.x));
	Arrays.stream(selection)
		.filter(sel -> sel.y > startOffset)
		.filter(sel -> sel.y > startOffset + replacedLength && sel.x < startOffset + replacedLength)
		.forEach(sel -> {
			// 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, sel.y + netNewLength - redrawStart);
		});
	setSelection(Arrays.stream(selection).map(sel -> {
			if (sel.y <= startOffset) {
				return sel;
			} else if (sel.x == startOffset && sel.y == startOffset + replacedLength) {
				return new Point(startOffset + newLength, startOffset + newLength);
			} else if (sel.y > startOffset && sel.x < startOffset + replacedLength) {
				// selection intersects replaced text. set caret behind text change
				return new Point(startOffset + newLength, startOffset + newLength);
			} else {
				// move selection to keep same text selected
				int x = sel.x + newLength - replacedLength;
				int y = sel.x + newLength - replacedLength + (sel.y - sel.x);

				return new Point(x < 0 ? 0 : x, y < 0 ? 0 : y);
			}
		}).flatMapToInt(p -> IntStream.of(p.x, p.y - p.x))
		.toArray(), true, false);
	setCaretLocations();
}

}
