Bug 343854 - Web Application to Workbench Examples

Transfer Orion examples from CVS.
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/.classpath b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/.classpath
new file mode 100644
index 0000000..2d1a430
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<classpath>

+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>

+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>

+	<classpathentry kind="src" path="src"/>

+	<classpathentry kind="output" path="bin"/>

+</classpath>

diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/.project b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/.project
new file mode 100644
index 0000000..73e5a2b
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<projectDescription>

+	<name>org.eclipse.e4.examples.webintegration.orion.editor.plugin</name>

+	<comment></comment>

+	<projects>

+	</projects>

+	<buildSpec>

+		<buildCommand>

+			<name>org.eclipse.jdt.core.javabuilder</name>

+			<arguments>

+			</arguments>

+		</buildCommand>

+		<buildCommand>

+			<name>org.eclipse.pde.ManifestBuilder</name>

+			<arguments>

+			</arguments>

+		</buildCommand>

+		<buildCommand>

+			<name>org.eclipse.pde.SchemaBuilder</name>

+			<arguments>

+			</arguments>

+		</buildCommand>

+	</buildSpec>

+	<natures>

+		<nature>org.eclipse.pde.PluginNature</nature>

+		<nature>org.eclipse.jdt.core.javanature</nature>

+	</natures>

+</projectDescription>

diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/.settings/org.eclipse.jdt.core.prefs b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..17d7d23
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,8 @@
+#Fri May 06 09:49:44 EDT 2011

+eclipse.preferences.version=1

+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled

+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5

+org.eclipse.jdt.core.compiler.compliance=1.5

+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error

+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error

+org.eclipse.jdt.core.compiler.source=1.5

diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/META-INF/MANIFEST.MF b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..23c7d4c
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/META-INF/MANIFEST.MF
@@ -0,0 +1,15 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Orion Editor Plugin
+Bundle-SymbolicName: org.eclipse.e4.examples.webintegration.orion.editor.plugin; singleton:=true
+Bundle-Version: 1.0.0.qualifier
+Bundle-Vendor: Eclipse.org
+Require-Bundle: org.eclipse.ui,
+ org.eclipse.core.runtime,
+ org.eclipse.jface.text,
+ org.eclipse.ui.editors,
+ org.eclipse.ui.ide;bundle-version="3.7.0",
+ org.eclipse.core.resources;bundle-version="3.7.100",
+ org.mortbay.jetty.util;bundle-version="6.1.23"
+Bundle-RequiredExecutionEnvironment: J2SE-1.5
+Bundle-ActivationPolicy: lazy
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/build.properties b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/build.properties
new file mode 100644
index 0000000..c662e2b
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/build.properties
@@ -0,0 +1,8 @@
+source.. = src/
+output.. = bin/
+bin.includes = plugin.xml,\
+               META-INF/,\
+               .,\
+               icons/,\
+               orion/,\
+               editorService/
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/editorService/embeddededitor.js b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/editorService/embeddededitor.js
new file mode 100644
index 0000000..d593cc1
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/editorService/embeddededitor.js
@@ -0,0 +1,253 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials are made 
+ * available under the terms of the Eclipse Public License v1.0 
+ * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
+ * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+/*global eclipse:true orion:true dojo window editorServiceHandler editor:true */
+/*jslint devel:true*/
+
+/**
+ * This file demonstrates one way in which a web application could be structured
+ * to easily allow different behavior depending on whether the application is hosted
+ * in a browser or in an Eclipse Workbench
+ **/
+
+/**
+ * This object encapsulates the set of actions an editor may perform that would
+ * behave differently depending on where the web application is hosted.
+ * 
+ * The web application implementor may install a set of functions in this object
+ * that may differ for browser hosting vs. Eclipse Workbench hosting.
+ * 
+ * Not all functions need to be hooked.  Also, some functions may only be hooked for
+ * one type of hosting scenerio.
+ * 
+ * This object would be created by the web application developer as they are defining
+ * the separation of responsibilities for their web application.
+ * 
+ * This object is global because it needs to be referenced by the Eclipse Workbench
+ **/
+var editorService = {
+	DIRTY_CHANGED : 1,
+	dirtyChanged: function(dirty) {}, // Called by the editor when its dirty state changes
+
+	GET_CONTENT_NAME : 2,
+	getContentName: function() {},		// Called to get the current content name.  A file name for example
+
+	GET_INITIAL_CONTENT : 3,
+	getInitialContent: function() {},	// Called to get the initial contents for the editor
+
+	SAVE : 4,
+	save: function(editor) {},			// Called to persist the contents of the editor
+
+	STATUS_CHANGED : 5,
+	statusChanged: function(message, isError) {}	// Called by the editor to report status line changes
+};
+
+var editor;
+
+function initEmbeddedEditor(){
+	var editorDomNode = dojo.byId("editor");
+	
+	var textViewFactory = function() {
+		return new orion.textview.TextView({
+			parent: editorDomNode,
+			stylesheet: ["../../orion/textview/textview.css", "../../orion/textview/rulers.css", "../../examples/textview/textstyler.css", "../../examples/editor/htmlStyles.css"],
+			tabSize: 4
+		});
+	};
+
+	var contentAssistFactory = function(editor) {
+		var contentAssist = new orion.editor.ContentAssist(editor, "contentassist");
+		contentAssist.addProvider(new orion.editor.CssContentAssistProvider(), "css", "\\.css$");
+		contentAssist.addProvider(new orion.editor.JavaScriptContentAssistProvider(), "js", "\\.js$");
+		return contentAssist;
+	};
+	
+	// Canned highlighters for js, java, and css. Grammar-based highlighter for html
+	var syntaxHighlighter = {
+		styler: null, 
+		
+		highlight: function(fileName, textView) {
+			if (this.styler) {
+				this.styler.destroy();
+				this.styler = null;
+			}
+			if (fileName) {
+				var splits = fileName.split(".");
+				var extension = splits.pop().toLowerCase();
+				if (splits.length > 0) {
+					switch(extension) {
+						case "js":
+							this.styler = new examples.textview.TextStyler(textView, "js");
+							break;
+						case "java":
+							this.styler = new examples.textview.TextStyler(textView, "java");
+							break;
+						case "css":
+							this.styler = new examples.textview.TextStyler(textView, "css");
+							break;
+						case "html":
+							this.styler = new orion.editor.TextMateStyler(textView, orion.editor.HtmlGrammar.grammar);
+							break;
+					}
+				}
+			}
+		}
+	};
+	
+	var annotationFactory = new orion.editor.AnnotationFactory();
+
+	var keyBindingFactory = function(editor, keyModeStack, undoStack, contentAssist) {
+		
+		// Create keybindings for generic editing
+		var genericBindings = new orion.editor.TextActions(editor, undoStack);
+		keyModeStack.push(genericBindings);
+		
+		// create keybindings for source editing
+		var codeBindings = new orion.editor.SourceCodeActions(editor, undoStack, contentAssist);
+		keyModeStack.push(codeBindings);
+		
+		// save binding
+		editor.getTextView().setKeyBinding(new orion.textview.KeyBinding("s", true), "save");
+		editor.getTextView().setAction("save", function(){
+			// The save function is called through the editorService allowing Eclipse and Browser hosted instances to behave differently
+			editorService.save(editor);
+			return true;
+		});
+		
+		// speaking of save...
+		dojo.byId("save").onclick = function() {editorService.save(editor);};
+
+	};
+	
+	editor = new orion.editor.Editor({
+		textViewFactory: textViewFactory,
+		undoStackFactory: new orion.editor.UndoFactory(),
+		annotationFactory: annotationFactory,
+		lineNumberRulerFactory: new orion.editor.LineNumberRulerFactory(),
+		contentAssistFactory: contentAssistFactory,
+		keyBindingFactory: keyBindingFactory, 
+		statusReporter: editorService.statusChanged,
+		domNode: editorDomNode
+	});
+	
+	dojo.connect(editor, "onDirtyChange", this, editorService.dirtyChanged); // Hooks the onDirtyChange event listener through the editorService
+	
+	editor.installTextView();
+	
+	// Set editor input by calling through editorService
+	editor.onInputChange(editorService.getContentName(), null, editorService.getInitialContent());
+	
+	// Set the syntax highlighter
+	syntaxHighlighter.highlight(editorService.getContentName(), editor.getTextView());
+} // end of initEmbeddedEditor
+	
+// Created embedded editor
+dojo.addOnLoad(function() {
+	
+	// Install functions for servicing browser hosted applications
+	function installBrowserHooks() {
+
+		// Register a getContentName implementation
+		editorService.getContentName = function() {
+			return "sample.js";
+		};
+		
+		// Register a getInitialContent implementation
+		editorService.getInitialContent = function() {
+			return "var foo = function() {window.alert('bar');}; //Initial text.  Try editing it";
+		};
+			
+		// Register a save implementation.
+		editorService.save = function(editor) {
+			window.alert(editor.getContents());
+	
+			// Mark editor as saved
+			editor.onInputChange(null, null, null, true);
+		};
+		
+		// Register an implementation to display status changes reported by the editor
+		editorService.statusChanged = function(message, isError) {
+			var status;
+			if (isError) {
+				status =  "ERROR: " + message;
+			} else {
+				status = message;
+			}
+			
+			var dirtyIndicator = "";
+			if (editor.isDirty()) {
+				dirtyIndicator = "*";
+			}
+			dojo.byId("status").innerHTML = dirtyIndicator + status;
+		};
+		
+		// Prevent the browser tab/window from closing with unsaved changes.
+		// Not needed when running in a workbench since the editor lifecycle code takes
+		// care of this
+		window.onbeforeunload = function() {
+			if (editor.isDirty()) {
+				 return "There are unsaved changes.";
+			}
+		};
+	}
+		
+	// Install functions for servicing Eclipse Workbench hosted applications
+	function installWorkbenchHooks() {
+		// Register a function that will be called by the editor when the editor's dirty state changes
+		editorService.dirtyChanged = function(dirty) {
+			// This is a function created in Eclipse and registered with the page.
+			editorServiceHandler(editorService.DIRTY_CHANGED, dirty);
+		};
+
+		// Register a getContentName implementation
+		editorService.getContentName = function() {
+			// This is a function created in Eclipse and registered with the page.
+			return editorServiceHandler(editorService.GET_CONTENT_NAME);
+		};
+		
+		// Register an implementation that can return initial content for the editor
+		editorService.getInitialContent = function() {
+			// This is a function created in Eclipse and registered with the page.
+			return editorServiceHandler(editorService.GET_INITIAL_CONTENT);
+		};
+		
+		// Register an implementation that should run when the editors status changes.
+		editorService.statusChanged = function(message, isError) {
+			// This is a function created in Eclipse and registered with the page.
+			editorServiceHandler(editorService.STATUS_CHANGED, message);
+		};
+
+		// Register an implementation that can save the editors contents.		
+		editorService.save = function() {
+			// This is a function created in Eclipse and registered with the page.
+			var result = editorServiceHandler(editorService.SAVE, editor.getContents());
+			if (result) {
+				editor.onInputChange(null, null, null, true);
+			}
+			return result;
+		};
+	}
+	
+	// Return true if the page is hosted in an Eclipse Workbench, false if hosted in a browser
+	function isHostedInWorkbench() {
+		// Check if Eclipse has registered the "EditorServiceHandler" BrowserFunction
+		return typeof editorServiceHandler=== 'function';
+	}
+	
+	// Install the appropriate editorService for the current hosting environment
+	if (isHostedInWorkbench()) {
+		installWorkbenchHooks();
+	} else {
+		installBrowserHooks();
+	}
+	
+	// Initialize the editor
+	initEmbeddedEditor();
+});
\ No newline at end of file
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/editor/embeddededitor.css b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/editor/embeddededitor.css
new file mode 100644
index 0000000..393ee55
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/editor/embeddededitor.css
@@ -0,0 +1,29 @@
+@import "/org.dojotoolkit/dojo/resources/dojo.css";
+@import "/org.dojotoolkit/dijit/themes/nihilo/nihilo.css";
+
+a {
+	cursor: hand;
+	text-decoration: none;
+	color: #ffffff;
+}
+
+a:hover {
+	cursor: hand;
+	text-decoration: underline;
+	color: #ffffff;
+}
+
+.contentassist {
+	display: none;
+	background-color: #ffffff;
+	padding: 2px;
+	position: fixed;
+	top: 100px;
+	left: 100px;
+	border: 1px solid #cccccc;
+	z-index:10;
+}
+
+.contentassist .selected {
+	background-color: #dddddd;
+}
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/editor/embeddededitor.html b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/editor/embeddededitor.html
new file mode 100644
index 0000000..b61c0fa
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/editor/embeddededitor.html
@@ -0,0 +1,55 @@
+<!doctype html>
+<html style="height: 100%">
+    <head>
+		<meta name="copyright" content="Copyright (c) IBM Corporation and others 2010." >
+		<meta http-equiv="Content-Language" content="en-us">
+		<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
+    	<title>Embedded Orion Editor</title>
+    	<link rel="stylesheet" type="text/css" href="embeddededitor.css" />
+		
+		<script type="text/javascript">
+			var djConfig = {
+				isDebug:false,
+				parseOnLoad:true
+			};
+		</script>
+		<script>
+			// temporary
+	    	var __originalDefine = window.define;
+	    </script>
+	    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/dojo/1.6/dojo/dojo.xd.js"></script>
+		<script>
+			// temporary
+	    	window.define = __originalDefine;
+	    </script>
+	
+		<!-- Web Editor -->
+		<script src="../../orion/textview/keyBinding.js"></script>
+		<script src="../../orion/textview/textModel.js"></script>
+		<script src="../../orion/textview/textView.js"></script>
+		<script src="../../orion/textview/rulers.js"></script>
+		<script src="../../orion/textview/undoStack.js"></script>
+		<script src="../../examples/textview/textStyler.js"></script>
+	 	<script src="../../orion/editor/editorFeatures.js"></script>
+	 	<script src="../../orion/editor/contentAssist.js"></script>
+	 	<script src="../../orion/editor/htmlGrammar.js"></script>
+	 	<script src="../../orion/editor/textMateStyler.js"></script>
+	 	<script src="../../orion/editor/webContentAssist.js"></script>
+	 	<script src="../../orion/editor/editor.js"></script>
+		<script src="../../../editorService/embeddededitor.js"></script>
+   </head>
+
+    <body class="nihilo" style="margin: 0px; width: 100%; height: 100%">
+	<div id="logo" style="height: 28px; background: #404040; text-align: left;">
+		<img src="images/skinnyheaderlogo.png" alt="Orion">
+		<span id="bar" style="height: 28px; vertical-align: top; color: #FFFFFF">
+			<span id="status" style="height: 28px; vertical-align: top; color: #FFFFFF"></span>
+			<div id="actions" style="height: 28px; padding-right: 8px; vertical-align: top; float: right; color: #FFFFFF">
+				<a id="save">Save</a>
+			</div>
+		</span>
+	</div>
+    <div id="editor" style="width: 100%; height: 90%"></div>
+	<div id="contentassist" class="contentassist"></div>
+</body>
+</html>
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/editor/htmlStyles.css b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/editor/htmlStyles.css
new file mode 100644
index 0000000..a0b22a9
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/editor/htmlStyles.css
@@ -0,0 +1,26 @@
+/* Styling for html syntax highlighting */
+.entity-name-tag {
+	color: #3f7f7f;
+}
+
+.entity-other-attribute-name {
+	color: #7f007f;
+}
+
+.punctuation-definition-comment {
+	color: #3f5fbf;
+}
+
+.comment {
+	color: #3f5fbf
+}
+
+.string-quoted {
+	color: #2a00ff;
+	font-style: italic;
+}
+
+.invalid {
+	color: red;
+	font-weight: bold;
+}
\ No newline at end of file
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/editor/images/skinnyheaderlogo.png b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/editor/images/skinnyheaderlogo.png
new file mode 100644
index 0000000..23f206f
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/editor/images/skinnyheaderlogo.png
Binary files differ
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/demo.css b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/demo.css
new file mode 100644
index 0000000..f4f6ed0
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/demo.css
@@ -0,0 +1,8 @@
+.button {
+  border: 2px dotted orange;
+  padding: 0 2 0 2
+}
+
+.parentElement {
+	border: 1px solid teal;
+}
\ No newline at end of file
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/demo.html b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/demo.html
new file mode 100644
index 0000000..4f3dda9
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/demo.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta name="copyright" content="Copyright (c) IBM Corporation and others 2010."/>
+<meta http-equiv="PRAGMA" content="NO-CACHE"/>
+<meta http-equiv="Expires" content="-1"/>
+<title>Orion TextView Demo</title>
+<link rel="stylesheet" type="text/css" href="demo.css" />
+<script type="text/javascript" src="/requirejs/require.js"></script>
+<script type="text/javascript">
+	require({
+	  baseUrl: '',
+	  paths: {
+	    orion: '/orion',
+	    examples: '/examples',
+	    tests: '/js-tests',
+	  }
+	});
+	require(["demo"]);
+</script>
+</head>
+<body>
+
+<h1>Orion Text View Demo</h1>
+
+<table width="100%">
+<tr>
+<th>View</th>
+<th>Console</th>
+</tr>
+<tr>
+<td width="100%">
+<div id='divParent' class='parentElement' style='width:100%;height:650px;'>
+Create the view by clicking one of the buttons at the bottom.
+</div>
+</td>
+<td>
+<div id='consoleParent' class='parentElement' style='width:300px;height:650px;'>
+<iframe id='console' frameBorder="0" style="width:100%;height:100%;"></iframe>
+</div>
+</td>
+</tr>
+</table>
+<span id='createJavaSample' class="button">Java file</span>
+<span id='createJavaScriptSample' class="button">JavaScript file</span>
+<span id='createPlainTextSample' class="button">Plain Text</span>
+<span id='createBidiTextSample' class="button">Bidi Text</span>
+<span id='clearLog' class="button">ClearConsole</span>
+<span id='test' class="button">Test</span>
+Performance tests:
+<select id="performanceTestSelect">
+  <option value="test_pageDown">Page Down</option>
+  <option value="test_pageUp">Page Up</option>
+  <option value="test_lineDown">Line Down</option>
+  <option value="test_lineUp">Line Up</option>
+  <option value="test_selectPageDown">Select Page Down</option>
+  <option value="test_selectPageUp">Select Page Up</option>
+  <option value="test_selectLineDown">Select Line Down</option>
+  <option value="test_selectLineUp">Select Line Up</option>
+  <option value="test_getLocationAtOffset">Location at Offset</option>
+  <option value="test_getOffsetAtLocation">Offset at Location</option>
+  <option value="test_getLocationAtOffsetStyled">Location at Offset[styles]</option>
+  <option value="test_getOffsetAtLocationStyled">Offset at Location[styles]</option>
+</select>
+<span id='performanceTest' class="button">Run</span>
+</body>
+</html>
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/demo.js b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/demo.js
new file mode 100644
index 0000000..759516c
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/demo.js
@@ -0,0 +1,199 @@
+/*******************************************************************************
+ * Copyright (c) 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials are made 
+ * available under the terms of the Eclipse Public License v1.0 
+ * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
+ * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+ 
+ /*globals window define document navigator setTimeout XMLHttpRequest PerformanceTest */
+ 
+ 
+function log (text) {
+	var console = window.document.getElementById('console');
+	if (!console) { return; }
+	for (var n = 1; n < arguments.length; n++) {
+		text += " ";
+		text += arguments[n];
+	}
+	
+	var document = console.contentWindow.document;
+	var t = document.createTextNode(text);
+	document.body.appendChild(t);
+	var br = document.createElement("br");
+	document.body.appendChild(br);
+	if (!console.scroll) {
+		console.scroll = true;
+		setTimeout(function() {
+			document.body.lastChild.scrollIntoView(false);
+			console.scroll = false;
+		}, 0);
+	}
+}
+ 
+ define(["orion/textview/keyBinding",
+		"orion/textview/textModel", 
+		"orion/textview/textView", 
+		"orion/textview/rulers",
+		"orion/textview/undoStack",
+		"examples/textview/textStyler",
+		"tests/textview/test-performance"],   
+ 
+function(mKeyBinding, mTextModel, mTextView, mRulers, mUndoStack, mTextStyler) {
+	var view = null;
+	var styler = null;
+	var isMac = navigator.platform.indexOf("Mac") !== -1;
+	
+	function clearLog () {
+		var console = window.document.getElementById('console');
+		if (!console) { return; }
+		var document = console.contentWindow.document;
+		var body = document.body;
+		while (body.hasChildNodes()) { body.removeChild(body.lastChild); }
+	}
+	
+	function getFile(file) {
+		try {
+			var objXml = new XMLHttpRequest();
+			objXml.open("GET",file,false);
+			objXml.send(null);
+			return objXml.responseText;
+		} catch (e) {
+			return null;
+		}
+	}
+	
+	function checkView() {
+		if (view) { return; }
+		var stylesheets = [
+			"/orion/textview/textview.css",
+			"/orion/textview/rulers.css",
+			"/examples/textview/textstyler.css"
+		];
+		var options = {
+			parent: "divParent",
+			model: new mTextModel.TextModel(),
+			stylesheet: stylesheets,
+			tabSize: 4
+		};
+		view = new mTextView.TextView(options);
+		
+		/* Undo stack */
+		var undoStack = new mUndoStack.UndoStack(view, 200);
+		view.setKeyBinding(new mKeyBinding.KeyBinding('z', true), "undo");
+		view.setAction("undo", function() {
+			undoStack.undo();
+			return true;
+		});
+		view.setKeyBinding(isMac ? new mKeyBinding.KeyBinding('z', true, true) : new mKeyBinding.KeyBinding('y', true), "redo");
+		view.setAction("redo", function() {
+			undoStack.redo();
+			return true;
+		});
+		
+		/* Example: Adding a keyBinding and action*/
+		view.setKeyBinding(new mKeyBinding.KeyBinding('s', true), "save");
+		view.setAction("save", function() {
+			log("*****************SAVE");
+			return true;
+		});
+	
+		/* Adding the Rulers */	
+		var breakpoint = {
+			html: "<img src='images/brkp_obj.gif'></img>",
+			style: {styleClass: "ruler_annotation_breakpoint"},
+			overviewStyle: {styleClass: "ruler_annotation_breakpoint_overview"}
+		};
+		var todo = {
+			html: "<img src='images/todo.gif'></img>",
+			style: {styleClass: "ruler_annotation_todo"},
+			overviewStyle: {styleClass: "ruler_annotation_todo_overview"}
+		};
+		var annotation = new mRulers.AnnotationRuler("left", {styleClass: "ruler_annotation"}, breakpoint);
+		annotation.onDblClick =  function(lineIndex, e) {
+			if (lineIndex === undefined) { return; }
+			annotation.setAnnotation(lineIndex, annotation.getAnnotation(lineIndex) !== undefined ? undefined : e.ctrlKey ? todo : breakpoint);
+		};
+		var lines = new mRulers.LineNumberRuler("left", {styleClass: "ruler_lines"}, {styleClass: "ruler_lines_odd"}, {styleClass: "ruler_lines_even"});
+		lines.onDblClick = annotation.onDblClick;
+		var overview = new mRulers.OverviewRuler("right", {styleClass: "ruler_overview"}, annotation);
+		view.addRuler(annotation);
+		view.addRuler(lines);
+		view.addRuler(overview);
+	}
+	
+	function createJavaSample() {
+		checkView();
+		var file =  getFile("text.txt");
+		if (styler) {
+			styler.destroy();
+			styler = null;
+		}
+		styler = new mTextStyler.TextStyler(view, "java");
+		view.setText(file);
+	}
+	
+	function createJavaScriptSample() {
+		checkView();
+		var file =  getFile("/orion/textview/textview.js");
+		if (styler) {
+			styler.destroy();
+			styler = null;
+		}
+		styler = new mTextStyler.TextStyler(view, "js");
+		view.setText(file);
+	}
+	
+	function createPlainTextSample() {
+		checkView();
+		var lineCount = 50000;
+		var lines = [];
+		for(var i = 0; i < lineCount; i++) {
+			lines.push("This is the line of text number "+i);
+		}
+		if (styler) {
+			styler.destroy();
+			styler = null;
+		}
+		view.setText(lines.join("\r\n"));
+	}
+	
+	function createBidiTextSample() {
+		checkView();
+		var lines = [];
+		lines.push("Hello \u0644\u0645\u0646\u0647");
+		if (styler) {
+			styler.destroy();
+			styler = null;
+		}
+		view.setText(lines.join("\r\n"));
+	}
+	
+	function test() {
+	}
+	
+	function performanceTest() {
+		checkView();
+		if (styler) {
+			styler.destroy();
+			styler = null;
+		}
+		/* Note: PerformanceTest is not using require js */
+		var test = new PerformanceTest(view);
+		var select = document.getElementById("performanceTestSelect");
+		test[select.value]();
+	}
+
+	/* Adding events */
+	document.getElementById("createJavaSample").onclick = createJavaSample;
+	document.getElementById("createJavaScriptSample").onclick = createJavaScriptSample;
+	document.getElementById("createPlainTextSample").onclick = createPlainTextSample;
+	document.getElementById("createBidiTextSample").onclick = createBidiTextSample;
+	document.getElementById("clearLog").onclick = clearLog;
+	document.getElementById("test").onclick = test;
+	document.getElementById("performanceTest").onclick = performanceTest;
+		 
+ });
\ No newline at end of file
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/images/brkp_obj.gif b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/images/brkp_obj.gif
new file mode 100644
index 0000000..a831fe7
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/images/brkp_obj.gif
Binary files differ
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/images/todo.gif b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/images/todo.gif
new file mode 100644
index 0000000..0bbc98a
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/images/todo.gif
Binary files differ
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/images/white_space.png b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/images/white_space.png
new file mode 100644
index 0000000..5722d5b
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/images/white_space.png
Binary files differ
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/images/white_tab.png b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/images/white_tab.png
new file mode 100644
index 0000000..47c570e
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/images/white_tab.png
Binary files differ
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/text.txt b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/text.txt
new file mode 100644
index 0000000..61429ef
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/text.txt
@@ -0,0 +1,7951 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swt.custom;
+
+
+import java.util.*;
+
+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:
+ * <ul>
+ * <li>getStyleRangeAtOffset(int)
+ * <li>getStyleRanges()
+ * <li>replaceStyleRanges(int,int,StyleRange[])
+ * <li>setStyleRange(StyleRange)
+ * <li>setStyleRanges(StyleRange[])
+ * </ul>
+ * </p>
+ * <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:
+ * <ul>
+ * <li>getLineBackground(int)
+ * <li>setLineBackground(int,int,Color)
+ * </ul>
+ * </p>
+ * <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>
+ * <p>
+ * IMPORTANT: This class is <em>not</em> intended to be subclassed.
+ * </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
+ * </dl>
+ */
+public class StyledText extends Canvas {
+	static final char TAB = '\t';
+	static final String PlatformLineDelimiter = System.getProperty("line.separator");
+	static final int BIDI_CARET_WIDTH = 3;
+	static final int DEFAULT_WIDTH	= 64;
+	static final int DEFAULT_HEIGHT = 64;
+	static final int V_SCROLL_RATE = 50;
+	static final int H_SCROLL_RATE = 10;
+	
+	static final int ExtendedModify = 3000;
+	static final int LineGetBackground = 3001;
+	static final int LineGetStyle = 3002;
+	static final int TextChanging = 3003;
+	static final int TextSet = 3004;
+	static final int VerifyKey = 3005;
+	static final int TextChanged = 3006;
+	static final int LineGetSegments = 3007;
+	
+	Color selectionBackground;	// selection background color
+	Color selectionForeground;	// selection foreground color
+	StyledTextContent logicalContent;	// native content (default or user specified)
+	StyledTextContent content;			// line wrapping content, same as logicalContent if word wrap is off
+	DisplayRenderer renderer;
+	Listener listener;
+	TextChangeListener textChangeListener;	// listener for TextChanging, TextChanged and TextSet events from StyledTextContent
+	DefaultLineStyler defaultLineStyler;// used for setStyles API when no LineStyleListener is registered
+	LineCache lineCache;
+	boolean userLineStyle = false;		// true=widget is using a user defined line style listener for line styles. false=widget is using the default line styler to store line styles
+	boolean userLineBackground = false;	// true=widget is using a user defined line background listener for line backgrounds. false=widget is using the default line styler to store line backgrounds
+	int verticalScrollOffset = 0;		// pixel based
+	int horizontalScrollOffset = 0;		// pixel based
+	int topIndex = 0;					// top visible line
+	int lastPaintTopIndex = -1;
+	int topOffset = 0;					// offset of first character in top line
+	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 lineHeight;						// line height=font height
+	int tabLength = 4;					// number of characters in a tab
+	int leftMargin;
+	int topMargin;
+	int rightMargin;
+	int bottomMargin;
+	Cursor ibeamCursor;		
+	int columnX;							// keep track of the horizontal caret position
+										// when changing lines/pages. Fixes bug 5935
+	int caretOffset = 0;
+	Point selection = new Point(0, 0);	// x and y are start and end caret offsets of selection
+	Point clipboardSelection;           // x and y are start and end caret offsets of previous selection
+	int selectionAnchor;				// position of selection anchor. 0 based offset from beginning of text
+	Point doubleClickSelection;			// selection after last mouse double click
+	boolean editable = true;
+	boolean wordWrap = false;
+	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.
+	Hashtable keyActionMap = new Hashtable();
+	Color background = null;			// workaround for bug 4791
+	Color foreground = null;			//
+	Clipboard clipboard;
+	boolean mouseDown = false;
+	boolean mouseDoubleClick = false;	// true=a double click ocurred. Don't do mouse swipe selection.
+	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;	
+	boolean isMirrored;
+	boolean bidiColoring = false;		// apply the BIDI algorithm on text segments of the same color
+	Image leftCaretBitmap = null;
+	Image rightCaretBitmap = null;
+	int caretDirection = SWT.NULL;
+	boolean advancing = true;
+	Caret defaultCaret = null;
+	boolean updateCaretDirection = true;
+
+	final static boolean IS_CARBON, IS_GTK, IS_MOTIF;
+	final static boolean DOUBLE_BUFFER;
+	static {
+		String platform = SWT.getPlatform();
+		IS_CARBON = "carbon".equals(platform);
+		IS_GTK = "gtk".equals(platform);
+		IS_MOTIF = "motif".equals(platform);
+		DOUBLE_BUFFER = !IS_CARBON;
+	}
+
+	/**
+	 * The Printing class implements printing of a range of text.
+	 * An instance of <class>Printing </class> 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
+
+		StyledText parent;
+		Printer printer;
+		PrintRenderer renderer;
+		StyledTextPrintOptions printOptions;
+		StyledTextContent printerContent;				// copy of the widget content
+		Rectangle clientArea;							// client area to print on
+		Font printerFont;
+		FontData displayFontData;
+		Hashtable printerColors;						// printer color cache for line backgrounds and style
+		Hashtable lineBackgrounds = new Hashtable();	// cached line backgrounds
+		Hashtable lineStyles = new Hashtable();			// cached line styles
+		Hashtable bidiSegments = new Hashtable();		// cached bidi segments when running on a bidi platform
+		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 pageSize;									// number of lines on a page
+		int startLine;									// first (wrapped) line to print
+		int endLine;									// last (wrapped) line to print
+		boolean singleLine;								// widget single line mode
+		Point selection = null;					// selected text
+		boolean mirrored;						//indicates the printing gc should be mirrored
+
+	/**
+	 * Creates an instance of <class>Printing</class>.
+	 * Copies the widget content and rendering data that needs 
+	 * to be requested from listeners.
+	 * </p>
+	 * @param parent StyledText widget to print.
+	 * @param printer printer device to print on.
+	 * @param printOptions print options
+	 */		
+	Printing(StyledText parent, Printer printer, StyledTextPrintOptions printOptions) {
+		PrinterData data = printer.getPrinterData();
+
+		this.parent = parent;
+		this.printer = printer;
+		this.printOptions = printOptions;
+		this.mirrored = (parent.getStyle() & SWT.MIRRORED) != 0;
+		singleLine = parent.isSingleLine();
+		startPage = 1;
+		endPage = Integer.MAX_VALUE;
+		if (data.scope == PrinterData.PAGE_RANGE) {
+			startPage = data.startPage;
+			endPage = data.endPage;
+			if (endPage < startPage) {
+				int temp = endPage;
+				endPage = startPage;
+				startPage = temp;
+			}			
+		} 
+		else 
+		if (data.scope == PrinterData.SELECTION) {
+			selection = parent.getSelectionRange();
+		}
+
+		displayFontData = parent.getFont().getFontData()[0];
+		copyContent(parent.getContent());
+		cacheLineData(printerContent);
+	}
+	/**
+	 * Caches the bidi segments of the given line.
+	 * </p>
+	 * @param lineOffset offset of the line to cache bidi segments for. 
+	 * 	Relative to the start of the document.
+	 * @param line line to cache bidi segments for. 
+	 */
+	void cacheBidiSegments(int lineOffset, String line) {
+		int[] segments = parent.getBidiSegments(lineOffset, line);
+		
+		if (segments != null) {
+			bidiSegments.put(new Integer(lineOffset), segments);
+		}
+	}
+	/**
+	 * Caches the line background color of the given line.
+	 * </p>
+	 * @param lineOffset offset of the line to cache the background 
+	 * 	color for. Relative to the start of the document.
+	 * @param line line to cache the background color for
+	 */
+	void cacheLineBackground(int lineOffset, String line) {
+		StyledTextEvent event = parent.getLineBackgroundData(lineOffset, line);
+		
+		if (event != null) {
+			lineBackgrounds.put(new Integer(lineOffset), event);
+		}
+	}
+	/**
+	 * Caches all line data that needs to be requested from a listener.
+	 * </p>
+	 * @param printerContent <class>StyledTextContent</class> to request 
+	 * 	line data for.
+	 */
+	void cacheLineData(StyledTextContent printerContent) {	
+		for (int i = 0; i < printerContent.getLineCount(); i++) {
+			int lineOffset = printerContent.getOffsetAtLine(i);
+			String line = printerContent.getLine(i);
+	
+			if (printOptions.printLineBackground) {
+				cacheLineBackground(lineOffset, line);
+			}
+			if (printOptions.printTextBackground ||
+				printOptions.printTextForeground ||
+				printOptions.printTextFontStyle) {
+				cacheLineStyle(lineOffset, line);
+			}
+			if (parent.isBidi()) {
+				cacheBidiSegments(lineOffset, line);
+			}
+		}
+	}
+	/**
+	 * Caches all line styles of the given line.
+	 * </p>
+	 * @param lineOffset offset of the line to cache the styles for.
+	 * 	Relative to the start of the document.
+	 * @param line line to cache the styles for.
+	 */
+	void cacheLineStyle(int lineOffset, String line) {
+		StyledTextEvent event = parent.getLineStyleData(lineOffset, line);
+		
+		if (event != null) {
+			StyleRange[] styles = event.styles;
+			for (int i = 0; i < styles.length; i++) {
+				StyleRange styleCopy = null;
+				if (!printOptions.printTextBackground && styles[i].background != null) {
+					styleCopy = (StyleRange) styles[i].clone();
+					styleCopy.background = null;
+				}
+				if (!printOptions.printTextForeground && styles[i].foreground != null) {
+					if (styleCopy == null) {
+						styleCopy = (StyleRange) styles[i].clone();
+					}
+					styleCopy.foreground = null;
+				}
+				if (!printOptions.printTextFontStyle && styles[i].fontStyle != SWT.NORMAL) {
+					if (styleCopy == null) {
+						styleCopy = (StyleRange) styles[i].clone();
+					}
+					styleCopy.fontStyle = SWT.NORMAL;
+				}
+				if (styleCopy != null) {
+					styles[i] = styleCopy;
+				}
+			}	
+			lineStyles.put(new Integer(lineOffset), event);
+		}
+	}
+	/**
+	 * Copies the text of the specified <class>StyledTextContent</class>.
+	 * </p>
+	 * @param original the <class>StyledTextContent</class> to copy.
+	 */
+	void copyContent(StyledTextContent original) {
+		int insertOffset = 0;
+		
+		printerContent = new DefaultContent();
+		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;
+		}
+	}
+	/**
+	 * Replaces all display colors in the cached line backgrounds and 
+	 * line styles with printer colors.
+	 */
+	void createPrinterColors() {
+		Enumeration values = lineBackgrounds.elements();
+		printerColors = new Hashtable();
+		while (values.hasMoreElements()) {
+			StyledTextEvent event = (StyledTextEvent) values.nextElement();
+			event.lineBackground = getPrinterColor(event.lineBackground);
+		}
+		
+		values = lineStyles.elements();
+		while (values.hasMoreElements()) {
+			StyledTextEvent event = (StyledTextEvent) values.nextElement();
+			for (int i = 0; i < event.styles.length; i++) {
+				StyleRange style = event.styles[i];
+				Color printerBackground = getPrinterColor(style.background);
+				Color printerForeground = getPrinterColor(style.foreground);
+				
+				if (printerBackground != style.background || 
+					printerForeground != style.foreground) {
+					style = (StyleRange) style.clone();
+					style.background = printerBackground;
+					style.foreground = printerForeground;
+					event.styles[i] = style;
+				}
+			}
+		}		
+	}
+	/**
+	 * Disposes of the resources and the <class>PrintRenderer</class>.
+	 */
+	void dispose() {
+		if (printerColors != null) {
+			Enumeration colors = printerColors.elements();
+			
+			while (colors.hasMoreElements()) {
+				Color color = (Color) colors.nextElement();
+				color.dispose();
+			}
+			printerColors = null;
+		}
+		if (gc != null) {
+			gc.dispose();
+			gc = null;
+		}
+		if (printerFont != null) {
+			printerFont.dispose();
+			printerFont = null;
+		}
+		if (renderer != null) {
+			renderer.dispose();
+			renderer = null;
+		}
+	}
+	/**
+	 * Finish printing the indicated page.
+	 * 
+	 * @param page page that was printed
+	 */
+	void endPage(int page) {
+		printDecoration(page, false);
+		printer.endPage();
+	}
+	/**
+	 * Creates a <class>PrintRenderer</class> and calculate the line range
+	 * to print.
+	 */
+	void initializeRenderer() {
+		Rectangle trim = printer.computeTrim(0, 0, 0, 0);
+		Point dpi = printer.getDPI();
+		
+		printerFont = new Font(printer, displayFontData.getName(), displayFontData.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); 
+		
+		// make the orientation of the printer gc match the control
+		int style = mirrored ? SWT.RIGHT_TO_LEFT : SWT.LEFT_TO_RIGHT;
+		gc = new GC(printer, style);
+		gc.setFont(printerFont);
+		renderer = new PrintRenderer(
+			printer, printerFont, gc, printerContent,
+			lineBackgrounds, lineStyles, bidiSegments, 
+			parent.tabLength, clientArea);
+		if (printOptions.header != null) {
+			int lineHeight = renderer.getLineHeight();
+			clientArea.y += lineHeight * 2;
+			clientArea.height -= lineHeight * 2;
+		}
+		if (printOptions.footer != null) {
+			clientArea.height -= renderer.getLineHeight() * 2;
+		}
+		pageSize = clientArea.height / renderer.getLineHeight();
+		StyledTextContent content = renderer.getContent();
+		startLine = 0;
+		if (singleLine) {
+			endLine = 0;
+		}
+		else {
+			endLine = content.getLineCount() - 1;
+		}
+		PrinterData data = printer.getPrinterData();
+		if (data.scope == PrinterData.PAGE_RANGE) {
+			startLine = (startPage - 1) * pageSize;
+		} 
+		else
+		if (data.scope == PrinterData.SELECTION) {
+			startLine = content.getLineAtOffset(selection.x);
+			if (selection.y > 0) {
+				endLine = content.getLineAtOffset(selection.x + selection.y - 1);
+			} 
+			else {
+				endLine = startLine - 1;
+			}
+		}
+	}
+	/**
+	 * Returns the printer color for the given display color.
+	 * </p>
+	 * @param color display color
+	 * @return color create on the printer with the same RGB values 
+	 * 	as the display color.
+ 	 */
+	Color getPrinterColor(Color color) {
+		Color printerColor = null;
+		
+		if (color != null) {
+			printerColor = (Color) printerColors.get(color);		
+			if (printerColor == null) {
+				printerColor = new Color(printer, color.getRGB());
+				printerColors.put(color, printerColor);
+			}
+		}
+		return printerColor;
+	}
+	/**
+	 * Prints the lines in the specified page range.
+	 */
+	void print() {
+		StyledTextContent content = renderer.getContent();
+		Color background = gc.getBackground();
+		Color foreground = gc.getForeground();
+		int lineHeight = renderer.getLineHeight();
+		int paintY = clientArea.y;
+		int page = startPage;
+		
+		for (int i = startLine; i <= endLine && page <= endPage; i++, paintY += lineHeight) {
+			String line = content.getLine(i);
+			
+			if (paintY == clientArea.y) {
+				startPage(page);
+			}
+			renderer.drawLine(
+				line, i, paintY, gc, background, foreground, true);
+			if (paintY + lineHeight * 2 > clientArea.y + clientArea.height) {
+				// close full page
+				endPage(page);
+				paintY = clientArea.y - lineHeight;
+				page++;
+			}
+		}
+		if (paintY > clientArea.y) {
+			// close partial page
+			endPage(page);
+		}
+	}
+	/**
+	 * 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) {
+		int lastSegmentIndex = 0;
+		final int SegmentCount = 3;
+		String text;
+		
+		if (header) {
+			text = printOptions.header;
+		}
+		else {
+			text = printOptions.footer;
+		}
+		if (text == null) {
+			return;
+		}
+		for (int i = 0; i < SegmentCount; i++) {
+			int segmentIndex = text.indexOf(StyledTextPrintOptions.SEPARATOR, lastSegmentIndex);
+			String segment;
+			
+			if (segmentIndex == -1) {
+				segment = text.substring(lastSegmentIndex);
+				printDecorationSegment(segment, i, page, header);
+				break;
+			}
+			else {
+				segment = text.substring(lastSegmentIndex, segmentIndex);
+				printDecorationSegment(segment, i, page, header);
+				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) {		
+		int pageIndex = segment.indexOf(StyledTextPrintOptions.PAGE_TAG);
+		
+		if (pageIndex != -1) {
+			final int PageTagLength = StyledTextPrintOptions.PAGE_TAG.length();
+			StringBuffer buffer = new StringBuffer(segment.substring (0, pageIndex));
+			buffer.append (page);
+			buffer.append (segment.substring(pageIndex + PageTagLength));
+			segment = buffer.toString();
+		}
+		if (segment.length() > 0) {
+			int segmentWidth;
+			int drawX = 0;
+			int drawY = 0;
+			TextLayout layout = new TextLayout(printer);
+			layout.setText(segment);
+			layout.setFont(printerFont);
+			segmentWidth = layout.getLineBounds(0).width;
+			if (header) {
+				drawY = clientArea.y - renderer.getLineHeight() * 2;
+			}
+			else {
+				drawY = clientArea.y + clientArea.height + renderer.getLineHeight();
+			}
+			if (alignment == LEFT) {
+				drawX = clientArea.x;
+			}
+			else				
+			if (alignment == CENTER) {
+				drawX = (pageWidth - segmentWidth) / 2;
+			}
+			else 
+			if (alignment == RIGHT) {
+				drawX = clientArea.x + clientArea.width - segmentWidth;
+			}
+			layout.draw(gc, drawX, drawY);
+			layout.dispose();
+		}
+	}
+	/**
+	 * Starts a print job and prints the pages specified in the constructor.
+	 */
+	public void run() {
+		String jobName = printOptions.jobName;
+		
+		if (jobName == null) {
+			jobName = "Printing";
+		}		
+		if (printer.startJob(jobName)) {
+			createPrinterColors();
+			initializeRenderer();
+			print();
+			dispose();
+			printer.endJob();			
+		}
+	}
+	/**
+	 * Start printing a new page.
+	 * 
+	 * @param page page number to be started
+	 */
+	void startPage(int page) {
+		printer.startPage();
+		printDecoration(page, true);
+	}	
+	}
+	/**
+	 * 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;
+		Vector colorTable = new Vector();
+		boolean WriteUnicode;
+		
+	/**
+	 * 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.
+	 * <p>
+	 *
+	 * @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.addElement(getForeground());
+		colorTable.addElement(getBackground());		
+		setUnicode();
+	}
+	/**
+	 * 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.
+	 */
+	public void close() {
+		if (!isClosed()) {
+			writeHeader();
+			write("\n}}\0");
+			super.close();
+		}
+	}	
+	/**
+	 * Returns the index of the specified color in the RTF color table.
+	 * <p>
+	 *
+	 * @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) {
+		int index;
+		
+		if (color == null) {
+			index = defaultIndex;
+		}
+		else {		
+			index = colorTable.indexOf(color);
+			if (index == -1) {
+				index = colorTable.size();
+				colorTable.addElement(color);
+			}
+		}
+		return index;
+	}
+	/**
+	 * Determines if Unicode RTF should be written.
+	 * Don't write Unicode RTF on Windows 95/98/ME or NT.
+	 */
+	void setUnicode() {
+		final String Win95 = "windows 95";
+		final String Win98 = "windows 98";
+		final String WinME = "windows me";		
+		final String WinNT = "windows nt";
+		String osName = System.getProperty("os.name").toLowerCase();
+		String osVersion = System.getProperty("os.version");
+		int majorVersion = 0;
+		
+		if (osName.startsWith(WinNT) && osVersion != null) {
+			int majorIndex = osVersion.indexOf('.');
+			if (majorIndex != -1) {
+				osVersion = osVersion.substring(0, majorIndex);
+				try {
+					majorVersion = Integer.parseInt(osVersion);
+				}
+				catch (NumberFormatException exception) {
+					// ignore exception. version number remains unknown.
+					// will write without Unicode
+				}
+			}
+		}
+		if (!osName.startsWith(Win95) &&
+			!osName.startsWith(Win98) &&
+			!osName.startsWith(WinME) &&
+			(!osName.startsWith(WinNT) || majorVersion > 4)) {
+			WriteUnicode = true;
+		}
+		else {
+			WriteUnicode = false;
+		}
+	}
+	/**
+	 * Appends the specified segment of "string" to the RTF data.
+	 * Copy from <code>start</code> up to, but excluding, <code>end</code>.
+	 * <p>
+	 *
+	 * @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 > 0xFF && WriteUnicode) {
+				// 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(' ');						// control word delimiter
+				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() {
+		StringBuffer header = new StringBuffer();
+		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("\\uc0\\deff0{\\fonttbl{\\f0\\fnil ");
+		header.append(fontData.getName());
+		header.append(";}}\n{\\colortbl");
+		for (int i = 0; i < colorTable.size(); i++) {
+			Color color = (Color) colorTable.elementAt(i);
+			header.append("\\red");
+			header.append(color.getRed());
+			header.append("\\green");
+			header.append(color.getGreen());
+			header.append("\\blue");
+			header.append(color.getBlue());
+			header.append(";");
+		} 
+		// some RTF readers ignore the deff0 font tag. Explicitly 
+		// set the font for the whole document to work around this.
+		header.append("}\n{\\f0\\fs");
+		// font size is specified in half points
+		header.append(fontData.getHeight() * 2);
+		header.append(" ");
+		write(header.toString(), 0);
+	}
+	/**
+	 * Appends the specified line text to the RTF data.  Lines will be formatted 
+	 * using the styles queried from the LineStyleListener, if set, or those set 
+	 * directly in the widget.
+	 * <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.
+	 * @exception SWTException <ul>
+	 *   <li>ERROR_IO when the writer is closed.</li>
+	 * </ul>
+	 */
+	public void writeLine(String line, int lineOffset) {
+		StyleRange[] styles = new StyleRange[0];
+		Color lineBackground = null;
+		StyledTextEvent event;
+		
+		if (isClosed()) {
+			SWT.error(SWT.ERROR_IO);
+		}
+		event = renderer.getLineStyleData(lineOffset, line);
+		if (event != null) {
+			styles = event.styles;
+		}
+		event = renderer.getLineBackgroundData(lineOffset, line);
+		if (event != null) {
+			lineBackground = event.lineBackground;
+		}
+		if (lineBackground == null) {
+			lineBackground = getBackground();
+		}
+		writeStyledLine(line, lineOffset, styles, lineBackground);
+	}
+	/**
+	 * Appends the specified line delmimiter to the RTF data.
+	 * <p>
+	 *
+	 * @param lineDelimiter line delimiter to write as RTF.
+	 * @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, 0, lineDelimiter.length());
+		write("\\par ");
+	}
+	/**
+	 * Appends the specified line text to the RTF data.
+	 * 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 \highlight 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, StyleRange[] styles, Color lineBackground) {
+		int lineLength = line.length();
+		int lineIndex;
+		int copyEnd;
+		int startOffset = getStart();		
+		int endOffset = startOffset + super.getCharCount();
+		int lineEndOffset = Math.min(lineLength, endOffset - lineOffset);
+		int writeOffset = startOffset - lineOffset;
+		
+		if (writeOffset >= line.length()) {
+			return;					// whole line is outside write range
+		}
+		else
+		if (writeOffset > 0) {
+			lineIndex = writeOffset;		// line starts before RTF write start
+		}
+		else {
+			lineIndex = 0;
+		}
+		if (lineBackground != null) {
+			write("{\\highlight");
+			write(getColorIndex(lineBackground, DEFAULT_BACKGROUND));
+			write(" "); 
+		}
+		for (int i = 0; i < styles.length; i++) {		
+			StyleRange style = styles[i];
+			int start = style.start - lineOffset;
+			int end = start + style.length;
+			int colorIndex;
+			// 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 betond end of write range or end of line 
+				// is guarded against above.
+				write(line, lineIndex, start);
+				lineIndex = start;
+			}
+			// write styled text
+			colorIndex = getColorIndex(style.background, DEFAULT_BACKGROUND);
+			write("{\\cf");
+			write(getColorIndex(style.foreground, DEFAULT_FOREGROUND));
+			if (colorIndex != DEFAULT_BACKGROUND) {
+				write("\\highlight");
+				write(colorIndex);
+			}
+			if ((style.fontStyle & SWT.BOLD) != 0) {
+				write("\\b"); 
+			}
+			if ((style.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
+			copyEnd = Math.min(end, lineEndOffset);
+			// guard against invalid styles and let style processing continue
+			copyEnd = Math.max(copyEnd, lineIndex);
+			write(line, lineIndex, copyEnd);
+			if ((style.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. 
+	 */
+	class TextWriter {
+		private StringBuffer 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.
+	 * <p>
+	 *
+	 * @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 StringBuffer(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
+	 */
+	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.
+	 * Do nothing if "offset" is < 0 or > getCharCount()
+	 * <p>
+	 *
+	 * @param string text to insert
+	 * @param offset offset in the existing data to insert "string" at.
+	 */
+	void write(String string, int offset) {
+		if (offset < 0 || offset > buffer.length()) {
+			return;
+		}
+		buffer.insert(offset, string);
+	}	
+	/**
+	 * Appends the given int to the data.
+	 */
+	void write(int i) {
+		buffer.append(i);
+	}
+	/**
+	 * Appends the given character to the data.
+	 */
+	void write(char i) {
+		buffer.append(i);
+	}			
+	/**
+	 * Appends the specified line text to the data.
+	 * <p>
+	 *
+	 * @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) {
+		int lineLength = line.length();
+		int lineIndex;
+		int copyEnd;
+		int writeOffset = startOffset - lineOffset;
+		
+		if (isClosed) {
+			SWT.error(SWT.ERROR_IO);
+		}		
+		if (writeOffset >= lineLength) {
+			return;							// whole line is outside write range
+		}
+		else
+		if (writeOffset > 0) {
+			lineIndex = writeOffset;		// line starts before write start
+		}
+		else {
+			lineIndex = 0;
+		}
+		copyEnd = Math.min(lineLength, endOffset - lineOffset);
+		if (lineIndex < copyEnd) {
+			write(line.substring(lineIndex, copyEnd));
+		}		
+	}
+	/**
+	 * Appends the specified line delmimiter to the data.
+	 * <p>
+	 *
+	 * @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);
+	}
+	}
+	/**
+	 * LineCache provides an interface to calculate and invalidate 
+	 * line based data.
+	 * Implementors need to return a line width in <code>getWidth</code>.
+	 */
+	interface LineCache {
+	/**
+	 * Calculates the lines in the specified range.
+	 * <p>
+	 * 
+	 * @param startLine first line to calculate
+	 * @param lineCount number of lines to calculate
+	 */
+	public void calculate(int startLine, int lineCount);
+	/**
+	 * Returns a width that will be used by the <code>StyledText</code> 
+	 * widget to size a horizontal scroll bar.
+	 * <p>
+	 *
+	 * @return the line width
+	 */
+	public int getWidth();
+	/**
+	 * Resets the lines in the specified range.
+	 * This method is called in <code>StyledText.redraw()</code>
+	 * and allows implementors to call redraw themselves during reset.
+	 * <p>
+	 *
+	 * @param startLine the first line to reset
+	 * @param lineCount the number of lines to reset
+	 * @param calculateMaxWidth true=implementors should retain a 
+	 * 	valid width even if it is affected by the reset operation.
+	 * 	false=the width may be set to 0
+	 */
+	public void redrawReset(int startLine, int lineCount, boolean calculateMaxWidth);
+	/**
+	 * Resets the lines in the specified range.
+	 * <p>
+	 *
+	 * @param startLine the first line to reset
+	 * @param lineCount the number of lines to reset
+	 * @param calculateMaxWidth true=implementors should retain a 
+	 * 	valid width even if it is affected by the reset operation.
+	 * 	false=the width may be set to 0
+	 */
+	public void reset(int startLine, int lineCount, boolean calculateMaxWidth);
+	/** 
+	 * Called when a text change occurred.
+	 * <p>
+	 *
+	 * @param startOffset	the start offset of the text change
+	 * @param newLineCount the number of inserted lines
+	 * @param replaceLineCount the number of deleted lines
+	 * @param newCharCount the number of new characters
+	 * @param replaceCharCount the number of deleted characters
+	 */  
+	public void textChanged(int startOffset, int newLineCount, int replaceLineCount, int newCharCount, int replaceCharCount);
+	}
+	/**
+	 * Keeps track of line widths and the longest line in the 
+	 * StyledText document.
+	 * Line widths are calculated when requested by a call to 
+	 * <code>calculate</code> and cached until reset by a call 
+	 * to <code>redrawReset</code> or <code>reset</code>.
+	 */
+	class ContentWidthCache implements LineCache {
+		StyledText parent;				// parent widget, used to create a GC for line measuring
+		int[] lineWidth;				// width in pixel of each line in the document, -1 for unknown width
+		StyledTextContent content;		// content to use for line width calculation
+		int lineCount;					// number of lines in lineWidth array
+		int maxWidth;					// maximum line width of all measured lines
+		int maxWidthLineIndex;			// index of the widest line
+				
+	/** 
+	 * Creates a new <code>ContentWidthCache</code> and allocates space 
+	 * for the given number of lines.
+	 * <p>
+	 *
+	 * @param parent the StyledText widget used to create a GC for 
+	 * 	line measuring
+	 * @param content a StyledTextContent containing the initial number
+	 *  of lines to allocate space for
+	 */
+	public ContentWidthCache(StyledText parent, StyledTextContent content) {
+		this.parent = parent;
+		this.content = content;
+		this.lineCount = content.getLineCount();
+		lineWidth = new int[lineCount];
+		reset(0, lineCount, false);
+	}
+	/**
+	 * Calculates the width of each line in the given range if it has
+	 * not been calculated yet.
+	 * If any line in the given range is wider than the currently widest
+	 * line, the maximum line width is updated,
+	 * <p>
+	 * 
+	 * @param startLine first line to calculate the line width of
+	 * @param lineCount number of lines to calculate the line width for
+	 */
+	public void calculate(int startLine, int lineCount) {
+		int caretWidth = 0;
+		int endLine = startLine + lineCount;
+			
+		if (startLine < 0 || endLine > lineWidth.length) {
+			return;
+		}
+		caretWidth = getCaretWidth();
+		for (int i = startLine; i < endLine; i++) {
+			if (lineWidth[i] == -1) {
+				String line = content.getLine(i);
+				int lineOffset = content.getOffsetAtLine(i);
+				lineWidth[i] = contentWidth(line, lineOffset) + caretWidth;
+			}
+			if (lineWidth[i] > maxWidth) {
+				maxWidth = lineWidth[i];
+				maxWidthLineIndex = i;
+			}
+		}
+	}
+	/** 
+	 * Calculates the width of the visible lines in the specified 
+	 * range.
+	 * <p>
+	 *
+	 * @param startLine	the first changed line
+	 * @param newLineCount the number of inserted lines
+	 */  
+	void calculateVisible(int startLine, int newLineCount) {
+		int topIndex = parent.getTopIndex();
+		int bottomLine = Math.min(getPartialBottomIndex(), startLine + newLineCount);
+		
+		startLine = Math.max(startLine, topIndex);
+		calculate(startLine, bottomLine - startLine + 1);
+	}
+	/**
+	 * Measures the width of the given line.
+	 * <p>
+	 * 
+	 * @param line the line to measure
+	 * @param lineOffset start offset of the line to measure, relative 
+	 * 	to the start of the document
+	 * @return the width of the given line
+	 */
+	int contentWidth(String line, int lineOffset) {
+		TextLayout layout = renderer.getTextLayout(line, lineOffset);
+		Rectangle rect = layout.getLineBounds(0);
+		renderer.disposeTextLayout(layout);
+		return rect.x + rect.width + leftMargin + rightMargin;
+	}
+	/**
+	 * Grows the <code>lineWidth</code> array to accomodate new line width
+	 * information.
+	 * <p>
+	 *
+	 * @param numLines the number of elements to increase the array by
+	 */
+	void expandLines(int numLines) {
+		int size = lineWidth.length;
+		if (size - lineCount >= numLines) {
+			return;
+		}
+		int[] newLines = new int[Math.max(size * 2, size + numLines)];
+		System.arraycopy(lineWidth, 0, newLines, 0, size);
+		lineWidth = newLines;
+		reset(size, lineWidth.length - size, false);
+	}
+	/**
+	 * Returns the width of the longest measured line.
+	 * <p>
+	 *
+	 * @return the width of the longest measured line.
+	 */
+	public int getWidth() {
+		return maxWidth;
+	}
+	/**
+	 * Updates the line width array to reflect inserted or deleted lines.
+	 * <p>
+	 *
+	 * @param startLine	the starting line of the change that took place
+	 * @param delta	the number of lines in the change, > 0 indicates lines inserted,
+	 * 	< 0 indicates lines deleted
+	 */
+	void linesChanged(int startLine, int delta) {
+		boolean inserting = delta > 0;
+		
+		if (delta == 0) {
+			return;
+		}
+		if (inserting) {
+			// shift the lines down to make room for new lines
+			expandLines(delta);
+			for (int i = lineCount - 1; i >= startLine; i--) {
+				lineWidth[i + delta] = lineWidth[i];
+			}
+			// reset the new lines
+			for (int i = startLine + 1; i <= startLine + delta && i < lineWidth.length; i++) {
+				lineWidth[i] = -1;
+			}
+			// have new lines been inserted above the longest line?
+			if (maxWidthLineIndex >= startLine) {
+				maxWidthLineIndex += delta;
+			}
+		} 
+		else {
+			// shift up the lines
+			for (int i = startLine - delta; i < lineCount; i++) {
+				lineWidth[i+delta] = lineWidth[i];
+			}
+			// has the longest line been removed?
+			if (maxWidthLineIndex > startLine && maxWidthLineIndex <= startLine - delta) {
+				maxWidth = 0;
+				maxWidthLineIndex = -1;
+			}
+			else
+			if (maxWidthLineIndex >= startLine - delta) {
+				maxWidthLineIndex += delta;
+			}
+		}
+		lineCount += delta;
+	}
+	/**
+	 * Resets the line width of the lines in the specified range.
+	 * <p>
+	 *
+	 * @param startLine	the first line to reset
+	 * @param lineCount the number of lines to reset
+	 * @param calculateMaxWidth true=if the widest line is being 
+	 * 	reset the maximum width of all remaining cached lines is 
+	 * 	calculated. false=the maximum width is set to 0 if the 
+	 * 	widest line is being reset.
+	 */
+	public void redrawReset(int startLine, int lineCount, boolean calculateMaxWidth) {
+		reset(startLine, lineCount, calculateMaxWidth);
+	}
+	/**
+	 * Resets the line width of the lines in the specified range.
+	 * <p>
+	 *
+	 * @param startLine	the first line to reset
+	 * @param lineCount the number of lines to reset
+	 * @param calculateMaxWidth true=if the widest line is being 
+	 * 	reset the maximum width of all remaining cached lines is 
+	 * 	calculated. false=the maximum width is set to 0 if the 
+	 * 	widest line is being reset.
+	 */
+	public void reset(int startLine, int lineCount, boolean calculateMaxWidth) {
+		int endLine = startLine + lineCount;
+		
+		if (startLine < 0 || endLine > lineWidth.length) {
+			return;
+		}
+		for (int i = startLine; i < endLine; i++) {
+			lineWidth[i] = -1;
+		}		
+		// if the longest line is one of the reset lines, the maximum line 
+		// width is no longer valid
+		if (maxWidthLineIndex >= startLine && maxWidthLineIndex < endLine) {
+			maxWidth = 0;
+			maxWidthLineIndex = -1;
+			if (calculateMaxWidth) {
+				for (int i = 0; i < lineCount; i++) {
+					if (lineWidth[i] > maxWidth) {
+						maxWidth = lineWidth[i];
+						maxWidthLineIndex = i;
+					}
+				}			
+			}
+		}
+	}
+	/** 
+	 * Updates the line width array to reflect a text change.
+	 * Lines affected by the text change will be reset.
+	 * <p>
+	 *
+	 * @param startOffset	the start offset of the text change
+	 * @param newLineCount the number of inserted lines
+	 * @param replaceLineCount the number of deleted lines
+	 * @param newCharCount the number of new characters
+	 * @param replaceCharCount the number of deleted characters
+	 */  
+	public void textChanged(int startOffset, int newLineCount, int replaceLineCount, int newCharCount, int replaceCharCount) {
+		int startLine = parent.getLineAtOffset(startOffset);
+		boolean removedMaxLine = (maxWidthLineIndex > startLine && maxWidthLineIndex <= startLine + replaceLineCount);
+		// entire text deleted?
+		if (startLine == 0 && replaceLineCount == lineCount) {
+			lineCount = newLineCount;
+			lineWidth = new int[lineCount];
+			reset(0, lineCount, false);
+			maxWidth = 0;
+		}
+		else {
+			linesChanged(startLine, -replaceLineCount);
+			linesChanged(startLine, newLineCount);
+			lineWidth[startLine] = -1;
+		}
+		// only calculate the visible lines. otherwise measurements of changed lines 
+		// outside the visible area may subsequently change again without the 
+		// lines ever being visible.
+		calculateVisible(startLine, newLineCount);
+		// maxWidthLineIndex will be -1 (i.e., unknown line width) if the widget has 
+		// not been visible yet and the changed lines have therefore not been
+		// calculated above.
+		if (removedMaxLine || 
+			(maxWidthLineIndex != -1 && lineWidth[maxWidthLineIndex] < maxWidth)) {
+			// longest line has been removed or changed and is now shorter.
+			// need to recalculate maximum content width for all lines
+			maxWidth = 0;
+			for (int i = 0; i < lineCount; i++) {
+				if (lineWidth[i] > maxWidth) {
+					maxWidth = lineWidth[i];
+					maxWidthLineIndex = i;
+				}
+			}			
+		}
+	}
+	}
+	/**
+	 * Updates the line wrapping of the content.
+	 * The line wrapping must always be in a consistent state. 
+	 * Therefore, when <code>reset</code> or <code>redrawReset</code>
+	 * is called, the line wrapping is recalculated immediately 
+	 * instead of in <code>calculate</code>.
+	 */
+	class WordWrapCache implements LineCache {
+		StyledText parent;
+		WrappedContent visualContent;
+				
+	/** 
+	 * Creates a new <code>WordWrapCache</code> and calculates an initial
+	 * line wrapping.
+	 * <p>
+	 *
+	 * @param parent the StyledText widget to wrap content in.
+	 * @param content the content provider that does the actual line wrapping.
+	 */
+	public WordWrapCache(StyledText parent, WrappedContent content) {
+		this.parent = parent;
+		visualContent = content;
+		visualContent.wrapLines();
+	}
+	/**
+	 * Do nothing. Lines are wrapped immediately after reset.
+	 * <p>
+	 * 
+	 * @param startLine first line to calculate
+	 * @param lineCount number of lines to calculate
+	 */
+	public void calculate(int startLine, int lineCount) {
+	}
+	/**
+	 * Returns the client area width. Lines are wrapped so there
+	 * is no horizontal scroll bar.
+	 * <p>
+	 *
+	 * @return the line width
+	 */
+	public int getWidth() {
+		return parent.getClientArea().width;
+	}
+	/**
+	 * Wraps the lines in the specified range.
+	 * This method is called in <code>StyledText.redraw()</code>.
+	 * A redraw is therefore not necessary.
+	 * <p>
+	 *
+	 * @param startLine the first line to reset
+	 * @param lineCount the number of lines to reset
+	 * @param calculateMaxWidth true=implementors should retain a 
+	 * 	valid width even if it is affected by the reset operation.
+	 * 	false=the width may be set to 0
+	 */
+	public void redrawReset(int startLine, int lineCount, boolean calculateMaxWidth) {
+	    if (lineCount == visualContent.getLineCount()) {
+			// do a full rewrap if all lines are reset
+			visualContent.wrapLines();
+	    }
+	    else {
+		    visualContent.reset(startLine, lineCount);
+	    }
+	}
+	/**
+	 * Rewraps the lines in the specified range and redraws
+	 * the widget if the line wrapping has changed.
+	 * <p>
+	 *
+	 * @param startLine the first line to reset
+	 * @param lineCount the number of lines to reset
+	 * @param calculateMaxWidth true=implementors should retain a 
+	 * 	valid width even if it is affected by the reset operation.
+	 * 	false=the width may be set to 0
+	 */
+	public void reset(int startLine, int lineCount, boolean calculateMaxWidth) {
+		int itemCount = getPartialBottomIndex() - topIndex + 1;
+	    int[] oldLineOffsets = new int[itemCount];
+	    
+	    for (int i = 0; i < itemCount; i++) {
+	    	oldLineOffsets[i] = visualContent.getOffsetAtLine(i + topIndex);
+	    }
+	    redrawReset(startLine, lineCount, calculateMaxWidth);
+		// check for cases which will require a full redraw
+	    if (getPartialBottomIndex() - topIndex + 1 != itemCount) {
+	    	// number of visible lines has changed
+	    	parent.internalRedraw();
+	    }
+	    else {
+		    for (int i = 0; i < itemCount; i++) {
+		    	if (visualContent.getOffsetAtLine(i + topIndex) != oldLineOffsets[i]) {
+		    		// wrapping of one of the visible lines has changed
+		    		parent.internalRedraw();
+		    		break;
+		    	}
+	    	}	    	
+	    }
+	}
+	/** 
+	 * Passes the text change notification to the line wrap content.
+	 * <p>
+	 *
+	 * @param startOffset	the start offset of the text change
+	 * @param newLineCount the number of inserted lines
+	 * @param replaceLineCount the number of deleted lines
+	 * @param newCharCount the number of new characters
+	 * @param replaceCharCount the number of deleted characters
+	 */  
+	public void textChanged(int startOffset, int newLineCount, int replaceLineCount, int newCharCount, int replaceCharCount) {
+		int startLine = visualContent.getLineAtOffset(startOffset);
+		visualContent.textChanged(startOffset, newLineCount, replaceLineCount, newCharCount, replaceCharCount);
+
+		// if we are wrapping then it is possible for a deletion on the last
+		// line of text to shorten the total text length by a line.  If this
+		// occurs then the startIndex must be adjusted such that a redraw will
+		// be performed if a visible region is affected.  fixes bug 42947.
+		if (wordWrap) {
+			int lineCount = content.getLineCount();
+			if (startLine >= lineCount) startLine = lineCount - 1;  
+		}
+		if (startLine <= getPartialBottomIndex()) {
+			// only redraw if the text change affects text inside or above 
+			// the visible lines. if it is below the visible lines it will
+			// not affect the word wrapping. fixes bug 14047.
+			parent.internalRedraw();
+		}
+	}
+	}
+
+/**
+ * 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 | SWT.NO_REDRAW_RESIZE | SWT.NO_BACKGROUND));
+	// set the bg/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.setBackground(getBackground());
+	Display display = getDisplay();
+	isMirrored = (super.getStyle() & SWT.MIRRORED) != 0;
+	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;
+	}
+	clipboard = new Clipboard(display);
+	installDefaultContent();
+	initializeRenderer();
+	if ((style & SWT.WRAP) != 0) {
+		setWordWrap(true);
+	}
+	else {
+		lineCache = new ContentWidthCache(this, content);
+	}	
+	defaultCaret = new Caret(this, SWT.NULL);
+	if (isBidiCaret()) {
+		createCaretBitmaps();
+		Runnable runnable = new Runnable() {
+			public void run() {
+				int direction = BidiUtil.getKeyboardLanguage() == BidiUtil.KEYBOARD_BIDI ? SWT.RIGHT : SWT.LEFT;
+				if (direction == caretDirection) return;
+				if (getCaret() != defaultCaret) return;
+				int lineIndex = getCaretLine();
+				String line = content.getLine(lineIndex);
+				int lineOffset = content.getOffsetAtLine(lineIndex);
+				int offsetInLine = caretOffset - lineOffset;
+				int newCaretX = getXAtOffset(line, lineIndex, offsetInLine);
+				setCaretLocation(newCaretX, getCaretLine(), direction);
+			}
+		};
+		BidiUtil.addLanguageListener(handle, runnable);
+	}
+	setCaret(defaultCaret);	
+	calculateScrollBars();
+	createKeyBindings();
+	ibeamCursor = new Cursor(display, SWT.CURSOR_IBEAM);
+	setCursor(ibeamCursor);
+	installListeners();
+	installDefaultLineStyler();
+	initializeAccessible();
+}
+/**	 
+ * Adds an extended modify listener. An ExtendedModify event is sent by the 
+ * widget when the widget text has changed.
+ * <p>
+ *
+ * @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(ExtendedModify, typedListener);
+}
+/** 
+ * Maps a key to an action.
+ * 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 keyValue = key & SWT.KEY_MASK;
+	int modifierValue = key & SWT.MODIFIER_MASK;
+	char keyChar = (char)keyValue;
+
+	if (Compatibility.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(new Integer(newKey));
+		}
+		else {
+		 	keyActionMap.put(new Integer(newKey), new Integer(action));
+		}
+		ch = Character.toLowerCase(keyChar);
+		newKey = ch | modifierValue;
+		if (action == SWT.NULL) {
+			keyActionMap.remove(new Integer(newKey));
+		}
+		else {
+		 	keyActionMap.put(new Integer(newKey), new Integer(action));
+		}
+	} else {
+		if (action == SWT.NULL) {
+			keyActionMap.remove(new Integer(key));
+		}
+		else {
+		 	keyActionMap.put(new Integer(key), new Integer(action));
+		}
+	}
+		
+}
+/**
+ * Adds a bidirectional segment listener. A BidiSegmentEvent is sent 
+ * whenever a line of text is measured or rendered. The user 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);
+	}
+	StyledTextListener typedListener = new StyledTextListener(listener);
+	addListener(LineGetSegments, typedListener);	
+}
+/**
+ * Adds a line background listener. A LineGetBackground event is sent by the 
+ * widget to determine the background color for a line.
+ * <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>
+ */
+public void addLineBackgroundListener(LineBackgroundListener listener) {
+	checkWidget();
+	if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+	if (!userLineBackground) {
+		removeLineBackgroundListener(defaultLineStyler);
+		defaultLineStyler.setLineBackground(0, logicalContent.getLineCount(), null);
+		userLineBackground = true;
+	}	
+	StyledTextListener typedListener = new StyledTextListener(listener);
+	addListener(LineGetBackground, typedListener);	
+}
+/**
+ * Adds a line style listener. A LineGetStyle event is sent by the widget to 
+ * determine the styles for a line.
+ * <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>
+ */
+public void addLineStyleListener(LineStyleListener listener) {
+	checkWidget();
+	if (listener == null) {
+		SWT.error(SWT.ERROR_NULL_ARGUMENT);
+	}
+	if (!userLineStyle) {
+		removeLineStyleListener(defaultLineStyler);
+		defaultLineStyler.setStyleRange(null);
+		userLineStyle = true;
+	}
+	StyledTextListener typedListener = new StyledTextListener(listener);
+	addListener(LineGetStyle, typedListener);	
+}
+/**	 
+ * Adds a modify listener. A Modify event is sent by the widget when the widget text 
+ * has changed.
+ * <p>
+ *
+ * @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);
+	}
+	TypedListener typedListener = new TypedListener(modifyListener);
+	addListener(SWT.Modify, typedListener);
+}
+/**	 
+ * Adds a selection listener. A Selection event is sent by the widget when the 
+ * selection has changed.
+ * <p>
+ * When <code>widgetSelected</code> is called, the event x amd y fields contain
+ * the start and end caret indices of the selection.
+ * <code>widgetDefaultSelected</code> is not called for StyledTexts.
+ * </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>
+ */
+public void addSelectionListener(SelectionListener listener) {
+	checkWidget();
+	if (listener == null) {
+		SWT.error(SWT.ERROR_NULL_ARGUMENT);
+	}
+	TypedListener typedListener = new TypedListener(listener);
+	addListener(SWT.Selection, typedListener);	
+}
+/**	 
+ * 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. 
+ * <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>
+ */
+public void addVerifyKeyListener(VerifyKeyListener listener) {
+	checkWidget();
+	if (listener == null) {
+		SWT.error(SWT.ERROR_NULL_ARGUMENT);
+	}
+	StyledTextListener typedListener = new StyledTextListener(listener);
+	addListener(VerifyKey, typedListener);	
+}
+/**	 
+ * 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.
+ * <p>
+ *
+ * @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);
+	}
+	TypedListener typedListener = new TypedListener(verifyListener);
+	addListener(SWT.Verify, typedListener);
+}
+/** 
+ * Appends a string to the text at the end of the widget.
+ * <p>
+ *
+ * @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 width of the widest visible line.
+ */
+void calculateContentWidth() {
+	lineCache = getLineCache(content);
+	lineCache.calculate(topIndex, getPartialBottomIndex() - topIndex + 1);
+}
+/**
+ * Calculates the scroll bars
+ */
+void calculateScrollBars() {
+	ScrollBar horizontalBar = getHorizontalBar();
+	ScrollBar verticalBar = getVerticalBar();
+	
+	setScrollBars();
+	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 oldTopIndex = topIndex;
+	int verticalIncrement = getVerticalIncrement();
+	int clientAreaHeight = getClientArea().height;
+	
+	if (verticalIncrement == 0) {
+		return;
+	}
+	topIndex = Compatibility.ceil(verticalScrollOffset, 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 = verticalScrollOffset + clientAreaHeight;
+			int fullLineTopPixel = topIndex * verticalIncrement;
+			int fullLineVisibleHeight = bottomPixel - fullLineTopPixel;
+			// set top index to partially visible line if no line fully fits in 
+			// client area or if space is available but not used (the latter should
+			// never happen because we use claimBottomFreeSpace)
+			if (fullLineVisibleHeight < verticalIncrement) {
+				topIndex--;
+			}
+		}
+		else 
+		if (topIndex >= content.getLineCount()) {
+			topIndex = content.getLineCount() - 1;
+		}
+	}
+	if (topIndex != oldTopIndex) {
+		topOffset = content.getOffsetAtLine(topIndex);
+		lineCache.calculate(topIndex, getPartialBottomIndex() - topIndex + 1);
+		setHorizontalScrollBar();
+	}
+}
+/**
+ * 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;
+		}
+	}
+	return style;
+}
+/**
+ * Scrolls down the text to use new space made available by a resize or by 
+ * deleted lines.
+ */
+void claimBottomFreeSpace() {
+	int newVerticalOffset = Math.max(0, content.getLineCount() * lineHeight - getClientArea().height);
+	
+	if (newVerticalOffset < verticalScrollOffset) {
+		// Scroll up so that empty lines below last text line are used.
+		// Fixes 1GEYJM0
+		setVerticalScrollOffset(newVerticalOffset, true);
+	}
+}
+/**
+ * Scrolls text to the right to use new space made available by a resize.
+ */
+void claimRightFreeSpace() {
+	int newHorizontalOffset = Math.max(0, lineCache.getWidth() - (getClientArea().width - leftMargin - rightMargin));
+	
+	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).
+		scrollHorizontalBar(newHorizontalOffset - horizontalScrollOffset);					
+	}
+}
+/**
+ * Clears the widget margin.
+ * 
+ * @param gc GC to render on
+ * @param background background color to use for clearing the margin
+ * @param clientArea widget client area dimensions
+ */
+void clearMargin(GC gc, Color background, Rectangle clientArea, int y) {
+	// clear the margin background
+	gc.setBackground(background);
+	if (topMargin > 0) {
+		gc.fillRectangle(0, -y, clientArea.width, topMargin);
+	}
+	if (bottomMargin > 0) {
+		gc.fillRectangle(0, clientArea.height - bottomMargin - y, clientArea.width, bottomMargin);
+	}
+	if (leftMargin > 0) {
+		gc.fillRectangle(0, -y, leftMargin, clientArea.height);
+	}
+	if (rightMargin > 0) {
+		gc.fillRectangle(clientArea.width - rightMargin, -y, rightMargin, clientArea.height);
+	}
+}
+/**
+ * Removes the widget selection.
+ * <p>
+ *
+ * @param sendEvent a Selection event is sent when set to true and when the selection is actually reset.
+ */
+void clearSelection(boolean sendEvent) {
+	int selectionStart = selection.x;
+	int selectionEnd = selection.y;
+	int length = content.getCharCount();
+	
+	resetSelection();
+	// redraw old selection, if any
+	if (selectionEnd - selectionStart > 0) {
+		// 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, true);
+		}
+		if (sendEvent) {
+			sendSelectionEvent();
+		}
+	}
+}
+public Point computeSize (int wHint, int hHint, boolean changed) {
+	checkWidget();
+	int count, width, height;
+	boolean singleLine = (getStyle() & SWT.SINGLE) != 0;
+	
+	if (singleLine) {
+		count = 1;
+	} else {
+		count = content.getLineCount();
+	}
+	if (wHint != SWT.DEFAULT) {
+		width = wHint;
+	} 
+	else {
+		width = DEFAULT_WIDTH;
+	}
+	if (wHint == SWT.DEFAULT) {
+		LineCache computeLineCache = lineCache;
+		if (wordWrap) {
+			// set non-wrapping content width calculator. Ensures ideal line width 
+			// that does not required wrapping. Fixes bug 31195.
+			computeLineCache = new ContentWidthCache(this, logicalContent);
+			if (!singleLine) {
+				count = logicalContent.getLineCount();
+			}
+		}
+		// Only calculate what can actually be displayed.
+		// Do this because measuring each text line is a 
+		// time-consuming process.
+		int visibleCount = Math.min (count, getDisplay().getBounds().height / lineHeight);
+		computeLineCache.calculate(0, visibleCount);
+		width = computeLineCache.getWidth() + leftMargin + rightMargin;
+	}
+	else
+	if (wordWrap && !singleLine) {
+		// calculate to wrap to width hint. Fixes bug 20377. 
+		// don't wrap live content. Fixes bug 38344.
+		WrappedContent wrappedContent = new WrappedContent(renderer, logicalContent);
+		wrappedContent.wrapLines(width);
+		count = wrappedContent.getLineCount();
+	}
+	if (hHint != SWT.DEFAULT) {
+		height = hHint;
+	} 
+	else {
+		height = count * lineHeight + topMargin + bottomMargin;
+	}
+	// Use default values if no text is defined.
+	if (width == 0) {
+		width = DEFAULT_WIDTH;
+	}
+	if (height == 0) {
+		if (singleLine) {
+			height = lineHeight;
+		}
+		else {
+			height = DEFAULT_HEIGHT;
+		}
+	}
+	Rectangle rect = computeTrim(0, 0, width, height);
+	return new Point (rect.width, rect.height);
+}
+/**
+ * Copies the selected text to the <code>DND.CLIPBOARD</code> clipboard.
+ * 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();
+	copy(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();
+	if (clipboardType != DND.CLIPBOARD && 
+		 clipboardType != DND.SELECTION_CLIPBOARD) return;
+	int length = selection.y - selection.x;
+	if (length > 0) {
+		try {
+			setClipboardContent(selection.x, length, clipboardType);
+		}
+		catch (SWTError error) {
+			// Copy to clipboard failed. This happens when another application 
+			// is accessing the clipboard while we copy. Ignore the error.
+			// Fixes 1GDQAVN
+			// Rethrow all other errors. Fixes bug 17578.
+			if (error.code != DND.ERROR_CANNOT_SET_CLIPBOARD) {
+				throw error;
+			}
+		}
+	}
+}
+/**
+ * Returns a string that uses only the line delimiter specified by the 
+ * StyledTextContent implementation.
+ * 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) {
+	StringBuffer convertedText;
+	String delimiter = getLineDelimiter();
+	int length = text.length();	
+	int crIndex = 0;
+	int lfIndex = 0;
+	int i = 0;
+	
+	if (length == 0) {
+		return text;
+	}
+	convertedText = new StringBuffer(length);
+	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									// CR occurs before LF or no LF present?
+		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();
+}
+/**
+ * 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);
+	setKeyBinding(SWT.HOME, ST.LINE_START);
+	setKeyBinding(SWT.END, ST.LINE_END);
+	setKeyBinding(SWT.PAGE_UP, ST.PAGE_UP);
+	setKeyBinding(SWT.PAGE_DOWN, ST.PAGE_DOWN);
+	setKeyBinding(SWT.HOME | SWT.MOD1, ST.TEXT_START);
+	setKeyBinding(SWT.END | SWT.MOD1, ST.TEXT_END);
+	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);
+	setKeyBinding(nextKey | SWT.MOD1, ST.WORD_NEXT);
+	setKeyBinding(previousKey | SWT.MOD1, ST.WORD_PREVIOUS);
+	
+	// Selection
+	setKeyBinding(SWT.ARROW_UP | SWT.MOD2, ST.SELECT_LINE_UP);	
+	setKeyBinding(SWT.ARROW_DOWN | SWT.MOD2, ST.SELECT_LINE_DOWN);
+	setKeyBinding(SWT.HOME | SWT.MOD2, ST.SELECT_LINE_START);
+	setKeyBinding(SWT.END | SWT.MOD2, ST.SELECT_LINE_END);
+	setKeyBinding(SWT.PAGE_UP | SWT.MOD2, ST.SELECT_PAGE_UP);
+	setKeyBinding(SWT.PAGE_DOWN | SWT.MOD2, ST.SELECT_PAGE_DOWN);
+	setKeyBinding(SWT.HOME | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_START);	
+	setKeyBinding(SWT.END | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_END);
+	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);	
+	setKeyBinding(nextKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_WORD_NEXT);
+	setKeyBinding(previousKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_WORD_PREVIOUS);
+           	  	
+	// Modification
+	// Cut, Copy, Paste
+	setKeyBinding('X' | SWT.MOD1, ST.CUT);
+	setKeyBinding('C' | SWT.MOD1, ST.COPY);
+	setKeyBinding('V' | SWT.MOD1, ST.PASTE);
+	// 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();
+	}
+	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.
+ * <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 cut(){
+	checkWidget();
+	int length = selection.y - selection.x;
+	
+	if (length > 0) {
+		try {
+			setClipboardContent(selection.x, length, DND.CLIPBOARD);
+		}
+		catch (SWTError error) {
+			// Copy to clipboard failed. This happens when another application 
+			// is accessing the clipboard while we copy. Ignore the error.
+			// Fixes 1GDQAVN
+			// Rethrow all other errors. Fixes bug 17578.
+			if (error.code != DND.ERROR_CANNOT_SET_CLIPBOARD) {
+				throw error;
+			}
+			// Abort cut operation if copy to clipboard fails.
+			// Fixes bug 21030.
+			return;
+		}
+		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) {
+	Rectangle area = getClientArea();		
+	
+	if (event.y > area.height) {
+		doAutoScroll(SWT.DOWN, event.y - area.height);
+	}
+	else 
+	if (event.y < 0) {
+		doAutoScroll(SWT.UP, -event.y);
+	}
+	else 
+	if (event.x < leftMargin && !wordWrap) {
+		doAutoScroll(ST.COLUMN_PREVIOUS, leftMargin - event.x);
+	}
+	else 
+	if (event.x > area.width - leftMargin - rightMargin && !wordWrap) {
+		doAutoScroll(ST.COLUMN_NEXT, event.x - (area.width - leftMargin - rightMargin));
+	}
+	else {
+		endAutoScroll();
+	}
+}
+/** 
+ * Initiates autoscrolling.
+ * <p>
+ *
+ * @param direction SWT.UP, SWT.DOWN, SWT.COLUMN_NEXT, SWT.COLUMN_PREVIOUS
+ */
+void doAutoScroll(int direction, int distance) {
+	Runnable timer = null;
+	
+	autoScrollDistance = distance;
+
+	// If we're already autoscrolling in the given direction do nothing
+	if (autoScrollDirection == direction) {
+		return;
+	}
+	
+	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() {
+			public void run() {
+				if (autoScrollDirection == SWT.UP) {
+					int lines = (autoScrollDistance / getLineHeight()) + 1;
+					doSelectionPageUp(lines);
+					display.timerExec(V_SCROLL_RATE, this);
+				}
+			}
+		};
+		autoScrollDirection = direction;
+		display.timerExec(V_SCROLL_RATE, timer);
+	} else if (direction == SWT.DOWN) {
+		timer = new Runnable() {
+			public void run() {
+				if (autoScrollDirection == SWT.DOWN) {
+					int lines = (autoScrollDistance / getLineHeight()) + 1;
+					doSelectionPageDown(lines);
+					display.timerExec(V_SCROLL_RATE, this);
+				}
+			}
+		};
+		autoScrollDirection = direction;
+		display.timerExec(V_SCROLL_RATE, timer);
+	} else if (direction == ST.COLUMN_NEXT) {
+		timer = new Runnable() {
+			public void run() {
+				if (autoScrollDirection == ST.COLUMN_NEXT) {
+					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() {
+			public void run() {
+				if (autoScrollDirection == ST.COLUMN_PREVIOUS) {
+					doVisualPrevious();
+					setMouseWordSelectionAnchor();
+					doMouseSelection();
+					display.timerExec(H_SCROLL_RATE, this);
+				}
+			}
+		};
+		autoScrollDirection = direction;
+		display.timerExec(H_SCROLL_RATE, timer);
+	}
+}
+/**
+ * Deletes the previous character. Delete the selected text if any.
+ * Move the caret in front of the deleted text.
+ */
+void doBackspace() {
+	Event event = new Event();
+	event.text = "";
+	if (selection.x != selection.y) {
+		event.start = selection.x;
+		event.end = selection.y;
+		sendKeyEvent(event);
+	}
+	else
+	if (caretOffset > 0) {
+		int line = content.getLineAtOffset(caretOffset);
+		int lineOffset = content.getOffsetAtLine(line);			
+	
+		if (caretOffset == lineOffset) {
+			lineOffset = content.getOffsetAtLine(line - 1);
+			event.start = lineOffset + content.getLine(line - 1).length();
+			event.end = caretOffset;
+		}
+		else {
+			String lineText = content.getLine(line);
+			TextLayout layout = renderer.getTextLayout(lineText, lineOffset);
+			int start = layout.getPreviousOffset(caretOffset - lineOffset, SWT.MOVEMENT_CHAR);
+			renderer.disposeTextLayout(layout); 
+			event.start = start + lineOffset;
+			event.end = caretOffset;
+		}
+		sendKeyEvent(event);
+	}
+}
+/**
+ * Replaces the selection with the character or insert the character at the 
+ * current caret position if no selection exists.
+ * If a carriage return was typed replace it with the line break character 
+ * used by the widget on this platform.
+ * <p>
+ *
+ * @param key the character typed by the user
+ */
+void doContent(char key) {
+	Event event;
+	
+	if (textLimit > 0 && 
+		content.getCharCount() - (selection.y - selection.x) >= textLimit) {
+		return;
+	}	
+	event = new Event();
+	event.start = selection.x;
+	event.end = selection.y;
+	// replace a CR line break with the widget line break
+	// CR does not make sense on Windows since most (all?) applications
+	// don't recognize CR as a line break.
+	if (key == SWT.CR || key == SWT.LF) {
+		if (!isSingleLine()) {
+			event.text = getLineDelimiter();
+		}
+	}
+	// no selection and overwrite mode is on and the typed key is not a
+	// tab character (tabs are always inserted without overwriting)?
+	else
+	if (selection.x == selection.y && overwrite && key != TAB) {
+		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) {
+		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();		
+		if (caretOffset < length) {
+			caretOffset = length;
+			showCaret();
+		}
+	}
+}
+/**
+ * Moves the caret in front of the first character of the widget content.
+ */
+void doContentStart() {
+	if (caretOffset > 0) {
+		caretOffset = 0;
+		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.
+ * <p>
+ *
+ * @see #doSelectionCursorPrevious
+ */
+void doCursorPrevious() {
+	advancing = false;
+	if (selection.y - selection.x > 0) {
+		int caretLine;
+		
+		caretOffset = selection.x;
+		caretLine = getCaretLine();
+		showCaret(caretLine);
+	}
+	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.
+ * <p>
+ *
+ * @see #doSelectionCursorNext
+ */
+void doCursorNext() {
+	advancing = true;
+	if (selection.y - selection.x > 0) {
+		int caretLine;
+
+		caretOffset = selection.y;
+		caretLine = getCaretLine();
+		showCaret(caretLine);
+	}
+	else {
+		doSelectionCursorNext();
+	}
+}
+/**
+ * Deletes the next character. Delete the selected text if any.
+ */
+void doDelete() {
+	Event event = new Event();
+	event.text = "";
+	if (selection.x != selection.y) {
+		event.start = selection.x;
+		event.end = selection.y;
+		sendKeyEvent(event);
+	}
+	else
+	if (caretOffset < content.getCharCount()) {
+		int line = content.getLineAtOffset(caretOffset);
+		int lineOffset = content.getOffsetAtLine(line);
+		int lineLength = content.getLine(line).length();
+				
+		if (caretOffset == lineOffset + lineLength) {
+			event.start = caretOffset;
+			event.end = content.getOffsetAtLine(line + 1);
+		}
+		else {
+			event.start = caretOffset;
+			event.end = getClusterNext(caretOffset, line);
+		}
+		sendKeyEvent(event);
+	}
+}
+/**
+ * Deletes the next word.
+ */
+void doDeleteWordNext() {
+	if (selection.x != selection.y) {
+		// if a selection exists, treat the as if 
+		// only the delete key was pressed
+		doDelete();
+	} else {
+		Event event = new Event();
+		event.text = "";
+		event.start = caretOffset;
+		event.end = getWordEnd(caretOffset);
+		sendKeyEvent(event);
+	}
+}
+/**
+ * Deletes the previous word.
+ */
+void doDeleteWordPrevious() {
+	if (selection.x != selection.y) {
+		// if a selection exists, treat as if 
+		// only the backspace key was pressed
+		doBackspace();
+	} else {
+		Event event = new Event();
+		event.text = "";
+		event.start = getWordStart(caretOffset);
+		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.
+ * 
+ * @return index of the new line relative to the first line in the document
+ */
+int doLineDown() {
+	if (isSingleLine()) {
+		return 0;
+	}
+	// allow line down action only if receiver is not in single line mode.
+	// fixes 4820.
+	int caretLine = getCaretLine(); 
+	if (caretLine < content.getLineCount() - 1) {
+		caretLine++;
+		caretOffset = getOffsetAtMouseLocation(columnX, caretLine);
+	}
+	return caretLine;
+}
+/**
+ * Moves the caret to the end of the line.
+ */
+void doLineEnd() {
+	int caretLine = getCaretLine();
+	int lineOffset = content.getOffsetAtLine(caretLine);	
+	int lineLength = content.getLine(caretLine).length();
+	int lineEndOffset = lineOffset + lineLength;
+	
+	if (caretOffset < lineEndOffset) {
+		caretOffset = lineEndOffset;
+		showCaret();
+	}
+}
+/**
+ * Moves the caret to the beginning of the line.
+ */
+void doLineStart() {
+	int caretLine = getCaretLine();
+	int lineOffset = content.getOffsetAtLine(caretLine);
+	if (caretOffset > lineOffset) {
+		caretOffset = lineOffset;
+		showCaret(caretLine);
+	}
+}
+/**
+ * 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.
+ * 
+ * @return index of the new line relative to the first line in the document
+ */
+int doLineUp() {
+	int caretLine = getCaretLine();
+	if (caretLine > 0) {
+		caretLine--;
+		caretOffset = getOffsetAtMouseLocation(columnX, caretLine);
+	}
+	return caretLine;
+}
+/**
+ * Moves the caret to the specified location.
+ * <p>
+ *
+ * @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 = (y + verticalScrollOffset) / lineHeight;
+	int lineCount = content.getLineCount();
+	int newCaretOffset;
+	int newCaretLine;
+	boolean oldAdvancing = advancing;
+
+	updateCaretDirection = true;
+	if (line > lineCount - 1) {
+		line = lineCount - 1;
+	}	
+	// 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;
+	}
+	newCaretOffset = getOffsetAtMouseLocation(x, line);
+	
+	if (mouseDoubleClick) {
+		// double click word select the previous/next word. fixes bug 15610
+		newCaretOffset = doMouseWordSelect(x, newCaretOffset, line);
+	}
+	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
+	if (y >= 0 && y < getClientArea().height && 
+		(x >= 0 && x < getClientArea().width || wordWrap ||	
+		newCaretLine != content.getLineAtOffset(caretOffset))) {
+		if (newCaretOffset != caretOffset || advancing != oldAdvancing) {
+			caretOffset = newCaretOffset;
+			if (select) {
+				doMouseSelection();
+			}
+			showCaret();
+		}
+	}
+	if (!select) {
+		caretOffset = newCaretOffset;
+		clearSelection(true);
+	}
+}
+/**
+ * Updates the selection based on the caret position
+ */
+void doMouseSelection() {
+	if (caretOffset <= selection.x || 
+		(caretOffset > selection.x && 
+		 caretOffset < selection.y && selectionAnchor == selection.x)) {
+		doSelection(ST.COLUMN_PREVIOUS);
+	}
+	else {
+		doSelection(ST.COLUMN_NEXT);
+	}
+}
+/**
+ * Returns the offset of the word at the specified offset. 
+ * If the current selection extends from high index to low index 
+ * (i.e., right to left, or caret is at left border of selecton on 
+ * non-bidi platforms) the start offset of the word preceeding 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) {
+	int wordOffset;
+
+	// flip selection anchor based on word selection direction from 
+	// base double click. Always do this here (and don't rely on doAutoScroll)
+	// because auto scroll only does not cover all possible mouse selections
+	// (e.g., mouse x < 0 && mouse y > caret line y)
+ 	if (newCaretOffset < selectionAnchor && selectionAnchor == selection.x) {
+		selectionAnchor = doubleClickSelection.y;
+	}
+	else
+	if (newCaretOffset > selectionAnchor && selectionAnchor == selection.y) {
+		selectionAnchor = doubleClickSelection.x;
+	}
+	if (x >= 0 && x < getClientArea().width) {
+		// find the previous/next word
+		if (caretOffset == selection.x) {
+			wordOffset = getWordStart(newCaretOffset);
+		}
+		else {
+			wordOffset = getWordEndNoSpaces(newCaretOffset);
+		}
+		// mouse word select only on same line mouse cursor is on
+		if (content.getLineAtOffset(wordOffset) == line) {
+			newCaretOffset = wordOffset;
+		}
+	}
+	return newCaretOffset;
+}
+/**
+ * Scrolls one page down so that the last line (truncated or whole)
+ * of the current page becomes the fully visible top 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 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 lines) {
+	int lineCount = content.getLineCount();
+	int oldColumnX = columnX;
+	int oldHScrollOffset = horizontalScrollOffset;
+	int caretLine;
+	
+	// do nothing if in single line mode. fixes 5673
+	if (isSingleLine()) {
+		return;
+	}
+	caretLine = getCaretLine();
+	if (caretLine < lineCount - 1) {
+		int verticalMaximum = lineCount * getVerticalIncrement();
+		int pageSize = getClientArea().height;
+		int scrollLines = Math.min(lineCount - caretLine - 1, lines);
+		int scrollOffset;
+		
+		// ensure that scrollLines never gets negative and at leat one 
+		// line is scrolled. fixes bug 5602.
+		scrollLines = Math.max(1, scrollLines);
+		caretLine += scrollLines;
+		caretOffset = getOffsetAtMouseLocation(columnX, caretLine); 
+		if (select) {
+			doSelection(ST.COLUMN_NEXT);
+		}
+		// scroll one page down or to the bottom
+		scrollOffset = verticalScrollOffset + scrollLines * getVerticalIncrement();
+		if (scrollOffset + pageSize > verticalMaximum) {
+			scrollOffset = verticalMaximum - pageSize;
+		}
+		if (scrollOffset > verticalScrollOffset) {		
+			setVerticalScrollOffset(scrollOffset, true);
+		}
+	}
+	// explicitly go to the calculated caret line. may be different 
+	// from content.getLineAtOffset(caretOffset) when in word wrap mode
+	showCaret(caretLine);
+	// restore the original horizontal caret position
+	int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
+	columnX = oldColumnX + hScrollChange;
+}
+/**
+ * Moves the cursor to the end of the last fully visible line.
+ */
+void doPageEnd() {
+	// go to end of line if in single line mode. fixes 5673
+	if (isSingleLine()) {
+		doLineEnd();
+	}
+	else {
+		int line = getBottomIndex();
+		int bottomCaretOffset = content.getOffsetAtLine(line) + content.getLine(line).length();	
+
+		if (caretOffset < bottomCaretOffset) {
+			caretOffset = bottomCaretOffset;
+			showCaret();
+		}
+	}
+}
+/**
+ * Moves the cursor to the beginning of the first fully visible line.
+ */
+void doPageStart() {
+	int topCaretOffset = content.getOffsetAtLine(topIndex);
+	
+	if (caretOffset > topCaretOffset) {
+		caretOffset = topCaretOffset;
+		// explicitly go to the calculated caret line. may be different 
+		// from content.getLineAtOffset(caretOffset) when in word wrap mode
+		showCaret(topIndex);
+	}
+}
+/**
+ * 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 lines) {
+	int oldColumnX = columnX;
+	int oldHScrollOffset = horizontalScrollOffset;
+	int caretLine = getCaretLine();
+	
+	if (caretLine > 0) {	
+		int scrollLines = Math.max(1, Math.min(caretLine, lines));
+		int scrollOffset;
+		
+		caretLine -= scrollLines;
+		caretOffset = getOffsetAtMouseLocation(columnX, caretLine);
+		if (select) {
+			doSelection(ST.COLUMN_PREVIOUS);
+		}
+		// scroll one page up or to the top
+		scrollOffset = Math.max(0, verticalScrollOffset - scrollLines * getVerticalIncrement());
+		if (scrollOffset < verticalScrollOffset) {
+			setVerticalScrollOffset(scrollOffset, true);
+		}
+	}
+	// explicitly go to the calculated caret line. may be different 
+	// from content.getLineAtOffset(caretOffset) when in word wrap mode
+	showCaret(caretLine);
+	// restore the original horizontal caret position
+	int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
+	columnX = oldColumnX + hScrollChange;
+}
+/**
+ * Updates the selection to extend to the current caret position.
+ */
+void doSelection(int direction) {
+	int redrawStart = -1;
+	int redrawEnd = -1;
+	
+	if (selectionAnchor == -1) {
+		selectionAnchor = selection.x;
+	}	
+	if (direction == ST.COLUMN_PREVIOUS) {
+		if (caretOffset < selection.x) {
+			// grow selection
+			redrawEnd = selection.x; 
+			redrawStart = selection.x = caretOffset;		
+			// check if selection has reversed direction
+			if (selection.y != selectionAnchor) {
+				redrawEnd = selection.y;
+				selection.y = selectionAnchor;
+			}
+		}
+		else	// test whether selection actually changed. Fixes 1G71EO1
+		if (selectionAnchor == selection.x && caretOffset < selection.y) {
+			// caret moved towards selection anchor (left side of selection). 
+			// shrink selection			
+			redrawEnd = selection.y;
+			redrawStart = selection.y = caretOffset;		
+		}
+	}
+	else {
+		if (caretOffset > selection.y) {
+			// grow selection
+			redrawStart = selection.y;
+			redrawEnd = selection.y = caretOffset;
+			// check if selection has reversed direction
+			if (selection.x != selectionAnchor) {
+				redrawStart = selection.x;				
+				selection.x = selectionAnchor;
+			}
+		}
+		else	// test whether selection actually changed. Fixes 1G71EO1
+		if (selectionAnchor == selection.y && caretOffset > selection.x) {
+			// caret moved towards selection anchor (right side of selection). 
+			// shrink selection			
+			redrawStart = selection.x;
+			redrawEnd = selection.x = caretOffset;		
+		}
+	}
+	if (redrawStart != -1 && redrawEnd != -1) {
+		internalRedrawRange(redrawStart, redrawEnd - redrawStart, true);
+		sendSelectionEvent();
+	}
+}
+/**
+ * Moves the caret to the next character or to the beginning of the 
+ * next line if the cursor is at the end of a line.
+ */
+void doSelectionCursorNext() {
+	int caretLine = getCaretLine();
+	int lineOffset = content.getOffsetAtLine(caretLine);
+	int offsetInLine = caretOffset - lineOffset;
+	advancing = true;
+	if (offsetInLine < content.getLine(caretLine).length()) {
+		caretOffset = getClusterNext(caretOffset, caretLine);
+		showCaret();
+	}
+	else
+	if (caretLine < content.getLineCount() - 1 && !isSingleLine()) {
+		// only go to next line if not in single line mode. fixes 5673
+		caretLine++;		
+		caretOffset = content.getOffsetAtLine(caretLine);
+		// explicitly go to the calculated caret line. may be different 
+		// from content.getLineAtOffset(caretOffset) when in word wrap mode
+		showCaret(caretLine);
+	}
+}
+/**
+ * Moves the caret to the previous character or to the end of the previous 
+ * line if the cursor is at the beginning of a line.
+ */
+void doSelectionCursorPrevious() {
+	int caretLine = getCaretLine();
+	int lineOffset = content.getOffsetAtLine(caretLine);
+	int offsetInLine = caretOffset - lineOffset;
+	advancing = false;
+	if (offsetInLine > 0) {
+		caretOffset = getClusterPrevious(caretOffset, caretLine);
+		showCaret(caretLine);
+	}
+	else
+	if (caretLine > 0) {
+		caretLine--;
+		lineOffset = content.getOffsetAtLine(caretLine);
+		caretOffset = lineOffset + content.getLine(caretLine).length();
+		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;
+	int caretLine;
+	int lineStartOffset;
+	
+	if (isSingleLine()) {
+		return;
+	}
+	caretLine = getCaretLine();	
+	lineStartOffset = content.getOffsetAtLine(caretLine);
+	// reset columnX on selection
+	oldColumnX = columnX = getXAtOffset(
+		content.getLine(caretLine), caretLine, caretOffset - lineStartOffset);
+	if (caretLine == content.getLineCount() - 1) {
+		caretOffset = content.getCharCount();
+	}
+	else {
+		caretLine = doLineDown();
+	}
+	setMouseWordSelectionAnchor();	
+	// select first and then scroll to reduce flash when key 
+	// repeat scrolls lots of lines
+	doSelection(ST.COLUMN_NEXT);
+	// explicitly go to the calculated caret line. may be different 
+	// from content.getLineAtOffset(caretOffset) when in word wrap mode
+	showCaret(caretLine);
+	// save the original horizontal caret position
+	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;
+	int caretLine = getCaretLine();	
+	int lineStartOffset = content.getOffsetAtLine(caretLine);
+	
+	// reset columnX on selection
+	oldColumnX = columnX = getXAtOffset(
+		content.getLine(caretLine), caretLine, caretOffset - lineStartOffset);	
+	if (caretLine == 0) {
+		caretOffset = 0;
+	}
+	else {
+		caretLine = doLineUp();
+	}
+	setMouseWordSelectionAnchor();
+	// explicitly go to the calculated caret line. may be different 
+	// from content.getLineAtOffset(caretOffset) when in word wrap mode
+	showCaret(caretLine);
+	doSelection(ST.COLUMN_PREVIOUS);
+	// save the original horizontal caret position	
+	columnX = oldColumnX;
+}
+/**
+ * Scrolls one page down so that the last line (truncated or whole)
+ * of the current page becomes the fully visible top 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 end 
+ * of the text where a full page scroll is not possible. In this case 
+ * the caret is moved after the last character.
+ * <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 lines) {
+	int oldColumnX;
+	int caretLine = getCaretLine();
+	int lineStartOffset = content.getOffsetAtLine(caretLine);
+	
+	// reset columnX on selection
+	oldColumnX = columnX = getXAtOffset(
+		content.getLine(caretLine), caretLine, caretOffset - lineStartOffset);
+	doPageDown(true, lines);
+	columnX = oldColumnX;
+}
+/**
+ * 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.
+ * <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 lines) {
+	int oldColumnX;
+	int caretLine = getCaretLine();
+	int lineStartOffset = content.getOffsetAtLine(caretLine);
+	
+	// reset columnX on selection
+	oldColumnX = columnX = getXAtOffset(
+		content.getLine(caretLine), caretLine, caretOffset - lineStartOffset);
+	doPageUp(true, lines);
+	columnX = oldColumnX;
+}
+/**
+ * Moves the caret to the end of the next word .
+ */
+void doSelectionWordNext() {
+	int newCaretOffset = getWordEnd(caretOffset);
+	// Force symmetrical movement for word next and previous. Fixes 14536
+	advancing = false;
+	// don't change caret position if in single line mode and the cursor 
+	// would be on a different line. fixes 5673
+	if (!isSingleLine() || 
+		content.getLineAtOffset(caretOffset) == content.getLineAtOffset(newCaretOffset)) {
+		caretOffset = newCaretOffset;
+		showCaret();
+	}
+}
+/**
+ * Moves the caret to the start of the previous word.
+ */
+void doSelectionWordPrevious() {
+	int caretLine;	
+	advancing = false;
+	caretOffset = getWordStart(caretOffset);
+	caretLine = content.getLineAtOffset(caretOffset);
+	// word previous always comes from bottom line. when
+	// wrapping lines, stay on bottom line when on line boundary
+	if (wordWrap && caretLine < content.getLineCount() - 1 &&
+		caretOffset == content.getOffsetAtLine(caretLine + 1)) {
+		caretLine++;
+	}
+	showCaret(caretLine);
+}
+/**
+ * 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() {
+	caretOffset = getClusterPrevious(caretOffset, getCaretLine());
+	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() {
+	caretOffset = getClusterNext(caretOffset, getCaretLine());
+	showCaret();
+}
+/**
+ * Moves the caret to the end of the next word.
+ * If a selection exists, move the caret to the end of the selection
+ * and remove the selection.
+ */
+void doWordNext() {
+	if (selection.y - selection.x > 0) {
+		int caretLine;
+		
+		caretOffset = selection.y;
+		caretLine = getCaretLine();
+		showCaret(caretLine);
+	}
+	else {
+		doSelectionWordNext();
+	}
+}
+/**
+ * Moves the caret to the start of the previous word.
+ * If a selection exists, move the caret to the start of the selection
+ * and remove the selection.
+ */
+void doWordPrevious() {
+	if (selection.y - selection.x > 0) {
+		int caretLine;
+		
+		caretOffset = selection.x;
+		caretLine = getCaretLine();
+		showCaret(caretLine);
+	}
+	else {
+		doSelectionWordPrevious();
+	}
+}
+/**
+ * Draws the specified rectangle.
+ * Draw directly without invalidating the affected area when clearBackground is 
+ * false.
+ * <p>
+ *
+ * @param x the x position
+ * @param y the y position
+ * @param width the width
+ * @param height the height
+ * @param clearBackground true=clear the background by invalidating the requested 
+ * 	redraw area, false=draw the foreground directly without invalidating the 
+ * 	redraw area.
+ */
+void draw(int x, int y, int width, int height, boolean clearBackground) {
+	if (clearBackground) {
+		redraw(x + leftMargin, y + topMargin, width, height, true);
+	}
+	else {
+		int startLine = (y + verticalScrollOffset) / lineHeight;
+		int endY = y + height;
+		int paintYFromTopLine = (startLine - topIndex) * lineHeight;
+		int topLineOffset = (topIndex * lineHeight - verticalScrollOffset);
+		int paintY = paintYFromTopLine + topLineOffset + topMargin;	// adjust y position for pixel based scrolling
+		int lineCount = content.getLineCount();
+		Color background = getBackground();
+		Color foreground = getForeground();
+		GC gc = getGC();
+	
+		if (isSingleLine()) {
+			lineCount = 1;
+		}
+		for (int i = startLine; paintY < endY && i < lineCount; i++, paintY += lineHeight) {
+			String line = content.getLine(i);
+			renderer.drawLine(line, i, paintY, gc, background, foreground, clearBackground);
+		}
+		gc.dispose();	
+	}
+}
+/** 
+ * Ends the autoscroll process.
+ */
+void endAutoScroll() {
+	autoScrollDirection = SWT.NULL;
+}
+public Color getBackground() {
+	checkWidget();
+	if (background == null) {
+		return getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND);
+	}
+	return background;
+}
+/**
+ * Returns the baseline, in pixels. 
+ * 
+ * @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
+ */
+public int getBaseline() {
+	checkWidget();
+	return renderer.getBaseline();
+}
+/**
+ * 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>
+ * <p>
+ * @deprecated use BidiSegmentListener instead.
+ * </p>
+ */
+public boolean getBidiColoring() {
+	checkWidget();
+	return bidiColoring;
+}
+/** 
+ * Returns the index of the last fully visible line.
+ * <p>
+ *
+ * @return index of the last fully visible line.
+ */
+int getBottomIndex() {
+	int lineCount = 1;
+	
+	if (lineHeight != 0) {
+		// calculate the number of lines that are fully visible
+		int partialTopLineHeight = topIndex * lineHeight - verticalScrollOffset;
+		lineCount = (getClientArea().height - partialTopLineHeight) / lineHeight;
+	}
+	return Math.min(content.getLineCount() - 1, topIndex + Math.max(0, lineCount - 1));
+}
+/**
+ * Returns the caret position relative to the start of the text.
+ * <p>
+ *
+ * @return the caret position relative to the start of the text.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public int getCaretOffset() {
+	checkWidget();
+	
+	return caretOffset;
+}
+/**
+ * Returns the caret offset at the given x location in the line.
+ * The caret offset is the offset of the character where the caret will be
+ * placed when a mouse click occurs. The caret offset will be the offset of 
+ * the character after the clicked one if the mouse click occurs at the second 
+ * half of a character.
+ * Doesn't properly handle ligatures and other context dependent characters 
+ * unless the current locale is a bidi locale. 
+ * Ligatures are handled properly as long as they don't occur at lineXOffset.
+ * <p>
+ *
+ * @param line text of the line to calculate the offset in
+ * @param lineOffset offset of the first character in the line. 
+ * 	0 based from the beginning of the document.
+ * @param lineXOffset x location in the line
+ * @return caret offset at the x location relative to the start of the line.
+ */
+int getOffsetAtX(String line, int lineOffset, int lineXOffset) {
+	int x = lineXOffset - leftMargin + horizontalScrollOffset;
+	TextLayout layout = renderer.getTextLayout(line, lineOffset);
+	int[] trailing = new int[1];
+	int offsetInLine = layout.getOffset(x, 0, trailing);
+	advancing = false;
+	if (trailing[0] != 0) {
+		int lineLength = line.length();
+		if (offsetInLine + trailing[0] >= lineLength) {
+			offsetInLine = lineLength;
+			advancing = true;
+		} else {
+			int level;
+			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];
+			int trailingLevel = layout.getLevel(offsetInLine) & 0x1;
+			advancing  = (level ^ trailingLevel) != 0;
+		}
+	}
+	renderer.disposeTextLayout(layout);
+	return offsetInLine;
+}
+/**
+ * Returns the caret width.
+ * <p>
+ *
+ * @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) {
+	String line = content.getLine(lineIndex);
+	int lineOffset = content.getOffsetAtLine(lineIndex);	
+	TextLayout layout = renderer.getTextLayout(line, lineOffset);
+	offset -= lineOffset;
+	offset = layout.getNextOffset(offset, SWT.MOVEMENT_CLUSTER);
+	offset += lineOffset;
+	renderer.disposeTextLayout(layout);
+	return offset;
+}
+int getClusterPrevious(int offset, int lineIndex) {
+	String line = content.getLine(lineIndex);
+	int lineOffset = content.getOffsetAtLine(lineIndex);	
+	TextLayout layout = renderer.getTextLayout(line, lineOffset);
+	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
+ * or null if no user defined content implementation has been set.
+ * <p>
+ *
+ * @return content implementation that is used for text storage or null 
+ * 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 logicalContent;
+}
+/** 
+ * Returns whether the widget implements double click mouse behavior.
+ * <p>
+ *
+ * @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.
+ * <p>
+ *
+ * @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;
+}
+public Color getForeground() {
+	checkWidget();
+	if (foreground == null) {
+		return getDisplay().getSystemColor(SWT.COLOR_LIST_FOREGROUND);
+	}
+	return foreground;
+}
+/** 
+ * Return a GC to use for rendering and update the cached font style to
+ * represent the current style.
+ * <p>
+ *
+ * @return GC.
+ */
+GC getGC() {
+	return new GC(this);
+}
+/** 
+ * Returns the horizontal scroll increment.
+ * <p>
+ *
+ * @return horizontal scroll increment.
+ */
+int getHorizontalIncrement() {
+	GC gc = getGC();
+	int increment = gc.getFontMetrics().getAverageCharWidth();
+	
+	gc.dispose();
+	return increment;
+}
+/** 
+ * Returns the horizontal scroll offset relative to the start of the line.
+ * <p>
+ *
+ * @return horizontal scroll offset relative to the start of the line,
+ * measured in character increments starting at 0, if > 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.
+ * <p>
+ *
+ * @return the horizontal scroll offset relative to the start of the line,
+ * measured in pixel starting at 0, if > 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 action assigned to the key.
+ * Returns SWT.NULL if there is no action associated with the key.
+ * <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.
+ * @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 = (Integer) keyActionMap.get(new Integer(key));
+	int intAction;
+	
+	if (action == null) {
+		intAction = SWT.NULL;
+	}
+	else {
+		intAction = action.intValue();
+	}
+	return intAction;
+}
+/**
+ * Gets the number of characters.
+ * <p>
+ *
+ * @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 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();
+	Color lineBackground = null;
+	
+	if (index < 0 || index > logicalContent.getLineCount()) {
+		SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+	}
+	if (!userLineBackground) {
+		lineBackground = defaultLineStyler.getLineBackground(index);
+	}
+	return lineBackground;
+}
+/**
+ * Returns the line background data for the given line or null if 
+ * there is none.
+ * <p>
+ * @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(LineGetBackground, lineOffset, line);
+}
+/** 
+ * Gets the number of text lines.
+ * <p>
+ *
+ * @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 getLineAtOffset(getCharCount()) + 1;
+}
+/**
+ * Returns the number of lines that can be completely displayed in the 
+ * widget client area.
+ * <p>
+ *
+ * @return number of lines that can be completely displayed in the widget 
+ * 	client area.
+ */
+int getLineCountWhole() {
+	int lineCount;
+	
+	if (lineHeight != 0) {
+		lineCount = getClientArea().height / lineHeight;
+	}
+	else {
+		lineCount = 1;
+	}
+	return lineCount;
+}
+/**
+ * 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 <= offset <= 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 (< 0 or > getCharCount())</li> 
+ * </ul>
+ */
+public int getLineAtOffset(int offset) {
+	checkWidget();
+	
+	if (offset < 0 || offset > getCharCount()) {
+		SWT.error(SWT.ERROR_INVALID_RANGE);		
+	}
+	return logicalContent.getLineAtOffset(offset);
+}
+/**
+ * Returns the line delimiter used for entering new lines by key down
+ * or paste operation.
+ * <p>
+ *
+ * @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 a StyledTextEvent that can be used to request data such 
+ * as styles and background color for a line.
+ * 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 visualline 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(logicalContent);		
+		if (wordWrap) {
+		    // if word wrap is on, the line offset and text may be visual (wrapped)
+		    int lineIndex = logicalContent.getLineAtOffset(lineOffset);
+		    
+		    event.detail = logicalContent.getOffsetAtLine(lineIndex);
+			event.text = logicalContent.getLine(lineIndex);
+		}
+		else {
+			event.detail = lineOffset;
+			event.text = line;
+		}
+		notifyListeners(eventType, event);
+	}
+	return event;	
+}
+/**
+ * Returns the line height.
+ * <p>
+ *
+ * @return line height in pixel.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 getLineHeight() {
+	checkWidget();
+	return lineHeight;
+}
+/**
+ * Returns a LineCache implementation. Depending on whether or not
+ * word wrap is on this may be a line wrapping or line width 
+ * calculating implementaiton.
+ * <p>
+ * 
+ * @param content StyledTextContent to create the LineCache on.
+ * @return a LineCache implementation
+ */
+LineCache getLineCache(StyledTextContent content) {
+	LineCache lineCache;
+    
+	if (wordWrap) {
+		lineCache = new WordWrapCache(this, (WrappedContent) content);
+	}
+	else {
+		lineCache = new ContentWidthCache(this, content);
+	}
+	return lineCache;
+}
+/**
+ * Returns the line style data for the given line or null if there is 
+ * none. 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(LineGetStyle, lineOffset, line);
+}
+/**
+ * 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.
+ * <p>
+ *
+ * @param offset offset relative to the start of the content. 
+ * 	0 <= offset <= 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 (< 0 or > getCharCount())</li> 
+ * </ul>
+ */
+public Point getLocationAtOffset(int offset) {
+	checkWidget();
+	if (offset < 0 || offset > getCharCount()) {
+		SWT.error(SWT.ERROR_INVALID_RANGE);		
+	}
+	int line = content.getLineAtOffset(offset);
+	int lineOffset = content.getOffsetAtLine(line);
+	String lineContent = content.getLine(line);
+	int x = getXAtOffset(lineContent, line, offset - lineOffset);
+	int y = line * lineHeight - verticalScrollOffset;
+	
+	return new Point(x, y);
+}
+/**
+ * Returns the character offset of the first character of the given line.
+ * <p>
+ *
+ * @param lineIndex index of the line, 0 based relative to the first 
+ * 	line in the content. 0 <= lineIndex < 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 offset is outside the valid range (< 0 or > getCharCount())</li> 
+ * </ul>
+ * @since 2.0
+ */
+public int getOffsetAtLine(int lineIndex) {
+	checkWidget();
+	
+	if (lineIndex < 0 || 
+		(lineIndex > 0 && lineIndex >= logicalContent.getLineCount())) {
+		SWT.error(SWT.ERROR_INVALID_RANGE);		
+	}
+	return logicalContent.getOffsetAtLine(lineIndex);
+}
+/**
+ * Returns the offset of the character at the given location relative 
+ * to the first character in the document.
+ * 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>
+ */
+public int getOffsetAtLocation(Point point) {
+	checkWidget();
+	TextLayout layout;
+	int line;
+	int lineOffset;
+	int offsetInLine;
+	String lineText;
+	
+	if (point == null) {
+		SWT.error(SWT.ERROR_NULL_ARGUMENT);
+	}
+	// is y above first line or is x before first column?
+	if (point.y + verticalScrollOffset < 0 || point.x + horizontalScrollOffset < 0) {
+		SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+	}	
+	line = (getTopPixel() + point.y) / lineHeight;	
+	// does the referenced line exist?
+	if (line >= content.getLineCount()) {
+		SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+	}	
+	lineText = content.getLine(line);
+	lineOffset = content.getOffsetAtLine(line);	
+	
+	int x = point.x - leftMargin + horizontalScrollOffset;
+	layout = renderer.getTextLayout(lineText, lineOffset);
+	Rectangle rect = layout.getLineBounds(0);
+	if (x > rect.x + rect.width) {
+		renderer.disposeTextLayout(layout);
+		SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+	}
+	int[] trailing = new int[1];
+	offsetInLine = layout.getOffset(x, 0, trailing);
+	if (offsetInLine != lineText.length() - 1) {
+		offsetInLine = Math.min(lineText.length(), offsetInLine + trailing[0]);		
+	}
+	renderer.disposeTextLayout(layout);
+	return lineOffset + offsetInLine;
+}
+/**
+ * Returns the offset at the specified x location in the specified line.
+ * <p>
+ *
+ * @param x	x location of the mouse location
+ * @param line	line the mouse location is in
+ * @return the offset at the specified x location in the specified line,
+ * 	relative to the beginning of the document
+ */
+int getOffsetAtMouseLocation(int x, int line) {
+	String lineText = content.getLine(line);
+	int lineOffset = content.getOffsetAtLine(line);
+	return getOffsetAtX(lineText, lineOffset, x) + lineOffset;
+}
+/**
+ * Return 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
+ */
+public int getOrientation () {
+	checkWidget();
+	return isMirrored() ? SWT.RIGHT_TO_LEFT : SWT.LEFT_TO_RIGHT;
+}
+/** 
+ * Returns the index of the last partially visible line.
+ *
+ * @return index of the last partially visible line.
+ */
+int getPartialBottomIndex() {
+	int partialLineCount = Compatibility.ceil(getClientArea().height, lineHeight);
+	return Math.min(content.getLineCount(), topIndex + partialLineCount) - 1;
+}
+/**
+ * Returns the content in the specified range using the platform line 
+ * delimiter to separate lines.
+ * <p>
+ *
+ * @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 = logicalContent.getLineAtOffset(writer.getStart());
+	int endLine = logicalContent.getLineAtOffset(end);
+	String endLineText = logicalContent.getLine(endLine);
+	int endLineOffset = logicalContent.getOffsetAtLine(endLine);
+	
+	for (int i = startLine; i <= endLine; i++) {
+		writer.writeLine(logicalContent.getLine(i), logicalContent.getOffsetAtLine(i));
+		if (i < endLine) {
+			writer.writeLineDelimiter(PlatformLineDelimiter);
+		}
+	}
+	if (end > endLineOffset + endLineText.length()) {
+		writer.writeLineDelimiter(PlatformLineDelimiter);
+	}
+	writer.close();
+	return writer.toString();
+}
+/**
+ * Returns the selection.
+ * <p>
+ * Text selections are specified in terms of caret positions.  In a text
+ * widget that contains N characters, there are N+1 caret positions, 
+ * ranging from 0..N
+ * <p>
+ *
+ * @return start and end of the selection, x is the offset of the first 
+ * 	selected character, y is the offset after the last selected character.
+ *  The selection values returned are visual (i.e., x will always always be   
+ *  <= y).  To determine if a selection is right-to-left (RtoL) vs. left-to-right 
+ *  (LtoR), compare the caretOffset to the start and end of the selection 
+ *  (e.g., caretOffset == start of selection implies that the selection is RtoL).
+ * @see #getSelectionRange
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public Point getSelection() {
+	checkWidget();
+	return new Point(selection.x, selection.y);
+}
+/**
+ * Returns the selection.
+ * <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>
+ */
+public Point getSelectionRange() {
+	checkWidget();
+	return new Point(selection.x, selection.y - selection.x);
+}
+/**
+ * Returns the receiver's selection background color.
+ *
+ * @return the selection background color
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @since 2.1
+ */
+public Color getSelectionBackground() {
+	checkWidget();
+	if (selectionBackground == null) {
+		return getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION);
+	}
+	return selectionBackground;
+}
+/**
+ * Gets the number of selected characters.
+ * <p>
+ *
+ * @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();
+	return getSelectionRange().y;
+}
+/**
+ * Returns the receiver's selection foreground color.
+ *
+ * @return the selection foreground color
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @since 2.1
+ */
+public Color getSelectionForeground() {
+	checkWidget();
+	if (selectionForeground == null) {
+		return getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT);
+	}
+	return selectionForeground;
+}
+/**
+ * Returns the selected text.
+ * <p>
+ *
+ * @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();
+	return content.getTextRange(selection.x, selection.y - selection.x);
+}
+
+public int getStyle() {
+	int style = super.getStyle();
+	style &= ~(SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT | SWT.MIRRORED);
+	if (isMirrored()) {
+		style |= SWT.RIGHT_TO_LEFT | SWT.MIRRORED;
+	} else {
+		style |= SWT.LEFT_TO_RIGHT;
+	}
+	return style;
+}
+
+/**
+ * Returns the text segments that should be treated as if they 
+ * had a different direction than the surrounding text.
+ * <p>
+ *
+ * @param lineOffset offset of the first character in the line. 
+ * 	0 based from the beginning of the document.
+ * @param line text of the line to specify bidi segments for
+ * @return text segments that should be treated as if they had a
+ * 	different direction than the surrounding text. Only the start 
+ * 	index of a segment is specified, relative to the start of the 
+ * 	line. Always starts with 0 and ends with the line length. 
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT - if the segment indices returned 
+ * 		by the listener do not start with 0, are not in ascending order,
+ * 		exceed the line length or have duplicates</li>
+ * </ul>
+ */
+int [] getBidiSegments(int lineOffset, String line) {
+	if (!isListening(LineGetSegments)) {
+		return getBidiSegmentsCompatibility(line, lineOffset);
+	}
+	StyledTextEvent event = sendLineEvent(LineGetSegments, lineOffset, line);
+	int lineLength = line.length();
+	int[] segments;
+	if (event == null || event.segments == null || event.segments.length == 0) {
+		segments = new int[] {0, lineLength};
+	}
+	else {
+		int segmentCount = event.segments.length;
+		
+		// test segment index consistency
+		if (event.segments[0] != 0) {
+			SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+		} 	
+		for (int i = 1; i < segmentCount; i++) {
+			if (event.segments[i] <= event.segments[i - 1] || event.segments[i] > lineLength) {
+				SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+			} 	
+		}
+		// ensure that last segment index is line end offset
+		if (event.segments[segmentCount - 1] != lineLength) {
+			segments = new int[segmentCount + 1];
+			System.arraycopy(event.segments, 0, segments, 0, segmentCount);
+			segments[segmentCount] = lineLength;
+		}
+		else {
+			segments = event.segments;
+		}
+	}
+	return segments;
+}
+/**
+ * @see #getBidiSegments
+ * Supports deprecated setBidiColoring API. Remove when API is removed.
+ */
+int [] getBidiSegmentsCompatibility(String line, int lineOffset) {
+	StyledTextEvent event;
+	StyleRange [] styles = new StyleRange [0];
+	int lineLength = line.length();
+	if (!bidiColoring) {
+		return new int[] {0, lineLength};
+	}
+	event = renderer.getLineStyleData(lineOffset, line);
+	if (event != null) {
+		styles = event.styles;
+	}
+	if (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.
+ * 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 <= offset < 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 (!userLineStyle) {
+		return defaultLineStyler.getStyleRangeAtOffset(offset);
+	} 
+	return null;
+}
+/**
+ * Returns the styles.
+ * 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>
+ *
+ * @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>
+ */
+public StyleRange [] getStyleRanges() {
+	checkWidget();
+	StyleRange styles[];
+	
+	if (!userLineStyle) {
+		styles = defaultLineStyler.getStyleRanges();
+	}
+	else {
+		styles = new StyleRange[0];
+	}
+	return styles;
+}
+/**
+ * Returns the styles for the given text range.
+ * 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.
+ * 
+ * @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 >= start 
+ *  and the last returned <code>StyleRange</code> will have an ending 
+ *  offset <= 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.0
+ */
+public StyleRange [] getStyleRanges(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);
+	}	
+	StyleRange styles[];
+	
+	if (!userLineStyle) {
+		styles = defaultLineStyler.getStyleRangesFor(start, length);
+		if (styles == null) return new StyleRange[0];
+		// adjust the first and last style to reflect the specified 
+		// range, clone these styles since the returned styles are the
+		// styles cached by the widget
+		if (styles.length == 1) {
+			StyleRange style = styles[0];
+			if (style.start < start) {
+				StyleRange newStyle = (StyleRange)styles[0].clone();
+				newStyle.length = newStyle.length - (start - newStyle.start);
+				newStyle.start = start;
+				styles[0] = newStyle;
+			}
+			if (style.start + style.length > (start + length)) {
+				StyleRange newStyle = (StyleRange)styles[0].clone();
+				newStyle.length = start + length - newStyle.start;
+				styles[0] = newStyle;
+			}
+		} else if (styles.length > 1) {
+			StyleRange style = styles[0];
+			if (style.start < start) {
+				StyleRange newStyle = (StyleRange)styles[0].clone();
+				newStyle.length = newStyle.length - (start - newStyle.start);
+				newStyle.start = start;
+				styles[0] = newStyle;
+			}
+			style = styles[styles.length - 1];
+			if (style.start + style.length > (start + length)) {
+				StyleRange newStyle = (StyleRange)styles[styles.length - 1].clone();
+				newStyle.length = start + length - newStyle.start;
+				styles[styles.length - 1] = newStyle;
+			}
+		}
+	}
+	else {
+		styles = new StyleRange[0];
+	}
+	return styles;
+}
+/**
+ * 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>
+ */
+public int getTabs() {
+	checkWidget();
+	return tabLength;
+}
+/**
+ * Returns a copy of the widget content.
+ * <p>
+ *
+ * @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.
+ * <p>
+ *
+ * @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.
+ * <p>
+ *
+ * @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 = lineStart * lineHeight;
+	int height = (lineEnd + 1) * lineHeight - y;
+	int left = 0x7fffffff, right = 0;
+	for (int i = lineStart; i <= lineEnd; i++) {
+		int lineOffset = content.getOffsetAtLine(i);
+		String line = content.getLine(i);
+		TextLayout layout = renderer.getTextLayout(line, lineOffset);
+		if (i == lineStart && i == lineEnd) {
+			rect = layout.getBounds(start - lineOffset, end - lineOffset);
+		} else if (i == lineStart) {
+			rect = layout.getBounds(start - lineOffset, line.length());
+		}	else	if (i == lineEnd) {
+			rect = layout.getBounds(0, end - lineOffset);
+		} else {
+			rect = layout.getLineBounds(0);
+		}
+		left = Math.min (left, rect.x);
+		right = Math.max (right, rect.x + rect.width);
+		renderer.disposeTextLayout(layout);
+	}
+	rect = new Rectangle (left, y, right-left, height);
+	rect.x += leftMargin - horizontalScrollOffset;
+	rect.y -= verticalScrollOffset;
+	return rect;
+}
+/**
+ * Returns the widget content starting at start for length characters.
+ * <p>
+ *
+ * @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.  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();
+	int logicalTopIndex = topIndex;
+	
+	if (wordWrap) {
+		int visualLineOffset = content.getOffsetAtLine(topIndex);
+		logicalTopIndex = logicalContent.getLineAtOffset(visualLineOffset);
+	}
+	return logicalTopIndex;
+}
+/**
+ * Gets the top pixel.  The top pixel is the pixel position of the line that is 
+ * currently at the top of the widget.The text widget can be scrolled by pixels 
+ * by dragging the scroll thumb so that a partial line may be displayed at the top 
+ * the widget.  The top pixel changes when the widget is scrolled.  The top pixel 
+ * does not include the widget trimming.
+ * <p>
+ *
+ * @return pixel 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 verticalScrollOffset;
+}
+/** 
+ * Returns the vertical scroll increment.
+ * <p>
+ *
+ * @return vertical scroll increment.
+ */
+int getVerticalIncrement() {
+	return lineHeight;
+}
+int getCaretDirection() {
+	if (!isBidiCaret()) return SWT.DEFAULT;
+	if (!updateCaretDirection && caretDirection != SWT.NULL) return caretDirection;
+	updateCaretDirection = false;
+	int caretLine = getCaretLine();
+	int lineOffset = content.getOffsetAtLine(caretLine);
+	String line = content.getLine(caretLine);
+	int offset = caretOffset - lineOffset;
+	int lineLength = line.length();
+	if (lineLength == 0) return isMirrored() ? SWT.RIGHT : SWT.LEFT;
+	if (advancing && 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(line, lineOffset);
+	int level = layout.getLevel(offset);
+	renderer.disposeTextLayout(layout);
+	return ((level & 1) != 0) ? SWT.RIGHT : SWT.LEFT;
+}
+/**
+ * Returns the index of the line the caret is on.
+ * When in word wrap mode and at the end of one wrapped line/ 
+ * beginning of the continuing wrapped line the caret offset
+ * is not sufficient to determine the caret line.
+ * 
+ * @return the index of the line the caret is on.
+ */
+int getCaretLine() {
+	int caretLine = content.getLineAtOffset(caretOffset);
+	int leftColumnX = leftMargin;
+	if (wordWrap && columnX <= leftColumnX &&
+		caretLine < content.getLineCount() - 1 &&
+		caretOffset == content.getOffsetAtLine(caretLine + 1)) {
+		caretLine++;
+	}
+	return caretLine;
+}
+/**
+ * Returns the offset of the character after the word at the specified
+ * offset.
+ * <p>
+ * There are two classes of words formed by a sequence of characters:
+ * <ul>
+ * <li>from 0-9 and A-z (ASCII 48-57 and 65-122)
+ * <li>every other character except line breaks
+ * </ul>
+ * </p>
+ * <p>
+ * Space characters ' ' (ASCII 20) are special as they are treated as
+ * part of the word leading up to the space character.  Line breaks are 
+ * treated as one word.
+ * </p>
+ */
+int getWordEnd(int offset) {
+	int line = logicalContent.getLineAtOffset(offset);
+	int lineOffset = logicalContent.getOffsetAtLine(line);
+	String lineText = logicalContent.getLine(line);
+	int lineLength = lineText.length();
+	
+	if (offset >= getCharCount()) {
+		return offset;
+	}
+	if (offset == lineOffset + lineLength) {
+		line++;
+		offset = logicalContent.getOffsetAtLine(line);
+	}
+	else {
+		TextLayout layout = renderer.getTextLayout(lineText, lineOffset);
+		offset -= lineOffset;
+		offset = layout.getNextOffset(offset, SWT.MOVEMENT_WORD);
+		offset += lineOffset;
+		renderer.disposeTextLayout(layout);
+	}
+	return offset;
+}
+/**
+ * Returns the offset of the character after the word at the specified
+ * offset.
+ * <p>
+ * There are two classes of words formed by a sequence of characters:
+ * <ul>
+ * <li>from 0-9 and A-z (ASCII 48-57 and 65-122)
+ * <li>every other character except line breaks
+ * </ul>
+ * </p>
+ * <p>
+ * Spaces are ignored and do not represent a word.  Line breaks are treated 
+ * as one word.
+ * </p>
+ */
+int getWordEndNoSpaces(int offset) {
+	int line = logicalContent.getLineAtOffset(offset);
+	int lineOffset = logicalContent.getOffsetAtLine(line);
+	String lineText = logicalContent.getLine(line);
+	int lineLength = lineText.length();
+	
+	if (offset >= getCharCount()) {
+		return offset;
+	}
+	if (offset == lineOffset + lineLength) {
+		line++;
+		offset = logicalContent.getOffsetAtLine(line);
+	}
+	else {
+		offset -= lineOffset;
+		char ch = lineText.charAt(offset);
+		boolean letterOrDigit = Compatibility.isLetterOrDigit(ch);
+		
+		while (offset < lineLength - 1 && Compatibility.isLetterOrDigit(ch) == letterOrDigit && !Compatibility.isSpaceChar(ch)) {
+			offset++;
+			ch = lineText.charAt(offset);
+		}
+		if (offset == lineLength - 1 && Compatibility.isLetterOrDigit(ch) == letterOrDigit && !Compatibility.isSpaceChar(ch)) {
+			offset++;
+		}
+		offset += lineOffset;
+	}
+	return offset;
+}
+/**
+ * Returns the start offset of the word at the specified offset.
+ * There are two classes of words formed by a sequence of characters:
+ * <p>
+ * <ul>
+ * <li>from 0-9 and A-z (ASCII 48-57 and 65-122)
+ * <li>every other character except line breaks
+ * </ul>
+ * </p>
+ * <p>
+ * Space characters ' ' (ASCII 20) are special as they are treated as
+ * part of the word leading up to the space character.  Line breaks are treated 
+ * as one word.
+ * </p>
+ */
+int getWordStart(int offset) {
+	int line = logicalContent.getLineAtOffset(offset);
+	int lineOffset = logicalContent.getOffsetAtLine(line);
+	String lineText = logicalContent.getLine(line);
+
+	if (offset <= 0) {
+		return offset;
+	}
+	if (offset == lineOffset) {
+		line--;
+		lineText = logicalContent.getLine(line);
+		offset = logicalContent.getOffsetAtLine(line) + lineText.length();
+	}
+	else {
+		TextLayout layout = renderer.getTextLayout(lineText, lineOffset);
+		offset -= lineOffset;
+		offset = layout.getPreviousOffset(offset, SWT.MOVEMENT_WORD);
+		offset += lineOffset;
+		renderer.disposeTextLayout(layout); 
+	}
+	return offset;
+}
+/**
+ * Returns whether the widget wraps lines.
+ * <p>
+ *
+ * @return true if widget wraps lines, false otherwise
+ * @since 2.0
+ */
+public boolean getWordWrap() {
+	checkWidget();
+	return wordWrap;
+}
+/** 
+ * Returns the x location of the character at the give offset in the line.
+ * <b>NOTE:</b> Does not return correct values for true italic fonts (vs. slanted fonts).
+ * <p>
+ *
+ * @return x location of the character at the given offset in the line.
+ */
+int getXAtOffset(String line, int lineIndex, int offsetInLine) {
+	int x = 0;
+	int lineLength = line.length();
+	if (lineIndex < content.getLineCount() - 1) {
+		int endLineOffset = content.getOffsetAtLine(lineIndex + 1) - 1;
+		if (lineLength < offsetInLine && offsetInLine <= endLineOffset) {
+			offsetInLine = lineLength;
+		}
+	}
+	if (lineLength != 0  && offsetInLine <= lineLength) {
+		int lineOffset = content.getOffsetAtLine(lineIndex);
+		TextLayout layout = renderer.getTextLayout(line, lineOffset);
+		if (!advancing || offsetInLine == 0) {
+			x = layout.getLocation(offsetInLine, false).x;
+		} else {
+			x = layout.getLocation(offsetInLine - 1, true).x;
+		}
+		renderer.disposeTextLayout(layout);
+	}
+	return x + leftMargin - horizontalScrollOffset;
+}
+/** 
+ * Inserts a string.  The old selection is replaced with the new text.  
+ * <p>
+ *
+ * @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);
+	}
+	Point sel = getSelectionRange();
+	replaceTextRange(sel.x, sel.y, string);
+}
+/**
+ * Creates content change listeners and set the default content model.
+ */
+void installDefaultContent() {
+	textChangeListener = new TextChangeListener() {
+		public void textChanging(TextChangingEvent event) {
+			handleTextChanging(event);
+		}
+		public void textChanged(TextChangedEvent event) {
+			handleTextChanged(event);
+		}
+		public void textSet(TextChangedEvent event) {
+			handleTextSet(event);
+		}
+	};
+	logicalContent = content = new DefaultContent();
+	content.addTextChangeListener(textChangeListener);
+}
+/**
+ * Creates a default line style listener.
+ * Used to store line background colors and styles.
+ * Removed when the user sets a LineStyleListener.
+ * <p>
+ *
+ * @see #addLineStyleListener
+ */
+void installDefaultLineStyler() {
+	defaultLineStyler = new DefaultLineStyler(logicalContent);
+	StyledTextListener typedListener = new StyledTextListener(defaultLineStyler);
+	if (!userLineStyle) {
+		addListener(LineGetStyle, typedListener);
+	}
+	if (!userLineBackground) {
+		addListener(LineGetBackground, typedListener);
+	}
+}
+/** 
+ * Adds event listeners
+ */
+void installListeners() {
+	ScrollBar verticalBar = getVerticalBar();
+	ScrollBar horizontalBar = getHorizontalBar();
+	
+	listener = new Listener() {
+		public void handleEvent(Event event) {
+			switch (event.type) {
+				case SWT.Dispose: handleDispose(event); break;
+				case SWT.KeyDown: handleKeyDown(event); break;
+				case SWT.KeyUp: handleKeyUp(event); break;
+				case SWT.MouseDown: handleMouseDown(event); break;
+				case SWT.MouseUp: handleMouseUp(event); break;
+				case SWT.MouseDoubleClick: handleMouseDoubleClick(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.MouseDown, listener);
+	addListener(SWT.MouseUp, listener);
+	addListener(SWT.MouseDoubleClick, listener);
+	addListener(SWT.MouseMove, listener);
+	addListener(SWT.Paint, listener);
+	addListener(SWT.Resize, listener);
+	addListener(SWT.Traverse, listener);
+	if (verticalBar != null) {
+		verticalBar.addListener(SWT.Selection, new Listener() {
+			public void handleEvent(Event event) {
+				handleVerticalScroll(event);
+			}
+		});
+	}
+	if (horizontalBar != null) {
+		horizontalBar.addListener(SWT.Selection, new Listener() {
+			public void handleEvent(Event event) {
+				handleHorizontalScroll(event);
+			}
+		});
+	}
+}
+StyledTextContent internalGetContent() {
+	return content;
+}
+int internalGetHorizontalPixel() {
+	return horizontalScrollOffset;
+}
+Point internalGetSelection() {
+	return selection;
+}
+boolean internalGetWordWrap() {
+	return wordWrap;
+}
+/**
+ * Used by WordWrapCache to bypass StyledText.redraw which does
+ * an unwanted cache reset.
+ */
+void internalRedraw() {
+	super.redraw();
+}
+/** 
+ * Redraws the specified text range.
+ * <p>
+ *
+ * @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. 
+ */
+void internalRedrawRange(int start, int length, boolean clearBackground) {
+	int end = start + length;
+	int firstLine = content.getLineAtOffset(start);
+	int lastLine = content.getLineAtOffset(end);
+	int offsetInFirstLine;
+	int partialBottomIndex = getPartialBottomIndex();
+	int partialTopIndex = verticalScrollOffset / lineHeight;
+	// do nothing if redraw range is completely invisible	
+	if (firstLine > partialBottomIndex || lastLine < partialTopIndex) {
+		return;
+	}
+	// only redraw visible lines
+	if (partialTopIndex > firstLine) {
+		firstLine = partialTopIndex;
+		offsetInFirstLine = 0;
+	}
+	else {
+		offsetInFirstLine = start - content.getOffsetAtLine(firstLine);
+	}
+	if (partialBottomIndex + 1 < lastLine) {
+		lastLine = partialBottomIndex + 1;	// + 1 to redraw whole bottom line, including line break
+		end = content.getOffsetAtLine(lastLine);
+	}
+	redrawLines(firstLine, offsetInFirstLine, lastLine, end, clearBackground);
+	
+	// redraw entire center lines if redraw range includes more than two lines
+	if (lastLine - firstLine > 1) {
+		Rectangle clientArea = getClientArea();
+		int redrawStopY = lastLine * lineHeight - verticalScrollOffset;		
+		int redrawY = (firstLine + 1) * lineHeight - verticalScrollOffset;		
+		draw(0, redrawY, clientArea.width, redrawStopY - redrawY, clearBackground);
+	}
+}
+/**
+ * Returns the widget text with style information encoded using RTF format
+ * specification version 1.5.
+ *
+ * @return the widget text with style information encoded using 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>
+ */
+String getRtf(){
+	checkWidget();
+	RTFWriter rtfWriter = new RTFWriter(0, getCharCount());
+	return getPlatformDelimitedText(rtfWriter);
+}
+/** 
+ * Frees resources.
+ */
+void handleDispose(Event event) {
+	removeListener(SWT.Dispose, listener);
+	notifyListeners(SWT.Dispose, event);
+	event.type = SWT.None;
+
+	clipboard.dispose();
+	ibeamCursor.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 (defaultLineStyler != null) {
+		defaultLineStyler.release();
+		defaultLineStyler = null;
+	}
+	if (isBidiCaret()) {
+		BidiUtil.removeLanguageListener(handle);
+	}
+	selectionBackground = null;
+	selectionForeground = null;
+	logicalContent = null;
+	textChangeListener = null;
+	lineCache = null;
+	ibeamCursor = null;
+	selection = null;
+	doubleClickSelection = null;
+	keyActionMap = null;
+	background = null;
+	foreground = null;
+	clipboard = null;
+}
+/** 
+ * Scrolls the widget horizontally.
+ */
+void handleHorizontalScroll(Event event) {
+	int scrollPixel = getHorizontalBar().getSelection() - horizontalScrollOffset;
+	scrollHorizontal(scrollPixel);
+}
+/**
+ * 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.
+ * <p>
+ *
+ * @param event keyboard event
+ */
+void handleKey(Event event) {
+	int action;
+	advancing = true;
+	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 >= 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_CARBON) {
+			// Ignore accelerator key combinations (we do not want to 
+			// insert a character in the text in this instance). Do not  
+			// ignore COMMAND+ALT combinations since that key sequence
+			// produces characters on the mac.
+			ignore = (event.stateMask ^ SWT.COMMAND) == 0 ||
+					(event.stateMask ^ (SWT.COMMAND | SWT.SHIFT)) == 0;
+		} else if (IS_MOTIF) {
+			// Ignore accelerator key combinations (we do not want to 
+			// insert a character in the text in this instance). Do not  
+			// ignore ALT combinations since this key sequence
+			// produces characters on motif.
+			ignore = (event.stateMask ^ SWT.CTRL) == 0 ||
+					(event.stateMask ^ (SWT.CTRL | SWT.SHIFT)) == 0;
+		} else {
+			// Ignore accelerator key combinations (we do not want to 
+			// insert a character in the text in this instance). Don't  
+			// ignore CTRL+ALT combinations since that is the Alt Gr 
+			// key on some keyboards.  See bug 20953. 
+			ignore = (event.stateMask ^ SWT.ALT) == 0 || 
+					(event.stateMask ^ SWT.CTRL) == 0 ||
+					(event.stateMask ^ (SWT.ALT | SWT.SHIFT)) == 0 ||
+					(event.stateMask ^ (SWT.CTRL | SWT.SHIFT)) == 0;
+		}
+		// -ignore anything below SPACE except for line delimiter keys and tab.
+		// -ignore DEL 
+		if (!ignore && event.character > 31 && event.character != SWT.DEL || 
+		    event.character == SWT.CR || event.character == SWT.LF || 
+		    event.character == TAB) {
+			doContent(event.character);
+		}
+	}
+	else {
+		invokeAction(action);		
+	}
+}
+/**
+ * If a VerifyKey listener exists, verify that the key that was entered
+ * should be processed.
+ * <p>
+ *
+ * @param event keyboard event
+ */
+void handleKeyDown(Event event) {
+	if (clipboardSelection == null) {
+		clipboardSelection = new Point(selection.x, selection.y);
+	}
+	
+	Event verifyEvent = new Event();
+	verifyEvent.character = event.character;
+	verifyEvent.keyCode = event.keyCode;
+	verifyEvent.stateMask = event.stateMask;
+	verifyEvent.doit = true;
+	notifyListeners(VerifyKey, verifyEvent);
+	if (verifyEvent.doit) {
+		handleKey(event);
+	}
+}
+/**
+ * Update the Selection Clipboard.
+ * <p>
+ *
+ * @param event keyboard event
+ */
+void handleKeyUp(Event event) {
+	if (clipboardSelection != null) {
+		if (clipboardSelection.x != selection.x || clipboardSelection.y != selection.y) {
+			try {
+				if (selection.y - selection.x > 0) {
+					setClipboardContent(selection.x, selection.y - selection.x, DND.SELECTION_CLIPBOARD);
+				}
+			}
+			catch (SWTError error) {
+				// Copy to clipboard failed. This happens when another application 
+				// is accessing the clipboard while we copy. Ignore the error.
+				// Fixes 1GDQAVN
+				// Rethrow all other errors. Fixes bug 17578.
+				if (error.code != DND.ERROR_CANNOT_SET_CLIPBOARD) {
+					throw error;
+				}
+			}
+		}
+	}
+	clipboardSelection = null;
+}
+/**
+ * Updates the caret location and selection if mouse button 1 has been 
+ * pressed.
+ */
+void handleMouseDoubleClick(Event event) {
+	if (event.button != 1 || !doubleClickEnabled) {
+		return;
+	}
+	event.y -= topMargin;
+	mouseDoubleClick = true;
+	caretOffset = getWordStart(caretOffset);
+	resetSelection();
+	caretOffset = getWordEndNoSpaces(caretOffset);
+	showCaret();
+	doMouseSelection();
+	doubleClickSelection = new Point(selection.x, selection.y);
+}
+/** 
+ * Updates the caret location and selection if mouse button 1 has been 
+ * pressed.
+ */
+void handleMouseDown(Event event) {
+	mouseDown = true;
+	mouseDoubleClick = false;
+	if (event.button == 2) {
+		String text = (String)getClipboardContent(DND.SELECTION_CLIPBOARD);
+		if (text != null && text.length() > 0) {
+			// position cursor
+			int x = event.x;
+			int y = event.y - topMargin;
+			doMouseLocationChange(x, y, false);
+			// insert text
+			Event e = new Event();
+			e.start = selection.x;
+			e.end = selection.y;
+			e.text = getModelDelimitedText(text);
+			sendKeyEvent(e);
+		}
+	}
+	if ((event.button != 1) || (IS_CARBON && (event.stateMask & SWT.MOD4) != 0)) {
+		return;	
+	}
+	boolean select = (event.stateMask & SWT.MOD2) != 0;	
+	event.y -= topMargin;
+	doMouseLocationChange(event.x, event.y, select);
+}
+/** 
+ * Updates the caret location and selection if mouse button 1 is pressed 
+ * during the mouse move.
+ */
+void handleMouseMove(Event event) {
+	if (!mouseDown) return;
+	if ((event.stateMask & SWT.BUTTON1) == 0) {
+		return;
+	}
+	event.y -= topMargin;
+	doMouseLocationChange(event.x, event.y, true);
+	update();
+	doAutoScroll(event);
+}
+/** 
+ * Autoscrolling ends when the mouse button is released.
+ */
+void handleMouseUp(Event event) {
+	mouseDown = false;
+	mouseDoubleClick = false;
+	event.y -= topMargin;
+	endAutoScroll();
+	if (event.button == 1) {
+		try {
+			if (selection.y - selection.x > 0) {
+				setClipboardContent(selection.x, selection.y - selection.x, DND.SELECTION_CLIPBOARD);
+			}
+		}
+		catch (SWTError error) {
+			// Copy to clipboard failed. This happens when another application 
+			// is accessing the clipboard while we copy. Ignore the error.
+			// Fixes 1GDQAVN
+			// Rethrow all other errors. Fixes bug 17578.
+			if (error.code != DND.ERROR_CANNOT_SET_CLIPBOARD) {
+				throw error;
+			}
+		}
+	}
+}
+/**
+ * Renders the invalidated area specified in the paint event.
+ * <p>
+ *
+ * @param event paint event
+ */
+void handlePaint(Event event) {
+	// Check if there is work to do
+	if (event.height == 0) return;
+	int startLine = Math.max(0, (event.y - topMargin + verticalScrollOffset) / lineHeight);
+	int paintYFromTopLine = (startLine - topIndex) * lineHeight;
+	int topLineOffset = topIndex * lineHeight - verticalScrollOffset;
+	int startY = paintYFromTopLine + topLineOffset + topMargin;	// adjust y position for pixel based scrolling and top margin
+	int renderHeight = event.y + event.height - startY;
+	performPaint(event.gc, startLine, startY, renderHeight);
+}	
+/**
+ * Recalculates the scroll bars. Rewraps all lines when in word 
+ * wrap mode.
+ * <p>
+ *
+ * @param event resize event
+ */
+void handleResize(Event event) {
+	int oldHeight = clientAreaHeight;
+	int oldWidth = clientAreaWidth;
+	
+	Rectangle clientArea = getClientArea();
+	clientAreaHeight = clientArea.height;
+	clientAreaWidth = clientArea.width;
+	/* Redraw the old or new right/bottom margin if needed */
+	if (oldWidth != clientAreaWidth) {
+		if (rightMargin > 0) {
+			int x = (oldWidth < clientAreaWidth ? oldWidth : clientAreaWidth)- rightMargin; 
+			redraw(x, 0, rightMargin, oldHeight, false);
+		}
+	}
+	if (oldHeight != clientAreaHeight) {
+		if (bottomMargin > 0) {
+			int y = (oldHeight < clientAreaHeight ? oldHeight : clientAreaHeight)- bottomMargin; 
+			redraw(0, y, oldWidth, bottomMargin, false);
+		}
+	}
+	if (wordWrap) {
+		if (oldWidth != clientAreaWidth) {	
+			wordWrapResize(oldWidth);
+		}
+	}
+	else
+	if (clientAreaHeight > oldHeight) {
+		int lineCount = content.getLineCount();
+		int oldBottomIndex = topIndex + oldHeight / lineHeight;
+		int newItemCount = Compatibility.ceil(clientAreaHeight - oldHeight, lineHeight);
+		
+		oldBottomIndex = Math.min(oldBottomIndex, lineCount);
+		newItemCount = Math.min(newItemCount, lineCount - oldBottomIndex);
+		lineCache.calculate(oldBottomIndex, newItemCount);
+	}
+	setScrollBars();
+	claimBottomFreeSpace();
+	claimRightFreeSpace();	
+	if (oldHeight != clientAreaHeight) {
+		calculateTopIndex();
+	}
+}
+/**
+ * Updates the caret position and selection and the scroll bars to reflect 
+ * the content change.
+ * <p>
+ */
+void handleTextChanged(TextChangedEvent event) {
+	lineCache.textChanged(lastTextChangeStart, 
+		lastTextChangeNewLineCount, 
+		lastTextChangeReplaceLineCount,
+		lastTextChangeNewCharCount,
+		lastTextChangeReplaceCharCount);
+	setScrollBars();
+	// 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.
+	// fixes 1GB93QT
+	updateSelection(
+		lastTextChangeStart, 
+		lastTextChangeReplaceCharCount, 
+		lastTextChangeNewCharCount);
+		
+	if (lastTextChangeReplaceLineCount > 0) {
+		// Only check for unused space when lines are deleted.
+		// Fixes 1GFL4LY
+		// Scroll up so that empty lines below last text line are used.
+		// Fixes 1GEYJM0
+		claimBottomFreeSpace();
+	}
+	if (lastTextChangeReplaceCharCount > 0) {
+		// fixes bug 8273
+		claimRightFreeSpace();
+	}
+	// do direct drawing if the text change is confined to a single line.
+	// optimization and fixes bug 13999. see also handleTextChanging.
+	if (lastTextChangeNewLineCount == 0 && lastTextChangeReplaceLineCount == 0) {
+		int startLine = content.getLineAtOffset(lastTextChangeStart);
+		int startY = startLine * lineHeight - verticalScrollOffset + topMargin;
+
+		if (DOUBLE_BUFFER) {
+			GC gc = getGC();
+			Caret caret = getCaret();
+			boolean caretVisible = false;
+			
+			if (caret != null) {
+				caretVisible = caret.getVisible();
+				caret.setVisible(false);
+			}
+			performPaint(gc, startLine, startY, lineHeight);
+			if (caret != null) {
+				caret.setVisible(caretVisible);
+			}
+			gc.dispose();
+		} else {
+			redraw(0, startY, getClientArea().width, lineHeight, false);
+			update();
+		}
+	}
+}
+/**
+ * Updates the screen to reflect a pending content change.
+ * <p>
+ *
+ * @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) {
+	int firstLine;	
+	int textChangeY;
+	boolean isMultiLineChange = event.replaceLineCount > 0 || event.newLineCount > 0;
+			
+	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;
+	firstLine = content.getLineAtOffset(event.start);
+	textChangeY = firstLine * lineHeight - verticalScrollOffset + topMargin;
+	if (isMultiLineChange) {
+		redrawMultiLineChange(textChangeY, event.newLineCount, event.replaceLineCount);
+	}
+	// notify default line styler about text change
+	if (defaultLineStyler != null) {
+		defaultLineStyler.textChanging(event);
+	}
+	
+	// Update the caret offset if it is greater than the length of the content.
+	// This is necessary since style range API may be called between the
+	// handleTextChanging and handleTextChanged events and this API sets the
+	// caretOffset.
+	int newEndOfText = content.getCharCount() - event.replaceCharCount + event.newCharCount;
+	if (caretOffset > newEndOfText) caretOffset = newEndOfText;
+}
+/**
+ * Called when the widget content is set programatically, overwriting 
+ * the old content. Resets the caret position, selection and scroll offsets. 
+ * Recalculates the content width and scroll bars. Redraws the widget.
+ * <p>
+ *
+ * @param event text change event. 
+ */
+void handleTextSet(TextChangedEvent event) {
+	reset();
+}
+/**
+ * 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.
+ * <p>
+ *
+ * @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) {
+	setVerticalScrollOffset(getVerticalBar().getSelection(), false);
+}
+/**
+ * Add accessibility support for the widget.
+ */
+void initializeAccessible() {
+	final Accessible accessible = getAccessible();
+	accessible.addAccessibleListener(new AccessibleAdapter() {
+		public void getHelp(AccessibleEvent e) {
+			e.result = getToolTipText();
+		}
+	});
+	accessible.addAccessibleTextListener(new AccessibleTextAdapter() {
+		public void getCaretOffset(AccessibleTextEvent e) {
+			e.offset = StyledText.this.getCaretOffset();
+		}
+		public void getSelectionRange(AccessibleTextEvent e) {
+			Point selection = StyledText.this.getSelectionRange();
+			e.offset = selection.x;
+			e.length = selection.y;
+		}
+	});
+	accessible.addAccessibleControlListener(new AccessibleControlAdapter() {
+		public void getRole(AccessibleControlEvent e) {
+			e.detail = ACC.ROLE_TEXT;
+		}
+		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;
+			e.detail = state;
+		}
+		public void getValue(AccessibleControlEvent e) {
+			e.result = StyledText.this.getText();
+		}
+	});		
+	addListener(SWT.FocusIn, new Listener() {
+		public void handleEvent(Event event) {
+			accessible.setFocus(ACC.CHILDID_SELF);
+		}
+	});
+}
+/** 
+ * Initializes the fonts used to render font styles.
+ * Presently only regular and bold fonts are supported.
+ */
+void initializeRenderer() {
+	if (renderer != null) {
+		renderer.dispose();
+	}
+	renderer = new DisplayRenderer(getDisplay(), getFont(), this, tabLength);
+	lineHeight = renderer.getLineHeight();
+	if (wordWrap) {
+		content = new WrappedContent(renderer, logicalContent);
+	}
+}
+/**
+ * Executes the action.
+ * <p>
+ *
+ * @param action one of the actions defined in ST.java
+ */
+public void invokeAction(int action) {
+	int oldColumnX, oldHScrollOffset, hScrollChange;
+	int caretLine;
+	
+	checkWidget();
+	updateCaretDirection = true;
+	switch (action) {
+		// Navigation
+		case ST.LINE_UP:
+			caretLine = doLineUp();
+			oldColumnX = columnX;
+			oldHScrollOffset = horizontalScrollOffset;
+			// explicitly go to the calculated caret line. may be different 
+			// from content.getLineAtOffset(caretOffset) when in word wrap mode
+			showCaret(caretLine);
+			// restore the original horizontal caret position
+			hScrollChange = oldHScrollOffset - horizontalScrollOffset;
+			columnX = oldColumnX + hScrollChange;
+			clearSelection(true);
+			break;
+		case ST.LINE_DOWN:
+			caretLine = doLineDown();
+			oldColumnX = columnX;
+			oldHScrollOffset = horizontalScrollOffset;
+			// explicitly go to the calculated caret line. may be different 
+			// from content.getLineAtOffset(caretOffset) when in word wrap mode
+			showCaret(caretLine);
+			// restore the original horizontal caret position
+			hScrollChange = oldHScrollOffset - horizontalScrollOffset;
+			columnX = oldColumnX + hScrollChange;
+			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, getLineCountWhole());
+			clearSelection(true);
+			break;
+		case ST.PAGE_DOWN:
+			doPageDown(false, getLineCountWhole());
+			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(getLineCountWhole());
+			break;
+		case ST.SELECT_PAGE_DOWN:
+			doSelectionPageDown(getLineCountWhole());
+			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;
+	}
+}
+/**
+ * Temporary until SWT provides this
+ */
+boolean isBidi() {
+	return IS_GTK || BidiUtil.isBidiPlatform() || isMirrored;
+}
+/**
+ * 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 isMirrored;
+}
+/**
+ * Returns whether or not the given lines are visible.
+ * <p>
+ *
+ * @return true if any of the lines is visible
+ * false if none of the lines is visible
+ */
+boolean isAreaVisible(int firstLine, int lastLine) {
+	int partialBottomIndex = getPartialBottomIndex();
+	int partialTopIndex = verticalScrollOffset / lineHeight;
+	boolean notVisible = firstLine > partialBottomIndex || lastLine < partialTopIndex;
+	return !notVisible;
+}
+/**
+ * Returns whether the widget can have only one line.
+ * <p>
+ *
+ * @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.
+ * <p>
+ *
+ * @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(ExtendedModify)) {
+			styledTextEvent = new StyledTextEvent(logicalContent);
+			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);
+				String lineText = content.getLine(lineIndex);
+				TextLayout layout = renderer.getTextLayout(lineText, lineOffset);
+				int levelStart = layout.getLevel(event.start - lineOffset);
+				int lineIndexEnd = content.getLineAtOffset(event.end);
+				if (lineIndex != lineIndexEnd) {
+					renderer.disposeTextLayout(layout);
+					lineOffset = content.getOffsetAtLine(lineIndexEnd);
+					lineText = content.getLine(lineIndexEnd);
+					layout = renderer.getTextLayout(lineText, lineOffset);
+				}
+				int levelEnd = layout.getLevel(event.end - lineOffset);
+				renderer.disposeTextLayout(layout);
+				advancing = levelStart != levelEnd;
+			}
+		}
+		content.replaceTextRange(event.start, replacedLength, event.text);
+		// set the caret position prior to sending the modify event.
+		// fixes 1GBB8NJ
+		if (updateCaret) {
+			// always update the caret location. fixes 1G8FODP
+			internalSetSelection(event.start + event.text.length(), 0, true);
+			showCaret();
+		}
+		sendModifyEvent(event);
+		if (isListening(ExtendedModify)) {
+			notifyListeners(ExtendedModify, styledTextEvent);
+		}
+	}
+}
+/** 
+ * 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.
+ * <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 paste(){
+	checkWidget();	
+	String text;
+	text = (String) getClipboardContent(DND.CLIPBOARD);
+	if (text != null && text.length() > 0) {
+		Event event = new Event();
+		event.start = selection.x;
+		event.end = selection.y;
+		event.text = getModelDelimitedText(text);
+		sendKeyEvent(event);
+	}
+}
+/**
+ * Render the specified area.  Broken out as its own method to support
+ * direct drawing.
+ * <p>
+ *
+ * @param gc GC to render on 
+ * @param startLine first line to render
+ * @param startY y pixel location to start rendering at
+ * @param renderHeight renderHeight widget area that needs to be filled with lines
+ */
+void performPaint(GC gc,int startLine,int startY, int renderHeight)	{
+	Rectangle clientArea = getClientArea();
+	Color background = getBackground();
+	
+	// Check if there is work to do. We never want to try and create 
+	// an Image with 0 width or 0 height.
+	if (clientArea.width == 0) {
+		return;
+	}
+	if (renderHeight > 0) {
+		// renderHeight will be negative when only top margin needs redrawing
+		Color foreground = getForeground();
+		int lineCount = content.getLineCount();
+		int gcStyle = isMirrored() ? SWT.RIGHT_TO_LEFT : SWT.LEFT_TO_RIGHT;
+		if (isSingleLine()) {
+			lineCount = 1;
+		}
+		int paintY, paintHeight;
+		Image lineBuffer;
+		GC lineGC;
+		boolean doubleBuffer = DOUBLE_BUFFER && lastPaintTopIndex == topIndex;
+		lastPaintTopIndex = topIndex;
+		if (doubleBuffer) {
+			paintY = 0;
+			paintHeight = renderHeight;
+			lineBuffer = new Image(getDisplay(), clientArea.width, renderHeight);
+			lineGC = new GC(lineBuffer, gcStyle);
+			lineGC.setFont(getFont());
+			lineGC.setForeground(foreground);
+			lineGC.setBackground(background);
+		} else {
+			paintY = startY;
+			paintHeight = startY + renderHeight;
+			lineBuffer = null;
+			lineGC = gc;
+		}		
+		for (int i = startLine; paintY < paintHeight && i < lineCount; i++, paintY += lineHeight) {
+			String line = content.getLine(i);
+			renderer.drawLine(line, i, paintY, lineGC, background, foreground, true);
+		}
+		if (paintY < paintHeight) {
+			lineGC.setBackground(background);
+			lineGC.fillRectangle(0, paintY, clientArea.width, paintHeight - paintY);
+		}
+		if (doubleBuffer) {
+			clearMargin(lineGC, background, clientArea, startY);
+			gc.drawImage(lineBuffer, 0, startY);
+			lineGC.dispose();
+			lineBuffer.dispose();
+		}
+	}
+	clearMargin(gc, background, clientArea, 0);
+}
+/** 
+ * 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
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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();	
+	StyledTextPrintOptions options = new StyledTextPrintOptions();
+	options.printTextForeground = true;
+	options.printTextBackground = true;
+	options.printTextFontStyle = true;
+	options.printLineBackground = true;
+	if (printer == null) {
+		SWT.error(SWT.ERROR_NULL_ARGUMENT);
+	}
+	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
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public void redraw() {
+	int itemCount;
+	
+	super.redraw();
+	itemCount = getPartialBottomIndex() - topIndex + 1;
+	lineCache.redrawReset(topIndex, itemCount, true);
+	lineCache.calculate(topIndex, itemCount);
+	setHorizontalScrollBar();
+}
+/**
+ * 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
+ */
+public void redraw(int x, int y, int width, int height, boolean all) {
+	super.redraw(x, y, width, height, all);
+	if (height > 0) {
+		int lineCount = content.getLineCount();
+		int startLine = (getTopPixel() + y) / lineHeight;
+		int endLine = startLine + Compatibility.ceil(height, lineHeight);
+		int itemCount;
+		
+		// reset all lines in the redraw rectangle
+		startLine = Math.min(startLine, lineCount);
+		itemCount = Math.min(endLine, lineCount) - startLine;
+		lineCache.reset(startLine, itemCount, true);
+		// only calculate the visible lines
+		itemCount = getPartialBottomIndex() - topIndex + 1;
+		lineCache.calculate(topIndex, itemCount);
+		setHorizontalScrollBar();
+	}
+}
+/** 
+ * Redraws a text range in the specified lines
+ * <p>
+ *
+ * @param firstLine first line to redraw at the specified offset
+ * @param offsetInFirstLine offset in firstLine to start redrawing
+ * @param lastLine last line to redraw
+ * @param endOffset offset in the last where redrawing should stop
+ * @param clearBackground true=clear the background by invalidating
+ *  the requested redraw range. 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.
+ *  false=draw the foreground directly without invalidating the 
+ * 	redraw range.
+ */
+void redrawLines(int firstLine, int offsetInFirstLine, int lastLine, int endOffset, boolean clearBackground) {
+	String line = content.getLine(firstLine);
+	int lineCount = lastLine - firstLine + 1;
+	int redrawY, redrawWidth;
+	int lineOffset = content.getOffsetAtLine(firstLine);
+	boolean fullLineRedraw;
+	Rectangle clientArea = getClientArea();
+	
+	fullLineRedraw = ((getStyle() & SWT.FULL_SELECTION) != 0 && lastLine > firstLine);
+	// if redraw range includes last character on the first line, 
+	// clear background to right widget border. fixes bug 19595.
+	if (clearBackground && endOffset - lineOffset >= line.length()) {
+		fullLineRedraw = true;
+	}	
+	TextLayout layout = renderer.getTextLayout(line, lineOffset);
+	Rectangle rect = layout.getBounds(offsetInFirstLine, Math.min(endOffset, line.length()) - 1);
+	renderer.disposeTextLayout(layout);
+	rect.x -= horizontalScrollOffset;
+	rect.intersect(clientArea);
+	redrawY = firstLine * lineHeight - verticalScrollOffset;
+	redrawWidth = fullLineRedraw ? clientArea.width - leftMargin - rightMargin : rect.width;
+	draw(rect.x, redrawY, redrawWidth, lineHeight, clearBackground);
+	
+	// redraw last line if more than one line needs redrawing 
+	if (lineCount > 1) {
+		lineOffset = content.getOffsetAtLine(lastLine);
+		int offsetInLastLine = endOffset - lineOffset;	
+		// no redraw necessary if redraw offset is 0
+		if (offsetInLastLine > 0) {
+			line = content.getLine(lastLine);
+			// if redraw range includes last character on the last line, 
+			// clear background to right widget border. fixes bug 19595.
+			if (clearBackground && offsetInLastLine >= line.length()) {
+				fullLineRedraw = true;
+			}
+			line = content.getLine(lastLine);
+			layout = renderer.getTextLayout(line, lineOffset);
+			rect = layout.getBounds(0, offsetInLastLine - 1);
+			renderer.disposeTextLayout(layout);
+			rect.x -= horizontalScrollOffset;
+			rect.intersect(clientArea);
+			redrawY = lastLine * lineHeight - verticalScrollOffset;
+			redrawWidth = fullLineRedraw ? clientArea.width - leftMargin - rightMargin : rect.width;
+			draw(rect.x, redrawY, redrawWidth, lineHeight, clearBackground);
+		}
+	}
+}
+/**
+ * Fixes the widget to display a text change.
+ * Bit blitting and redrawing is done as necessary.
+ * <p>
+ *
+ * @param y y location of the text change
+ * @param newLineCount number of new lines.
+ * @param replacedLineCount number of replaced lines.
+ */
+void redrawMultiLineChange(int y, int newLineCount, int replacedLineCount) {
+	Rectangle clientArea = getClientArea();
+	int lineCount = newLineCount - replacedLineCount;
+	int sourceY;
+	int destinationY;
+		
+	if (lineCount > 0) {
+		sourceY = Math.max(0, y + lineHeight);
+		destinationY = sourceY + lineCount * lineHeight;
+	} 
+	else {
+		destinationY = Math.max(0, y + lineHeight);
+		sourceY = destinationY - lineCount * lineHeight;
+	}	
+	scroll(
+		0, destinationY,			// destination x, y
+		0, sourceY,					// source x, y
+		clientArea.width, clientArea.height, true);
+	// Always redrawing causes the bottom line to flash when a line is
+	// deleted. This is because SWT merges the paint area of the scroll
+	// with the paint area of the redraw call below.
+	// To prevent this we could call update after the scroll. However,
+	// adding update can cause even more flash if the client does other 
+	// redraw/update calls (ie. for syntax highlighting).
+	// We could also redraw only when a line has been added or when 
+	// contents has been added to a line. This would require getting 
+	// line index info from the content and is not worth the trouble
+	// (the flash is only on the bottom line and minor).
+	// Specifying the NO_MERGE_PAINTS style bit prevents the merged 
+	// redraw but could cause flash/slowness elsewhere.
+	if (y + lineHeight > 0 && y <= clientArea.height) {
+		// redraw first changed line in case a line was split/joined
+		super.redraw(0, y, clientArea.width, lineHeight, true);
+	}
+	if (newLineCount > 0) {
+		int redrawStartY = y + lineHeight;
+		int redrawHeight = newLineCount * lineHeight;
+		
+		if (redrawStartY + redrawHeight > 0 && redrawStartY <= clientArea.height) {
+			// display new text
+			super.redraw(0, redrawStartY, clientArea.width, redrawHeight, true);
+		}
+	}
+}
+/** 
+ * Redraws the specified text range.
+ * <p>
+ *
+ * @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();
+	int firstLine;
+	int lastLine;
+	
+	if (start > end || start < 0 || end > contentLength) {
+		SWT.error(SWT.ERROR_INVALID_RANGE);
+	}	
+	firstLine = content.getLineAtOffset(start);
+	lastLine = content.getLineAtOffset(end);
+	// reset all affected lines but let the redraw recalculate only 
+	// those that are visible.
+	lineCache.reset(firstLine, lastLine - firstLine + 1, true);
+	internalRedrawRange(start, length, clearBackground);
+}
+/**
+ * Removes the specified bidirectional segment listener.
+ * <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>
+ * @since 2.0
+ */
+public void removeBidiSegmentListener(BidiSegmentListener listener) {
+	checkWidget();
+	if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+	removeListener(LineGetSegments, listener);	
+}
+/**
+ * Removes the specified extended modify listener.
+ * <p>
+ *
+ * @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 removeExtendedModifyListener(ExtendedModifyListener extendedModifyListener) {
+	checkWidget();
+	if (extendedModifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+	removeListener(ExtendedModify, extendedModifyListener);	
+}
+/**
+ * Removes the specified line background listener.
+ * <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>
+ */
+public void removeLineBackgroundListener(LineBackgroundListener listener) {
+	checkWidget();
+	if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+	removeListener(LineGetBackground, listener);	
+	// use default line styler if last user line styler was removed.
+	if (!isListening(LineGetBackground) && userLineBackground) {
+		StyledTextListener typedListener = new StyledTextListener(defaultLineStyler);
+		addListener(LineGetBackground, typedListener);	
+		userLineBackground = false;
+	}
+}
+/**
+ * Removes the specified line style listener.
+ * <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>
+ */
+public void removeLineStyleListener(LineStyleListener listener) {
+	checkWidget();
+	if (listener == null) {
+		SWT.error(SWT.ERROR_NULL_ARGUMENT);
+	}
+	removeListener(LineGetStyle, listener);	
+	// use default line styler if last user line styler was removed. Fixes 1G7B1X2
+	if (!isListening(LineGetStyle) && userLineStyle) {
+		StyledTextListener typedListener = new StyledTextListener(defaultLineStyler);
+		addListener(LineGetStyle, typedListener);	
+		userLineStyle = false;
+	}
+}
+/**
+ * Removes the specified modify listener.
+ * <p>
+ *
+ * @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 removeModifyListener(ModifyListener modifyListener) {
+	checkWidget();
+	if (modifyListener == null) {
+		SWT.error(SWT.ERROR_NULL_ARGUMENT);
+	}
+	removeListener(SWT.Modify, modifyListener);	
+}
+/**
+ * Removes the specified selection listener.
+ * <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>
+ */
+public void removeSelectionListener(SelectionListener listener) {
+	checkWidget();
+	if (listener == null) {
+		SWT.error(SWT.ERROR_NULL_ARGUMENT);
+	}
+	removeListener(SWT.Selection, listener);	
+}
+/**
+ * Removes the specified verify listener.
+ * <p>
+ *
+ * @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 removeVerifyListener(VerifyListener verifyListener) {
+	checkWidget();
+	if (verifyListener == null) {
+		SWT.error(SWT.ERROR_NULL_ARGUMENT);
+	}
+	removeListener(SWT.Verify, verifyListener);	
+}
+/**
+ * Removes the specified key verify listener.
+ * <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>
+ */
+public void removeVerifyKeyListener(VerifyKeyListener listener) {
+	if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+	removeListener(VerifyKey, 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>
+ * 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 <= offset <= getCharCount())</li> 
+ *   <li>ERROR_NULL_ARGUMENT when string is null</li>
+ * </ul>
+ * @since 2.0
+ */
+public void replaceStyleRanges(int start, int length, StyleRange[] ranges) {
+	checkWidget();
+	if (userLineStyle) {
+		return;
+	}
+ 	if (ranges == null) {
+ 		SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ 	}
+ 	if (ranges.length == 0) {
+ 		setStyleRange(new StyleRange(start, length, null, null));
+ 		return;
+ 	}
+	int end = start + length;
+	if (start > end || start < 0 || end > getCharCount()) {
+		SWT.error(SWT.ERROR_INVALID_RANGE);
+	}	
+	int firstLine = content.getLineAtOffset(start);
+	int lastLine = content.getLineAtOffset(end);
+
+	defaultLineStyler.replaceStyleRanges(start, length, ranges);
+	lineCache.reset(firstLine, lastLine - firstLine + 1, true);
+
+	// if the area is not visible, there is no need to redraw
+	if (isAreaVisible(firstLine, lastLine)) {
+		int redrawY = firstLine * lineHeight - verticalScrollOffset;
+		int redrawStopY = (lastLine + 1) * lineHeight - verticalScrollOffset;		
+		draw(0, redrawY, getClientArea().width, redrawStopY - redrawY, true);
+	}
+
+	// make sure that the caret is positioned correctly.
+	// caret location may change if font style changes.
+	// fixes 1G8FODP
+	setCaretLocation();
+}
+/**
+ * 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:
+ * <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>
+ * </p>
+ *
+ * @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 <= offset <= 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();
+	int contentLength = getCharCount();
+	int end = start + length;
+	Event event = new Event();
+	
+	if (start > end || start < 0 || end > contentLength) {
+		SWT.error(SWT.ERROR_INVALID_RANGE);
+	}	
+	if (text == null) {
+		SWT.error(SWT.ERROR_NULL_ARGUMENT);
+	}
+	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();
+	caretOffset = 0;
+	topIndex = 0;
+	topOffset = 0;
+	verticalScrollOffset = 0;
+	horizontalScrollOffset = 0;	
+	resetSelection();
+	// discard any styles that may have been set by creating a 
+	// new default line styler
+	if (defaultLineStyler != null) {
+		removeLineBackgroundListener(defaultLineStyler);
+		removeLineStyleListener(defaultLineStyler);
+		installDefaultLineStyler();
+	}	
+	calculateContentWidth();
+	if (verticalBar != null) {
+		verticalBar.setSelection(0);
+	}
+	if (horizontalBar != null) {
+		horizontalBar.setSelection(0);	
+	}
+	setScrollBars();
+	setCaretLocation();
+	super.redraw();
+}
+/**
+ * Resets the selection.
+ */
+void resetSelection() {
+	selection.x = selection.y = caretOffset;
+	selectionAnchor = -1;
+}
+/**
+ * Scrolls the widget horizontally.
+ * <p>
+ *
+ * @param pixels number of pixels to scroll, > 0 = scroll left,
+ * 	< 0 scroll right
+ */
+void scrollHorizontal(int pixels) {
+	Rectangle clientArea;
+	
+	if (pixels == 0) {
+		return;
+	}
+	clientArea = getClientArea();
+	if (pixels > 0) {
+		int sourceX = leftMargin + pixels;
+		int scrollWidth = clientArea.width - sourceX - rightMargin;
+		int scrollHeight = clientArea.height - topMargin - bottomMargin;
+		scroll(
+			leftMargin, topMargin, 						// destination x, y
+			sourceX, topMargin,							// source x, y
+			scrollWidth, scrollHeight, true);
+		if (sourceX > scrollWidth) {
+			// redraw from end of scrolled area to beginning of scroll 
+			// invalidated area
+			super.redraw(
+				leftMargin + scrollWidth, topMargin, 
+				pixels - scrollWidth, scrollHeight, true);
+		}
+	}
+	else {
+		int destinationX = leftMargin - pixels;
+		int scrollWidth = clientArea.width - destinationX - rightMargin;
+		int scrollHeight = clientArea.height - topMargin - bottomMargin;
+		scroll(
+			destinationX, topMargin,					// destination x, y
+			leftMargin, topMargin,						// source x, y
+			scrollWidth, scrollHeight, true);
+		if (destinationX > scrollWidth) {
+			// redraw from end of scroll invalidated area to scroll 
+			// destination
+			super.redraw(
+				leftMargin + scrollWidth, topMargin, 
+				-pixels - scrollWidth, scrollHeight, true);	
+		}
+	}
+	horizontalScrollOffset += pixels;
+	int oldColumnX = columnX - pixels;
+	setCaretLocation();
+	// restore the original horizontal caret index
+	columnX = oldColumnX;
+}
+/**
+ * Scrolls the widget horizontally and adjust the horizontal scroll
+ * bar to reflect the new horizontal offset..
+ * <p>
+ *
+ * @param pixels number of pixels to scroll, > 0 = scroll left,
+ * 	< 0 scroll right
+ * @return
+ *	true=the widget was scrolled 
+ *	false=the widget was not scrolled, the given offset is not valid.
+ */
+boolean scrollHorizontalBar(int pixels) {
+	if (pixels == 0) {
+		return false;
+	}
+	ScrollBar horizontalBar = getHorizontalBar();
+	if (horizontalBar != null) {
+		horizontalBar.setSelection(horizontalScrollOffset + pixels);
+	}
+	scrollHorizontal(pixels);
+	return true;
+}
+/** 
+ * Selects all the text.
+ * <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 selectAll() {
+	checkWidget();
+	setSelection(0, Math.max(getCharCount(),0));
+}
+/**
+ * Replaces/inserts text as defined by the event.
+ * <p>
+ *
+ * @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);
+	}
+}
+void sendModifyEvent(Event event) {
+	Accessible accessible = getAccessible();
+	if (event.text.length() == 0) {
+		accessible.textChanged(ACC.TEXT_DELETE, event.start, event.end - event.start);
+	} else {
+		if (event.start == event.end) {
+			accessible.textChanged(ACC.TEXT_INSERT, event.start, event.text.length());
+		} else {
+			accessible.textChanged(ACC.TEXT_DELETE, event.start, event.end - event.start);
+			accessible.textChanged(ACC.TEXT_INSERT, event.start, event.text.length());	
+		}
+	}
+	notifyListeners(SWT.Modify, event);
+}
+/**
+ * Sends the specified selection event.
+ */
+void sendSelectionEvent() {
+	getAccessible().textSelectionChanged();
+	Event event = new Event();
+	event.x = selection.x;
+	event.y = selection.y;
+	notifyListeners(SWT.Selection, event);
+}
+/**
+ * Sets whether the widget wraps lines.
+ * 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 (wrap != wordWrap) {
+		ScrollBar horizontalBar = getHorizontalBar();
+		
+		wordWrap = wrap;
+		if (wordWrap) {
+			logicalContent = content;
+			content = new WrappedContent(renderer, logicalContent);
+		}
+		else {
+			content = logicalContent;
+		}
+		calculateContentWidth();
+		horizontalScrollOffset = 0;
+		if (horizontalBar != null) {
+			horizontalBar.setVisible(!wordWrap);
+		}
+		setScrollBars();
+		setCaretLocation();
+		super.redraw();		
+	}
+}
+/**
+ * Sets the receiver's caret.  Set the caret's height and location.
+ * 
+ * </p>
+ * @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>
+ */
+public void setCaret(Caret caret) {
+	checkWidget ();
+	super.setCaret(caret);
+	caretDirection = SWT.NULL; 
+	if (caret != null) {
+		setCaretLocation();
+	}
+}
+/**
+ * @see org.eclipse.swt.widgets.Control#setBackground
+ */
+public void setBackground(Color color) {
+	checkWidget();
+	background = color;
+	super.setBackground(getBackground());
+	redraw();
+}
+/**
+ * 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>
+ * <p>
+ * @deprecated use BidiSegmentListener instead.
+ * </p>
+ */
+public void setBidiColoring(boolean mode) {
+	checkWidget();
+	bidiColoring = mode;
+}
+void setCaretLocation(int newCaretX, int line, int direction) {
+	Caret caret = getCaret();
+	if (caret != null) {
+		boolean updateImage = caret == defaultCaret;
+		int imageDirection = direction;
+		if (isMirrored()) {
+			if (imageDirection == SWT.LEFT) {
+				imageDirection = SWT.RIGHT;
+			} else if (imageDirection == SWT.RIGHT) {
+				imageDirection = SWT.LEFT;
+			}
+		}
+		if (updateImage && imageDirection == SWT.RIGHT) {
+			newCaretX -= (caret.getSize().x - 1);
+		}
+		int newCaretY = line * lineHeight - verticalScrollOffset + topMargin;
+		caret.setLocation(newCaretX, newCaretY);
+		getAccessible().textCaretMoved(getCaretOffset());
+		if (direction != caretDirection) {
+			caretDirection = direction;
+			if (updateImage) {
+				if (imageDirection == SWT.DEFAULT) {
+					defaultCaret.setImage(null);
+				} else if (imageDirection == SWT.LEFT) {
+					defaultCaret.setImage(leftCaretBitmap);
+				} else if (imageDirection == SWT.RIGHT) {
+					defaultCaret.setImage(rightCaretBitmap);
+				}
+			}
+			caret.setSize(caret.getSize().x, lineHeight);
+			if (caretDirection == SWT.LEFT) {
+				BidiUtil.setKeyboardLanguage(BidiUtil.KEYBOARD_NON_BIDI);
+			} else if (caretDirection == SWT.RIGHT) {
+				BidiUtil.setKeyboardLanguage(BidiUtil.KEYBOARD_BIDI);
+			}
+		}
+	}
+	columnX = newCaretX;
+}
+/**
+ * Moves the Caret to the current caret offset.
+ */
+void setCaretLocation() {
+	int lineIndex = getCaretLine();
+	String line = content.getLine(lineIndex);
+	int lineOffset = content.getOffsetAtLine(lineIndex);
+	int offsetInLine = caretOffset - lineOffset;
+	int newCaretX = getXAtOffset(line, lineIndex, offsetInLine);
+	setCaretLocation(newCaretX, lineIndex, getCaretDirection());
+}
+/**
+ * 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 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 setCaretOffset(int offset) {
+	checkWidget();
+	int length = getCharCount();
+				
+	if (length > 0 && offset != caretOffset) {
+		if (offset < 0) {
+			caretOffset = 0;
+		}
+		else
+		if (offset > length) {
+			caretOffset = 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);
+			}
+			caretOffset = offset;
+		}
+		// clear the selection if the caret is moved.
+		// don't notify listeners about the selection change.
+		clearSelection(false);
+	}
+	// always update the caret location. fixes 1G8FODP
+	setCaretLocation();
+}	
+/**
+ * Copies the specified text range to the clipboard.  The text will be placed
+ * in the clipboard in plain text format and RTF format.
+ * <p>
+ *
+ * @param start start index of the text
+ * @param length length of text to place in clipboard
+ * 
+ * @exception SWTError, see Clipboard.setContents
+ * @see org.eclipse.swt.dnd.Clipboard#setContents
+ */
+void setClipboardContent(int start, int length, int clipboardType) throws SWTError {
+	if (clipboardType == DND.SELECTION_CLIPBOARD && !(IS_MOTIF || 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.
+ * <p>
+ *
+ * @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);
+	}	
+	logicalContent = newContent;
+	if (wordWrap) {
+		content = new WrappedContent(renderer, logicalContent);
+	}
+	else {
+		content = logicalContent;
+	}
+	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 org.eclipse.swt.widgets.Control#setCursor
+ */
+public void setCursor (Cursor cursor) {
+	if (cursor == null) {
+		super.setCursor(ibeamCursor);
+	} else {
+		super.setCursor(cursor);
+	}
+}
+/** 
+ * Sets whether the widget implements double click mouse behavior.
+ * </p>
+ *
+ * @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;
+}
+/**
+ * Sets whether the widget content can be edited.
+ * </p>
+ *
+ * @param editable if true content can be edited, if false content can not be 
+ * 	edited
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public void setEditable(boolean editable) {
+	checkWidget();
+	this.editable = editable;
+}
+/**
+ * Sets a new font to render text with.
+ * <p>
+ * <b>NOTE:</b> Italic fonts are not supported unless they have no overhang
+ * and the same baseline as regular fonts.
+ * </p>
+ *
+ * @param font new font
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public void setFont(Font font) {
+	checkWidget();
+	int oldLineHeight = lineHeight;
+	
+	super.setFont(font);	
+	initializeRenderer();
+	// keep the same top line visible. fixes 5815
+	if (lineHeight != oldLineHeight) {
+		setVerticalScrollOffset(verticalScrollOffset * lineHeight / oldLineHeight, true);
+		claimBottomFreeSpace();
+	}
+	calculateContentWidth();
+	calculateScrollBars();
+	if (isBidiCaret()) createCaretBitmaps();
+	caretDirection = SWT.NULL;
+	// always set the caret location. Fixes 6685
+	setCaretLocation();
+	super.redraw();
+}
+/**
+ * @see org.eclipse.swt.widgets.Control#setForeground
+ */
+public void setForeground(Color color) {
+	checkWidget();
+	foreground = color;
+	super.setForeground(getForeground());
+	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 > 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();
+	int clientAreaWidth = getClientArea().width;
+	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 = lineCache.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);
+		}
+	}
+	scrollHorizontalBar(offset - horizontalScrollOffset);
+}
+/** 
+ * Sets the horizontal pixel offset relative to the start of the line.
+ * Do nothing if there is no text set.
+ * <p>
+ * <b>NOTE:</b> The horizontal pixel offset is reset to 0 when new text 
+ * is set in the widget.
+ * </p>
+ *
+ * @param pixel horizontal pixel 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();
+	int clientAreaWidth = getClientArea().width;
+	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 = lineCache.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);
+		}
+	}
+	scrollHorizontalBar(pixel - horizontalScrollOffset);
+}
+/**
+ * Adjusts the maximum and the page size of the horizontal scroll bar 
+ * to reflect content width changes.
+ */
+void setHorizontalScrollBar() {
+	ScrollBar horizontalBar = getHorizontalBar();
+	
+	if (horizontalBar != null && horizontalBar.getVisible()) {
+		final int INACTIVE = 1;
+		Rectangle clientArea = getClientArea();
+		// only set the real values if the scroll bar can be used 
+		// (ie. because the thumb size is less than the scroll maximum)
+		// avoids flashing on Motif, fixes 1G7RE1J and 1G5SE92
+		if (clientArea.width < lineCache.getWidth()) {
+			horizontalBar.setValues(
+				horizontalBar.getSelection(),
+				horizontalBar.getMinimum(),
+				lineCache.getWidth(),							// maximum
+				clientArea.width - leftMargin - rightMargin,	// thumb size
+				horizontalBar.getIncrement(),
+				clientArea.width - leftMargin - rightMargin);	// page size
+		}
+		else 
+		if (horizontalBar.getThumb() != INACTIVE || horizontalBar.getMaximum() != INACTIVE) {
+			horizontalBar.setValues(
+				horizontalBar.getSelection(),
+				horizontalBar.getMinimum(),
+				INACTIVE,
+				INACTIVE,
+				horizontalBar.getIncrement(),
+				INACTIVE);
+		}
+	}
+}
+/** 
+ * Sets the background color of the specified lines.
+ * 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. Should not be called if a LineBackgroundListener 
+ * has been set since the listener maintains the line backgrounds.
+ * <p>
+ * Line background colors 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 
+ * background colors 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 
+ * background takes precedence and the color of the bottom line is deleted. 
+ * For all other text changes line background colors 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();
+	int partialBottomIndex = getPartialBottomIndex();
+	
+	// this API can not be used if the client is providing the line background
+	if (userLineBackground) {
+		return;
+	}
+	if (startLine < 0 || startLine + lineCount > logicalContent.getLineCount()) {
+		SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+	} 
+	defaultLineStyler.setLineBackground(startLine, lineCount, background);
+	// do nothing if redraw range is completely invisible	
+	if (startLine > partialBottomIndex || startLine + lineCount - 1 < topIndex) {
+		return;
+	}
+	// only redraw visible lines
+	if (startLine < topIndex) {
+		lineCount -= topIndex - startLine;
+		startLine = topIndex;
+	}
+	if (startLine + lineCount - 1 > partialBottomIndex) {
+		lineCount = partialBottomIndex - startLine + 1;
+	}
+	startLine -= topIndex;
+	super.redraw(
+		leftMargin, startLine * lineHeight + topMargin, 
+		getClientArea().width - leftMargin - rightMargin, lineCount * lineHeight, true);
+}
+/**
+ * Flips selection anchor based on word selection direction.
+ */
+void setMouseWordSelectionAnchor() {
+	if (mouseDoubleClick) {
+		if (caretOffset < doubleClickSelection.x) {
+			selectionAnchor = doubleClickSelection.y;
+		}
+		else if (caretOffset > doubleClickSelection.y) {
+			selectionAnchor = doubleClickSelection.x;
+		}
+	}
+}
+/**
+ * Sets the orientation of the receiver, which must be one
+ * of the constants <code>SWT.LEFT_TO_RIGHT</code> or <code>SWT.RIGHT_TO_LEFT</code>.
+ * <p>
+ *
+ * @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
+ */
+public void setOrientation(int orientation) {
+	if ((orientation & (SWT.RIGHT_TO_LEFT | SWT.LEFT_TO_RIGHT)) == 0) { 
+		return;
+	}
+	if ((orientation & SWT.RIGHT_TO_LEFT) != 0 && (orientation & SWT.LEFT_TO_RIGHT) != 0) {
+		return;	
+	}
+	if ((orientation & SWT.RIGHT_TO_LEFT) != 0 && isMirrored()) {
+		return;	
+	} 
+	if ((orientation & SWT.LEFT_TO_RIGHT) != 0 && !isMirrored()) {
+		return;
+	}
+	if (!BidiUtil.setOrientation(handle, orientation)) {
+		return;
+	}
+	isMirrored = (orientation & SWT.RIGHT_TO_LEFT) != 0;
+	initializeRenderer();
+	caretDirection = SWT.NULL;
+	setCaretLocation();
+	keyActionMap.clear();
+	createKeyBindings();
+	super.redraw();
+}
+/**
+ * Adjusts the maximum and the page size of the scroll bars to 
+ * reflect content width/length changes.
+ */
+void setScrollBars() {
+	ScrollBar verticalBar = getVerticalBar();
+	
+	if (verticalBar != null) {
+		Rectangle clientArea = getClientArea();
+		final int INACTIVE = 1;
+		int maximum = content.getLineCount() * getVerticalIncrement();
+		
+		// only set the real values if the scroll bar can be used 
+		// (ie. because the thumb size is less than the scroll maximum)
+		// avoids flashing on Motif, fixes 1G7RE1J and 1G5SE92
+		if (clientArea.height < maximum) {
+			verticalBar.setValues(
+				verticalBar.getSelection(),
+				verticalBar.getMinimum(),
+				maximum,
+				clientArea.height,				// thumb size
+				verticalBar.getIncrement(),
+				clientArea.height);				// page size
+		}
+		else
+		if (verticalBar.getThumb() != INACTIVE || verticalBar.getMaximum() != INACTIVE) {
+			verticalBar.setValues(
+				verticalBar.getSelection(),
+				verticalBar.getMinimum(),
+				INACTIVE,
+				INACTIVE,
+				verticalBar.getIncrement(),
+				INACTIVE);
+		}		
+	}
+	setHorizontalScrollBar();
+}
+/** 
+ * Sets the selection to the given position and scrolls it into view.  Equivalent to setSelection(start,start).
+ * <p>
+ *
+ * @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 > 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;
+	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.
+ *
+ * @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;
+	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 > 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) {
+	// checkWidget test done in setSelectionRange
+	setSelectionRange(start, end - start);
+	showSelection();
+}
+/** 
+ * Sets the selection. The new selection may not be visible. Call showSelection to scroll 
+ * the selection into view. A negative length places the caret at the visual start of the 
+ * selection. <p>
+ *
+ * @param start offset of the first selected character
+ * @param length number of characters to select
+ * 
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a 
+ * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
+ * </ul>
+ */
+public void setSelectionRange(int start, int length) {
+	checkWidget();
+	int contentLength = getCharCount();
+	start = Math.max(0, Math.min (start, contentLength));
+	int end = start + length;
+	if (end < 0) {
+		length = -start;
+	} else {
+		if (end > contentLength) length = contentLength - start;
+	}
+	if (isLineDelimiter(start) || isLineDelimiter(start + length)) {
+		// the start offset or end offset of the selection range is inside a 
+		// multi byte line delimiter. This is an illegal operation and an exception 
+		// is thrown. Fixes 1GDKK3R
+		SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+	}					
+	internalSetSelection(start, length, false);
+	// always update the caret location. fixes 1G8FODP
+	setCaretLocation();
+}
+/** 
+ * Sets the selection. 
+ * The new selection may not be visible. Call showSelection to scroll 
+ * the selection into view.
+ * <p>
+ *
+ * @param start offset of the first selected character, start >= 0 must be true.
+ * @param length number of characters to select, 0 <= start + length 
+ * 	<= getCharCount() must be true. 
+ * 	A negative length places the caret at the selection start.
+ * @param sendEvent a Selection event is sent when set to true and when 
+ * 	the selection is reset.
+ */
+void internalSetSelection(int start, int length, boolean sendEvent) {
+	int end = start + length;
+	
+	if (start > end) {
+		int temp = end;
+		end = start;
+		start = temp;
+	}
+	// is the selection range different or is the selection direction 
+	// different?
+	if (selection.x != start || selection.y != end || 
+		(length > 0 && selectionAnchor != selection.x) || 
+		(length < 0 && selectionAnchor != selection.y)) {
+		clearSelection(sendEvent);
+		if (length < 0) {
+			selectionAnchor = selection.y = end;
+			caretOffset = selection.x = start;
+		}
+		else {
+			selectionAnchor = selection.x = start;
+			caretOffset = selection.y = end;
+		}
+		internalRedrawRange(selection.x, selection.y - selection.x, true);
+	}
+}
+/** 
+ * Adds the specified style. 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>
+ * 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 (> getCharCount())</li> 
+ * </ul>
+ */
+public void setStyleRange(StyleRange range) {
+	checkWidget();
+	
+	// this API can not be used if the client is providing the line styles
+	if (userLineStyle) {
+		return;
+	}
+ 	// check the range, make sure it falls within the range of the text 
+	if (range != null && range.start + range.length > content.getCharCount()) {
+		SWT.error(SWT.ERROR_INVALID_RANGE);
+	} 	
+	defaultLineStyler.setStyleRange(range);
+	if (range != null) {
+		int firstLine = content.getLineAtOffset(range.start);
+		int lastLine = content.getLineAtOffset(range.start + range.length);
+		lineCache.reset(firstLine, lastLine - firstLine + 1, true);
+
+		// if the style is not visible, there is no need to redraw
+		if (isAreaVisible(firstLine, lastLine)) {
+			int redrawY = firstLine * lineHeight - verticalScrollOffset;
+			int redrawStopY = (lastLine + 1) * lineHeight - verticalScrollOffset;		
+			draw(0, redrawY, getClientArea().width, redrawStopY - redrawY, true);
+		}
+	} else {
+		// clearing all styles
+		lineCache.reset(0, content.getLineCount(), false);
+		redraw();
+	}
+	
+	// make sure that the caret is positioned correctly.
+	// caret location may change if font style changes.
+	// fixes 1G8FODP
+	setCaretLocation();
+}
+/** 
+ * 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>
+ * 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 listener is null</li>
+ *    <li>ERROR_INVALID_RANGE when the last of the style ranges is outside the valid range (> getCharCount())</li> 
+ * </ul>
+ */
+public void setStyleRanges(StyleRange[] ranges) {
+	checkWidget();
+	// this API can not be used if the client is providing the line styles
+	if (userLineStyle) {
+		return;
+	}
+ 	if (ranges == null) {
+ 		SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ 	}
+ 	// check the last range, make sure it falls within the range of the
+ 	// current text 
+ 	if (ranges.length != 0) {
+ 		StyleRange last = ranges[ranges.length-1];
+ 		int lastEnd = last.start + last.length;
+		int firstLine = content.getLineAtOffset(ranges[0].start);
+		int lastLine;
+		if (lastEnd > content.getCharCount()) {
+			SWT.error(SWT.ERROR_INVALID_RANGE);
+		} 	
+		lastLine = content.getLineAtOffset(lastEnd);
+		// reset all lines affected by the style change
+		lineCache.reset(firstLine, lastLine - firstLine + 1, true);
+ 	}
+ 	else {
+		// reset all lines
+		lineCache.reset(0, content.getLineCount(), false);
+ 	}
+	defaultLineStyler.setStyleRanges(ranges);
+	redraw(); // should only redraw affected area to avoid flashing
+	// make sure that the caret is positioned correctly.
+	// caret location may change if font style changes.
+	// fixes 1G8FODP
+	setCaretLocation();
+}
+/** 
+ * Sets the tab width. 
+ * <p>
+ *
+ * @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>
+ */
+public void setTabs(int tabs) {
+	checkWidget();	
+	tabLength = tabs;
+	renderer.setTabLength(tabLength);
+	if (caretOffset > 0) {
+		caretOffset = 0;
+		showCaret();
+		clearSelection(false);
+	}
+	// reset all line widths when the tab width changes
+	lineCache.reset(0, content.getLineCount(), false);
+	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();
+	Event event = new Event();
+	
+	if (text == null) {
+		SWT.error(SWT.ERROR_NULL_ARGUMENT);
+	}
+	event.start = 0;
+	event.end = getCharCount();
+	event.text = text;
+	event.doit = true;	
+	notifyListeners(SWT.Verify, event);
+	if (event.doit) {
+		StyledTextEvent styledTextEvent = null;
+		
+		if (isListening(ExtendedModify)) {		
+			styledTextEvent = new StyledTextEvent(logicalContent);
+			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);
+		sendModifyEvent(event);	
+		if (styledTextEvent != null) {
+			notifyListeners(ExtendedModify, styledTextEvent);
+		}
+	}
+}
+/**
+ * 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();
+	int lineCount = logicalContent.getLineCount();
+	int pageSize = Math.max(1, Math.min(lineCount, getLineCountWhole()));
+	
+	if (getCharCount() == 0) {
+		return;
+	}	
+	if (topIndex < 0) {
+		topIndex = 0;
+	}
+	else 
+	if (topIndex > lineCount - pageSize) {
+		topIndex = lineCount - pageSize;
+	}
+	if (wordWrap) {
+		int logicalLineOffset = logicalContent.getOffsetAtLine(topIndex);
+		topIndex = content.getLineAtOffset(logicalLineOffset);
+	}
+	setVerticalScrollOffset(topIndex * getVerticalIncrement(), true);
+}
+/**
+ * Sets the top pixel offset. Do nothing if there is no text set.
+ * <p>
+ * The top pixel offset is the vertical pixel offset of the widget. The
+ * widget is scrolled so that the given pixel position is at the top.
+ * The top index is adjusted to the corresponding top line.
+ * Note: The top pixel is reset to 0 when new text is set in the widget.
+ * </p>
+ *
+ * @param pixel new top pixel 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();
+	int lineCount =content.getLineCount();
+	int height = getClientArea().height;
+	int maxTopPixel = Math.max(0, lineCount * getVerticalIncrement() - height);
+	
+	if (getCharCount() == 0) {
+		return;
+	}	
+	if (pixel < 0) {
+		pixel = 0;
+	}
+	else 
+	if (pixel > maxTopPixel) {
+		pixel = maxTopPixel;
+	}
+	setVerticalScrollOffset(pixel, true);
+}
+/**
+ * Scrolls the widget vertically.
+ * <p>
+ *
+ * @param pixelOffset 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, the given offset is not valid.
+ */
+boolean setVerticalScrollOffset(int pixelOffset, boolean adjustScrollBar) {
+	Rectangle clientArea;
+	ScrollBar verticalBar = getVerticalBar();
+	
+	if (pixelOffset == verticalScrollOffset) {
+		return false;
+	}
+	if (verticalBar != null && adjustScrollBar) {
+		verticalBar.setSelection(pixelOffset);
+	}
+	clientArea = getClientArea();
+	scroll(
+		0, 0, 									// destination x, y
+		0, pixelOffset - verticalScrollOffset,	// source x, y
+		clientArea.width, clientArea.height, true);
+
+	verticalScrollOffset = pixelOffset;
+	calculateTopIndex();
+	int oldColumnX = columnX;
+	setCaretLocation();
+	// restore the original horizontal caret index
+	columnX = oldColumnX;
+	return true;
+}
+/**
+ * Scrolls the specified location into view.
+ * <p>
+ * 
+ * @param x the x coordinate that should be made visible.
+ * @param line the line that should be made visible. Relative to the
+ *	first line in the document.
+ * @return 
+ *	true=the widget was scrolled to make the specified location visible. 
+ *	false=the specified location is already visible, the widget was 
+ *	not scrolled. 	
+ */
+boolean showLocation(int x, int line) {
+	int clientAreaWidth = getClientArea().width - leftMargin;
+	int verticalIncrement = getVerticalIncrement();
+	int horizontalIncrement = clientAreaWidth / 4;
+	boolean scrolled = false;		
+	
+	if (x < leftMargin) {
+		// always make 1/4 of a page visible
+		x = Math.max(horizontalScrollOffset * -1, x - horizontalIncrement);	
+		scrolled = scrollHorizontalBar(x);
+	}
+	else 
+	if (x >= clientAreaWidth) {
+		// always make 1/4 of a page visible
+		x = Math.min(lineCache.getWidth() - horizontalScrollOffset, x + horizontalIncrement);
+		scrolled = scrollHorizontalBar(x - clientAreaWidth);
+	}
+	if (line < topIndex) {
+		scrolled = setVerticalScrollOffset(line * verticalIncrement, true);
+	}
+	else
+	if (line > getBottomIndex()) {
+		scrolled = setVerticalScrollOffset((line + 1) * verticalIncrement - getClientArea().height, true);
+	}
+	return scrolled;
+}
+/**
+ * Sets the caret location and scrolls the caret offset into view.
+ */
+void showCaret() {
+	int caretLine = content.getLineAtOffset(caretOffset);
+	
+	showCaret(caretLine);
+}
+/**
+ * Sets the caret location and scrolls the caret offset into view.
+ */
+void showCaret(int caretLine) {
+	int lineOffset = content.getOffsetAtLine(caretLine);
+	String line = content.getLine(caretLine);
+	int offsetInLine = caretOffset - lineOffset;
+	int newCaretX = getXAtOffset(line, caretLine, offsetInLine);	
+	boolean scrolled = showLocation(newCaretX, caretLine);
+	boolean setWrapCaretLocation = false;
+	Caret caret = getCaret();
+
+	if (wordWrap && caret != null) {
+		int caretY = caret.getLocation().y;
+		if ((caretY + verticalScrollOffset) / getVerticalIncrement() - 1 != caretLine) {
+			setWrapCaretLocation = true;
+		}
+	}
+	if (!scrolled || setWrapCaretLocation) {
+		// set the caret location if a scroll operation did not set it (as a 
+		// sideeffect of scrolling) or when in word wrap mode and the caret 
+		// line was explicitly specified (i.e., because getWrapCaretLine does 
+		// not return the desired line causing scrolling to not set it correctly)
+		setCaretLocation(newCaretX, caretLine, getCaretDirection());
+	}
+}
+/**
+ * Scrolls the specified offset into view.
+ * <p>
+ *
+ * @param offset offset that should be scolled into view
+ */
+void showOffset(int offset) {
+	int line = content.getLineAtOffset(offset);
+	int lineOffset = content.getOffsetAtLine(line);
+	int offsetInLine = offset - lineOffset;
+	String lineText = content.getLine(line);
+	int xAtOffset = getXAtOffset(lineText, line, offsetInLine);
+	
+	showLocation(xAtOffset, line);	
+}
+/**
+/**
+ * Scrolls the selection into view.  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();
+	boolean selectionFits;
+	int startOffset, startLine, startX, endOffset, endLine, endX, offsetInLine;
+
+	// is selection from right-to-left?
+	boolean rightToLeft = caretOffset == selection.x;
+
+	if (rightToLeft) {
+		startOffset = selection.y;
+		endOffset = selection.x;
+	} else {
+		startOffset = selection.x;
+		endOffset = selection.y;
+	}
+	
+	// calculate the logical start and end values for the selection
+	startLine = content.getLineAtOffset(startOffset);
+	offsetInLine = startOffset - content.getOffsetAtLine(startLine);
+	startX = getXAtOffset(content.getLine(startLine), startLine, offsetInLine);	
+	endLine  = content.getLineAtOffset(endOffset);
+	offsetInLine = endOffset - content.getOffsetAtLine(endLine);
+	endX = getXAtOffset(content.getLine(endLine), endLine, offsetInLine);	
+
+	// can the selection be fully displayed within the widget's visible width?
+	int w = getClientArea().width;
+	if (rightToLeft) {
+		selectionFits = startX - endX <= w;
+	} else {
+		selectionFits = endX - startX <= w;
+	}
+	
+	if (selectionFits) {
+		// show as much of the selection as possible by first showing
+		// the start of the selection
+		showLocation(startX, startLine);
+		// endX value could change if showing startX caused a scroll to occur
+		endX = getXAtOffset(content.getLine(endLine), endLine, offsetInLine);	
+		showLocation(endX, endLine);
+	} else {
+		// just show the end of the selection since the selection start 
+		// will not be visible
+		showLocation(endX, endLine);
+	}	 
+}
+boolean isBidiCaret() {
+	return BidiUtil.isBidiPlatform();
+}
+/**
+ * Updates the selection and caret position depending on the text change.
+ * If the selection intersects with the replaced text, the selection is 
+ * reset and the caret moved to the end of the new text.
+ * If the selection is behind the replaced text it is moved so that the
+ * same text remains selected.  If the selection is before the replaced text 
+ * it is left unchanged.
+ * <p>
+ *
+ * @param startOffset offset of the text change
+ * @param replacedLength length of text being replaced
+ * @param newLength length of new text
+ */
+void updateSelection(int startOffset, int replacedLength, int newLength) {
+	if (selection.y <= startOffset) {
+		// selection ends before text change
+		return;
+	}
+	if (selection.x < startOffset) {
+		// clear selection fragment before text change
+		internalRedrawRange(selection.x, startOffset - selection.x, true);
+	}
+	if (selection.y > startOffset + replacedLength && selection.x < startOffset + replacedLength) {
+		// clear selection fragment after text change.
+		// do this only when the selection is actually affected by the 
+		// change. Selection is only affected if it intersects the change (1GDY217).
+		int netNewLength = newLength - replacedLength;
+		int redrawStart = startOffset + newLength;
+		internalRedrawRange(redrawStart, selection.y + netNewLength - redrawStart, true);
+	}
+	if (selection.y > startOffset && selection.x < startOffset + replacedLength) {
+		// selection intersects replaced text. set caret behind text change
+		internalSetSelection(startOffset + newLength, 0, true);
+		// always update the caret location. fixes 1G8FODP
+		setCaretLocation();
+	}
+	else {
+		// move selection to keep same text selected
+		internalSetSelection(selection.x + newLength - replacedLength, selection.y - selection.x, true);
+		// always update the caret location. fixes 1G8FODP
+		setCaretLocation();
+	}	
+}
+/**
+ * Rewraps all lines
+ * <p>
+ * 
+ * @param oldClientAreaWidth client area width before resize 
+ * 	occurred
+ */
+void wordWrapResize(int oldClientAreaWidth) {
+	WrappedContent wrappedContent = (WrappedContent) content;
+	int newTopIndex;
+
+	// all lines are wrapped and no rewrap required if widget has already 
+	// been visible, client area is now wider and visual (wrapped) line 
+	// count equals logical line count.
+	if (oldClientAreaWidth != 0 && clientAreaWidth > oldClientAreaWidth &&
+		wrappedContent.getLineCount() == logicalContent.getLineCount()) {
+		return;
+	}
+	wrappedContent.wrapLines();
+    
+	// adjust the top index so that top line remains the same
+	newTopIndex = content.getLineAtOffset(topOffset);
+	// topOffset is the beginning of the top line. therefore it 
+	// needs to be adjusted because in a wrapped line this is also 
+	// the end of the preceeding line.  
+	if (newTopIndex < content.getLineCount() - 1 &&
+		topOffset == content.getOffsetAtLine(newTopIndex + 1)) {
+		newTopIndex++;
+	}
+	if (newTopIndex != topIndex) {
+		ScrollBar verticalBar = getVerticalBar();
+		// adjust index and pixel offset manually instead of calling
+		// setVerticalScrollOffset because the widget does not actually need
+		// to be scrolled. causes flash otherwise.
+		verticalScrollOffset += (newTopIndex - topIndex) * getVerticalIncrement();
+		// verticalScrollOffset may become negative if first line was 
+		// partially visible and second line was top line. prevent this from 
+		// happening to fix 8503.
+		if (verticalScrollOffset < 0) {
+			verticalScrollOffset = 0;
+		}
+		topIndex = newTopIndex;
+		topOffset = content.getOffsetAtLine(topIndex);
+		if (verticalBar != null) {
+			verticalBar.setSelection(verticalScrollOffset);
+		}
+	}
+	// caret may be on a different line after a rewrap.
+	// call setCaretLocation after fixing vertical scroll offset.
+	setCaretLocation();    
+	// word wrap may have changed on one of the visible lines
+	super.redraw();
+}
+}
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/textStyler.js b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/textStyler.js
new file mode 100644
index 0000000..b88fec8
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/textStyler.js
@@ -0,0 +1,713 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials are made 
+ * available under the terms of the Eclipse Public License v1.0 
+ * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
+ * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
+ * 
+ * Contributors: IBM Corporation - initial API and implementation
+ ******************************************************************************/
+
+/*global document window navigator */
+
+var examples = examples || {};
+examples.textview = examples.textview || {};
+
+examples.textview.TextStyler = (function() {
+
+	var JS_KEYWORDS =
+		["break", "continue", "do", "for", /*"import",*/ "new", "this", /*"void",*/ 
+		 "case", "default", "else", "function", "in", "return", "typeof", "while",
+		 "comment", "delete", "export", "if", /*"label",*/ "switch", "var", "with",
+		 "abstract", "implements", "protected", /*"boolean",*/ /*"instanceOf",*/ "public", 
+		 /*"byte", "int", "short", "char",*/ "interface", "static", 
+		 /*"double", "long",*/ "synchronized", "false", /*"native",*/ "throws", 
+		 "final", "null", "transient", /*"float",*/ "package", "true", 
+		 "goto", "private", "catch", "enum", "throw", "class", "extends", "try", 
+		 "const", "finally", "debugger", "super", "undefined"];
+
+	var JAVA_KEYWORDS =
+		["abstract",
+		 "boolean", "break", "byte",
+		 "case", "catch", "char", "class", "continue",
+		 "default", "do", "double",
+		 "else", "extends",
+		 "false", "final", "finally", "float", "for",
+		 "if", "implements", "import", "instanceof", "int", "interface",
+		 "long",
+		 "native", "new", "null",
+		 "package", "private", "protected", "public",
+		 "return",
+		 "short", "static", "super", "switch", "synchronized",
+		 "this", "throw", "throws", "transient", "true", "try",
+		 "void", "volatile",
+		 "while"];
+
+	var CSS_KEYWORDS =
+		["color", "text-align", "text-indent", "text-decoration", 
+		 "font", "font-style", "font-family", "font-weight", "font-size", "font-variant", "line-height",
+		 "background", "background-color", "background-image", "background-position", "background-repeat", "background-attachment",
+		 "list-style", "list-style-image", "list-style-position", "list-style-type", 
+		 "outline", "outline-color", "outline-style", "outline-width",
+		 "border", "border-left", "border-top", "border-bottom", "border-right", "border-color", "border-width", "border-style",
+		 "border-bottom-color", "border-bottom-style", "border-bottom-width",
+		 "border-left-color", "border-left-style", "border-left-width",
+		 "border-top-color", "border-top-style", "border-top-width",
+		 "border-right-color", "border-right-style", "border-right-width",
+		 "padding", "padding-left", "padding-top", "padding-bottom", "padding-right",
+		 "margin", "margin-left", "margin-top", "margin-bottom", "margin-right",
+		 "width", "height", "left", "top", "right", "bottom",
+		 "min-width", "max-width", "min-height", "max-height",
+		 "display", "visibility",
+		 "clip", "cursor", "overflow", "overflow-x", "overflow-y", "position", "z-index",
+		 "vertical-align", "horizontal-align",
+		 "float", "clear"
+		];
+
+	// Scanner constants
+	var UNKOWN = 1;
+	var KEYWORD = 2;
+	var STRING = 3;
+	var COMMENT = 4;
+	var WHITE = 5;
+	var WHITE_TAB = 6;
+	var WHITE_SPACE = 7;
+
+	// Styles 
+	var isIE = document.selection && window.ActiveXObject && /MSIE/.test(navigator.userAgent) ? document.documentMode : undefined;
+	var commentStyle = {styleClass: "token_comment"};
+	var javadocStyle = {styleClass: "token_javadoc"};
+	var stringStyle = {styleClass: "token_string"};
+	var keywordStyle = {styleClass: "token_keyword"};
+	var spaceStyle = {styleClass: "token_space"};
+	var tabStyle = {styleClass: "token_tab"};
+	var bracketStyle = {styleClass: isIE < 9 ? "token_bracket" : "token_bracket_outline"};
+	var caretLineStyle = {styleClass: "line_caret"};
+	
+	var Scanner = (function() {
+		function Scanner (keywords, whitespacesVisible) {
+			this.keywords = keywords;
+			this.whitespacesVisible = whitespacesVisible;
+			this.setText("");
+		}
+		
+		Scanner.prototype = {
+			getOffset: function() {
+				return this.offset;
+			},
+			getStartOffset: function() {
+				return this.startOffset;
+			},
+			getData: function() {
+				return this.text.substring(this.startOffset, this.offset);
+			},
+			getDataLength: function() {
+				return this.offset - this.startOffset;
+			},
+			_read: function() {
+				if (this.offset < this.text.length) {
+					return this.text.charCodeAt(this.offset++);
+				}
+				return -1;
+			},
+			_unread: function(c) {
+				if (c !== -1) { this.offset--; }
+			},
+			nextToken: function() {
+				this.startOffset = this.offset;
+				while (true) {
+					var c = this._read();
+					switch (c) {
+						case -1: return null;
+						case 47:	// SLASH -> comment
+							c = this._read();
+							if (c === 47) {
+								while (true) {
+									c = this._read();
+									if ((c === -1) || (c === 10)) {
+										this._unread(c);
+										return COMMENT;
+									}
+								}
+							}
+							this._unread(c);
+							return UNKOWN;
+						case 39:	// SINGLE QUOTE -> char const
+							while(true) {
+								c = this._read();
+								switch (c) {
+									case 39:
+										return STRING;
+									case -1:
+										this._unread(c);
+										return STRING;
+									case 92: // BACKSLASH
+										c = this._read();
+										break;
+								}
+							}
+							break;
+						case 34:	// DOUBLE QUOTE -> string
+							while(true) {
+								c = this._read();
+								switch (c) {
+									case 34: // DOUBLE QUOTE
+										return STRING;
+									case -1:
+										this._unread(c);
+										return STRING;
+									case 92: // BACKSLASH
+										c = this._read();
+										break;
+								}
+							}
+							break;
+						case 32: // SPACE
+						case 9: // TAB
+							if (this.whitespacesVisible) {
+								return c === 32 ? WHITE_SPACE : WHITE_TAB;
+							}
+							do {
+								c = this._read();
+							} while(c === 32 || c === 9);
+							this._unread(c);
+							return WHITE;
+						default:
+							var isCSS = this.isCSS;
+							if ((97 <= c && c <= 122) || (65 <= c && c <= 90) || c === 95 || (48 <= c && c <= 57) || (0x2d === c && isCSS)) { //LETTER OR UNDERSCORE OR NUMBER
+								var off = this.offset - 1;
+								do {
+									c = this._read();
+								} while((97 <= c && c <= 122) || (65 <= c && c <= 90) || c === 95 || (48 <= c && c <= 57) || (0x2d === c && isCSS));  //LETTER OR UNDERSCORE OR NUMBER
+								this._unread(c);
+								var word = this.text.substring(off, this.offset);
+								//TODO slow
+								for (var i=0; i<this.keywords.length; i++) {
+									if (this.keywords[i] === word) { return KEYWORD; }
+								}
+							}
+							return UNKOWN;
+					}
+				}
+			},
+			setText: function(text) {
+				this.text = text;
+				this.offset = 0;
+				this.startOffset = 0;
+			}
+		};
+		return Scanner;
+	}());
+	
+	var WhitespaceScanner = (function() {
+		function WhitespaceScanner () {
+			Scanner.call(this, null, true);
+		}
+		WhitespaceScanner.prototype = new Scanner(null);
+		WhitespaceScanner.prototype.nextToken = function() {
+			this.startOffset = this.offset;
+			while (true) {
+				var c = this._read();
+				switch (c) {
+					case -1: return null;
+					case 32: // SPACE
+						return WHITE_SPACE;
+					case 9: // TAB
+						return WHITE_TAB;
+					default:
+						do {
+							c = this._read();
+						} while(!(c === 32 || c === 9 || c === -1));
+						this._unread(c);
+						return UNKOWN;
+				}
+			}
+		};
+		
+		return WhitespaceScanner;
+	}());
+	
+	function TextStyler (view, lang) {
+		this.commentStart = "/*";
+		this.commentEnd = "*/";
+		var keywords = [];
+		switch (lang) {
+			case "java": keywords = JAVA_KEYWORDS; break;
+			case "js": keywords = JS_KEYWORDS; break;
+			case "css": keywords = CSS_KEYWORDS; break;
+		}
+		this.whitespacesVisible = false;
+		this.highlightCaretLine = true;
+		this._scanner = new Scanner(keywords, this.whitespacesVisible);
+		//TODO this scanner is not the best/correct way to parse CSS
+		if (lang === "css") {
+			this._scanner.isCSS = true;
+		}
+		this._whitespaceScanner = new WhitespaceScanner();
+		this.view = view;
+		this.commentOffset = 0;
+		this.commentOffsets = [];
+		this._currentBracket = undefined; 
+		this._matchingBracket = undefined;
+		
+		view.addEventListener("Selection", this, this._onSelection);
+		view.addEventListener("ModelChanged", this, this._onModelChanged);
+		view.addEventListener("Destroy", this, this._onDestroy);
+		view.addEventListener("LineStyle", this, this._onLineStyle);
+		view.redrawLines();
+	}
+	
+	TextStyler.prototype = {
+		destroy: function() {
+			var view = this.view;
+			if (view) {
+				view.removeEventListener("Selection", this, this._onSelection);
+				view.removeEventListener("ModelChanged", this, this._onModelChanged);
+				view.removeEventListener("Destroy", this, this._onDestroy);
+				view.removeEventListener("LineStyle", this, this._onLineStyle);
+				this.view = null;
+			}
+		},
+		setHighlightCaretLine: function(highlight) {
+			this.highlightCaretLine = highlight;
+		},
+		setWhitespacesVisible: function(visible) {
+			this.whitespacesVisible = visible;
+			this._scanner.whitespacesVisible = visible;
+		},
+		_binarySearch: function(offsets, offset, low, high) {
+			while (high - low > 2) {
+				var index = (((high + low) >> 1) >> 1) << 1;
+				var end = offsets[index + 1];
+				if (end > offset) {
+					high = index;
+				} else {
+					low = index;
+				}
+			}
+			return high;
+		},
+		_computeComments: function(end) {
+			// compute comments between commentOffset and end
+			if (end <= this.commentOffset) { return; }
+			var model = this.view.getModel();
+			var charCount = model.getCharCount();
+			var e = end;
+			// Uncomment to compute all comments
+//			e = charCount;
+			var t = /*start == this.commentOffset && e == end ? text : */model.getText(this.commentOffset, e);
+			if (this.commentOffsets.length > 1 && this.commentOffsets[this.commentOffsets.length - 1] === charCount) {
+				this.commentOffsets.length--;
+			}
+			var offset = 0;
+			while (offset < t.length) {
+				var begin = (this.commentOffsets.length & 1) === 0;
+				var search = begin ? this.commentStart : this.commentEnd;
+				var index = t.indexOf(search, offset);
+				if (index !== -1) {
+					this.commentOffsets.push(this.commentOffset + (begin ? index : index + search.length));
+				} else {
+					break;
+				}
+				offset = index + search.length;
+			}
+			if ((this.commentOffsets.length & 1) === 1) { this.commentOffsets.push(charCount); }
+			this.commentOffset = e;
+		},
+		_getCommentRanges: function(start, end) {
+			this._computeComments (end);
+			var commentCount = this.commentOffsets.length;
+			var commentStart = this._binarySearch(this.commentOffsets, start, -1, commentCount);
+			if (commentStart >= commentCount) { return []; }
+			if (this.commentOffsets[commentStart] > end) { return []; }
+			var commentEnd = Math.min(commentCount - 2, this._binarySearch(this.commentOffsets, end, commentStart - 1, commentCount));
+			if (this.commentOffsets[commentEnd] > end) { commentEnd = Math.max(commentStart, commentEnd - 2); }
+			return this.commentOffsets.slice(commentStart, commentEnd + 2);
+		},
+		_getLineStyle: function(lineIndex) {
+			if (this.highlightCaretLine) {
+				var view = this.view;
+				var model = view.getModel();
+				var selection = view.getSelection();
+				if (selection.start === selection.end && model.getLineAtOffset(selection.start) === lineIndex) {
+					return caretLineStyle;
+				}
+			}
+			return null;
+		},
+		_getStyles: function(text, start) {
+			var end = start + text.length;
+			var model = this.view.getModel();
+			
+			// get comment ranges that intersect with range
+			var commentRanges = this._getCommentRanges (start, end);
+			var styles = [];
+			
+			// for any sub range that is not a comment, parse code generating tokens (keywords, numbers, brackets, line comments, etc)
+			var offset = start;
+			for (var i = 0; i < commentRanges.length; i+= 2) {
+				var commentStart = commentRanges[i];
+				if (offset < commentStart) {
+					this._parse(text.substring(offset - start, commentStart - start), offset, styles);
+				}
+				var style = commentStyle;
+				if ((commentRanges[i+1] - commentStart) > (this.commentStart.length + this.commentEnd.length)) {
+					var o = commentStart + this.commentStart.length;
+					if (model.getText(o, o + 1) === "*") { style = javadocStyle; }
+				}
+				if (this.whitespacesVisible) {
+					var s = Math.max(offset, commentStart);
+					var e = Math.min(end, commentRanges[i+1]);
+					this._parseWhitespace(text.substring(s - start, e - start), s, styles, style);
+				} else {
+					styles.push({start: commentRanges[i], end: commentRanges[i+1], style: style});
+				}
+				offset = commentRanges[i+1];
+			}
+			if (offset < end) {
+				this._parse(text.substring(offset - start, end - start), offset, styles);
+			}
+			return styles;
+		},
+		_parse: function(text, offset, styles) {
+			var scanner = this._scanner;
+			scanner.setText(text);
+			var token;
+			while ((token = scanner.nextToken())) {
+				var tokenStart = scanner.getStartOffset() + offset;
+				var style = null;
+				if (tokenStart === this._matchingBracket) {
+					style = bracketStyle;
+				} else {
+					switch (token) {
+						case KEYWORD: style = keywordStyle; break;
+						case STRING:
+							if (this.whitespacesVisible) {
+								this._parseWhitespace(scanner.getData(), tokenStart, styles, stringStyle);
+								continue;
+							} else {
+								style = stringStyle;
+							}
+							break;
+						case COMMENT: 
+							if (this.whitespacesVisible) {
+								this._parseWhitespace(scanner.getData(), tokenStart, styles, commentStyle);
+								continue;
+							} else {
+								style = commentStyle;
+							}
+							break;
+						case WHITE_TAB:
+							if (this.whitespacesVisible) {
+								style = tabStyle;
+							}
+							break;
+						case WHITE_SPACE:
+							if (this.whitespacesVisible) {
+								style = spaceStyle;
+							}
+							break;
+					}
+				}
+				styles.push({start: tokenStart, end: scanner.getOffset() + offset, style: style});
+			}
+		},
+		_parseWhitespace: function(text, offset, styles, s) {
+			var scanner = this._whitespaceScanner;
+			scanner.setText(text);
+			var token;
+			while ((token = scanner.nextToken())) {
+				var tokenStart = scanner.getStartOffset() + offset;
+				var style = s;
+				switch (token) {
+					case WHITE_TAB:
+						style = tabStyle;
+						break;
+					case WHITE_SPACE:
+						style = spaceStyle;
+						break;
+				}
+				styles.push({start: tokenStart, end: scanner.getOffset() + offset, style: style});
+			}
+		},
+		_findBrackets: function(bracket, closingBracket, text, textOffset, start, end) {
+			var result = [];
+			
+			// get comment ranges that intersect with range
+			var commentRanges = this._getCommentRanges (start, end);
+			
+			// for any sub range that is not a comment, parse code generating tokens (keywords, numbers, brackets, line comments, etc)
+			var offset = start, scanner = this._scanner, token, tokenData;
+			for (var i = 0; i < commentRanges.length; i+= 2) {
+				var commentStart = commentRanges[i];
+				if (offset < commentStart) {
+					scanner.setText(text.substring(offset - start, commentStart - start));
+					while ((token = scanner.nextToken())) {
+						if (scanner.getDataLength() !== 1) { continue; }
+						tokenData = scanner.getData();
+						if (tokenData === bracket) {
+							result.push(scanner.getStartOffset() + offset - start + textOffset);
+						}
+						if (tokenData === closingBracket) {
+							result.push(-(scanner.getStartOffset() + offset - start + textOffset));
+						}
+					}
+				}
+				offset = commentRanges[i+1];
+			}
+			if (offset < end) {
+				scanner.setText(text.substring(offset - start, end - start));
+				while ((token = scanner.nextToken())) {
+					if (scanner.getDataLength() !== 1) { continue; }
+					tokenData = scanner.getData();
+					if (tokenData === bracket) {
+						result.push(scanner.getStartOffset() + offset - start + textOffset);
+					}
+					if (tokenData === closingBracket) {
+						result.push(-(scanner.getStartOffset() + offset - start + textOffset));
+					}
+				}
+			}
+			return result;
+		},
+		_onDestroy: function(e) {
+			this.destroy();
+		},
+		_onLineStyle: function (e) {
+			e.style = this._getLineStyle(e.lineIndex);
+			e.ranges = this._getStyles(e.lineText, e.lineStart);
+		},
+		_onSelection: function(e) {
+			var oldSelection = e.oldValue;
+			var newSelection = e.newValue;
+			var view = this.view;
+			var model = view.getModel();
+			var lineIndex;
+			if (this._matchingBracket !== undefined) {
+				lineIndex = model.getLineAtOffset(this._matchingBracket);
+				view.redrawLines(lineIndex, lineIndex + 1);
+				this._matchingBracket = this._currentBracket = undefined;
+			}
+			if (this.highlightCaretLine) {
+				var oldLineIndex = model.getLineAtOffset(oldSelection.start);
+				lineIndex = model.getLineAtOffset(newSelection.start);
+				var newEmpty = newSelection.start === newSelection.end;
+				var oldEmpty = oldSelection.start === oldSelection.end;
+				if (!(oldLineIndex === lineIndex && oldEmpty && newEmpty)) {
+					if (oldEmpty) {
+						view.redrawLines(oldLineIndex, oldLineIndex + 1);
+					}
+					if ((oldLineIndex !== lineIndex || !oldEmpty) && newEmpty) {
+						view.redrawLines(lineIndex, lineIndex + 1);
+					}
+				}
+			}
+			if (newSelection.start !== newSelection.end || newSelection.start === 0) {
+				return;
+			}
+			var caret = view.getCaretOffset();
+			if (caret === 0) { return; }
+			var brackets = "{}()[]<>";
+			var bracket = model.getText(caret - 1, caret);
+			var bracketIndex = brackets.indexOf(bracket, 0);
+			if (bracketIndex === -1) { return; }
+			var closingBracket;
+			if (bracketIndex & 1) {
+				closingBracket = brackets.substring(bracketIndex - 1, bracketIndex);
+			} else {
+				closingBracket = brackets.substring(bracketIndex + 1, bracketIndex + 2);
+			}
+			lineIndex = model.getLineAtOffset(caret);
+			var lineText = model.getLine(lineIndex);
+			var lineStart = model.getLineStart(lineIndex);
+			var lineEnd = model.getLineEnd(lineIndex);
+			brackets = this._findBrackets(bracket, closingBracket, lineText, lineStart, lineStart, lineEnd);
+			for (var i=0; i<brackets.length; i++) {
+				var sign = brackets[i] >= 0 ? 1 : -1;
+				if (brackets[i] * sign === caret - 1) {
+					var level = 1;
+					this._currentBracket = brackets[i] * sign;
+					if (bracketIndex & 1) {
+						i--;
+						for (; i>=0; i--) {
+							sign = brackets[i] >= 0 ? 1 : -1;
+							level += sign;
+							if (level === 0) {
+								this._matchingBracket = brackets[i] * sign;
+								view.redrawLines(lineIndex, lineIndex + 1);
+								return;
+							}
+						}
+						lineIndex -= 1;
+						while (lineIndex >= 0) {
+							lineText = model.getLine(lineIndex);
+							lineStart = model.getLineStart(lineIndex);
+							lineEnd = model.getLineEnd(lineIndex);
+							brackets = this._findBrackets(bracket, closingBracket, lineText, lineStart, lineStart, lineEnd);
+							for (var j=brackets.length - 1; j>=0; j--) {
+								sign = brackets[j] >= 0 ? 1 : -1;
+								level += sign;
+								if (level === 0) {
+									this._matchingBracket = brackets[j] * sign;
+									view.redrawLines(lineIndex, lineIndex + 1);
+									return;
+								}
+							}
+							lineIndex--;
+						}
+					} else {
+						i++;
+						for (; i<brackets.length; i++) {
+							sign = brackets[i] >= 0 ? 1 : -1;
+							level += sign;
+							if (level === 0) {
+								this._matchingBracket = brackets[i] * sign;
+								view.redrawLines(lineIndex, lineIndex + 1);
+								return;
+							}
+						}
+						lineIndex += 1;
+						var lineCount = model.getLineCount ();
+						while (lineIndex < lineCount) {
+							lineText = model.getLine(lineIndex);
+							lineStart = model.getLineStart(lineIndex);
+							lineEnd = model.getLineEnd(lineIndex);
+							brackets = this._findBrackets(bracket, closingBracket, lineText, lineStart, lineStart, lineEnd);
+							for (var k=0; k<brackets.length; k++) {
+								sign = brackets[k] >= 0 ? 1 : -1;
+								level += sign;
+								if (level === 0) {
+									this._matchingBracket = brackets[k] * sign;
+									view.redrawLines(lineIndex, lineIndex + 1);
+									return;
+								}
+							}
+							lineIndex++;
+						}
+					}
+					break;
+				}
+			}
+		},
+		_onModelChanged: function(e) {
+			var start = e.start;
+			var removedCharCount = e.removedCharCount;
+			var addedCharCount = e.addedCharCount;
+			if (this._matchingBracket && start < this._matchingBracket) { this._matchingBracket += addedCharCount + removedCharCount; }
+			if (this._currentBracket && start < this._currentBracket) { this._currentBracket += addedCharCount + removedCharCount; }
+			if (start >= this.commentOffset) { return; }
+			var model = this.view.getModel();
+			
+//			window.console.log("start=" + start + " added=" + addedCharCount + " removed=" + removedCharCount)
+//			for (var i=0; i< this.commentOffsets.length; i++) {
+//				window.console.log(i +"="+ this.commentOffsets[i]);
+//			}
+
+			var commentCount = this.commentOffsets.length;
+			var extra = Math.max(this.commentStart.length - 1, this.commentEnd.length - 1);
+			if (commentCount === 0) {
+				this.commentOffset = Math.max(0, start - extra);
+				return;
+			}
+			var charCount = model.getCharCount();
+			var oldCharCount = charCount - addedCharCount + removedCharCount;
+			var commentStart = this._binarySearch(this.commentOffsets, start, -1, commentCount);
+			var end = start + removedCharCount;
+			var commentEnd = this._binarySearch(this.commentOffsets, end, commentStart - 1, commentCount);
+//			window.console.log("s=" + commentStart + " e=" + commentEnd);
+			var ts;
+			if (commentStart > 0) {
+				ts = this.commentOffsets[--commentStart];
+			} else {
+				ts = Math.max(0, Math.min(this.commentOffsets[commentStart], start) - extra);
+				--commentStart;
+			}
+			var te;
+			var redrawEnd = charCount;
+			if (commentEnd + 1 < this.commentOffsets.length) {
+				te = this.commentOffsets[++commentEnd];
+				if (end > (te - this.commentEnd.length)) {
+					if (commentEnd + 2 < this.commentOffsets.length) { 
+						commentEnd += 2;
+						te = this.commentOffsets[commentEnd];
+						redrawEnd = te + 1;
+						if (redrawEnd > start) { redrawEnd += addedCharCount - removedCharCount; }
+					} else {
+						te = Math.min(oldCharCount, end + extra);
+						this.commentOffset = te;
+					}
+				}
+			} else {
+				te = Math.min(oldCharCount, end + extra);
+				this.commentOffset = te;
+				if (commentEnd > 0 && commentEnd === this.commentOffsets.length) {
+					commentEnd = this.commentOffsets.length - 1;
+				}
+			}
+			if (ts > start) { ts += addedCharCount - removedCharCount; }
+			if (te > start) { te += addedCharCount - removedCharCount; }
+			
+//			window.console.log("commentStart="+ commentStart + " commentEnd=" + commentEnd + " ts=" + ts + " te=" + te)
+
+			if (this.commentOffsets.length > 1 && this.commentOffsets[this.commentOffsets.length - 1] === oldCharCount) {
+				this.commentOffsets.length--;
+			}
+			
+			var offset = 0;
+			var newComments = [];
+			var t = model.getText(ts, te);
+			if (this.commentOffset < te) { this.commentOffset = te; }
+			while (offset < t.length) {
+				var begin = ((commentStart + 1 + newComments.length) & 1) === 0;
+				var search = begin ? this.commentStart : this.commentEnd;
+				var index = t.indexOf(search, offset);
+				if (index !== -1) {
+					newComments.push(ts + (begin ? index : index + search.length));
+				} else {
+					break;
+				}
+				offset = index + search.length;
+			}
+//			window.console.log("lengths=" + newComments.length + " " + (commentEnd - commentStart) + " t=<" + t + ">")
+//			for (var i=0; i< newComments.length; i++) {
+//				window.console.log(i +"=>"+ newComments[i]);
+//			}
+			var redraw = (commentEnd - commentStart) !== newComments.length;
+			if (!redraw) {
+				for (var i=0; i<newComments.length; i++) {
+					offset = this.commentOffsets[commentStart + 1 + i];
+					if (offset > start) { offset += addedCharCount - removedCharCount; }
+					if (offset !== newComments[i]) {
+						redraw = true;
+						break;
+					} 
+				}
+			}
+			
+			var args = [commentStart + 1, (commentEnd - commentStart)].concat(newComments);
+			Array.prototype.splice.apply(this.commentOffsets, args);
+			for (var k=commentStart + 1 + newComments.length; k< this.commentOffsets.length; k++) {
+				this.commentOffsets[k] += addedCharCount - removedCharCount;
+			}
+			
+			if ((this.commentOffsets.length & 1) === 1) { this.commentOffsets.push(charCount); }
+			
+			if (redraw) {
+//				window.console.log ("redraw " + (start + addedCharCount) + " " + redrawEnd);
+				this.view.redrawRange(start + addedCharCount, redrawEnd);
+			}
+
+//			for (var i=0; i< this.commentOffsets.length; i++) {
+//				window.console.log(i +"="+ this.commentOffsets[i]);
+//			}
+
+		}
+	};
+	return TextStyler;
+}());
+
+if (typeof window !== "undefined" && typeof window.define !== "undefined") {
+	define([], function() {
+		return examples.textview;
+	});
+}
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/textstyler.css b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/textstyler.css
new file mode 100644
index 0000000..72f339c
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/examples/textview/textstyler.css
@@ -0,0 +1,41 @@
+.token_comment {
+	color: green;
+}
+
+.token_javadoc {
+	color: #00008F;
+}
+
+.token_string {
+	color: blue;
+}
+
+.token_keyword {
+	color: darkred;
+	font-weight: bold;
+}
+
+.token_bracket_outline {
+	outline: 1px solid red;
+}
+
+.token_bracket {
+	color: white;
+	background-color: grey;
+}
+
+.token_space {
+	background-image: url('/examples/textview/images/white_space.png');
+	background-repeat: no-repeat;
+ 	background-position: center center;
+}
+
+.token_tab {
+	background-image: url('/examples/textview/images/white_tab.png');
+	background-repeat: no-repeat;
+ 	background-position: left center;
+}
+
+.line_caret {
+	background-color: #EAF2FE;
+}
\ No newline at end of file
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/editor/contentAssist.js b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/editor/contentAssist.js
new file mode 100644
index 0000000..cd1a54b
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/editor/contentAssist.js
@@ -0,0 +1,258 @@
+/*******************************************************************************
+ * Copyright (c) 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials are made 
+ * available under the terms of the Eclipse Public License v1.0 
+ * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
+ * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+/*global eclipse:true dojo */
+/*jslint maxerr:150 browser:true devel:true */
+
+
+/**
+ * @namespace The global container for eclipse APIs.
+ */ 
+	var orion = orion || {};
+	orion.editor = orion.editor || {};
+
+/**
+ * A ContentAssist will look for content assist providers in the service registry (if provided).
+ * Alternately providers can be registered directly by calling {@link #addProvider}.
+ * @name eclipse.ContentAssist
+ * @param {orion.editor.Editor} editor
+ * @param {String} contentAssistId
+ * @param {eclipse.ServiceRegistry} [serviceRegistry] If omitted, providers must be registered via {@link #addProvider}.
+ */
+orion.editor.ContentAssist = (function() {
+	function ContentAssist(editor, contentAssistId, serviceRegistry) {
+		this.editor = editor;
+		this.textView = editor.getTextView();
+		this.contentAssistPanel = dojo.byId(contentAssistId);
+		this.active = false;
+		this.prefix = "";
+		this.serviceRegistry = serviceRegistry;
+		this.contentAssistProviders = [];
+		this.activeServiceReferences = [];
+		this.activeContentAssistProviders = [];
+		this.contentAssistListener = {
+			onVerify: function(event){
+				this.showContentAssist(false);
+			},
+			onSelectionChanged: function() {
+				this.showContentAssist(false);
+			}
+		};
+		this.init();
+	}
+	ContentAssist.prototype = {
+		init: function() {
+			var isMac = navigator.platform.indexOf("Mac") !== -1;
+			this.textView.setKeyBinding(isMac ? new orion.textview.KeyBinding(' ', false, false, false, true) : new orion.textview.KeyBinding(' ', true), "Content Assist");
+			this.textView.setAction("Content Assist", dojo.hitch(this, function() {
+				this.showContentAssist(true);
+				return true;
+			}));
+			dojo.connect(this.editor, "onInputChange", this, this.inputChanged);
+		},
+	
+		inputChanged: function(fileName) {
+			if (this.serviceRegistry) {
+				// Filter the ServiceReferences
+				this.activeServiceReferences = [];
+				var serviceReferences = this.serviceRegistry.getServiceReferences("orion.edit.contentAssist");
+				var serviceReference;
+				dojo.forEach(serviceReferences, dojo.hitch(this, function(serviceReference) {
+					var info = {};
+					var propertyNames = serviceReference.getPropertyNames();
+					for (var i = 0; i < propertyNames.length; i++) {
+						info[propertyNames[i]] = serviceReference.getProperty(propertyNames[i]);
+					}
+					if (new RegExp(info.pattern).test(fileName)) {
+						this.activeServiceReferences.push(serviceReference);
+					}
+				}));
+			}
+			// Filter the registered providers
+			for (var i=0; i < this.contentAssistProviders.length; i++) {
+				var provider = this.contentAssistProviders[i];
+				if (new RegExp(provider.pattern).test(fileName)) {
+					this.activeContentAssistProviders.push(provider.provider);
+				}
+			}
+		},
+		
+		cancel: function() {
+			this.showContentAssist(false);
+		},
+		isActive: function() {
+			return this.active;
+		},
+		lineUp: function() {
+			if (this.contentAssistPanel) {
+				var nodes = dojo.query('> div', this.contentAssistPanel);
+				var index = 0;
+				for (var i=0; i<nodes.length; i++) {
+					if (nodes[i].className === "selected") {
+						nodes[i].className = "";
+						index = i;
+						break;
+					}
+				}
+				if (index > 0) {
+					nodes[index-1].className = "selected";
+				} else {
+					nodes[nodes.length - 1].className = "selected";
+				}
+				return true;
+			}
+		},
+		lineDown: function() {
+			if (this.contentAssistPanel) {
+				var nodes = dojo.query('> div', this.contentAssistPanel);
+				var index = 0;
+				for (var i=0; i<nodes.length; i++) {
+					if (nodes[i].className === "selected") {
+						nodes[i].className = "";
+						index = i;
+						break;
+					}
+				}
+				if (index < nodes.length - 1) {
+					nodes[index+1].className = "selected";
+				} else {
+					nodes[0].className = "selected";
+				}
+				return true;
+			}
+		},
+		enter: function() {
+			if (this.contentAssistPanel) {
+				var proposal = dojo.query("> .selected", this.contentAssistPanel);
+				this.textView.setText(proposal[0].innerHTML.substring(this.prefix.length), this.textView.getCaretOffset(), this.textView.getCaretOffset());
+				this.showContentAssist(false);
+				return true;
+			}
+		},
+		showContentAssist: function(enable) {
+			if (!this.contentAssistPanel) {
+				return;
+			}
+			function createDiv(proposal, isSelected, parent) {
+				var attributes = {innerHTML: proposal, onclick: function(){alert(proposal);}};
+				if (isSelected) {
+					attributes.className = "selected";
+				}
+				dojo.create("div", attributes, parent, this);
+			}
+			if (!enable) {
+				this.textView.removeEventListener("Verify", this, this.contentAssistListener.onVerify);
+				this.textView.removeEventListener("Selection", this, this.contentAssistListener.onSelectionChanged);
+				this.active = false;
+				this.contentAssistPanel.style.display = "none";
+			} else {
+				var offset = this.textView.getCaretOffset();
+				var index = offset;
+				var c;
+				while (index > 0 && ((97 <= (c = this.textView.getText(index - 1, index).charCodeAt(0)) && c <= 122) || (65 <= c && c <= 90) || c === 95 || (48 <= c && c <= 57))) { //LETTER OR UNDERSCORE OR NUMBER
+					index--;
+				}
+				
+				// Show all proposals
+//				if (index === offset) {
+//					return;
+//				}
+				this.prefix = this.textView.getText(index, offset);
+				
+				var proposals = [],
+				    buffer = this.textView.getText(),
+				    selection = this.textView.getSelection();
+				this.getKeywords(this.prefix, buffer, selection).then(
+					dojo.hitch(this, function(keywords) {
+						for (var i = 0; i < keywords.length; i++) {
+							var proposal = keywords[i];
+							if (proposal.substr(0, this.prefix.length) === this.prefix) {
+								proposals.push(proposal);
+							}
+						}
+						if (proposals.length === 0) {
+							return;
+						}
+						
+						var caretLocation = this.textView.getLocationAtOffset(offset);
+						caretLocation.y += this.textView.getLineHeight();
+						this.contentAssistPanel.innerHTML = "";
+						for (i = 0; i<proposals.length; i++) {
+							createDiv(proposals[i], i===0, this.contentAssistPanel);
+						}
+						this.textView.convert(caretLocation, "document", "page");
+						this.contentAssistPanel.style.position = "absolute";
+						this.contentAssistPanel.style.left = caretLocation.x + "px";
+						this.contentAssistPanel.style.top = caretLocation.y + "px";
+						this.contentAssistPanel.style.display = "block";
+						this.textView.addEventListener("Verify", this, this.contentAssistListener.onVerify);
+						this.textView.addEventListener("Selection", this, this.contentAssistListener.onSelectionChanged);
+						this.active = true;
+					}));
+			}
+		},
+		/**
+		 * @param {String} The string buffer.substring(w+1, c) where c is the caret offset and w is the index of the 
+		 * rightmost whitespace character preceding c.
+		 * @param {String} buffer The entire buffer being edited
+		 * @param {eclipse.Selection} selection The current textView selection.
+		 * @returns {dojo.Deferred} A future that will provide the keywords.
+		 */
+		getKeywords: function(prefix, buffer, selection) {
+			var keywords = [];
+			
+			// Add keywords from directly registered providers
+			dojo.forEach(this.activeContentAssistProviders, function(provider) {
+				keywords = keywords.concat(provider.getKeywords() || []);
+			});
+			
+			// Add keywords from providers registered through service registry
+			if (this.serviceRegistry) {
+				var keywordPromises = dojo.map(this.activeServiceReferences, dojo.hitch(this, function(serviceRef) {
+						return this.serviceRegistry.getService(serviceRef).then(function(service) {
+							return service.getKeywords(prefix, buffer, selection);
+						});
+					}));
+				var dl = new dojo.DeferredList(keywordPromises);
+				return dl.then(function(results) {
+					for (var i=0; i < results.length; i++) {
+						var result = results[i];
+						if (result[0]) {
+							var serviceKeywords = result[1];
+							keywords = keywords.concat(serviceKeywords);
+						}
+					}
+					return keywords;
+				});
+			} else {
+				var d = new dojo.Deferred();
+				d.callback(keywords);
+				return d;
+			}
+		},
+		/**
+		 * Adds a content assist provider.
+		 * @param {Object} provider The provider object. See {@link orion.contentAssist.CssContentAssistProvider} for an example.
+		 * @param {String} name Name for this provider.
+		 * @param {String} pattern The regex pattern matching filenames that provider can offer content assist for.
+		 */
+		addProvider: function(provider, name, pattern) {
+			this.contentAssistProviders = this.contentAssistProviders || [];
+			this.contentAssistProviders.push({name: name, pattern: pattern, provider: provider});
+		}
+	};
+	return ContentAssist;
+}());
+
+if (typeof window !== "undefined" && typeof window.define !== "undefined") {
+	define(['dojo', 'orion/textview/keyBinding', 'dojo/DeferredList'], function() {
+		return orion.editor;	
+	});
+}
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/editor/editor.js b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/editor/editor.js
new file mode 100644
index 0000000..91fee9e
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/editor/editor.js
@@ -0,0 +1,352 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials are made 
+ * available under the terms of the Eclipse Public License v1.0 
+ * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
+ * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
+ * 
+ * Contributors: IBM Corporation - initial API and implementation
+ ******************************************************************************/
+ 
+ /*global window dojo orion:true eclipse:true handleGetAuthenticationError*/
+ /*jslint maxerr:150 browser:true devel:true regexp:false*/
+
+var orion = orion || {};
+orion.editor = orion.editor || {};	
+	
+orion.editor.Editor = (function() {
+	function Editor(options) {
+		this._textViewFactory = options.textViewFactory;
+		this._undoStackFactory = options.undoStackFactory;
+		this._annotationFactory = options.annotationFactory;
+		this._lineNumberRulerFactory = options.lineNumberRulerFactory;
+		this._contentAssistFactory = options.contentAssistFactory;
+		this._keyBindingFactory = options.keyBindingFactory;
+		this._statusReporter = options.statusReporter;
+		this._domNode = options.domNode;
+		this._syntaxHighlightProviders = options.syntaxHighlightProviders;
+		
+		this._annotationsRuler = null;
+		this._overviewRuler = null;
+		this._dirty = false;
+		this._contentAssist = null;
+		this._keyModes = [];		
+	}
+	Editor.prototype = {
+		getTextView: function() {
+			return this._textView;
+		},
+		
+		reportStatus: function(message, isError) {
+			if (this._statusReporter) {
+				this._statusReporter(message, isError);
+			} else {
+				window.alert(isError ? "ERROR: " + message : message);
+			}
+		},
+				
+		/**
+		 * @static
+		 * @param textView
+		 * @param start
+		 * @param end
+		 */
+		moveSelection: function(textView, start, end) {
+			end = end || start;
+			textView.setSelection(start, end, false);
+			var topPixel = textView.getTopPixel();
+			var bottomPixel = textView.getBottomPixel();
+			var line = textView.getModel().getLineAtOffset(start);
+			var linePixel = textView.getLinePixel(line);
+			if (linePixel < topPixel || linePixel > bottomPixel) {
+				var height = bottomPixel - topPixel;
+				var target = Math.max(0, linePixel- Math.floor((linePixel<topPixel?3:1)*height / 4));
+				var a = new dojo.Animation({
+					node: textView,
+					duration: 300,
+					curve: [topPixel, target],
+					onAnimate: function(x){
+						textView.setTopPixel(Math.floor(x));
+					},
+					onEnd: function() {
+						textView.showSelection();
+						textView.focus();
+					}
+				});
+				a.play();
+			} else {
+				textView.showSelection();
+				textView.focus();
+			}
+		},
+		getContents : function() {
+			if (this._textView) {
+				return this._textView.getText();
+			}
+		},
+		isDirty : function() {
+			return this._dirty;
+		},
+		checkDirty : function() {
+			var dirty = !this._undoStack.isClean();
+			if (this._dirty === dirty) {
+				return;
+			}
+			this.onDirtyChange(dirty);
+		},
+		
+		getAnnotationsRuler : function() {
+			return this._annotationsRuler;
+		},
+
+		/**
+		 * Helper for finding occurrences of str in the textView.
+		 * @param str {String}
+		 * @param startIndex {number}
+		 * @param [ignoreCase] {boolean} Default is false
+		 * @param [reverse] {boolean} Default is false
+		 * @return {index: number, length: number} giving the match details, or null if no match found.
+		 */
+		doFind: function(str, startIndex, ignoreCase, reverse) {
+			var text = this._textView.getText();
+			if (ignoreCase) {
+				str = str.toLowerCase();
+				text = text.toLowerCase();
+			}
+			
+			var i;
+			if (reverse) {
+				text = text.split("").reverse().join("");
+				str = str.split("").reverse().join("");
+				startIndex = text.length - startIndex - 1;
+				i = text.indexOf(str, startIndex);
+				if (i !== -1) {
+					return {index: text.length - str.length - i, length: str.length};
+				}
+			} else {
+				i = text.indexOf(str, startIndex);
+				if (i !== -1) {
+					return {index: i, length: str.length};
+				}
+			}
+			return null;
+		},
+		
+		/**
+		 * Helper for finding regexp matches in the textView. Use doFind() for simple string searches.
+		 * @param pattern {String} A valid regexp pattern
+		 * @param flags {String} Valid regexp flags: [is]
+		 * @param [startIndex] {number} Default is false
+		 * @param [reverse] {boolean} Default is false
+		 * @return {index: number, length: number} giving the match details, or null if no match found.
+		 */
+		doFindRegExp: function(pattern, flags, startIndex, reverse) {
+			if (!pattern) {
+				return null;
+			}
+			
+			flags = flags || "";
+			// 'g' makes exec() iterate all matches, 'm' makes ^$ work linewise
+			flags += (flags.indexOf("g") === -1 ? "g" : "") + (flags.indexOf("m") === -1 ? "m" : "");
+			var regexp = new RegExp(pattern, flags);
+			var text = this._textView.getText();
+			var result = null,
+			    match = null;
+			if (reverse) {
+				while (true) {
+					result = regexp.exec(text);
+					if (result && result.index <= startIndex) {
+						match = {index: result.index, length: result[0].length};
+					} else {
+						return match;
+					}
+				}
+			} else {
+				result = regexp.exec(text.substring(startIndex));
+				return result && {index: result.index + startIndex, length: result[0].length};
+			}
+		},
+		
+		/**
+		 * @param {String} Input string
+		 * @return {pattern:String, flags:String} if str looks like a RegExp, or null otherwise
+		 */
+		parseRegExp: function(str) {
+			var regexp = /^\s*\/(.+)\/([gim]{0,3})\s*$/.exec(str);
+			if (regexp) {
+				return {pattern: regexp[1], flags: regexp[2]};
+			}
+			return null;
+		},
+		
+		installTextView : function() {
+			// Create textView and install optional features
+			this._textView = this._textViewFactory();
+			if (this._undoStackFactory) {
+				this._undoStack = this._undoStackFactory.createUndoStack(this);
+			}
+			if (this._contentAssistFactory) {
+				this._contentAssist = this._contentAssistFactory(this);
+				this._keyModes.push(this._contentAssist);
+			}
+			
+			var editor = this,
+				textView = this._textView;
+						
+			// Set up keybindings
+			if (this._keyBindingFactory) {
+				this._keyBindingFactory(this, this._keyModes, this._undoStack, this._contentAssist);
+			}
+			
+			// Set keybindings for keys that apply to different modes
+			textView.setKeyBinding(new orion.textview.KeyBinding(27), "Cancel Current Mode");
+			textView.setAction("Cancel Current Mode", dojo.hitch(this, function() {
+				for (var i=0; i<this._keyModes.length; i++) {
+					if (this._keyModes[i].isActive()) {
+						return this._keyModes[i].cancel();
+					}
+				}
+				return false;
+			}));
+
+			textView.setAction("lineUp", dojo.hitch(this, function() {
+				for (var i=0; i<this._keyModes.length; i++) {
+					if (this._keyModes[i].isActive()) {
+						return this._keyModes[i].lineUp();
+					}
+				}
+				return false;
+			}));
+			textView.setAction("lineDown", dojo.hitch(this, function() {
+				for (var i=0; i<this._keyModes.length; i++) {
+					if (this._keyModes[i].isActive()) {
+						return this._keyModes[i].lineDown();
+					}
+				}
+				return false;
+			}));
+						
+			/**@this {orion.editor.Editor} */
+			function updateCursorStatus() {
+				var model = textView.getModel();
+				var caretOffset = textView.getCaretOffset();
+				var lineIndex = model.getLineAtOffset(caretOffset);
+				var lineStart = model.getLineStart(lineIndex);
+				var offsetInLine = caretOffset - lineStart;
+				// If we are in a mode, we will bail out from reporting the cursor position.
+				for (var i=0; i<this._keyModes.length; i++) {
+					if (this._keyModes[i].isActive()) {
+						return;
+					}
+				}
+				this.reportStatus("Line " + (lineIndex + 1) + " : Col " + offsetInLine);
+			}
+			
+			// Listener for dirty state
+			textView.addEventListener("ModelChanged", this, this.checkDirty);
+					
+			//Adding selection changed listener
+			textView.addEventListener("Selection", this, updateCursorStatus);
+			
+			// Create rulers
+			if (this._annotationFactory) {
+				var annotations = this._annotationFactory.createAnnotationRulers();
+				this._annotationsRuler = annotations.annotationRuler;
+			
+				this._annotationsRuler.onClick = function(lineIndex, e) {
+					if (lineIndex === undefined) { return; }
+					if (lineIndex === -1) { return; }
+					var annotation = this.getAnnotation(lineIndex);
+					if (annotation === undefined) { return; }
+					editor.onGotoLine(annotation.line, annotation.column);
+				};
+				
+				this._overviewRuler = annotations.overviewRuler;
+				this._overviewRuler.onClick = function(lineIndex, e) {
+					if (lineIndex === undefined) { return; }
+					editor.moveSelection(textView, textView.getModel().getLineStart(lineIndex));
+				};
+			
+				textView.addRuler(this._annotationsRuler);
+				textView.addRuler(this._overviewRuler);
+			}
+			
+			if (this._lineNumberRulerFactory) {
+				this._lineNumberRuler = this._lineNumberRulerFactory.createLineNumberRuler();
+				textView.addRuler(this._lineNumberRuler);
+			}
+		},
+		
+		showSelection : function(start, end, line, offset, length) {
+			// We use typeof because we need to distinguish the number 0 from an undefined or null parameter
+			if (typeof(start) === "number") {
+				if (typeof(end) !== "number") {
+					end = start;
+				}
+				this.moveSelection(this._textView, start, end);
+			} else if (typeof(line) === "number") {
+				var pos = this._textView.getModel().getLineStart(line-1);
+				if (typeof(offset) === "number") {
+					pos = pos + offset;
+				}
+				if (typeof(length) !== "number") {
+					length = 0;
+				}
+				this.moveSelection(this._textView, pos, pos+length);
+			}
+		},
+		
+		onInputChange : function (title, message, contents, contentsSaved) {
+			if (contentsSaved && this._textView) {
+				// don't reset undo stack on save, just mark it clean so that we don't lose the undo past the save
+				this._undoStack.markClean();
+				this.checkDirty();
+				return;
+			}
+			if (this._textView) {
+				if (message) {
+					this._textView.setText(message);
+				} else {
+					if (contents !== null && contents !== undefined) {
+						this._textView.setText(contents);
+					}
+				}
+				this._undoStack.reset();
+				this.checkDirty();
+				this._textView.focus();
+			}
+		},
+		
+		onGotoLine : function (line, column, end) {
+			if (this._textView) {
+				var lineStart = this._textView.getModel().getLineStart(line);
+				if (typeof column === "string") {
+					var index = this._textView.getModel().getLine(line).indexOf(column);
+					if (index !== -1) {
+						end = index + column.length;
+						column = index;
+					} else {
+						column = 0;
+					}
+				}
+				var col = Math.min(this._textView.getModel().getLineEnd(line), column);
+				if (end===undefined) {
+					end = col;
+				}
+				var offset = lineStart + col;
+				this.moveSelection(this._textView, offset, lineStart + end);
+			}
+		},
+		
+		onDirtyChange: function(isDirty) {
+			this._dirty = isDirty;
+		}
+	};
+	return Editor;
+}());
+
+if (typeof window !== "undefined" && typeof window.define !== "undefined") {
+	define(['dojo', 'dijit', 'orion/textview/keyBinding', 'dijit/TitlePane', 'dijit/layout/ContentPane' ], function(){
+		return orion.editor;
+	});
+}
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/editor/editorFeatures.js b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/editor/editorFeatures.js
new file mode 100644
index 0000000..573c341
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/editor/editorFeatures.js
@@ -0,0 +1,798 @@
+/*******************************************************************************
+ * Copyright (c) 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials are made 
+ * available under the terms of the Eclipse Public License v1.0 
+ * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
+ * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+/*global window widgets eclipse:true orion:true serviceRegistry dojo dijit */
+/*jslint maxerr:150 browser:true devel:true regexp:false*/
+
+
+/**
+ * @namespace The global container for orion APIs.
+ */ 
+
+var orion = orion || {};
+orion.editor = orion.editor || {};	
+
+orion.editor.UndoFactory = (function() {
+	function UndoFactory() {
+	}
+	UndoFactory.prototype = {
+		createUndoStack: function(editor) {
+			var undoStack =  new orion.textview.UndoStack(editor.getTextView(), 200);
+			editor.getTextView().setKeyBinding(new orion.textview.KeyBinding('z', true), "Undo");
+			editor.getTextView().setAction("Undo", function() {
+				undoStack.undo();
+				return true;
+			});
+			
+			var isMac = navigator.platform.indexOf("Mac") !== -1;
+			editor.getTextView().setKeyBinding(isMac ? new orion.textview.KeyBinding('z', true, true) : new orion.textview.KeyBinding('y', true), "Redo");
+			editor.getTextView().setAction("Redo", function() {
+				undoStack.redo();
+				return true;
+			});
+			return undoStack;
+		}
+	};
+	return UndoFactory;
+}());
+
+orion.editor.LineNumberRulerFactory = (function() {
+	function LineNumberRulerFactory() {
+	}
+	LineNumberRulerFactory.prototype = {
+		createLineNumberRuler: function() {
+			return new orion.textview.LineNumberRuler("left", {style: {backgroundColor: "#ffffff", textAlign: "right", borderLeft:"1px solid #ddd", borderRight:"1px solid #ddd"}}, {style: { backgroundColor: "#ffffff" }}, {style: { backgroundColor: "#ffffff" }});
+		}
+	};
+	return LineNumberRulerFactory;
+}());
+
+
+orion.editor.AnnotationFactory = (function() {
+	function AnnotationFactory() {
+	}
+	AnnotationFactory.prototype = {
+		createAnnotationRulers: function() {
+			var rulerStyle = {style: { backgroundColor: "#ffffff" }};
+			this.annotationRuler = new orion.textview.AnnotationRuler("left", rulerStyle, {html: "<img src='/images/problem.gif'></img>"});
+			this.overviewRuler = new orion.textview.OverviewRuler("right", rulerStyle, this.annotationRuler);
+			return {annotationRuler: this.annotationRuler, overviewRuler: this.overviewRuler};
+		},
+		
+		showProblems : function(problems) {
+			var errors, i, k, escapedReason, functions;
+			errors = problems || [];
+			i = 0;
+			if (errors.length>0 && errors[errors.length - 1] === null) {
+				errors.pop();
+			}
+			var ruler = this.annotationRuler;
+			if (!ruler) {
+				return;
+			}
+			ruler.clearAnnotations();
+			var lastLine = -1;
+			for (k in errors) {
+				if (errors[k]) {
+					// escaping voodoo... we need to construct HTML that contains valid JavaScript.
+					escapedReason = errors[k].reason.replace(/'/g, "&#39;").replace(/"/g, '&#34;');
+					// console.log(escapedReason);
+					var annotation = {
+						line: errors[k].line - 1,
+						column: errors[k].character,
+						html: "<img src='/images/problem.gif' title='" + escapedReason + "' alt='" + escapedReason + "'></img>",
+						overviewStyle: {style: {"backgroundColor": "lightcoral", "border": "1px solid red"}}
+					};
+					
+					// only one error reported per line, unless we want to merge them.  
+					// For now, just show the first one, and the next one will show when the first is fixed...
+					if (lastLine !== errors[k].line) {
+						// console.log("adding annotation at line " + errors[k].line);
+						ruler.setAnnotation(errors[k].line - 1, annotation);
+						lastLine = errors[k].line;
+					}
+				}
+			}
+		}
+	};
+	return AnnotationFactory;
+}());
+
+/**
+ * TextCommands connects common text editing keybindings onto an editor.
+ */
+orion.editor.TextActions = (function() {
+	function TextActions(editor, undoStack) {
+		this.editor = editor;
+		this.textView = editor.getTextView();
+		this.undoStack = undoStack;
+		this._incrementalFindActive = false;
+		this._incrementalFindSuccess = true;
+		this._incrementalFindIgnoreSelection = false;
+		this._incrementalFindPrefix = "";
+
+		this.init();
+	}
+	TextActions.prototype = {
+		init: function() {
+			this._incrementalFindListener = {
+				onVerify: dojo.hitch(this, function(event){
+					var prefix = this._incrementalFindPrefix,
+						txt = this.textView.getText(event.start, event.end),
+						match = prefix.match(new RegExp("^"+dojo.regexp.escapeString(txt), "i"));
+					if (match && match.length > 0) {
+						prefix = this._incrementalFindPrefix += event.text;
+						this.editor.reportStatus("Incremental find: " + prefix);
+						var ignoreCase = prefix.toLowerCase() === prefix;
+						var result = this.editor.doFind(prefix, this.textView.getSelection().start, ignoreCase);
+						if (result) {
+							this._incrementalFindSuccess = true;
+							this._incrementalFindIgnoreSelection = true;
+							this.editor.moveSelection(this.textView, result.index, result.index+result.length);
+							this._incrementalFindIgnoreSelection = false;
+						} else {
+							this.editor.reportStatus("Incremental find: " + prefix + " (not found)", true);
+							this._incrementalFindSuccess = false;
+						}
+						event.text = null;
+					} else {
+					}
+				}),
+				onSelection: dojo.hitch(this, function() {
+					if (!this._incrementalFindIgnoreSelection) {
+						this.toggleIncrementalFind();
+					}
+				})
+			};
+			// Find actions
+			// These variables are used among the various find actions:
+			var searchString = "",
+			    pattern,
+			    flags;
+			this.textView.setKeyBinding(new orion.textview.KeyBinding("f", true), "Find...");
+			this.textView.setAction("Find...", dojo.hitch(this, function() {
+				setTimeout(dojo.hitch(this, function() {
+					var selection = this.textView.getSelection();
+					if (selection.end > selection.start) {
+						searchString = this.textView.getText().substring(selection.start, selection.end);
+					} else {
+						searchString = "";
+					}
+					searchString = prompt("Enter search term or /regex/:", searchString);
+					if (!searchString) {
+						return;
+					}
+					
+					var ignoreCase = searchString.toLowerCase() === searchString,
+					    regexp = this.editor.parseRegExp(searchString),
+					    result;
+					if (regexp) {
+						pattern = regexp.pattern;
+						flags = regexp.flags;
+						flags = flags + (ignoreCase && flags.indexOf("i") === -1 ? "i" : "");
+						result = this.editor.doFindRegExp(pattern, flags, this.textView.getCaretOffset());
+					} else {
+						pattern = null;
+						flags = null;
+						result = this.editor.doFind(searchString, this.textView.getCaretOffset(), ignoreCase);
+					}
+					
+					if (result) {
+						this.editor.moveSelection(this.textView, result.index, result.index+result.length);
+					} else {
+						this.editor.reportStatus("not found", true);
+					}
+				}), 0);
+				return true;
+			}));
+			this.textView.setKeyBinding(new orion.textview.KeyBinding("k", true), "Find Next Occurrence");
+			this.textView.setAction("Find Next Occurrence", dojo.hitch(this, function() {
+				var result, ignoreCase, selection;
+				if (this._incrementalFindActive) {
+					var str = this._incrementalFindPrefix;
+					ignoreCase = str.toLowerCase() === str;
+					result = this.editor.doFind(str, this.textView.getCaretOffset(), ignoreCase);
+				} else if (pattern) {
+					// RegExp search
+					result = this.editor.doFindRegExp(pattern, flags, this.textView.getCaretOffset());
+				} else {
+					// use selection if there is one, otherwise use last stored string.  
+					// Since we aren't sure how/why text is highlighted, we will always ignore case.
+					// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=342334
+					selection = this.textView.getSelection();
+					if (selection.end > selection.start) {
+						searchString = this.textView.getText().substring(selection.start, selection.end);
+					}
+					result = this.editor.doFind(searchString, this.textView.getCaretOffset(), true);
+				}
+				
+				if (result) {
+					this._incrementalFindIgnoreSelection = true;
+					this.editor.moveSelection(this.textView, result.index, result.index+result.length);
+					this._incrementalFindIgnoreSelection = false;
+				} else {
+					this.editor.reportStatus("not found", true);
+				}
+				return true;
+			}));
+			this.textView.setKeyBinding(new orion.textview.KeyBinding("k", true, true), "Find Previous Occurrence");
+			this.textView.setAction("Find Previous Occurrence", dojo.hitch(this, function() {
+				var selection = this.textView.getSelection();
+				var selectionSize = (selection.end > selection.start) ? selection.end - selection.start : 0;
+				var result, ignoreCase;
+				if (this._incrementalFindActive) {
+					var str = this._incrementalFindPrefix;
+					ignoreCase = str.toLowerCase() === str;
+					result = this.editor.doFind(str, this.textView.getCaretOffset() - selectionSize - 1, ignoreCase, true);
+				} else if (pattern) {
+					// RegExp search
+					result = this.editor.doFindRegExp(pattern, flags, this.textView.getCaretOffset() - selectionSize - 1, true);
+				} else {
+					if (selectionSize > 0) {
+						searchString = this.textView.getText().substring(selection.start, selection.end);
+					}
+					result = this.editor.doFind(searchString, this.textView.getCaretOffset() - selectionSize - 1, true, true);
+				}
+				
+				if (result) {
+					this._incrementalFindIgnoreSelection = true;
+					this.editor.moveSelection(this.textView, result.index, result.index+result.length);
+					this._incrementalFindIgnoreSelection = false;
+				} else {
+					this.editor.reportStatus("not found", true);
+				}
+				return true;
+			}));
+			this.textView.setKeyBinding(new orion.textview.KeyBinding("j", true), "Incremental Find");
+			this.textView.setAction("Incremental Find", dojo.hitch(this, function() {
+				if (!this._incrementalFindActive) {
+					this.textView.setCaretOffset(this.textView.getCaretOffset());
+					this.toggleIncrementalFind();
+				} else {
+					var p = this._incrementalFindPrefix;
+					if (p.length !== 0) {
+						var start = this.textView.getSelection().start + 1;
+						if (this._incrementalFindSuccess === false) {
+							start = 0;
+						}
+						
+						var caseInsensitive = p.toLowerCase() === p;
+						var result = this.editor.doFind(p, start, caseInsensitive);
+						if (result) {
+							this._incrementalFindSuccess = true;
+							this._incrementalFindIgnoreSelection = true;
+							this.editor.moveSelection(this.textView, result.index, result.index + result.length);
+							this._incrementalFindIgnoreSelection = false;
+							this.editor.reportStatus("Incremental find: " + p);
+						} else {
+							this.editor.reportStatus("Incremental find: " + p + " (not found)", true);
+							this._incrementalFindSuccess = false;
+						}
+					}
+				}
+				return true;
+			}));
+			this.textView.setAction("deletePrevious", dojo.hitch(this, function() {
+				if (this._incrementalFindActive) {
+					var p = this._incrementalFindPrefix;
+					p = this._incrementalFindPrefix = p.substring(0, p.length-1);
+					if (p.length===0) {
+						this._incrementalFindSuccess = true;
+						this._incrementalFindIgnoreSelection = true;
+						this.textView.setCaretOffset(this.textView.getSelection().start);
+						this._incrementalFindIgnoreSelection = false;
+						this.toggleIncrementalFind();
+						return true;
+					}
+					this.editor.reportStatus("Incremental find: " + p);
+					var index = this.textView.getText().lastIndexOf(p, this.textView.getCaretOffset() - p.length - 1);
+					if (index !== -1) {
+						this._incrementalFindSuccess = true;
+						this._incrementalFindIgnoreSelection = true;
+						this.editor.moveSelection(this.textView, index,index+p.length);
+						this._incrementalFindIgnoreSelection = false;
+					} else {
+						this.editor.reportStatus("Incremental find: " + p + " (not found)", true);
+					}
+					return true;
+				} else {
+					return false;
+				}
+			}));
+			
+			// Tab actions
+			this.textView.setAction("tab", dojo.hitch(this, function() {
+				var selection = this.textView.getSelection();
+				var model = this.textView.getModel();
+				var firstLine = model.getLineAtOffset(selection.start);
+				var lastLine = model.getLineAtOffset(selection.end>selection.start?selection.end - 1:selection.end);
+				if (firstLine !== lastLine) {
+					var lines = [];
+					lines.push("");
+					for (var i = firstLine; i <= lastLine; i++) {
+						lines.push(model.getLine(i, true));
+					}
+					this.startUndo();
+					var firstLineStart = model.getLineStart(firstLine);
+					this.textView.setText(lines.join("\t"), firstLineStart, model.getLineEnd(lastLine, true));
+					this.textView.setSelection(firstLineStart===selection.start?selection.start:selection.start + 1, selection.end + (lastLine - firstLine + 1));
+					this.endUndo();
+					return true;
+				}
+				return false;
+			}));
+			this.textView.setKeyBinding(new orion.textview.KeyBinding(9, false, true), "Unindent Lines");
+			this.textView.setAction("Unindent Lines", dojo.hitch(this, function() {
+				var selection = this.textView.getSelection();
+				var model = this.textView.getModel();
+				var firstLine = model.getLineAtOffset(selection.start);
+				var lastLine = model.getLineAtOffset(selection.end>selection.start?selection.end - 1:selection.end);
+				var lines = [];
+				for (var i = firstLine; i <= lastLine; i++) {
+					var line = model.getLine(i, true);
+					if (line.indexOf("\t") !== 0) { return false; }
+					lines.push(line.substring(1));
+				}
+				this.startUndo();
+				var firstLineStart = model.getLineStart(firstLine);
+				var lastLineStart = model.getLineStart(lastLine);
+				this.textView.setText(lines.join(""), firstLineStart, model.getLineEnd(lastLine, true));
+				this.textView.setSelection(firstLineStart===selection.start?selection.start:selection.start - 1, selection.end - (lastLine - firstLine + 1) + (selection.end===lastLineStart+1?1:0));
+				this.endUndo();
+				return true;
+			}));
+			
+			this.textView.setKeyBinding(new orion.textview.KeyBinding(38, false, false, true), "Move Lines Up");
+			this.textView.setAction("Move Lines Up", dojo.hitch(this, function() {
+				var selection = this.textView.getSelection();
+				var model = this.textView.getModel();
+				var firstLine = model.getLineAtOffset(selection.start);
+				if (firstLine===0) {
+					return true;
+				}
+				this.startUndo();
+				var lastLine = model.getLineAtOffset(selection.end>selection.start?selection.end - 1:selection.end);
+				var isMoveFromLastLine = model.getLineCount()-1===lastLine;
+				var lineStart = model.getLineStart(firstLine);
+				var lineEnd = isMoveFromLastLine?model.getCharCount():model.getLineStart(lastLine+1);
+				if (isMoveFromLastLine) {
+					// Move delimiter preceding selection to end
+					var delimiterStart = model.getLineEnd(firstLine-1);
+					var delimiterEnd = model.getLineEnd(firstLine-1, true);
+					var delimiter = model.getText(delimiterStart, delimiterEnd);
+					lineStart = delimiterStart;
+					model.setText(model.getText(delimiterEnd, lineEnd)+delimiter, lineStart, lineEnd);
+				}
+				var text = model.getText(lineStart, lineEnd);
+				model.setText("", lineStart, lineEnd);
+				var insertPos = model.getLineStart(firstLine-1);
+				model.setText(text, insertPos, insertPos);
+				var selectionEnd = insertPos+text.length-(isMoveFromLastLine?model.getLineDelimiter().length:0);
+				this.textView.setSelection(insertPos, selectionEnd);
+				this.endUndo();
+				return true;
+			}));
+			
+			this.textView.setKeyBinding(new orion.textview.KeyBinding(40, false, false, true), "Move Lines Down");
+			this.textView.setAction("Move Lines Down", dojo.hitch(this, function() {
+				var selection = this.textView.getSelection();
+				var model = this.textView.getModel();
+				var firstLine = model.getLineAtOffset(selection.start);
+				var lastLine = model.getLineAtOffset(selection.end>selection.start?selection.end - 1:selection.end);
+				if (lastLine===model.getLineCount()-1) {
+					return true;
+				}
+				this.startUndo();
+				var isMoveIntoLastLine = lastLine===model.getLineCount()-2;
+				var lineStart = model.getLineStart(firstLine);
+				var lineEnd = model.getLineStart(lastLine+1);
+				if (isMoveIntoLastLine) {
+					// Move delimiter following selection to front
+					var delimiterStart = model.getLineStart(lastLine+1)-model.getLineDelimiter().length;
+					var delimiterEnd = model.getLineStart(lastLine+1);
+					var delimiter = model.getText(delimiterStart, delimiterEnd);
+					model.setText(delimiter + model.getText(lineStart, delimiterStart), lineStart, lineEnd);
+				}
+				var text = model.getText(lineStart, lineEnd);
+				var insertPos = (isMoveIntoLastLine?model.getCharCount():model.getLineStart(lastLine+2))-(lineEnd-lineStart);
+				model.setText("", lineStart, lineEnd);
+				model.setText(text, insertPos, insertPos);
+				var selStart = insertPos+(isMoveIntoLastLine?model.getLineDelimiter().length:0);
+				var selEnd = insertPos+text.length;
+				this.textView.setSelection(selStart, selEnd);
+				this.endUndo();
+				return true;
+			}));
+			
+			this.textView.setKeyBinding(new orion.textview.KeyBinding(38, true, false, true), "Copy Lines Up");
+			this.textView.setAction("Copy Lines Up", dojo.hitch(this, function() {
+				this.startUndo();
+				var selection = this.textView.getSelection();
+				var model = this.textView.getModel();
+				var delimiter = model.getLineDelimiter();
+				var firstLine = model.getLineAtOffset(selection.start);
+				var lastLine = model.getLineAtOffset(selection.end>selection.start?selection.end - 1:selection.end);
+				var lineStart = model.getLineStart(firstLine);
+				var isCopyFromLastLine = model.getLineCount()-1===lastLine;
+				var lineEnd = isCopyFromLastLine?model.getCharCount():model.getLineStart(lastLine+1);
+				var text = model.getText(lineStart, lineEnd)+(isCopyFromLastLine?delimiter:""); //+ delimiter;
+				//var insertPos = model.getLineStart(firstLine - 1);
+				var insertPos = lineStart;
+				model.setText(text, insertPos, insertPos);
+				this.textView.setSelection(insertPos, insertPos+text.length-(isCopyFromLastLine?delimiter.length:0));
+				this.endUndo();
+				return true;
+			}));
+			
+			this.textView.setKeyBinding(new orion.textview.KeyBinding(40, true, false, true), "Copy Lines Down");
+			this.textView.setAction("Copy Lines Down", dojo.hitch(this, function() {
+				this.startUndo();
+				var selection = this.textView.getSelection();
+				var model = this.textView.getModel();
+				var delimiter = model.getLineDelimiter();
+				var firstLine = model.getLineAtOffset(selection.start);
+				var lastLine = model.getLineAtOffset(selection.end>selection.start?selection.end - 1:selection.end);
+				var lineStart = model.getLineStart(firstLine);
+				var isCopyFromLastLine = model.getLineCount()-1===lastLine;
+				var lineEnd = isCopyFromLastLine?model.getCharCount():model.getLineStart(lastLine+1);
+				var text = (isCopyFromLastLine?delimiter:"")+model.getText(lineStart, lineEnd);
+				//model.setText("", lineStart, lineEnd);
+				//var insertPos = model.getLineStart(firstLine - 1);
+				var insertPos = lineEnd;
+				model.setText(text, insertPos, insertPos);
+				this.textView.setSelection(insertPos+(isCopyFromLastLine?delimiter.length:0), insertPos+text.length);
+				this.endUndo();
+				return true;
+			}));
+			
+			this.textView.setKeyBinding(new orion.textview.KeyBinding('d', true, false, false), "Delete Selected Lines");
+			this.textView.setAction("Delete Selected Lines", dojo.hitch(this, function() {
+				this.startUndo();
+				var selection = this.textView.getSelection();
+				var model = this.textView.getModel();
+				var firstLine = model.getLineAtOffset(selection.start);
+				var lastLine = model.getLineAtOffset(selection.end>selection.start?selection.end - 1:selection.end);
+				var lineStart = model.getLineStart(firstLine);
+				var lineEnd = model.getLineCount()-1===lastLine?model.getCharCount():model.getLineStart(lastLine+1);
+				model.setText("", lineStart, lineEnd);
+				this.endUndo();
+				return true;
+			}));
+			
+			// Go To Line action
+			this.textView.setKeyBinding(new orion.textview.KeyBinding("l", true), "Goto Line...");
+			this.textView.setAction("Goto Line...", dojo.hitch(this, function() {
+				var line = this.textView.getModel().getLineAtOffset(this.textView.getCaretOffset());
+				line = prompt("Go to line:", line + 1);
+				if (line) {
+					line = parseInt(line, 10);
+					this.editor.onGotoLine(line-1, 0);
+				}
+				return true;
+			}));
+			
+		},
+			
+		toggleIncrementalFind: function() {
+			this._incrementalFindActive = !this._incrementalFindActive;
+			if (this._incrementalFindActive) {
+				this.editor.reportStatus("Incremental find: " + this._incrementalFindPrefix);
+				this.textView.addEventListener("Verify", this, this._incrementalFindListener.onVerify);
+				this.textView.addEventListener("Selection", this, this._incrementalFindListener.onSelection);
+			} else {
+				this._incrementalFindPrefix = "";
+				this.editor.reportStatus("");
+				this.textView.removeEventListener("Verify", this, this._incrementalFindListener.onVerify);
+				this.textView.removeEventListener("Selection", this, this._incrementalFindListener.onSelection);
+				this.textView.setCaretOffset(this.textView.getCaretOffset());
+			}
+		},
+		
+		startUndo: function() {
+			if (this.undoStack) {
+				this.undoStack.startCompoundChange();
+			}
+		}, 
+		
+		endUndo: function() {
+			if (this.undoStack) {
+				this.undoStack.endCompoundChange();
+			}
+		}, 
+	
+		cancel: function() {
+			this.toggleIncrementalFind();
+		},
+		
+		isActive: function() {
+			return this._incrementalFindActive;
+		},
+		
+		lineUp: function() {
+			var index;
+			if (this._incrementalFindActive) {
+				var p = this._incrementalFindPrefix;
+				var start = this.textView.getCaretOffset() - p.length - 1;
+				if (this._incrementalFindSuccess === false) {
+					start = this.textView.getModel().getCharCount() - 1;
+				}
+				index = this.textView.getText().lastIndexOf(p, start);
+				if (index !== -1) {
+					this._incrementalFindSuccess = true;
+					this._incrementalFindIgnoreSelection = true;
+					this.editor.moveSelection(this.textView, index,index+p.length);
+					this._incrementalFindIgnoreSelection = false;
+				} else {
+					this.editor.reportStatus("Incremental find: " + p + " (not found)", true);	
+					this._incrementalFindSuccess = false;
+				}
+				return true;
+			}
+			return false;
+		},
+		lineDown: function() {	
+			var index;
+			if (this._incrementalFindActive) {
+				var p = this._incrementalFindPrefix;
+				if (p.length===0) {
+					return;
+				}
+				var start = this.textView.getSelection().start + 1;
+				if (this._incrementalFindSuccess === false) {
+					start = 0;
+				}
+				index = this.textView.getText().indexOf(p, start);
+				if (index !== -1) {
+					this._incrementalFindSuccess = true;
+					this._incrementalFindIgnoreSelection = true;
+					this.editor.moveSelection(this.textView, index, index+p.length);
+					this._incrementalFindIgnoreSelection = false;
+					this.editor.reportStatus("Incremental find: " + p);
+				} else {
+					this.editor.reportStatus("Incremental find: " + p + " (not found)", true);
+					this._incrementalFindSuccess = false;
+				}
+				return true;
+			}
+			return false;
+		},
+		enter: function() {
+			return false;
+		}
+	};
+	return TextActions;
+}());
+
+orion.editor.SourceCodeActions = (function() {
+	function SourceCodeActions(editor, undoStack, contentAssist) {
+		this.editor = editor;
+		this.textView = editor.getTextView();
+		this.undoStack = undoStack;
+		this.contentAssist = contentAssist;
+		this.init();
+	}
+	SourceCodeActions.prototype = {
+		startUndo: function() {
+			if (this.undoStack) {
+				this.undoStack.startCompoundChange();
+			}
+		}, 
+		
+		endUndo: function() {
+			if (this.undoStack) {
+				this.undoStack.endCompoundChange();
+			}
+		}, 
+		init: function() {
+		
+			// Block comment operations
+			this.textView.setKeyBinding(new orion.textview.KeyBinding(191, true), "Toggle Line Comment");
+			this.textView.setAction("Toggle Line Comment", dojo.hitch(this, function() {
+				this.startUndo();
+				var selection = this.textView.getSelection();
+				var model = this.textView.getModel();
+				var firstLine = model.getLineAtOffset(selection.start);
+				var lastLine = model.getLineAtOffset(selection.end>selection.start?selection.end - 1:selection.end);
+				var uncomment = true;
+				var lineText;
+				for (var i = firstLine; i <= lastLine && uncomment; i++) {
+					lineText = this.textView.getModel().getLine(i);
+					var index = lineText.indexOf("//");
+					if (index === -1) {
+						uncomment = false;
+					} else {
+						if (index !== 0) {
+							var j;
+							for (j=0; j<index; j++) {
+								var c = lineText.charCodeAt(j);
+								if (!(c === 32 || c === 9)) {
+									break;
+								}
+							}
+							uncomment = j === index;
+						}
+					}
+				}
+				var k, lines = [];
+				var firstLineStart = model.getLineStart(firstLine);
+				if (uncomment) {
+					var lastLineStart = model.getLineStart(lastLine);
+					for (k = firstLine; k <= lastLine; k++) {
+						var line = model.getLine(k, true);
+						var commentIndex = lineText.indexOf("//");
+						lines.push(line.substring(0, commentIndex) + line.substring(commentIndex + 2));
+					}
+					this.textView.setText(lines.join(""), firstLineStart, model.getLineEnd(lastLine, true));
+					this.textView.setSelection(firstLineStart===selection.start?selection.start:selection.start - 2, selection.end - (2 * (lastLine - firstLine + 1)) + (selection.end===lastLineStart+1?2:0));
+				} else {
+					lines.push("");
+					for (k = firstLine; k <= lastLine; k++) {
+						lines.push(model.getLine(k, true));
+					}
+					this.textView.setText(lines.join("//"), firstLineStart, model.getLineEnd(lastLine, true));
+					this.textView.setSelection(firstLineStart===selection.start?selection.start:selection.start + 2, selection.end + (2 * (lastLine - firstLine + 1)));
+				}
+				this.endUndo();
+				return true;
+			}));
+			
+			function findEnclosingComment(model, start, end) {
+				var open = "/*", close = "*/";
+				var firstLine = model.getLineAtOffset(start);
+				var lastLine = model.getLineAtOffset(end);
+				var i, line, extent, openPos, closePos;
+				var commentStart, commentEnd;
+				for (i=firstLine; i >= 0; i--) {
+					line = model.getLine(i);
+					extent = (i === firstLine) ? start - model.getLineStart(firstLine) : line.length;
+					openPos = line.lastIndexOf(open, extent);
+					closePos = line.lastIndexOf(close, extent);
+					if (closePos > openPos) {
+						break; // not inside a comment
+					} else if (openPos !== -1) {
+						commentStart = model.getLineStart(i) + openPos;
+						break;
+					}
+				}
+				for (i=lastLine; i < model.getLineCount(); i++) {
+					line = model.getLine(i);
+					extent = (i === lastLine) ? end - model.getLineStart(lastLine) : 0;
+					openPos = line.indexOf(open, extent);
+					closePos = line.indexOf(close, extent);
+					if (openPos !== -1 && openPos < closePos) {
+						break;
+					} else if (closePos !== -1) {
+						commentEnd = model.getLineStart(i) + closePos;
+						break;
+					}
+				}
+				return {commentStart: commentStart, commentEnd: commentEnd};
+			}
+			
+			this.textView.setKeyBinding(new orion.textview.KeyBinding(191, true, true), "Add Block Comment");
+			this.textView.setAction("Add Block Comment", dojo.hitch(this, function() {
+				var selection = this.textView.getSelection();
+				var model = this.textView.getModel();
+				var open = "/*", close = "*/", commentTags = new RegExp("/\\*" + "|" + "\\*/", "g");
+				var firstLine = model.getLineAtOffset(selection.start);
+				var lastLine = model.getLineAtOffset(selection.end);
+				
+				var result = findEnclosingComment(model, selection.start, selection.end);
+				if (result.commentStart !== undefined && result.commentEnd !== undefined) {
+					return true; // Already in a comment
+				}
+				
+				var text = model.getText(selection.start, selection.end);
+				if (text.length === 0) { return true; }
+				
+				var oldLength = text.length;
+				text = text.replace(commentTags, "");
+				var newLength = text.length;
+				
+				this.startUndo();
+				model.setText(open + text + close, selection.start, selection.end);
+				this.textView.setSelection(selection.start + open.length, selection.end + open.length + (newLength-oldLength));
+				this.endUndo();
+				return true;
+			}));
+			
+			this.textView.setKeyBinding(new orion.textview.KeyBinding(220, true, true), "Remove Block Comment");
+			this.textView.setAction("Remove Block Comment", dojo.hitch(this, function() {
+				var selection = this.textView.getSelection();
+				var model = this.textView.getModel();
+				var open = "/*", close = "*/";
+				var firstLine = model.getLineAtOffset(selection.start);
+				var lastLine = model.getLineAtOffset(selection.end);
+				
+				// Try to shrink selection to a comment block
+				var selectedText = model.getText(selection.start, selection.end);
+				var newStart, newEnd;
+				var i;
+				for(i=0; i < selectedText.length; i++) {
+					if (selectedText.substring(i, i + open.length) === open) {
+						newStart = selection.start + i;
+						break;
+					}
+				}
+				for (; i < selectedText.length; i++) {
+					if (selectedText.substring(i, i + close.length) === close) {
+						newEnd = selection.start + i;
+						break;
+					}
+				}
+				
+				this.startUndo();
+				if (newStart !== undefined && newEnd !== undefined) {
+					model.setText(model.getText(newStart + open.length, newEnd), newStart, newEnd + close.length);
+					this.textView.setSelection(newStart, newEnd);
+				} else {
+					// Otherwise find enclosing comment block
+					var result = findEnclosingComment(model, selection.start, selection.end);
+					if (result.commentStart === undefined || result.commentEnd === undefined) {
+						return true;
+					}
+					
+					var text = model.getText(result.commentStart + open.length, result.commentEnd);
+					model.setText(text, result.commentStart, result.commentEnd + close.length);
+					this.textView.setSelection(selection.start - open.length, selection.end - close.length);
+				}
+				this.endUndo();
+				return true;
+			}));
+			
+			//Auto indent
+			this.textView.setAction("enter", dojo.hitch(this, function() {
+				if (this.contentAssist && this.contentAssist.isActive()) {
+					return this.contentAssist.enter();
+				}
+				var selection = this.textView.getSelection();
+				if (selection.start === selection.end) {
+					var model = this.textView.getModel();
+					var lineIndex = model.getLineAtOffset(selection.start);
+					var lineText = model.getLine(lineIndex);
+					var lineStart = model.getLineStart(lineIndex);
+					var index = 0, end = selection.start - lineStart, c;
+					while (index < end && ((c = lineText.charCodeAt(index)) === 32 || c === 9)) { index++; }
+					if (index > 0) {
+						var prefix = lineText.substring(0, index);
+						index = end;
+						while (index < lineText.length && ((c = lineText.charCodeAt(index++)) === 32 || c === 9)) { selection.end++; }
+						this.textView.setText(model.getLineDelimiter() + prefix, selection.start, selection.end);
+						return true;
+					}
+				}
+				return false;
+			}));
+		},
+	
+		cancel: function() {
+			return false;
+		},
+		isActive: function() {
+			return false;  // we have no modal interactions
+		},
+		lineUp: function() {
+			return false;
+		},
+		lineDown: function() {
+			return false;
+		},
+		enter: function() {
+			return false;
+		}
+	};
+	return SourceCodeActions;
+}());
+
+if (typeof window !== "undefined" && typeof window.define !== "undefined") {
+	define(['dojo', 'orion/textview/undoStack', 'orion/textview/keyBinding', 'orion/textview/rulers'], function() {
+		return orion.editor;
+	});
+}
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/editor/htmlGrammar.js b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/editor/htmlGrammar.js
new file mode 100644
index 0000000..bcbc5f5
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/editor/htmlGrammar.js
@@ -0,0 +1,112 @@
+/******************************************************************************* 
+ * Copyright (c) 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials are made 
+ * available under the terms of the Eclipse Public License v1.0 
+ * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
+ * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
+ * 
+ * Contributors: IBM Corporation - initial API and implementation 
+ ******************************************************************************/
+
+/*jslint */
+/*global dojo orion:true*/
+
+var orion = orion || {};
+
+orion.editor = orion.editor || {};
+
+/**
+ * Uses a grammar to provide some very rough syntax highlighting for HTML.<p>
+ * @class orion.syntax.HtmlGrammar
+ */
+orion.editor.HtmlGrammar = (function() {
+	var _fileTypes = [ "html", "htm" ];
+	return {
+		/**
+		 * What kind of highlight provider we are.
+		 * @public
+		 * @type "grammar"|"parser"
+		 */
+		type: "grammar",
+		
+		/**
+		 * The file extensions that we provide rules for.
+		 * @public
+		 * @type String[]
+		 */
+		fileTypes: _fileTypes,
+		
+		/**
+		 * Object containing the grammar rules.
+		 * @public
+		 * @type JSONObject
+		 */
+		grammar: {
+			"comment": "HTML syntax rules",
+			"name": "HTML",
+			"fileTypes": _fileTypes,
+			"scopeName": "source.html",
+			"uuid": "3B5C76FB-EBB5-D930-F40C-047D082CE99B",
+			"patterns": [
+				// TODO unicode?
+				{
+					"match": "<!(doctype|DOCTYPE)[^>]+>",
+					"name": "entity.name.tag.doctype.html"
+				},
+				{
+					"begin": "<!--",
+					"end": "-->",
+					"beginCaptures": {
+						"0": { "name": "punctuation.definition.comment.html" }
+					},
+					"endCaptures": {
+						"0": { "name": "punctuation.definition.comment.html" }
+					},
+					"patterns": [
+						{
+							// For testing nested subpatterns
+							"match": "--",
+							"name": "invalid.illegal.badcomment.html"
+						}
+					],
+					"contentName": "comment.block.html"
+				},
+				{ // startDelimiter + tagName
+					"match": "<[A-Za-z0-9_\\-:]+(?= ?)",
+					"name": "entity.name.tag.html"
+				},
+				{ "include": "#attrName" },
+				{ "include": "#qString" },
+				{ "include": "#qqString" },
+				// TODO attrName, qString, qqString should be applied first while inside a tag
+				{ // startDelimiter + slash + tagName + endDelimiter
+					"match": "</[A-Za-z0-9_\\-:]+>",
+					"name": "entity.name.tag.html"
+				},
+				{ // end delimiter of open tag
+					"match": ">", 
+					"name": "entity.name.tag.html"
+				} ],
+			"repository": {
+				"attrName": { // attribute name
+					"match": "[A-Za-z\\-:]+(?=\\s*=\\s*['\"])",
+					"name": "entity.other.attribute.name.html"
+				},
+				"qqString": { // double quoted string
+					"match": "(\")[^\"]+(\")",
+					"name": "string.quoted.double.html"
+				},
+				"qString": { // single quoted string
+					"match": "(')[^']+(\')",
+					"name": "string.quoted.single.html"
+				}
+			}
+		}
+	};
+}());
+
+if (typeof window !== "undefined" && typeof window.define !== "undefined") {
+	define([], function() {
+		return orion.editor;
+	});
+}
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/editor/textMateStyler.js b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/editor/textMateStyler.js
new file mode 100644
index 0000000..6baa882
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/editor/textMateStyler.js
@@ -0,0 +1,1322 @@
+/******************************************************************************* 
+ * Copyright (c) 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials are made 
+ * available under the terms of the Eclipse Public License v1.0 
+ * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
+ * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
+ * 
+ * Contributors: IBM Corporation - initial API and implementation 
+ ******************************************************************************/
+
+/*jslint regexp:false laxbreak:true*/
+/*global define dojo window*/
+
+var orion = orion || {};
+orion.editor = orion.editor || {};
+
+/**
+ * A styler that does nothing, but can be extended by concrete stylers. Extenders can call 
+ * {@link orion.editor.AbstractStyler.extend} and provide their own {@link #_onSelection}, 
+ * {@link #_onModelChanged}, {@link #_onDestroy} and {@link #_onLineStyle} methods.
+ * @class orion.editor.AbstractStyler
+ */
+orion.editor.AbstractStyler = (function() {
+	/** @inner */
+	function AbstractStyler() {
+	}
+	AbstractStyler.prototype = /** @lends orion.editor.AbstractStyler.prototype */ {
+		/**
+		 * Initializes this styler with a textView. Extenders <b>must</b> call this from their constructor.
+		 * @param {orion.textview.TextView} textView
+		 */
+		initialize: function(textView) {
+			this.textView = textView;
+			
+			textView.addEventListener("Selection", this, this._onSelection);
+			textView.addEventListener("ModelChanged", this, this._onModelChanged);
+			textView.addEventListener("Destroy", this, this._onDestroy);
+			textView.addEventListener("LineStyle", this, this._onLineStyle);
+			textView.redrawLines();
+		},
+		
+		/**
+		 * Destroys this styler and removes all listeners. Called by the editor.
+		 */
+		destroy: function() {
+			if (this.textView) {
+				this.textView.removeEventListener("Selection", this, this._onSelection);
+				this.textView.removeEventListener("ModelChanged", this, this._onModelChanged);
+				this.textView.removeEventListener("Destroy", this, this._onDestroy);
+				this.textView.removeEventListener("LineStyle", this, this._onLineStyle);
+				this.textView = null;
+			}
+		},
+		
+		/** To be overridden by subclass.
+		 * @public
+		 */
+		_onSelection: function(/**eclipse.SelectionEvent*/ e) {},
+		
+		/** To be overridden by subclass.
+		 * @public
+		 */
+		_onModelChanged: function(/**eclipse.ModelChangedEvent*/ e) {},
+		
+		/** To be overridden by subclass.
+		 * @public
+		 */
+		_onDestroy: function(/**eclipse.DestroyEvent*/ e) {},
+		
+		/** To be overridden by subclass.
+		 * @public
+		 */
+		_onLineStyle: function(/**eclipse.LineStyleEvent*/ e) {}
+	};
+	
+	return AbstractStyler;
+}());
+
+/**
+ * Helper for extending AbstractStyler.
+ * @methodOf orion.editor.AbstractStyler
+ * @static
+ * @param {Function} subCtor The constructor function for the subclass.
+ * @param {Object} [proto] Object to be mixed into the subclass's prototype. This object can contain your 
+ * implementation of _onSelection, _onModelChanged, etc.
+ * @see orion.editor.TextMateStyler for example usage.
+ */
+orion.editor.AbstractStyler.extend = function(subCtor, proto) {
+	if (typeof(subCtor) !== "function") { throw new Error("Function expected"); }
+	subCtor.prototype = new orion.editor.AbstractStyler();
+	subCtor.constructor = subCtor;
+	for (var p in proto) {
+		if (proto.hasOwnProperty(p)) { subCtor.prototype[p] = proto[p]; }
+	}
+};
+
+orion.editor.RegexUtil = {
+	// Rules to detect some unsupported Oniguruma features
+	unsupported: [
+		{regex: /\(\?[ims\-]:/, func: function(match) { return "option on/off for subexp"; }},
+		{regex: /\(\?<([=!])/, func: function(match) { return (match[1] === "=") ? "lookbehind" : "negative lookbehind"; }},
+		{regex: /\(\?>/, func: function(match) { return "atomic group"; }}
+	],
+	
+	/**
+	 * @param {String} str String giving a regular expression pattern from a TextMate grammar.
+	 * @param {String} [flags] [ismg]+
+	 * @returns {RegExp}
+	 */
+	toRegExp: function(str) {
+		function fail(feature, match) {
+			throw new Error("Unsupported regex feature \"" + feature + "\": \"" + match[0] + "\" at index: "
+					+ match.index + " in " + match.input);
+		}
+		function getMatchingCloseParen(str, start) {
+			var depth = 0,
+			    len = str.length,
+			    xStop = -1;
+			for (var i=start; i < len && xStop === -1; i++) {
+				switch (str[i]) {
+					case "\\":
+						i += 1; // skip next char
+						break;
+					case "(":
+						depth++;
+						break;
+					case ")":
+						depth--;
+						if (depth === 0) {
+							xStop = i;
+						}
+						break;
+				}
+			}
+			return xStop;
+		}
+		// Turns an extended regex into a normal one
+		function normalize(/**String*/ str) {
+			var result = "";
+			var insideCharacterClass = false;
+			var len = str.length;
+			for (var i=0; i < len; ) {
+				var chr = str[i];
+				if (!insideCharacterClass && chr === "#") {
+					// skip to eol
+					while (i < len && chr !== "\r" && chr !== "\n") {
+						chr = str[++i];
+					}
+				} else if (!insideCharacterClass && /\s/.test(chr)) {
+					// skip whitespace
+					while (i < len && /\s/.test(chr)) { 
+						chr = str[++i];
+					}
+				} else if (chr === "\\") {
+					result += chr;
+					if (!/\s/.test(str[i+1])) {
+						result += str[i+1];
+						i += 1;
+					}
+					i += 1;
+				} else if (chr === "[") {
+					insideCharacterClass = true;
+					result += chr;
+					i += 1;
+				} else if (chr === "]") {
+					insideCharacterClass = false;
+					result += chr;
+					i += 1;
+				} else {
+					result += chr;
+					i += 1;
+				}
+			}
+			return result;
+		}
+		
+		var flags = "";
+		var i;
+		
+		// Check for unsupported syntax
+		for (i=0; i < this.unsupported.length; i++) {
+			var match;
+			if ((match = this.unsupported[i].regex.exec(str))) {
+				fail(this.unsupported[i].func(match));
+			}
+		}
+		
+		// Deal with "x" flag (whitespace/comments)
+		if (str.substring(0, 4) === "(?x)") {
+			// Leading (?x) term (means "x" flag applies to entire regex)
+			str = normalize(str.substring(4));
+		} else if (str.substring(0, 4) === "(?x:") {
+			// Regex wrapped in a (?x: ...) -- again "x" applies to entire regex
+			var xStop = getMatchingCloseParen(str, 0);
+			if (xStop < str.length-1) {
+				throw new Error("Only a (?x:) group that encloses the entire regex is supported: " + str);
+			}
+			str = normalize(str.substring(4, xStop));
+		}
+		// TODO: tolerate /(?iSubExp)/ -- eg. in PHP grammar (trickier)
+		return new RegExp(str, flags);
+	},
+	
+	hasBackReference: function(/**RegExp*/ regex) {
+		return (/\\\d+/).test(regex.source);
+	},
+	
+	/** @returns {RegExp} A regex made by substituting any backreferences in <tt>regex</tt> for the value of the property
+	 * in <tt>sub</tt> with the same name as the backreferenced group number. */
+	getSubstitutedRegex: function(/**RegExp*/ regex, /**Object*/ sub, /**Boolean*/ escape) {
+		escape = (typeof escape === "undefined") ? true : false;
+		var exploded = regex.source.split(/(\\\d+)/g);
+		var array = [];
+		for (var i=0; i < exploded.length; i++) {
+			var term = exploded[i];
+			var backrefMatch = /\\(\d+)/.exec(term);
+			if (backrefMatch) {
+				var text = sub[backrefMatch[1]] || "";
+				array.push(escape ? orion.editor.RegexUtil.escapeRegex(text) : text);
+			} else {
+				array.push(term);
+			}
+		}
+		return new RegExp(array.join(""));
+	},
+	
+	/** @returns {String} The input string with regex special characters escaped. */
+	escapeRegex: function(/**String*/ str) {
+		return str.replace(/([\\$\^*\/+?\.\(\)|{}\[\]])/g, "\\$&");
+	},
+	
+	/**
+	 * Builds a version of <tt>regex</tt> with every non-capturing term converted into a capturing group. This is a workaround
+	 * for JavaScript's lack of API to get the index at which a matched group begins in the input string.<p>
+	 * Using the "groupified" regex, we can sum the lengths of matches from <i>consuming groups</i> 1..n-1 to obtain the 
+	 * starting index of group n. (A consuming group is a capturing group that is not inside a lookahead assertion).</p>
+	 * Example: groupify(/(a+)x+(b+)/) === /(a+)(x+)(b+)/<br />
+	 * Example: groupify(/(?:x+(a+))b+/) === /(?:(x+)(a+))(b+)/
+	 * @param {RegExp} regex The regex to groupify.
+	 * @param {Object} [backRefOld2NewMap] Optional. If provided, the backreference numbers in regex will be updated using the 
+	 * properties of this object rather than the new group numbers of regex itself.
+	 * <ul><li>[0] {RegExp} The groupified version of the input regex.</li>
+	 * <li>[1] {Object} A map containing old-group to new-group info. Each property is a capturing group number of <tt>regex</tt>
+	 * and its value is the corresponding capturing group number of [0].</li>
+	 * <li>[2] {Object} A map indicating which capturing groups of [0] are also consuming groups. If a group number is found
+	 * as a property in this object, then it's a consuming group.</li></ul>
+	 */
+	groupify: function(regex, backRefOld2NewMap) {
+		var NON_CAPTURING = 1,
+		    CAPTURING = 2,
+		    LOOKAHEAD = 3,
+		    NEW_CAPTURING = 4;
+		var src = regex.source,
+		    len = src.length;
+		var groups = [],
+		    lookaheadDepth = 0,
+		    newGroups = [],
+		    oldGroupNumber = 1,
+		    newGroupNumber = 1;
+		var result = [],
+		    old2New = {},
+		    consuming = {};
+		for (var i=0; i < len; i++) {
+			var curGroup = groups[groups.length-1];
+			var chr = src[i];
+			switch (chr) {
+				case "(":
+					// If we're in new capturing group, close it since ( signals end-of-term
+					if (curGroup === NEW_CAPTURING) {
+						groups.pop();
+						result.push(")");
+						newGroups[newGroups.length-1].end = i;
+					}
+					var peek2 = (i + 2 < len) ? (src[i+1] + "" + src[i+2]) : null;
+					if (peek2 === "?:" || peek2 === "?=" || peek2 === "?!") {
+						// Found non-capturing group or lookahead assertion. Note that we preserve non-capturing groups
+						// as such, but any term inside them will become a new capturing group (unless it happens to
+						// also be inside a lookahead).
+						var groupType;
+						if (peek2 === "?:") {
+							groupType = NON_CAPTURING;
+						} else {
+							groupType = LOOKAHEAD;
+							lookaheadDepth++;
+						}
+						groups.push(groupType);
+						newGroups.push({ start: i, end: -1, type: groupType /*non capturing*/ });
+						result.push(chr);
+						result.push(peek2);
+						i += peek2.length;
+					} else {
+						groups.push(CAPTURING);
+						newGroups.push({ start: i, end: -1, type: CAPTURING, oldNum: oldGroupNumber, num: newGroupNumber });
+						result.push(chr);
+						if (lookaheadDepth === 0) {
+							consuming[newGroupNumber] = null;
+						}
+						old2New[oldGroupNumber] = newGroupNumber;
+						oldGroupNumber++;
+						newGroupNumber++;
+					}
+					break;
+				case ")":
+					var group = groups.pop();
+					if (group === LOOKAHEAD) { lookaheadDepth--; }
+					newGroups[newGroups.length-1].end = i;
+					result.push(chr);
+					break;
+				case "*":
+				case "+":
+				case "?":
+				case "}":
+					// Unary operator. If it's being applied to a capturing group, we need to add a new capturing group
+					// enclosing the pair
+					var op = chr;
+					var prev = src[i-1],
+					    prevIndex = i-1;
+					if (chr === "}") {
+						for (var j=i-1; src[j] !== "{" && j >= 0; j--) {}
+						prev = src[j-1];
+						prevIndex = j-1;
+						op = src.substring(j, i+1);
+					}
+					var lastGroup = newGroups[newGroups.length-1];
+					if (prev === ")" && (lastGroup.type === CAPTURING || lastGroup.type === NEW_CAPTURING)) {
+						// Shove in the new group's (, increment num/start in from [lastGroup.start .. end]
+						result.splice(lastGroup.start, 0, "(");
+						result.push(op);
+						result.push(")");
+						var newGroup = { start: lastGroup.start, end: result.length-1, type: NEW_CAPTURING, num: lastGroup.num };
+						for (var k=0; k < newGroups.length; k++) {
+							group = newGroups[k];
+							if (group.type === CAPTURING || group.type === NEW_CAPTURING) {
+								if (group.start >= lastGroup.start && group.end <= prevIndex) {
+									group.start += 1;
+									group.end += 1;
+									group.num = group.num + 1;
+									if (group.type === CAPTURING) {
+										old2New[group.oldNum] = group.num;
+									}
+								}
+							}
+						}
+						newGroups.push(newGroup);
+						newGroupNumber++;
+						break;
+					} else {
+						// Fallthrough to default
+					}
+				default:
+					if (chr !== "|" && curGroup !== CAPTURING && curGroup !== NEW_CAPTURING) {
+						// Not in a capturing group, so make a new one to hold this term.
+						// Perf improvement: don't create the new group if we're inside a lookahead, since we don't 
+						// care about them (nothing inside a lookahead actually consumes input so we don't need it)
+						if (lookaheadDepth === 0) {
+							groups.push(NEW_CAPTURING);
+							newGroups.push({ start: i, end: -1, type: NEW_CAPTURING, num: newGroupNumber });
+							result.push("(");
+							consuming[newGroupNumber] = null;
+							newGroupNumber++;
+						}
+					}
+					result.push(chr);
+					if (chr === "\\") {
+						var peek = src[i+1];
+						// Eat next so following iteration doesn't think it's a real special character
+						result.push(peek);
+						i += 1;
+					}
+					break;
+			}
+		}
+		while (groups.length) {	
+			// Close any remaining new capturing groups
+			groups.pop();
+			result.push(")");
+		}
+		var newRegex = new RegExp(result.join(""));
+		
+		// Update backreferences so they refer to the new group numbers. Use backRefOld2NewMap if provided
+		var subst = {};
+		backRefOld2NewMap = backRefOld2NewMap || old2New;
+		for (var prop in backRefOld2NewMap) {
+			if (backRefOld2NewMap.hasOwnProperty(prop)) {
+				subst[prop] = "\\" + backRefOld2NewMap[prop];
+			}
+		}
+		newRegex = this.getSubstitutedRegex(newRegex, subst, false);
+		
+		return [newRegex, old2New, consuming];
+	},
+	
+	/** @returns {Boolean} True if the captures object assigns scope to a matching group other than "0". */
+	complexCaptures: function(capturesObj) {
+		if (!capturesObj) { return false; }
+		for (var prop in capturesObj) {
+			if (capturesObj.hasOwnProperty(prop)) {
+				if (prop !== "0") {
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+};
+
+/**
+ * A styler that knows how to apply a limited subset of the TextMate grammar format to style a line.<p>
+ *
+ * <h4>Styling from a grammar:</h4>
+ * Each scope name given in the grammar is converted to an array of CSS class names. For example 
+ * a region of text with scope <tt>keyword.control.php</tt> will be assigned the CSS classes 
+ * <pre>keyword, keyword-control, keyword-control-php</pre>
+ *
+ * A CSS file can give rules matching any of these class names to provide generic or more specific styling.
+ * For example, <pre>.keyword { font-color: blue; }</pre> colors all keywords blue, while
+ * <pre>.keyword-control-php { font-weight: bold; }</pre> bolds only PHP control keywords.
+ *
+ * This is useful when using grammars that adhere to TextMate's
+ * <a href="http://manual.macromates.com/en/language_grammars.html#naming_conventions">scope name conventions</a>,
+ * as a single CSS rule can provide consistent styling to similar constructs across different languages.<p>
+ * 
+ * <h4>Supported top-level grammar features:</h4>
+ * <ul><li><tt>fileTypes, patterns, repository</tt> (but see below) are supported.</li>
+ * <li><tt>scopeName, firstLineMatch, foldingStartMarker, foldingStopMarker</tt> are <b>not</b> supported.</li>
+ * </ul>
+ *
+ * <p>TODO update this section</p>
+ * <del>
+ * <h4>Supported grammar rule features:</h4>
+ * <ul><li><tt>match</tt> patterns are supported.</li>
+ * <li><tt>name</tt> scope is supported.</li>
+ * <li><tt>captures</tt> is <b>not</b> supported. Any scopes given inside a <tt>captures</tt> object are not applied.</li>
+ * <li><tt>begin/end</tt> patterns are <b>not</b> supported and are ignored, along with their subrules. Consequently, 
+ *   matched constructs may <b>not</b> span multiple lines.</li>
+ * <li><tt>contentName, beginCaptures, endCaptures, applyEndPatternLast</tt> are <b>not</b> supported.</li>
+ * <li><tt>include</tt> is supported, but only when it references a rule in the current grammar's <tt>repository</tt>.
+ *   Including <tt>$self</tt>, <tt>$base</tt>, or <tt>rule.from.another.grammar</tt> is <b>not</b> supported.</li>
+ * <li>The <tt>(?x)</tt> option ("extended" regex format) is supported, but only when it appears at the beginning of a regex pattern.</li>
+ * <li>Matching is done using native JavaScript {@link RegExp}s. As a result, many Oniguruma features are <b>not</b> supported.
+ *   Unsupported features include:
+ *   <ul><li>Named captures</li>
+ *   <li>Setting flags inside groups (eg. <tt>(?i:a)b</tt>)</li>
+ *   <li>Lookbehind and negative lookbehind</li>
+ *   <li>Subexpression call</li>
+ *   <li>etc.</li>
+ *   </ul>
+ * </li>
+ * </ul>
+ * </del>
+ *
+ * @class orion.editor.TextMateStyler
+ * @extends orion.editor.AbstractStyler
+ * @param {orion.textview.TextView} textView The textView.
+ * @param {Object} grammar The TextMate grammar as a JavaScript object. You can use a plist-to-JavaScript conversion tool 
+ * to produce this object. Note that some features of TextMate grammars are not supported.
+ */
+orion.editor.TextMateStyler = (function() {
+	/** @inner */
+	function TextMateStyler(textView, grammar) {
+		this.initialize(textView);
+		// Copy the grammar since we'll mutate it
+		this.grammar = this.copy(grammar);
+		this._styles = {}; /* key: {String} scopeName, value: {String[]} cssClassNames */
+		this._tree = null;
+		
+		this.preprocess();
+	}
+	orion.editor.AbstractStyler.extend(TextMateStyler, /** @lends orion.editor.TextMateStyler.prototype */ {
+		copy: function(obj) {
+			return JSON.parse(JSON.stringify(obj));
+		},
+		preprocess: function() {
+			var stack = [this.grammar];
+			for (; stack.length !== 0; ) {
+				var rule = stack.pop();
+				if (rule._resolvedRule && rule._typedRule) {
+					continue;
+				}
+//				console.debug("Process " + (rule.include || rule.name));
+				
+				// Look up include'd rule, create typed *Rule instance
+				rule._resolvedRule = this._resolve(rule);
+				rule._typedRule = this._createTypedRule(rule);
+				
+				// Convert the scope names to styles and cache them for later
+				this.addStyles(rule.name);
+				this.addStyles(rule.contentName);
+				this.addStylesForCaptures(rule.captures);
+				this.addStylesForCaptures(rule.beginCaptures);
+				this.addStylesForCaptures(rule.endCaptures);
+				
+				if (rule._resolvedRule !== rule) {
+					// Add include target
+					stack.push(rule._resolvedRule);
+				}
+				if (rule.patterns) {
+					// Add subrules
+					for (var i=0; i < rule.patterns.length; i++) {
+						stack.push(rule.patterns[i]);
+					}
+				}
+			}
+		},
+		
+		/**
+		 * Adds eclipse.Style objects for scope to our _styles cache.
+		 * @param {String} scope A scope name, like "constant.character.php".
+		 */
+		addStyles: function(scope) {
+			if (scope && !this._styles[scope]) {
+				this._styles[scope] = dojo.map(scope.split("."),
+						function(segment, i, segments) {
+							return segments.slice(0, i+1).join("-");
+						});
+//				console.debug("add style for " + scope + " = [" + this._styles[scope].join(", ") + "]");
+			}
+		},
+		addStylesForCaptures: function(/**Object*/ captures) {
+			for (var prop in captures) {
+				if (captures.hasOwnProperty(prop)) {
+					var scope = captures[prop].name;
+					this.addStyles(scope);
+				}
+			}
+		},
+		/**
+		 * A rule that contains subrules ("patterns" in TextMate parlance) but has no "begin" or "end".
+		 * Also handles top level of grammar.
+		 * @private
+		 */
+		ContainerRule: (function() {
+			function ContainerRule(/**Object*/ rule) {
+				this.rule = rule;
+				this.subrules = rule.patterns;
+			}
+			ContainerRule.prototype.valueOf = function() { return "aa"; };
+			return ContainerRule;
+		}()),
+		/**
+		 * A rule that is delimited by "begin" and "end" matches, which may be separated by any number of
+		 * lines. This type of rule may contain subrules, which apply only inside the begin .. end region.
+		 * @private
+		 */
+		BeginEndRule: (function() {
+			function BeginEndRule(/**Object*/ rule) {
+				this.rule = rule;
+				// TODO: the TextMate blog claims that "end" is optional.
+				this.beginRegex = orion.editor.RegexUtil.toRegExp(rule.begin);
+				this.endRegex = orion.editor.RegexUtil.toRegExp(rule.end);
+				this.subrules = rule.patterns || [];
+				
+				this.endRegexHasBackRef = orion.editor.RegexUtil.hasBackReference(this.endRegex);
+				
+				// Deal with non-0 captures
+				var complexCaptures = orion.editor.RegexUtil.complexCaptures(rule.captures);
+				var complexBeginEnd = orion.editor.RegexUtil.complexCaptures(rule.beginCaptures) || orion.editor.RegexUtil.complexCaptures(rule.endCaptures);
+				this.isComplex = complexCaptures || complexBeginEnd;
+				if (this.isComplex) {
+					var bg = orion.editor.RegexUtil.groupify(this.beginRegex);
+					this.beginRegex = bg[0];
+					this.beginOld2New = bg[1];
+					this.beginConsuming = bg[2];
+					
+					var eg = orion.editor.RegexUtil.groupify(this.endRegex, this.beginOld2New /*Update end's backrefs to begin's new group #s*/);
+					this.endRegex = eg[0];
+					this.endOld2New = eg[1];
+					this.endConsuming = eg[2];
+				}
+			}
+			BeginEndRule.prototype.valueOf = function() { return this.beginRegex; };
+			return BeginEndRule;
+		}()),
+		/**
+		 * A rule with a "match" pattern.
+		 * @private
+		 */
+		MatchRule: (function() {
+			function MatchRule(/**Object*/ rule) {
+				this.rule = rule;
+				this.matchRegex = orion.editor.RegexUtil.toRegExp(rule.match);
+				this.isComplex = orion.editor.RegexUtil.complexCaptures(rule.captures);
+				if (this.isComplex) {
+					var mg = orion.editor.RegexUtil.groupify(this.matchRegex);
+					this.matchRegex = mg[0];
+					this.matchOld2New = mg[1];
+					this.matchConsuming = mg[2];
+				}
+			}
+			MatchRule.prototype.valueOf = function() { return this.matchRegex; };
+			return MatchRule;
+		}()),
+		/**
+		 * @param {Object} rule A rule from the grammar.
+		 * @returns {MatchRule|BeginEndRule|ContainerRule}
+		 */
+		_createTypedRule: function(rule) {
+			if (rule.match) {
+				return new this.MatchRule(rule);
+			} else if (rule.begin) {
+				return new this.BeginEndRule(rule);
+			} else {
+				return new this.ContainerRule(rule);
+			}
+		},
+		/**
+		 * Resolves a rule from the grammar (which may be an include) into the real rule that it points to.
+		 */
+		_resolve: function(rule) {
+			var resolved = rule;
+			if (rule.include) {
+				if (rule.begin || rule.end || rule.match) {
+					throw new Error("Unexpected regex pattern in \"include\" rule " + rule.include);
+				}
+				var name = rule.include;
+				if (name.charAt(0) === "#") {
+					resolved = this.grammar.repository && this.grammar.repository[name.substring(1)];
+					if (!resolved) { throw new Error("Couldn't find included rule " + name + " in grammar repository"); }
+				} else if (name === "$self") {
+					resolved = this.grammar;
+				} else if (name === "$base") {
+					// $base is only relevant when including rules from foreign grammars
+					throw new Error("Include \"$base\" is not supported"); 
+				} else {
+					throw new Error("Include external rule \"" + name + "\" is not supported");
+				}
+			}
+			return resolved;
+		},
+		ContainerNode: (function() {
+			function ContainerNode(parent, rule) {
+				this.parent = parent;
+				this.rule = rule;
+				this.children = [];
+				
+				this.start = null;
+				this.end = null;
+			}
+			ContainerNode.prototype.addChild = function(child) {
+				this.children.push(child);
+			};
+			ContainerNode.prototype.valueOf = function() {
+				var r = this.rule;
+				return "ContainerNode { " + (r.include || "") + " " + (r.name || "") + (r.comment || "") + "}";
+			};
+			return ContainerNode;
+		}()),
+		BeginEndNode: (function() {
+			function BeginEndNode(parent, rule, beginMatch) {
+				this.parent = parent;
+				this.rule = rule;
+				this.children = [];
+				
+				this.setStart(beginMatch);
+				this.end = null; // will be set eventually during parsing (may be EOF)
+				this.endMatch = null; // may remain null if we never match our "end" pattern
+				
+				// Build a new regex if the "end" regex has backrefs since they refer to matched groups of beginMatch
+				if (rule.endRegexHasBackRef) {
+					this.endRegexSubstituted = orion.editor.RegexUtil.getSubstitutedRegex(rule.endRegex, beginMatch);
+				} else {
+					this.endRegexSubstituted = null;
+				}
+			}
+			BeginEndNode.prototype.addChild = function(child) {
+				this.children.push(child);
+			};
+			/** @return {Number} This node's index in its parent's "children" list */
+			BeginEndNode.prototype.getIndexInParent = function(node) {
+				return this.parent ? this.parent.children.indexOf(this) : -1;
+			};
+			/** @param {RegExp.match} beginMatch */
+			BeginEndNode.prototype.setStart = function(beginMatch) {
+				this.start = beginMatch.index;
+				this.beginMatch = beginMatch;
+			};
+			/** @param {RegExp.match|Number} endMatchOrLastChar */
+			BeginEndNode.prototype.setEnd = function(endMatchOrLastChar) {
+				if (endMatchOrLastChar && typeof(endMatchOrLastChar) === "object") {
+					var endMatch = endMatchOrLastChar;
+					this.endMatch = endMatch;
+					this.end = endMatch.index + endMatch[0].length;
+				} else {
+					var lastChar = endMatchOrLastChar;
+					this.endMatch = null;
+					this.end = lastChar;
+				}
+			};
+			BeginEndNode.prototype.shiftStart = function(amount) {
+				this.start += amount;
+				this.beginMatch.index += amount;
+			};
+			BeginEndNode.prototype.shiftEnd = function(amount) {
+				this.end += amount;
+				if (this.endMatch) { this.endMatch.index += amount; }
+			};
+			BeginEndNode.prototype.valueOf = function() {
+				return "{" + this.rule.beginRegex + " range=" + this.start + ".." + this.end + "}";
+			};
+			return BeginEndNode;
+		}()),
+		/** Pushes rules onto stack so that rules[startFrom] is on top */
+		push: function(/**Array*/ stack, /**Array*/ rules) {
+			if (!rules) { return; }
+			for (var i = rules.length; i > 0; ) {
+				stack.push(rules[--i]);
+			}
+		},
+		/** Execs regex on text, and returns the match object with its index offset by the given amount. */
+		exec: function(/**RegExp*/ regex, /**String*/ text, /**Number*/ offset) {
+			var match = regex.exec(text);
+			if (match) { match.index += offset; }
+			regex.lastIndex = 0; // Just in case
+			return match;
+		},
+		/** @returns {Number} The position immediately following the match. */
+		afterMatch: function(/**RegExp.match*/ match) {
+			return match.index + match[0].length;
+		},
+		/** @returns {RegExp.match} If node is a BeginEndNode and its rule's "end" pattern matches the text. */
+		getEndMatch: function(/**Node*/ node, /**String*/ text, /**Number*/ offset) {
+			if (node instanceof this.BeginEndNode) {
+				var rule = node.rule;
+				var endRegex = node.endRegexSubstituted || rule.endRegex;
+				if (!endRegex) { return null; }
+				return this.exec(endRegex, text, offset);
+			}
+			return null;
+		},
+		/** Called once when file is first loaded to build the parse tree. Tree is updated incrementally thereafter as buffer is modified */
+		initialParse: function() {
+			var last = this.textView.getModel().getCharCount();
+			// First time; make parse tree for whole buffer
+			var root = new this.ContainerNode(null, this.grammar._typedRule);
+			this._tree = root;
+			this.parse(this._tree, false, 0);
+		},
+		_onModelChanged: function(/**eclipse.ModelChangedEvent*/ e) {
+			var addedCharCount = e.addedCharCount,
+			    addedLineCount = e.addedLineCount,
+			    removedCharCount = e.removedCharCount,
+			    removedLineCount = e.removedLineCount,
+			    start = e.start;
+			if (!this._tree) {
+				this.initialParse();
+			} else {
+				var model = this.textView.getModel();
+				var charCount = model.getCharCount();
+				
+				// For rs, we must rewind to the line preceding the line 'start' is on. We can't rely on start's
+				// line since it may've been changed in a way that would cause a new beginMatch at its lineStart.
+				var rs = model.getLineEnd(model.getLineAtOffset(start) - 1); // may be < 0
+				var fd = this.getFirstDamaged(rs, rs);
+				rs = rs === -1 ? 0 : rs;
+				var stoppedAt;
+				if (fd) {
+					// [rs, re] is the region we need to verify. If we find the structure of the tree
+					// has changed in that area, then we may need to reparse the rest of the file.
+					stoppedAt = this.parse(fd, true, rs, start, addedCharCount, removedCharCount);
+				} else {
+					// FIXME: fd == null ?
+					stoppedAt = charCount;
+				}
+				this.textView.redrawRange(rs, stoppedAt);
+			}
+		},
+		/** @returns {BeginEndNode|ContainerNode} The result of taking the first (smallest "start" value) 
+		 * node overlapping [start,end] and drilling down to get its deepest damaged descendant (if any).
+		 */
+		getFirstDamaged: function(start, end) {
+			// If start === 0 we actually have to start from the root because there is no position
+			// we can rely on. (First index is damaged)
+			if (start < 0) {
+				return this._tree;
+			}
+			
+			var nodes = [this._tree];
+			var result = null;
+			while (nodes.length) {
+				var n = nodes.pop();
+				if (!n.parent /*n is root*/ || this.isDamaged(n, start, end)) {
+					// n is damaged by the edit, so go into its children
+					// Note: If a node is damaged, then some of its descendents MAY be damaged
+					// If a node is undamaged, then ALL of its descendents are undamaged
+					if (n instanceof this.BeginEndNode) {
+						result = n;
+					}
+					// Examine children[0] last
+					for (var i=0; i < n.children.length; i++) {
+						nodes.push(n.children[i]);
+					}
+				}
+			}
+			return result || this._tree;
+		},
+		/** @returns true If n overlaps the interval [start,end] */
+		isDamaged: function(/**BeginEndNode*/ n, start, end) {
+			// Note strict > since [2,5] doesn't overlap [5,7]
+			return (n.start <= end && n.end > start);
+		},
+		/**
+		 * Builds tree from some of the buffer content
+		 *
+		 * TODO cleanup params
+		 * @param {BeginEndNode|ContainerNode} origNode The deepest node that overlaps [rs,rs], or the root.
+		 * @param {Boolean} repairing 
+		 * @param {Number} rs See _onModelChanged()
+		 * @param {Number} [editStart] Only used for repairing === true
+		 * @param {Number} [addedCharCount] Only used for repairing === true
+		 * @param {Number} [removedCharCount] Only used for repairing === true
+		 */
+		parse: function(origNode, repairing, rs, editStart, addedCharCount, removedCharCount) {
+			var model = this.textView.getModel();
+			var lastLineStart = model.getLineStart(model.getLineCount() - 1);
+			var eof = model.getCharCount();
+			var initialExpected = this.getInitialExpected(origNode, rs);
+			
+			// re is best-case stopping point; if we detect change to tree, we must continue past it
+			var re = -1;
+			if (repairing) {
+				origNode.repaired = true;
+				origNode.endNeedsUpdate = true;
+				var lastChild = origNode.children[origNode.children.length-1];
+				var delta = addedCharCount - removedCharCount;
+				var lastChildLineEnd = lastChild ? model.getLineEnd(model.getLineAtOffset(lastChild.end + delta)) : -1;
+				var editLineEnd = model.getLineEnd(model.getLineAtOffset(editStart + removedCharCount));
+				re = Math.max(lastChildLineEnd, editLineEnd);
+			}
+			re = (re === -1) ? eof : re;
+			
+			var expected = initialExpected;
+			var node = origNode;
+			var matchedChildOrEnd = false;
+			var pos = rs;
+			while (node && (!repairing || (pos < re))) {
+				var matchInfo = this.getNextMatch(model, node, pos);
+				if (!matchInfo) {
+					// Go to next line, if any
+					pos = (pos >= lastLineStart) ? eof : model.getLineStart(model.getLineAtOffset(pos) + 1);
+				}
+				var match = matchInfo && matchInfo.match,
+				    rule = matchInfo && matchInfo.rule,
+				    isSub = matchInfo && matchInfo.isSub,
+				    isEnd = matchInfo && matchInfo.isEnd;
+				if (isSub) {
+					pos = this.afterMatch(match);
+					if (rule instanceof this.BeginEndRule) {
+						matchedChildOrEnd = true;
+						// Matched a child. Did we expect that?
+						if (repairing && rule === expected.rule && node === expected.parent) {
+							// Yes: matched expected child
+							var foundChild = expected;
+							foundChild.setStart(match);
+							// Note: the 'end' position for this node will either be matched, or fixed up by us post-loop
+							foundChild.repaired = true;
+							foundChild.endNeedsUpdate = true;
+							node = foundChild; // descend
+							expected = this.getNextExpected(expected, "begin");
+						} else {
+							if (repairing) {
+								// No: matched unexpected child.
+								this.prune(node, expected);
+								repairing = false;
+							}
+							
+							// Add the new child (will replace 'expected' in node's children list)
+							var subNode = new this.BeginEndNode(node, rule, match);
+							node.addChild(subNode);
+							node = subNode; // descend
+						}
+					} else {
+						// Matched a MatchRule; no changes to tree required
+					}
+				} else if (isEnd || pos === eof) {
+					if (node instanceof this.BeginEndNode) {
+						if (match) {
+							matchedChildOrEnd = true;
+							node.setEnd(match);
+							pos = this.afterMatch(match);
+							// Matched node's end. Did we expect that?
+							if (repairing && node === expected && node.parent === expected.parent) {
+								// Yes: found the expected end of node
+								node.repaired = true;
+								delete node.endNeedsUpdate;
+								expected = this.getNextExpected(expected, "end");
+							} else {
+								if (repairing) {
+									// No: found an unexpected end
+									this.prune(node, expected);
+									repairing = false;
+								}
+							}
+						} else {
+							// Force-ending a BeginEndNode that runs until eof
+							node.setEnd(eof);
+							delete node.endNeedsUpdate;
+						}
+					}
+					node = node.parent; // ascend
+				}
+				
+//				if (repairing && pos >= re && !matchedChildOrEnd) {
+//					// Reached re without matching any begin/end => initialExpected itself was removed => repair fail
+//					this.prune(origNode, initialExpected);
+//					repairing = false;
+//				}
+			} // end loop
+			// TODO: do this for every node we end?
+			this.removeUnrepairedChildren(origNode, repairing, rs);
+			
+			//console.debug("parsed " + (pos - rs) + " of " + model.getCharCount + "buf");
+			this.cleanup(repairing, origNode, rs, re, eof, addedCharCount, removedCharCount);
+			return pos; // where we stopped repairing/reparsing
+		},
+		/** Helper for parse() in the repair case. To be called when ending a node, as any children that
+		 * lie in [rs,node.end] and were not repaired must've been deleted.
+		 */
+		removeUnrepairedChildren: function(node, repairing, start) {
+			if (repairing) {
+				var children = node.children;
+				var removeFrom = -1;
+				for (var i=0; i < children.length; i++) {
+					var child = children[i];
+					if (!child.repaired && this.isDamaged(child, start, Number.MAX_VALUE /*end doesn't matter*/)) {
+						removeFrom = i;
+						break;
+					}
+				}
+				if (removeFrom !== -1) {
+					node.children.length = removeFrom;
+				}
+			}
+		},
+		/** Helper for parse() in the repair case */
+		cleanup: function(repairing, origNode, rs, re, eof, addedCharCount, removedCharCount) {
+			var i, node, maybeRepairedNodes;
+			if (repairing) {
+				// The repair succeeded, so update stale begin/end indices by simple translation.
+				var delta = addedCharCount - removedCharCount;
+				// A repaired node's end can't exceed re, but it may exceed re-delta+1.
+				// TODO: find a way to guarantee disjoint intervals for repaired vs unrepaired, then stop using flag
+				var maybeUnrepairedNodes = this.getIntersecting(re-delta+1, eof);
+				maybeRepairedNodes = this.getIntersecting(rs, re);
+				// Handle unrepaired nodes. They are those intersecting [re-delta+1, eof] that don't have the flag
+				for (i=0; i < maybeUnrepairedNodes.length; i++) {
+					node = maybeUnrepairedNodes[i];
+					if (!node.repaired && node instanceof this.BeginEndNode) {
+						node.shiftEnd(delta);
+						node.shiftStart(delta);
+					}
+				}
+				// Translate 'end' index of repaired node whose 'end' was not matched in loop (>= re)
+				for (i=0; i < maybeRepairedNodes.length; i++) {
+					node = maybeRepairedNodes[i];
+					if (node.repaired && node.endNeedsUpdate) {
+						node.shiftEnd(delta);
+					}
+					delete node.endNeedsUpdate;
+					delete node.repaired;
+				}
+			} else {
+				// Clean up after ourself
+				maybeRepairedNodes = this.getIntersecting(rs, re);
+				for (i=0; i < maybeRepairedNodes.length; i++) {
+					delete maybeRepairedNodes[i].repaired;
+				}
+			}
+		},
+		/**
+		 * @param model {orion.textview.TextModel}
+		 * @param node {Node}
+		 * @param pos {Number}
+		 * @param [matchRulesOnly] {Boolean} Optional, if true only "match" subrules will be considered.
+		 * @returns {Object} A match info object with properties:
+		 * {Boolean} isEnd
+		 * {Boolean} isSub
+		 * {RegExp.match} match
+		 * {(Match|BeginEnd)Rule} rule
+		 */
+		getNextMatch: function(model, node, pos, matchRulesOnly) {
+			var lineIndex = model.getLineAtOffset(pos);
+			var lineEnd = model.getLineEnd(lineIndex);
+			var line = model.getText(pos, lineEnd);
+
+			var stack = [],
+			    expandedContainers = [],
+			    subMatches = [],
+			    subrules = [];
+			this.push(stack, node.rule.subrules);
+			while (stack.length) {
+				var next = stack.length ? stack.pop() : null;
+				var subrule = next && next._resolvedRule._typedRule;
+				if (subrule instanceof this.ContainerRule && expandedContainers.indexOf(subrule) === -1) {
+					// Expand ContainerRule by pushing its subrules on
+					expandedContainers.push(subrule);
+					this.push(stack, subrule.subrules);
+					continue;
+				}
+				if (subrule && matchRulesOnly && !(subrule.matchRegex)) {
+					continue;
+				}
+				var subMatch = subrule && this.exec(subrule.matchRegex || subrule.beginRegex, line, pos);
+				if (subMatch) {
+					subMatches.push(subMatch);
+					subrules.push(subrule);
+				}
+			}
+
+			var bestSub = Number.MAX_VALUE,
+			    bestSubIndex = -1;
+			for (var i=0; i < subMatches.length; i++) {
+				var match = subMatches[i];
+				if (match.index < bestSub) {
+					bestSub = match.index;
+					bestSubIndex = i;
+				}
+			}
+			
+			if (!matchRulesOnly) {
+				// See if the "end" pattern of the active begin/end node matches.
+				// TODO: The active begin/end node may not be the same as the node that holds the subrules
+				var activeBENode = node;
+				var endMatch = this.getEndMatch(node, line, pos);
+				if (endMatch) {
+					var doEndLast = activeBENode.rule.applyEndPatternLast;
+					var endWins = bestSubIndex === -1 || (endMatch.index < bestSub) || (!doEndLast && endMatch.index === bestSub);
+					if (endWins) {
+						return {isEnd: true, rule: activeBENode.rule, match: endMatch};
+					}
+				}
+			}
+			return bestSubIndex === -1 ? null : {isSub: true, rule: subrules[bestSubIndex], match: subMatches[bestSubIndex]};
+		},
+		/**
+		 * Gets the node corresponding to the first match we expect to see in the repair.
+		 * @param {BeginEndNode|ContainerNode} node The node returned via getFirstDamaged(rs,rs) -- may be the root.
+		 * @param {Number} rs See _onModelChanged()
+		 * Note that because rs is a line end (or 0, a line start), it will intersect a beginMatch or 
+		 * endMatch either at their 0th character, or not at all. (begin/endMatches can't cross lines).
+		 * This is the only time we rely on the start/end values from the pre-change tree. After this 
+		 * we only look at node ordering, never use the old indices.
+		 * @returns {Node}
+		 */
+		getInitialExpected: function(node, rs) {
+			// TODO: Kind of weird.. maybe ContainerNodes should have start & end set, like BeginEndNodes
+			var i, child;
+			if (node === this._tree) {
+				// get whichever of our children comes after rs
+				for (i=0; i < node.children.length; i++) {
+					child = node.children[i]; // BeginEndNode
+					if (child.start >= rs) {
+						return child;
+					}
+				}
+			} else if (node instanceof this.BeginEndNode) {
+				if (node.endMatch) {
+					// Which comes next after rs: our nodeEnd or one of our children?
+					var nodeEnd = node.endMatch.index;
+					for (i=0; i < node.children.length; i++) {
+						child = node.children[i]; // BeginEndNode
+						if (child.start >= rs) {
+							break;
+						}
+					}
+					if (child && child.start < nodeEnd) {
+						return child; // Expect child as the next match
+					}
+				} else {
+					// No endMatch => node goes until eof => it end should be the next match
+				}
+			}
+			return node; // We expect node to end, so it should be the next match
+		},
+		/**
+		 * Helper for repair() to tell us what kind of event we expect next.
+		 * @param {Node} expected Last value returned by this method.
+		 * @param {String} event "begin" if the last value of expected was matched as "begin",
+		 *  or "end" if it was matched as an end.
+		 * @returns {Node} The next expected node to match, or null.
+		 */
+		getNextExpected: function(/**Node*/ expected, event) {
+			var node = expected;
+			if (event === "begin") {
+				var child = node.children[0];
+				if (child) {
+					return child;
+				} else {
+					return node;
+				}
+			} else if (event === "end") {
+				var parent = node.parent;
+				if (parent) {
+					var nextSibling = parent.children[parent.children.indexOf(node) + 1];
+					if (nextSibling) {
+						return nextSibling;
+					} else {
+						return parent;
+					}
+				}
+			}
+			return null;
+		},
+		/** Helper for parse() when repairing. Prunes out the unmatched nodes from the tree so we can continue parsing. */
+		prune: function(/**BeginEndNode|ContainerNode*/ node, /**Node*/ expected) {
+			var expectedAChild = expected.parent === node;
+			if (expectedAChild) {
+				// Expected child wasn't matched; prune it and all siblings after it
+				node.children.length = expected.getIndexInParent();
+			} else if (node instanceof this.BeginEndNode) {
+				// Expected node to end but it didn't; set its end unknown and we'll match it eventually
+				node.endMatch = null;
+				node.end = null;
+			}
+			// Reparsing from node, so prune the successors outside of node's subtree
+			if (node.parent) {
+				node.parent.children.length = node.getIndexInParent() + 1;
+			}
+		},
+		_onLineStyle: function(/**eclipse.LineStyleEvent*/ e) {
+			function byStart(r1, r2) {
+				return r1.start - r2.start;
+			}
+			
+			if (!this._tree) {
+				// In some cases it seems onLineStyle is called before onModelChanged, so we need to parse here
+				this.initialParse();
+			}
+			var lineStart = e.lineStart,
+			    model = this.textView.getModel(),
+			    lineEnd = model.getLineEnd(e.lineIndex);
+			
+			var rs = model.getLineEnd(model.getLineAtOffset(lineStart) - 1); // may be < 0
+			var node = this.getFirstDamaged(rs, rs);
+			
+			var scopes = this.getLineScope(model, node, lineStart, lineEnd);
+			e.ranges = this.toStyleRanges(scopes);
+//			// Editor requires StyleRanges must be in ascending order by 'start', or else some will be ignored
+			e.ranges.sort(byStart);
+		},
+		// Run parse algorithm on [start, end] in the context of node, assigning scope as we find matches
+		getLineScope: function(model, node, start, end) {
+			var pos = start;
+			var expected = this.getInitialExpected(node, start);
+			var scopes = [],
+			    gaps = [];
+			while (node && (pos < end)) {
+				var matchInfo = this.getNextMatch(model, node, pos);
+				if (!matchInfo) { 
+					break; // line is over
+				}
+				var match = matchInfo && matchInfo.match,
+				    rule = matchInfo && matchInfo.rule,
+				    isSub = matchInfo && matchInfo.isSub,
+				    isEnd = matchInfo && matchInfo.isEnd;
+				if (match.index !== pos) {
+					// gap [pos..match.index]
+					gaps.push({ start: pos, end: match.index, node: node});
+				}
+				if (isSub) {
+					pos = this.afterMatch(match);
+					if (rule instanceof this.BeginEndRule) {
+						// Matched a "begin", assign its scope and descend into it
+						this.addBeginScope(scopes, match, rule);
+						node = expected; // descend
+						expected = this.getNextExpected(expected, "begin");
+					} else {
+						// Matched a child MatchRule;
+						this.addMatchScope(scopes, match, rule);
+					}
+				} else if (isEnd) {
+					pos = this.afterMatch(match);
+					// Matched and "end", assign its end scope and go up
+					this.addEndScope(scopes, match, rule);
+					expected = this.getNextExpected(expected, "end");
+					node = node.parent; // ascend
+				}
+			}
+			if (pos < end) {
+				gaps.push({ start: pos, end: end, node: node });
+			}
+			var inherited = this.getInheritedLineScope(gaps, start, end);
+			return scopes.concat(inherited);
+		},
+		getInheritedLineScope: function(gaps, start, end) {
+			var scopes = [];
+			for (var i=0; i < gaps.length; i++) {
+				var gap = gaps[i];
+				var node = gap.node;
+				while (node) {
+					// if node defines a contentName or name, apply it
+					var rule = node.rule.rule;
+					var name = rule.name,
+					    contentName = rule.contentName;
+					// TODO: if both are given, we don't resolve the conflict. contentName always wins
+					var scope = contentName || name;
+					if (scope) {
+						this.addScopeRange(scopes, gap.start, gap.end, scope);
+						break;
+					}
+					node = node.parent;
+				}
+			}
+			return scopes;
+		},
+		addBeginScope: function(scopes, match, typedRule) {
+			var rule = typedRule.rule;
+			this.addCapturesScope(scopes, match, (rule.beginCaptures || rule.captures), typedRule.isComplex, typedRule.beginOld2New, typedRule.beginConsuming);
+		},
+		addEndScope: function(scopes, match, typedRule) {
+			var rule = typedRule.rule;
+			this.addCapturesScope(scopes, match, (rule.endCaptures || rule.captures), typedRule.isComplex, typedRule.endOld2New, typedRule.endConsuming);
+		},
+		addMatchScope: function(scopes, match, typedRule) {
+			var rule = typedRule.rule,
+			    name = rule.name,
+			    captures = rule.captures;
+			if (captures) {	
+				// captures takes priority over name
+				this.addCapturesScope(scopes, match, captures, typedRule.isComplex, typedRule.matchOld2New, typedRule.matchConsuming);
+			} else {
+				this.addScope(scopes, match, name);
+			}
+		},
+		addScope: function(scopes, match, name) {
+			if (!name) { return; }
+			scopes.push({start: match.index, end: this.afterMatch(match), scope: name });
+		},
+		addScopeRange: function(scopes, start, end, name) {
+			if (!name) { return; }
+			scopes.push({start: start, end: end, scope: name });
+		},
+		addCapturesScope: function(/**Array*/scopes, /*RegExp.match*/ match, /**Object*/captures, /**Boolean*/isComplex, /**Object*/old2New, /**Object*/consuming) {
+			if (!captures) { return; }
+			if (!isComplex) {
+				this.addScope(scopes, match, captures[0] && captures[0].name);
+			} else {
+				// apply scopes captures[1..n] to matching groups [1]..[n] of match
+				
+				// Sum up the lengths of preceding consuming groups to get the start offset for each matched group.
+				var newGroupStarts = {1: 0};
+				var sum = 0;
+				for (var num = 1; match[num] !== undefined; num++) {
+					if (consuming[num] !== undefined) {
+						sum += match[num].length;
+					}
+					if (match[num+1] !== undefined) {
+						newGroupStarts[num + 1] = sum;
+					}
+				}
+				// Map the group numbers referred to in captures object to the new group numbers, and get the actual matched range.
+				var start = match.index;
+				for (var oldGroupNum = 1; captures[oldGroupNum]; oldGroupNum++) {
+					var scope = captures[oldGroupNum].name;
+					var newGroupNum = old2New[oldGroupNum];
+					var groupStart = start + newGroupStarts[newGroupNum];
+					// Not every capturing group defined in regex need match every time the regex is run.
+					// eg. (a)|b matches "b" but group 1 is undefined
+					if (typeof match[newGroupNum] !== "undefined") {
+						var groupEnd = groupStart + match[newGroupNum].length;
+						this.addScopeRange(scopes, groupStart, groupEnd, scope);
+					}
+				}
+			}
+		},
+		/** @returns {Node[]} In depth-first order */
+		getIntersecting: function(start, end) {
+			var result = [];
+			var nodes = this._tree ? [this._tree] : [];
+			while (nodes.length) {
+				var n = nodes.pop();
+				var visitChildren = false;
+				if (n instanceof this.ContainerNode) {
+					visitChildren = true;
+				} else if (this.isDamaged(n, start, end)) {
+					visitChildren = true;
+					result.push(n);
+				}
+				if (visitChildren) {
+					var len = n.children.length;
+//					for (var i=len-1; i >= 0; i--) {
+//						nodes.push(n.children[i]);
+//					}
+					for (var i=0; i < len; i++) {
+						nodes.push(n.children[i]);
+					}
+				}
+			}
+			return result.reverse();
+		},
+		_onSelection: function(e) {
+		},
+		_onDestroy: function(/**eclipse.DestroyEvent*/ e) {
+			this.grammar = null;
+			this._styles = null;
+			this._tree = null;
+		},
+		/**
+		 * Applies the grammar to obtain the {@link eclipse.StyleRange[]} for the given line.
+		 * @returns eclipse.StyleRange[]
+		 */
+		toStyleRanges: function(/**ScopeRange[]*/ scopeRanges) {
+			var styleRanges = [];
+			for (var i=0; i < scopeRanges.length; i++) {
+				var scopeRange = scopeRanges[i];
+				var classNames = this._styles[scopeRange.scope];
+				if (!classNames) { throw new Error("styles not found for " + scopeRange.scope); }
+				var classNamesString = classNames.join(" ");
+				styleRanges.push({start: scopeRange.start, end: scopeRange.end, style: {styleClass: classNamesString}});
+//				console.debug("{start " + styleRanges[i].start + ", end " + styleRanges[i].end + ", style: " + styleRanges[i].style.styleClass + "}");
+			}
+			return styleRanges;
+		}
+	});
+	return TextMateStyler;
+}());
+
+if (typeof window !== "undefined" && typeof window.define !== "undefined") {
+	define(['dojo'], function() {
+		return orion.editor;
+	});
+}
+
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/editor/webContentAssist.js b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/editor/webContentAssist.js
new file mode 100644
index 0000000..8d83776
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/editor/webContentAssist.js
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials are made 
+ * available under the terms of the Eclipse Public License v1.0 
+ * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
+ * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+/*global orion:true*/
+
+/** @namespace */
+var orion = orion || {};
+orion.editor = orion.editor || {};
+
+/**
+ * @class orion.contentAssist.CssContentAssistProvider
+ */
+orion.editor.CssContentAssistProvider = (function() {
+	/** @private */
+	function CssContentAssistProvider() {
+	}
+	CssContentAssistProvider.prototype = /** @lends orion.contentAssist.CssContentAssistProvider.prototype */ {
+		/**
+		 * @param {String} The string buffer.substring(w+1, c) where c is the caret offset and w is the index of the 
+		 * rightmost whitespace character preceding c.
+		 * @param {String} buffer The entire buffer being edited
+		 * @param {orion.editor.Selection} selection The current textView selection.
+		 * @returns {dojo.Deferred} A future that will provide the keywords.
+		 */
+		getKeywords: function(prefix, buffer, selection) {
+			return [ "background", "background-attachment", "background-color", "background-image",
+					"background-position", "background-repeat", "border", "border-bottom",
+					"border-bottom-color", "border-bottom-style", "border-bottom-width", "border-color",
+					"border-left", "border-left-color", "border-left-style", "border-left-width",
+					"border-right", "border-right-color", "border-right-style", "border-right-width",
+					"border-style", "border-top", "border-top-color", "border-top-style", "border-top-width",
+					"border-width", "bottom", "clear", "clip", "color", "cursor", "display", "float", "font",
+					"font-family", "font-size", "font-style", "font-variant", "font-weight", "height",
+					"horizontal-align", "left", "line-height", "list-style", "list-style-image",
+					"list-style-position", "list-style-type", "margin", "margin-bottom", "margin-left",
+					"margin-right", "margin-top", "max-height", "max-width", "min-height", "min-width",
+					"outline", "outline-color", "outline-style", "outline-width", "overflow", "overflow-x",
+					"overflow-y", "padding", "padding-bottom", "padding-left", "padding-right",
+					"padding-top", "position", "right", "text-align", "text-decoration", "text-indent",
+					"top", "vertical-align", "visibility", "width", "z-index" ];
+		}
+	};
+	return CssContentAssistProvider;
+}());
+
+/**
+ * @class orion.contentAssist.JavaScriptContentAssistProvider
+ */
+orion.editor.JavaScriptContentAssistProvider = (function() {
+	/** @private */
+	function JavaScriptContentAssistProvider() {
+	}
+	JavaScriptContentAssistProvider.prototype = /** @lends orion.contentAssist.JavaScriptContentAssistProvider.prototype */ {
+		/**
+		 * @param {String} The string buffer.substring(w+1, c) where c is the caret offset and w is the index of the 
+		 * rightmost whitespace character preceding c.
+		 * @param {String} buffer The entire buffer being edited
+		 * @param {orion.editor.Selection} selection The current textView selection.
+		 * @returns {dojo.Deferred} A future that will provide the keywords.
+		 */
+		getKeywords: function(prefix, buffer, selection) {
+			return [ "break", "case", "catch", "continue", "debugger", "default", "delete", "do", "else",
+					"finally", "for", "function", "if", "in", "instanceof", "new", "return", "switch",
+					"this", "throw", "try", "typeof", "var", "void", "while", "with" ];
+		}
+	};
+	return JavaScriptContentAssistProvider;
+}());
+
+if (typeof window !== "undefined" && typeof window.define !== "undefined") {
+	define([], function() {
+		return orion.editor;
+	});
+}
+
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/textview/keyBinding.js b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/textview/keyBinding.js
new file mode 100644
index 0000000..252233b
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/textview/keyBinding.js
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials are made 
+ * available under the terms of the Eclipse Public License v1.0 
+ * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
+ * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
+ * 
+ * Contributors: 
+ *		Felipe Heidrich (IBM Corporation) - initial API and implementation
+ *		Silenio Quarti (IBM Corporation) - initial API and implementation
+ ******************************************************************************/
+
+/*global window define */
+
+/**
+ * @namespace The global container for Orion APIs.
+ */ 
+var orion = orion || {};
+orion.textview = orion.textview || {};
+
+/**
+ * Constructs a new key binding with the given key code and modifiers.
+ * 
+ * @param {String|Number} keyCode the key code.
+ * @param {Boolean} mod1 the primary modifier (usually Command on Mac and Control on other platforms).
+ * @param {Boolean} mod2 the secondary modifier (usually Shift).
+ * @param {Boolean} mod3 the third modifier (usually Alt).
+ * @param {Boolean} mod4 the fourth modifier (usually Control on the Mac).
+ * 
+ * @class A KeyBinding represents of a key code and a modifier state that can be triggered by the user using the keyboard.
+ * @name orion.textview.KeyBinding
+ * 
+ * @property {String|Number} keyCode The key code.
+ * @property {Boolean} mod1 The primary modifier (usually Command on Mac and Control on other platforms).
+ * @property {Boolean} mod2 The secondary modifier (usually Shift).
+ * @property {Boolean} mod3 The third modifier (usually Alt).
+ * @property {Boolean} mod4 The fourth modifier (usually Control on the Mac).
+ *
+ * @see orion.textview.TextView#setKeyBinding
+ */
+orion.textview.KeyBinding = (function() {
+	var isMac = window.navigator.platform.indexOf("Mac") !== -1;
+	/** @private */
+	function KeyBinding (keyCode, mod1, mod2, mod3, mod4) {
+		if (typeof(keyCode) === "string") {
+			this.keyCode = keyCode.toUpperCase().charCodeAt(0);
+		} else {
+			this.keyCode = keyCode;
+		}
+		this.mod1 = mod1 !== undefined && mod1 !== null ? mod1 : false;
+		this.mod2 = mod2 !== undefined && mod2 !== null ? mod2 : false;
+		this.mod3 = mod3 !== undefined && mod3 !== null ? mod3 : false;
+		this.mod4 = mod4 !== undefined && mod4 !== null ? mod4 : false;
+	}
+	KeyBinding.prototype = /** @lends orion.textview.KeyBinding.prototype */ {
+		/**
+		 * Returns whether this key binding matches the given key event.
+		 * 
+		 * @param e the key event.
+		 * @returns {Boolean} <code>true</code> whether the key binding matches the key event.
+		 */
+		match: function (e) {
+			if (this.keyCode === e.keyCode) {
+				var mod1 = isMac ? e.metaKey : e.ctrlKey;
+				if (this.mod1 !== mod1) { return false; }
+				if (this.mod2 !== e.shiftKey) { return false; }
+				if (this.mod3 !== e.altKey) { return false; }
+				if (isMac && this.mod4 !== e.ctrlKey) { return false; }
+				return true;
+			}
+			return false;
+		},
+		/**
+		 * Returns whether this key binding is the same as the given parameter.
+		 * 
+		 * @param {orion.textview.KeyBinding} kb the key binding to compare with.
+		 * @returns {Boolean} whether or not the parameter and the receiver describe the same key binding.
+		 */
+		equals: function(kb) {
+			if (!kb) { return false; }
+			if (this.keyCode !== kb.keyCode) { return false; }
+			if (this.mod1 !== kb.mod1) { return false; }
+			if (this.mod2 !== kb.mod2) { return false; }
+			if (this.mod3 !== kb.mod3) { return false; }
+			if (this.mod4 !== kb.mod4) { return false; }
+			return true;
+		} 
+	};
+	return KeyBinding;
+}());
+
+if (typeof window !== "undefined" && typeof window.define !== "undefined") {
+	define([], function() {
+		return orion.textview;
+	});
+}
+
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/textview/rulers.css b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/textview/rulers.css
new file mode 100644
index 0000000..24becbc
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/textview/rulers.css
@@ -0,0 +1,39 @@
+.ruler_annotation {
+	background-color: #e1ebfb;
+	width: 16px;
+}
+
+.ruler_annotation_todo {
+}
+
+.ruler_annotation_todo_overview {
+	background-color: lightgreen;
+	border: 1px solid green;
+}
+
+.ruler_annotation_breakpoint {
+}
+
+.ruler_annotation_breakpoint_overview {
+	background-color: lightblue;
+	border: 1px solid blue;
+}
+
+.ruler_lines {
+	background-color: #e1ebfb;
+	border-right: 1px solid #b1badf;
+	text-align: right;
+}
+
+.ruler_overview {
+	background-color: #e1ebfb;
+}
+
+.ruler_lines_even {
+	background-color: #e1ebfb;
+}
+
+.ruler_lines_odd {
+	background-color: white;
+}
+
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/textview/rulers.js b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/textview/rulers.js
new file mode 100644
index 0000000..17c890b
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/textview/rulers.js
@@ -0,0 +1,223 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials are made 
+ * available under the terms of the Eclipse Public License v1.0 
+ * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
+ * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
+ * 
+ * Contributors: IBM Corporation - initial API and implementation
+ ******************************************************************************/
+
+/*global window define */
+
+/**
+ * @namespace The global container for Orion APIs.
+ */ 
+var orion = orion || {};
+orion.textview = orion.textview || {};
+
+orion.textview.Ruler = (function() {
+	function Ruler (rulerLocation, rulerOverview, rulerStyle) {
+		this._location = rulerLocation || "left";
+		this._overview = rulerOverview || "page";
+		this._rulerStyle = rulerStyle;
+		this._view = null;
+	}
+	Ruler.prototype = {
+		setView: function (view) {
+			if (this._onModelChanged && this._view) {
+				this._view.removeEventListener("ModelChanged", this, this._onModelChanged); 
+			}
+			this._view = view;
+			if (this._onModelChanged && this._view) {
+				this._view.addEventListener("ModelChanged", this, this._onModelChanged);
+			}
+		},
+		getLocation: function() {
+			return this._location;
+		},
+		getOverview: function(view) {
+			return this._overview;
+		}
+	};
+	return Ruler;
+}());
+
+orion.textview.LineNumberRuler = (function() {
+	function LineNumberRuler (rulerLocation, rulerStyle, oddStyle, evenStyle) {
+		orion.textview.Ruler.call(this, rulerLocation, "page", rulerStyle);
+		this._oddStyle = oddStyle || {style: {backgroundColor: "white"}};
+		this._evenStyle = evenStyle || {style: {backgroundColor: "white"}};
+		this._numOfDigits = 0;
+	}
+	LineNumberRuler.prototype = new orion.textview.Ruler(); 
+	LineNumberRuler.prototype.getStyle = function(lineIndex) {
+		if (lineIndex === undefined) {
+			return this._rulerStyle;
+		} else {
+			return lineIndex & 1 ? this._oddStyle : this._evenStyle;
+		}
+	};
+	LineNumberRuler.prototype.getHTML = function(lineIndex) {
+		if (lineIndex === -1) {
+			var model = this._view.getModel();
+			return model.getLineCount();
+		} else {
+			return lineIndex + 1;
+		}
+	};
+	LineNumberRuler.prototype._onModelChanged = function(e) {
+		var start = e.start;
+		var model = this._view.getModel();
+		var lineCount = model.getLineCount();
+		var numOfDigits = (lineCount+"").length;
+		if (this._numOfDigits !== numOfDigits) {
+			this._numOfDigits = numOfDigits;
+			var startLine = model.getLineAtOffset(start);
+			this._view.redrawLines(startLine, lineCount, this);
+		}
+	};
+	return LineNumberRuler;
+}());
+
+orion.textview.AnnotationRuler = (function() {
+	function AnnotationRuler (rulerLocation, rulerStyle, defaultAnnotation) {
+		orion.textview.Ruler.call(this, rulerLocation, "page", rulerStyle);
+		this._defaultAnnotation = defaultAnnotation;
+		this._annotations = [];
+	}
+	AnnotationRuler.prototype = new orion.textview.Ruler();
+	AnnotationRuler.prototype.clearAnnotations = function() {
+		this._annotations = [];
+		var lineCount = this._view.getModel().getLineCount();
+		this._view.redrawLines(0, lineCount, this);
+		if (this._overviewRuler) {
+			this._view.redrawLines(0, lineCount, this._overviewRuler);
+		}
+	};
+	AnnotationRuler.prototype.getAnnotation = function(lineIndex) {
+		return this._annotations[lineIndex];
+	};
+	AnnotationRuler.prototype.getAnnotations = function() {
+		return this._annotations;
+	};
+	AnnotationRuler.prototype.getStyle = function(lineIndex) {
+		switch (lineIndex) {
+			case undefined:
+				return this._rulerStyle;
+			case -1:
+				return this._defaultAnnotation ? this._defaultAnnotation.style : null;
+			default:
+				return this._annotations[lineIndex] && this._annotations[lineIndex].style ? this._annotations[lineIndex].style : null;
+		}
+	};
+	AnnotationRuler.prototype.getHTML = function(lineIndex) {
+		if (lineIndex === -1) {
+			return this._defaultAnnotation ? this._defaultAnnotation.html : "";
+		} else {
+			return this._annotations[lineIndex] && this._annotations[lineIndex].html ? this._annotations[lineIndex].html : "";
+		}
+	};
+	AnnotationRuler.prototype.setAnnotation = function(lineIndex, annotation) {
+		if (lineIndex === undefined) { return; }
+		this._annotations[lineIndex] = annotation;
+		this._view.redrawLines(lineIndex, lineIndex + 1, this);
+		if (this._overviewRuler) {
+			this._view.redrawLines(lineIndex, lineIndex + 1, this._overviewRuler);
+		}
+	};
+	AnnotationRuler.prototype._onModelChanged = function(e) {
+		var start = e.start;
+		var removedLineCount = e.removedLineCount;
+		var addedLineCount = e.addedLineCount;
+		var linesChanged = addedLineCount - removedLineCount;
+		if (linesChanged) {
+			var model = this._view.getModel();
+			var startLine = model.getLineAtOffset(start);
+			var newLines = [], lines = this._annotations;
+			var changed = false;
+			for (var prop in lines) {
+				var i = prop >>> 0;
+				if (!(startLine < i && i < startLine + removedLineCount)) {
+					var newIndex = i;
+					if (i > startLine) {
+						newIndex += linesChanged;
+						changed = true;
+					}
+					newLines[newIndex] = lines[i];
+				} else {
+					changed = true;
+				}
+			}
+			this._annotations = newLines;
+			if (changed) {
+				var lineCount = model.getLineCount();
+				this._view.redrawLines(startLine, lineCount, this);
+				//TODO redraw overview (batch it for performance)
+				if (this._overviewRuler) {
+					this._view.redrawLines(0, lineCount, this._overviewRuler);
+				}
+			}
+		}
+	};
+	return AnnotationRuler;
+}());
+
+orion.textview.OverviewRuler = (function() {
+	function OverviewRuler (rulerLocation, rulerStyle, annotationRuler) {
+		orion.textview.Ruler.call(this, rulerLocation, "document", rulerStyle);
+		this._annotationRuler = annotationRuler;
+		if (annotationRuler) {
+			annotationRuler._overviewRuler = this;
+		}
+	}
+	OverviewRuler.prototype = new orion.textview.Ruler();
+	OverviewRuler.prototype.getAnnotations = function() {
+		var annotations = this._annotationRuler.getAnnotations();
+		var lines = [];
+		for (var prop in annotations) {
+			var i = prop >>> 0;
+			if (annotations[i] !== undefined) {
+				lines.push(i);
+			}
+		}
+		return lines;
+	};
+	OverviewRuler.prototype.getStyle = function(lineIndex) {
+		var result, style;
+		if (lineIndex === undefined) {
+			result = this._rulerStyle || {};
+			style = result.style || (result.style = {});
+			style.lineHeight = "1px";
+			style.fontSize = "1px";
+			style.width = "14px";
+		} else {
+			if (lineIndex !== -1) {
+				var annotation = this._annotationRuler.getAnnotation(lineIndex);
+				result = annotation.overviewStyle || {};
+			} else {
+				result = {};
+			}
+			style = result.style || (result.style = {});
+			style.cursor = "pointer";
+			style.width = "8px";
+			style.height = "3px";
+			style.left = "2px";
+		}
+		return result;
+	};
+	OverviewRuler.prototype.getHTML = function(lineIndex) {
+		return "&nbsp;";
+	};
+	OverviewRuler.prototype.onClick = function(lineIndex, e) {
+		if (lineIndex === undefined) { return; }
+		this._view.setTopIndex(lineIndex);
+	};
+	return OverviewRuler;
+}());
+
+if (typeof window !== "undefined" && typeof window.define !== "undefined") {
+	define([], function() {
+		return orion.textview;
+	});
+}
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/textview/textModel.js b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/textview/textModel.js
new file mode 100644
index 0000000..88921cb
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/textview/textModel.js
@@ -0,0 +1,458 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials are made 
+ * available under the terms of the Eclipse Public License v1.0 
+ * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
+ * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
+ * 
+ * Contributors: 
+ *		Felipe Heidrich (IBM Corporation) - initial API and implementation
+ *		Silenio Quarti (IBM Corporation) - initial API and implementation
+ ******************************************************************************/
+ 
+/*global window define */
+
+/**
+ * @namespace The global container for Orion APIs.
+ */ 
+var orion = orion || {};
+orion.textview = orion.textview || {};
+
+/**
+ * Constructs a new TextModel with the given text and default line delimiter.
+ *
+ * @param {String} [text=""] the text that the model will store
+ * @param {String} [lineDelimiter=platform delimiter] the line delimiter used when inserting new lines to the model.
+ *
+ * @name orion.textview.TextModel
+ * @class The TextModel is an interface that provides text for the view. Applications may
+ * implement the TextModel interface to provide a custom store for the view content. The
+ * view interacts with its text model in order to access and update the text that is being
+ * displayed and edited in the view. This is the default implementation.
+ * <p>
+ * <b>See:</b><br/>
+ * {@link orion.textview.TextView}<br/>
+ * {@link orion.textview.TextView#setModel}
+ * </p>
+ */
+orion.textview.TextModel = (function() {
+	var isWindows = window.navigator.platform.indexOf("Win") !== -1;
+
+	/** @private */
+	function TextModel(text, lineDelimiter) {
+		this._listeners = [];
+		this._lineDelimiter = lineDelimiter ? lineDelimiter : (isWindows ? "\r\n" : "\n"); 
+		this._lastLineIndex = -1;
+		this._text = [""];
+		this._lineOffsets = [0];
+		this.setText(text);
+	}
+
+	TextModel.prototype = /** @lends orion.textview.TextModel.prototype */ {
+		/**
+		 * Adds a listener to the model.
+		 * 
+		 * @param {Object} listener the listener to add.
+		 * @param {Function} [listener.onChanged] see {@link #onChanged}.
+		 * @param {Function} [listener.onChanging] see {@link #onChanging}.
+		 * 
+		 * @see removeListener
+		 */
+		addListener: function(listener) {
+			this._listeners.push(listener);
+		},
+		/**
+		 * Removes a listener from the model.
+		 * 
+		 * @param {Object} listener the listener to remove
+		 * 
+		 * @see #addListener
+		 */
+		removeListener: function(listener) {
+			for (var i = 0; i < this._listeners.length; i++) {
+				if (this._listeners[i] === listener) {
+					this._listeners.splice(i, 1);
+					return;
+				}
+			}
+		},
+		/**
+		 * Returns the number of characters in the model.
+		 *
+		 * @returns {Number} the number of characters in the model.
+		 */
+		getCharCount: function() {
+			var count = 0;
+			for (var i = 0; i<this._text.length; i++) {
+				count += this._text[i].length;
+			}
+			return count;
+		},
+		/**
+		 * Returns the text of the line at the given index.
+		 * <p>
+		 * The valid indices are 0 to line count exclusive.  Returns <code>null</code> 
+		 * if the index is out of range. 
+		 * </p>
+		 *
+		 * @param {Number} lineIndex the zero based index of the line.
+		 * @param {Boolean} [includeDelimiter=false] whether or not to include the line delimiter. 
+		 * @returns {String} the line text or <code>null</code> if out of range.
+		 *
+		 * @see #getLineAtOffset
+		 */
+		getLine: function(lineIndex, includeDelimiter) {
+			var lineCount = this.getLineCount();
+			if (!(0 <= lineIndex && lineIndex < lineCount)) {
+				return null;
+			}
+			var start = this._lineOffsets[lineIndex];
+			if (lineIndex + 1 < lineCount) {
+				var text = this.getText(start, this._lineOffsets[lineIndex + 1]);
+				if (includeDelimiter) {
+					return text;
+				}
+				var end = text.length, c;
+				while (((c = text.charCodeAt(end - 1)) === 10) || (c === 13)) {
+					end--;
+				}
+				return text.substring(0, end);
+			} else {
+				return this.getText(start); 
+			}
+		},
+		/**
+		 * Returns the line index at the given character offset.
+		 * <p>
+		 * The valid offsets are 0 to char count inclusive. The line index for
+		 * char count is <code>line count - 1</code>. Returns <code>-1</code> if
+		 * the offset is out of range.
+		 * </p>
+		 *
+		 * @param {Number} offset a character offset.
+		 * @returns {Number} the zero based line index or <code>-1</code> if out of range.
+		 */
+		getLineAtOffset: function(offset) {
+			if (!(0 <= offset && offset <= this.getCharCount())) {
+				return -1;
+			}
+			var lineCount = this.getLineCount();
+			var charCount = this.getCharCount();
+			if (offset === charCount) {
+				return lineCount - 1; 
+			}
+			var lineStart, lineEnd;
+			var index = this._lastLineIndex;
+			if (0 <= index && index < lineCount) {
+				lineStart = this._lineOffsets[index];
+				lineEnd = index + 1 < lineCount ? this._lineOffsets[index + 1] : charCount;
+				if (lineStart <= offset && offset < lineEnd) {
+					return index;
+				}
+			}
+			var high = lineCount;
+			var low = -1;
+			while (high - low > 1) {
+				index = Math.floor((high + low) / 2);
+				lineStart = this._lineOffsets[index];
+				lineEnd = index + 1 < lineCount ? this._lineOffsets[index + 1] : charCount;
+				if (offset <= lineStart) {
+					high = index;
+				} else if (offset < lineEnd) {
+					high = index;
+					break;
+				} else {
+					low = index;
+				}
+			}
+			this._lastLineIndex = high;
+			return high;
+		},
+		/**
+		 * Returns the number of lines in the model.
+		 * <p>
+		 * The model always has at least one line.
+		 * </p>
+		 *
+		 * @returns {Number} the number of lines.
+		 */
+		getLineCount: function() {
+			return this._lineOffsets.length;
+		},
+		/**
+		 * Returns the line delimiter that is used by the view
+		 * when inserting new lines. New lines entered using key strokes 
+		 * and paste operations use this line delimiter.
+		 *
+		 * @return {String} the line delimiter that is used by the view when inserting new lines.
+		 */
+		getLineDelimiter: function() {
+			return this._lineDelimiter;
+		},
+		/**
+		 * Returns the end character offset for the given line. 
+		 * <p>
+		 * The end offset is not inclusive. This means that when the line delimiter is included, the 
+		 * offset is either the start offset of the next line or char count. When the line delimiter is
+		 * not included, the offset is the offset of the line delimiter.
+		 * </p>
+		 * <p>
+		 * The valid indices are 0 to line count exclusive.  Returns <code>-1</code> 
+		 * if the index is out of range. 
+		 * </p>
+		 *
+		 * @param {Number} lineIndex the zero based index of the line.
+		 * @param {Boolean} [includeDelimiter=false] whether or not to include the line delimiter. 
+		 * @return {Number} the line end offset or <code>-1</code> if out of range.
+		 *
+		 * @see #getLineStart
+		 */
+		getLineEnd: function(lineIndex, includeDelimiter) {
+			var lineCount = this.getLineCount();
+			if (!(0 <= lineIndex && lineIndex < lineCount)) {
+				return -1;
+			}
+			if (lineIndex + 1 < lineCount) {
+				var end = this._lineOffsets[lineIndex + 1];
+				if (includeDelimiter) {
+					return end;
+				}
+				var text = this.getText(Math.max(this._lineOffsets[lineIndex], end - 2), end);
+				var i = text.length, c;
+				while (((c = text.charCodeAt(i - 1)) === 10) || (c === 13)) {
+					i--;
+				}
+				return end - (text.length - i);
+			} else {
+				return this.getCharCount();
+			}
+		},
+		/**
+		 * Returns the start character offset for the given line.
+		 * <p>
+		 * The valid indices are 0 to line count exclusive.  Returns <code>-1</code> 
+		 * if the index is out of range. 
+		 * </p>
+		 *
+		 * @param {Number} lineIndex the zero based index of the line.
+		 * @return {Number} the line start offset or <code>-1</code> if out of range.
+		 *
+		 * @see #getLineEnd
+		 */
+		getLineStart: function(lineIndex) {
+			if (!(0 <= lineIndex && lineIndex < this.getLineCount())) {
+				return -1;
+			}
+			return this._lineOffsets[lineIndex];
+		},
+		/**
+		 * Returns the text for the given range.
+		 * <p>
+		 * The end offset is not inclusive. This means that character at the end offset
+		 * is not included in the returned text.
+		 * </p>
+		 *
+		 * @param {Number} [start=0] the zero based start offset of text range.
+		 * @param {Number} [end=char count] the zero based end offset of text range.
+		 *
+		 * @see #setText
+		 */
+		getText: function(start, end) {
+			if (start === undefined) { start = 0; }
+			if (end === undefined) { end = this.getCharCount(); }
+			var offset = 0, chunk = 0, length;
+			while (chunk<this._text.length) {
+				length = this._text[chunk].length; 
+				if (start <= offset + length) { break; }
+				offset += length;
+				chunk++;
+			}
+			var firstOffset = offset;
+			var firstChunk = chunk;
+			while (chunk<this._text.length) {
+				length = this._text[chunk].length; 
+				if (end <= offset + length) { break; }
+				offset += length;
+				chunk++;
+			}
+			var lastOffset = offset;
+			var lastChunk = chunk;
+			if (firstChunk === lastChunk) {
+				return this._text[firstChunk].substring(start - firstOffset, end - lastOffset);
+			}
+			var beforeText = this._text[firstChunk].substring(start - firstOffset);
+			var afterText = this._text[lastChunk].substring(0, end - lastOffset);
+			return beforeText + this._text.slice(firstChunk+1, lastChunk).join("") + afterText; 
+		},
+		/**
+		 * Notifies all listeners that the text is about to change.
+		 * <p>
+		 * This notification is intended to be used only by the view. Application clients should
+		 * use {@link orion.textview.TextView#event:onModelChanging}.
+		 * </p>
+		 * <p>
+		 * NOTE: This method is not meant to called directly by application code. It is called internally by the TextModel
+		 * as part of the implementation of {@link #setText}. This method is included in the public API for documentation
+		 * purposes and to allow integration with other toolkit frameworks.
+		 * </p>
+		 *
+		 * @param {String} text the text that is about to be inserted in the model.
+		 * @param {Number} start the character offset in the model where the change will occur.
+		 * @param {Number} removedCharCount the number of characters being removed from the model.
+		 * @param {Number} addedCharCount the number of characters being added to the model.
+		 * @param {Number} removedLineCount the number of lines being removed from the model.
+		 * @param {Number} addedLineCount the number of lines being added to the model.
+		 */
+		onChanging: function(text, start, removedCharCount, addedCharCount, removedLineCount, addedLineCount) {
+			for (var i = 0; i < this._listeners.length; i++) {
+				var l = this._listeners[i]; 
+				if (l && l.onChanging) { 
+					l.onChanging(text, start, removedCharCount, addedCharCount, removedLineCount, addedLineCount);
+				}
+			}
+		},
+		/**
+		 * Notifies all listeners that the text has changed.
+		 * <p>
+		 * This notification is intended to be used only by the view. Application clients should
+		 * use {@link orion.textview.TextView#event:onModelChanged}.
+		 * </p>
+		 * <p>
+		 * NOTE: This method is not meant to called directly by application code. It is called internally by the TextModel
+		 * as part of the implementation of {@link #setText}. This method is included in the public API for documentation
+		 * purposes and to allow integration with other toolkit frameworks.
+		 * </p>
+		 *
+		 * @param {Number} start the character offset in the model where the change occurred.
+		 * @param {Number} removedCharCount the number of characters removed from the model.
+		 * @param {Number} addedCharCount the number of characters added to the model.
+		 * @param {Number} removedLineCount the number of lines removed from the model.
+		 * @param {Number} addedLineCount the number of lines added to the model.
+		 */
+		onChanged: function(start, removedCharCount, addedCharCount, removedLineCount, addedLineCount) {
+			for (var i = 0; i < this._listeners.length; i++) {
+				var l = this._listeners[i]; 
+				if (l && l.onChanged) { 
+					l.onChanged(start, removedCharCount, addedCharCount, removedLineCount, addedLineCount);
+				}
+			}
+		},
+		/**
+		 * Replaces the text in the given range with the given text.
+		 * <p>
+		 * The end offset is not inclusive. This means that the character at the 
+		 * end offset is not replaced.
+		 * </p>
+		 * <p>
+		 * The text model must notify the listeners before and after the
+		 * the text is changed by calling {@link #onChanging} and {@link #onChanged}
+		 * respectively. 
+		 * </p>
+		 *
+		 * @param {String} [text=""] the new text.
+		 * @param {Number} [start=0] the zero based start offset of text range.
+		 * @param {Number} [end=char count] the zero based end offset of text range.
+		 *
+		 * @see #getText
+		 */
+		setText: function(text, start, end) {
+			if (text === undefined) { text = ""; }
+			if (start === undefined) { start = 0; }
+			if (end === undefined) { end = this.getCharCount(); }
+			var startLine = this.getLineAtOffset(start);
+			var endLine = this.getLineAtOffset(end);
+			var eventStart = start;
+			var removedCharCount = end - start;
+			var removedLineCount = endLine - startLine;
+			var addedCharCount = text.length;
+			var addedLineCount = 0;
+			var lineCount = this.getLineCount();
+			
+			var cr = 0, lf = 0, index = 0;
+			var newLineOffsets = [];
+			while (true) {
+				if (cr !== -1 && cr <= index) { cr = text.indexOf("\r", index); }
+				if (lf !== -1 && lf <= index) { lf = text.indexOf("\n", index); }
+				if (lf === -1 && cr === -1) { break; }
+				if (cr !== -1 && lf !== -1) {
+					if (cr + 1 === lf) {
+						index = lf + 1;
+					} else {
+						index = (cr < lf ? cr : lf) + 1;
+					}
+				} else if (cr !== -1) {
+					index = cr + 1;
+				} else {
+					index = lf + 1;
+				}
+				newLineOffsets.push(start + index);
+				addedLineCount++;
+			}
+		
+			this.onChanging(text, eventStart, removedCharCount, addedCharCount, removedLineCount, addedLineCount);
+			
+			//TODO this should be done the loops below to avoid getText()
+			if (newLineOffsets.length === 0) {
+				var startLineOffset = this.getLineStart(startLine), endLineOffset;
+				if (endLine + 1 < lineCount) {
+					endLineOffset = this.getLineStart(endLine + 1);
+				} else {
+					endLineOffset = this.getCharCount();
+				}
+				if (start !== startLineOffset) {
+					text = this.getText(startLineOffset, start) + text;
+					start = startLineOffset;
+				}
+				if (end !== endLineOffset) {
+					text = text + this.getText(end, endLineOffset);
+					end = endLineOffset;
+				}
+			}
+			
+			var changeCount = addedCharCount - removedCharCount;
+			for (var j = startLine + removedLineCount + 1; j < lineCount; j++) {
+				this._lineOffsets[j] += changeCount;
+			}
+			var args = [startLine + 1, removedLineCount].concat(newLineOffsets);
+			Array.prototype.splice.apply(this._lineOffsets, args);
+			
+			var offset = 0, chunk = 0, length;
+			while (chunk<this._text.length) {
+				length = this._text[chunk].length; 
+				if (start <= offset + length) { break; }
+				offset += length;
+				chunk++;
+			}
+			var firstOffset = offset;
+			var firstChunk = chunk;
+			while (chunk<this._text.length) {
+				length = this._text[chunk].length; 
+				if (end <= offset + length) { break; }
+				offset += length;
+				chunk++;
+			}
+			var lastOffset = offset;
+			var lastChunk = chunk;
+			var firstText = this._text[firstChunk];
+			var lastText = this._text[lastChunk];
+			var beforeText = firstText.substring(0, start - firstOffset);
+			var afterText = lastText.substring(end - lastOffset);
+			var params = [firstChunk, lastChunk - firstChunk + 1];
+			if (beforeText) { params.push(beforeText); }
+			if (text) { params.push(text); }
+			if (afterText) { params.push(afterText); }
+			Array.prototype.splice.apply(this._text, params);
+			if (this._text.length === 0) { this._text = [""]; }
+			
+			this.onChanged(eventStart, removedCharCount, addedCharCount, removedLineCount, addedLineCount);
+		}
+	};
+	
+	return TextModel;
+}());
+
+if (typeof window !== "undefined" && typeof window.define !== "undefined") {
+	define([], function() {
+		return orion.textview;
+	});
+}
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/textview/textView.js b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/textview/textView.js
new file mode 100644
index 0000000..0bd5b27
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/textview/textView.js
@@ -0,0 +1,4827 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials are made 
+ * available under the terms of the Eclipse Public License v1.0 
+ * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
+ * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
+ * 
+ * Contributors: 
+ *		Felipe Heidrich (IBM Corporation) - initial API and implementation
+ *		Silenio Quarti (IBM Corporation) - initial API and implementation
+ *		Mihai Sucan (Mozilla Foundation) - fix for Bugs 334583, 348471
+ ******************************************************************************/
+
+/*global window document navigator setTimeout clearTimeout XMLHttpRequest define */
+
+/**
+ * @namespace The global container for Orion APIs.
+ */ 
+var orion = orion || {};
+orion.textview = orion.textview || {};
+
+/**
+ * Constructs a new text view.
+ * 
+ * @param options the view options.
+ * @param {String|DOMElement} options.parent the parent element for the view, it can be either a DOM element or an ID for a DOM element.
+ * @param {orion.textview.TextModel} [options.model] the text model for the view. If this options is not set the view creates an empty {@link orion.textview.TextModel}.
+ * @param {Boolean} [options.readonly=false] whether or not the view is read-only.
+ * @param {Boolean} [options.fullSelection=true] whether or not the view is in full selection mode.
+ * @param {String|String[]} [options.stylesheet] one or more stylesheet URIs for the view.
+ * @param {Number} [options.tabSize] The number of spaces in a tab.
+ * 
+ * @class A TextView is a user interface for editing text.
+ * @name orion.textview.TextView
+ */
+orion.textview.TextView = (function() {
+
+	/** @private */
+	function addHandler(node, type, handler, capture) {
+		if (typeof node.addEventListener === "function") {
+			node.addEventListener(type, handler, capture === true);
+		} else {
+			node.attachEvent("on" + type, handler);
+		}
+	}
+	/** @private */
+	function removeHandler(node, type, handler, capture) {
+		if (typeof node.removeEventListener === "function") {
+			node.removeEventListener(type, handler, capture === true);
+		} else {
+			node.detachEvent("on" + type, handler);
+		}
+	}
+	var isIE = document.selection && window.ActiveXObject && /MSIE/.test(navigator.userAgent) ? document.documentMode : undefined;
+	var isFirefox = parseFloat(navigator.userAgent.split("Firefox/")[1] || navigator.userAgent.split("Minefield/")[1]) || undefined;
+	var isOpera = navigator.userAgent.indexOf("Opera") !== -1;
+	var isChrome = navigator.userAgent.indexOf("Chrome") !== -1;
+	var isSafari = navigator.userAgent.indexOf("Safari") !== -1;
+	var isWebkit = navigator.userAgent.indexOf("WebKit") !== -1;
+	var isPad = navigator.userAgent.indexOf("iPad") !== -1;
+	var isMac = navigator.platform.indexOf("Mac") !== -1;
+	var isWindows = navigator.platform.indexOf("Win") !== -1;
+	var isLinux = navigator.platform.indexOf("Linux") !== -1;
+	var isW3CEvents = typeof window.document.documentElement.addEventListener === "function";
+	var isRangeRects = (!isIE || isIE >= 9) && typeof window.document.createRange().getBoundingClientRect === "function";
+	var platformDelimiter = isWindows ? "\r\n" : "\n";
+	
+	/** 
+	 * Constructs a new Selection object.
+	 * 
+	 * @class A Selection represents a range of selected text in the view.
+	 * @name orion.textview.Selection
+	 */
+	var Selection = (function() {
+		/** @private */
+		function Selection (start, end, caret) {
+			/**
+			 * The selection start offset.
+			 *
+			 * @name orion.textview.Selection#start
+			 */
+			this.start = start;
+			/**
+			 * The selection end offset.
+			 *
+			 * @name orion.textview.Selection#end
+			 */
+			this.end = end;
+			/** @private */
+			this.caret = caret; //true if the start, false if the caret is at end
+		}
+		Selection.prototype = /** @lends orion.textview.Selection.prototype */ {
+			/** @private */
+			clone: function() {
+				return new Selection(this.start, this.end, this.caret);
+			},
+			/** @private */
+			collapse: function() {
+				if (this.caret) {
+					this.end = this.start;
+				} else {
+					this.start = this.end;
+				}
+			},
+			/** @private */
+			extend: function (offset) {
+				if (this.caret) {
+					this.start = offset;
+				} else {
+					this.end = offset;
+				}
+				if (this.start > this.end) {
+					var tmp = this.start;
+					this.start = this.end;
+					this.end = tmp;
+					this.caret = !this.caret;
+				}
+			},
+			/** @private */
+			setCaret: function(offset) {
+				this.start = offset;
+				this.end = offset;
+				this.caret = false;
+			},
+			/** @private */
+			getCaret: function() {
+				return this.caret ? this.start : this.end;
+			},
+			/** @private */
+			toString: function() {
+				return "start=" + this.start + " end=" + this.end + (this.caret ? " caret is at start" : " caret is at end");
+			},
+			/** @private */
+			isEmpty: function() {
+				return this.start === this.end;
+			},
+			/** @private */
+			equals: function(object) {
+				return this.caret === object.caret && this.start === object.start && this.end === object.end;
+			}
+		};
+		return Selection;
+	}());
+
+	/** 
+	 * Constructs a new EventTable object.
+	 * 
+	 * @class 
+	 * @name orion.textview.EventTable
+	 * @private
+	 */
+	var EventTable = (function() {
+		/** @private */
+		function EventTable(){
+		    this._listeners = {};
+		}
+		EventTable.prototype = /** @lends EventTable.prototype */ {
+			/** @private */
+			addEventListener: function(type, context, func, data) {
+				if (!this._listeners[type]) {
+					this._listeners[type] = [];
+				}
+				var listener = {
+						context: context,
+						func: func,
+						data: data
+				};
+				this._listeners[type].push(listener);
+			},
+			/** @private */
+			sendEvent: function(type, event) {
+				var listeners = this._listeners[type];
+				if (listeners) {
+					for (var i=0, len=listeners.length; i < len; i++){
+						var l = listeners[i];
+						if (l && l.context && l.func) {
+							l.func.call(l.context, event, l.data);
+						}
+					}
+				}
+			},
+			/** @private */
+			removeEventListener: function(type, context, func, data){
+				var listeners = this._listeners[type];
+				if (listeners) {
+					for (var i=0, len=listeners.length; i < len; i++){
+						var l = listeners[i];
+						if (l.context === context && l.func === func && l.data === data) {
+							listeners.splice(i, 1);
+							break;
+						}
+					}
+				}
+			}
+		};
+		return EventTable;
+	}());
+	
+	/** @private */
+	function TextView (options) {
+		this._init(options);
+	}
+	
+	TextView.prototype = /** @lends orion.textview.TextView.prototype */ {
+		/**
+		 * Adds an event listener to the text view.
+		 * 
+		 * @param {String} type the event type. The supported events are:
+		 * <ul>
+		 * <li>"Modify" See {@link #onModify} </li>
+		 * <li>"Selection" See {@link #onSelection} </li>
+		 * <li>"Scroll" See {@link #onScroll} </li>
+		 * <li>"Verify" See {@link #onVerify} </li>
+		 * <li>"Destroy" See {@link #onDestroy} </li>
+		 * <li>"LineStyle" See {@link #onLineStyle} </li>
+		 * <li>"ModelChanging" See {@link #onModelChanging} </li>
+		 * <li>"ModelChanged" See {@link #onModelChanged} </li>
+		 * </ul>
+		 * @param {Object} context the context of the function.
+		 * @param {Function} func the function that will be executed when the event happens. 
+		 *   The function should take an event as the first parameter and optional data as the second parameter.
+		 * @param {Object} [data] optional data passed to the function.
+		 * 
+		 * @see #removeEventListener
+		 */
+		addEventListener: function(type, context, func, data) {
+			this._eventTable.addEventListener(type, context, func, data);
+		},
+		/**
+		 * @class This interface represents a ruler for the text view.
+		 * <p>
+		 * A Ruler is a graphical element that is placed either on the left or on the right side of 
+		 * the view. It can be used to provide the view with per line decoration such as line numbering,
+		 * bookmarks, breakpoints, folding disclosures, etc. 
+		 * </p><p>
+		 * There are two types of rulers: page and document. A page ruler only shows the content for the lines that are
+		 * visible, while a document ruler always shows the whole content.
+		 * </p>
+		 * <b>See:</b><br/>
+		 * {@link orion.textview.TextView}<br/>
+		 * {@link orion.textview.TextView#addRuler}
+		 * </p>		 
+		 * @name orion.textview.Ruler
+		 * 
+		 */
+		/**
+		 * Returns the ruler overview type.
+		 *
+		 * @name getOverview
+		 * @methodOf orion.textview.Ruler#
+		 * @returns {String} the overview type, which is either "page" or "document".
+		 *
+		 * @see #getLocation
+		 */
+		/**
+		 * Returns the ruler location.
+		 *
+		 * @name getLocation
+		 * @methodOf orion.textview.Ruler#
+		 * @returns {String} the ruler location, which is either "left" or "right".
+		 */
+		/**
+		 * Returns the HTML content for the decoration of a given line.
+		 * <p>
+		 * If the line index is <code>-1</code>, the HTML content for the decoration
+		 * that determines the width of the ruler should be returned.
+		 * </p>
+		 *
+		 * @name getHTML
+		 * @methodOf orion.textview.Ruler#
+		 * @param {Number} lineIndex
+		 * @returns {String} the HTML content for a given line, or generic line.
+		 *
+		 * @see #getStyle
+		 */
+		/**
+		 * Returns the CSS styling information for the decoration of a given line.
+		 * <p>
+		 * If the line index is <code>-1</code>, the CSS styling information for the decoration
+		 * that determines the width of the ruler should be returned. If the line is
+		 * <code>undefined</code>, the ruler styling information should be returned.
+		 * </p>
+		 *
+		 * @name getStyle
+		 * @methodOf orion.textview.Ruler#
+		 * @param {Number} lineIndex
+		 * @returns {orion.textview.Style} the CSS styling for ruler, given line, or generic line.
+		 *
+		 * @see #getHTML
+		 */
+		/**
+		 * Returns the indices of the lines that have decoration.
+		 * <p>
+		 * This function is only called for rulers with "document" overview type.
+		 * </p>
+		 * @name getAnnotations
+		 * @methodOf orion.textview.Ruler#
+		 * @returns {Number[]} an array of line indices.
+		 */
+		/**
+		 * This event is sent when the user clicks a line decoration.
+		 *
+		 * @name onClick
+		 * @event
+		 * @methodOf orion.textview.Ruler#
+		 * @param {Number} lineIndex the line index of the clicked decoration
+		 * @param {DOMEvent} e the click event
+		 */
+		/**
+		 * This event is sent when the user double clicks a line decoration.
+		 *
+		 * @name onDblClick
+		 * @event
+		 * @methodOf orion.textview.Ruler#
+		 * @param {Number} lineIndex the line index of the double clicked decoration
+		 * @param {DOMEvent} e the double click event
+		 */
+		/**
+		 * Adds a ruler to the text view.
+		 *
+		 * @param {orion.textview.Ruler} ruler the ruler.
+		 */
+		addRuler: function (ruler) {
+			var document = this._frameDocument;
+			var body = document.body;
+			var side = ruler.getLocation();
+			var rulerParent = side === "left" ? this._leftDiv : this._rightDiv;
+			if (!rulerParent) {
+				rulerParent = document.createElement("DIV");
+				rulerParent.style.overflow = "hidden";
+				rulerParent.style.MozUserSelect = "none";
+				rulerParent.style.WebkitUserSelect = "none";
+				if (isIE) {
+					rulerParent.attachEvent("onselectstart", function() {return false;});
+				}
+				rulerParent.style.position = "absolute";
+				rulerParent.style.top = "0px";
+				rulerParent.style.cursor = "default";
+				body.appendChild(rulerParent);
+				if (side === "left") {
+					this._leftDiv = rulerParent;
+					rulerParent.className = "viewLeftRuler";
+				} else {
+					this._rightDiv = rulerParent;
+					rulerParent.className = "viewRightRuler";
+				}
+				var table = document.createElement("TABLE");
+				rulerParent.appendChild(table);
+				table.cellPadding = "0px";
+				table.cellSpacing = "0px";
+				table.border = "0px";
+				table.insertRow(0);
+				var self = this;
+				addHandler(rulerParent, "click", function(e) { self._handleRulerEvent(e); });
+				addHandler(rulerParent, "dblclick", function(e) { self._handleRulerEvent(e); });
+			}
+			var div = document.createElement("DIV");
+			div._ruler = ruler;
+			div.rulerChanged = true;
+			div.style.position = "relative";
+			var row = rulerParent.firstChild.rows[0];
+			var index = row.cells.length;
+			var cell = row.insertCell(index);
+			cell.vAlign = "top";
+			cell.appendChild(div);
+			ruler.setView(this);
+			this._updatePage();
+		},
+		/**
+		 * Converts the given rectangle from one coordinate spaces to another.
+		 * <p>The supported coordinate spaces are:
+		 * <ul>
+		 *   <li>"document" - relative to document, the origin is the top-left corner of first line</li>
+		 *   <li>"page" - relative to html page that contains the text view</li>
+		 *   <li>"view" - relative to text view, the origin is the top-left corner of the view container</li>
+		 * </ul>
+		 * </p>
+		 * <p>All methods in the view that take or return a position are in the document coordinate space.</p>
+		 *
+		 * @param rect the rectangle to convert.
+		 * @param rect.x the x of the rectangle.
+		 * @param rect.y the y of the rectangle.
+		 * @param rect.width the width of the rectangle.
+		 * @param rect.height the height of the rectangle.
+		 * @param {String} from the source coordinate space.
+		 * @param {String} to the destination coordinate space.
+		 *
+		 * @see #getLocationAtOffset
+		 * @see #getOffsetAtLocation
+		 * @see #getTopPixel
+		 * @see #setTopPixel
+		 */
+		convert: function(rect, from, to) {
+			var scroll = this._getScroll();
+			var viewPad = this._getViewPadding();
+			var frame = this._frame.getBoundingClientRect();
+			var viewRect = this._viewDiv.getBoundingClientRect();
+			switch(from) {
+				case "document":
+					if (rect.x !== undefined) {
+						rect.x += - scroll.x + viewRect.left + viewPad.left;
+					}
+					if (rect.y !== undefined) {
+						rect.y += - scroll.y + viewRect.top + viewPad.top;
+					}
+					break;
+				case "page":
+					if (rect.x !== undefined) {
+						rect.x += - frame.left;
+					}
+					if (rect.y !== undefined) {
+						rect.y += - frame.top;
+					}
+					break;
+			}
+			//At this point rect is in the widget coordinate space
+			switch (to) {
+				case "document":
+					if (rect.x !== undefined) {
+						rect.x += scroll.x - viewRect.left - viewPad.left;
+					}
+					if (rect.y !== undefined) {
+						rect.y += scroll.y - viewRect.top - viewPad.top;
+					}
+					break;
+				case "page":
+					if (rect.x !== undefined) {
+						rect.x += frame.left;
+					}
+					if (rect.y !== undefined) {
+						rect.y += frame.top;
+					}
+					break;
+			}
+		},
+		/**
+		 * Destroys the text view. 
+		 * <p>
+		 * Removes the view from the page and frees all resources created by the view.
+		 * Calling this function causes the "Destroy" event to be fire so that all components
+		 * attached to view can release their references.
+		 * </p>
+		 *
+		 * @see #onDestroy
+		 */
+		destroy: function() {
+			this._setGrab(null);
+			this._unhookEvents();
+			
+			/* Destroy rulers*/
+			var destroyRulers = function(rulerDiv) {
+				if (!rulerDiv) {
+					return;
+				}
+				var cells = rulerDiv.firstChild.rows[0].cells;
+				for (var i = 0; i < cells.length; i++) {
+					var div = cells[i].firstChild;
+					div._ruler.setView(null);
+				}
+			};
+			destroyRulers (this._leftDiv);
+			destroyRulers (this._rightDiv);
+
+			/* Destroy timers */
+			if (this._autoScrollTimerID) {
+				clearTimeout(this._autoScrollTimerID);
+				this._autoScrollTimerID = null;
+			}
+			if (this._updateTimer) {
+				clearTimeout(this._updateTimer);
+				this._updateTimer = null;
+			}
+			
+			/* Destroy DOM */
+			var parent = this._parent;
+			var frame = this._frame;
+			parent.removeChild(frame);
+			
+			if (isPad) {
+				parent.removeChild(this._touchDiv);
+				this._touchDiv = null;
+				this._selDiv1 = null;
+				this._selDiv2 = null;
+				this._selDiv3 = null;
+				this._textArea = null;
+			}
+			
+			var e = {};
+			this.onDestroy(e);
+			
+			this._parent = null;
+			this._parentDocument = null;
+			this._model = null;
+			this._selection = null;
+			this._doubleClickSelection = null;
+			this._eventTable = null;
+			this._frame = null;
+			this._frameDocument = null;
+			this._frameWindow = null;
+			this._scrollDiv = null;
+			this._viewDiv = null;
+			this._clientDiv = null;
+			this._overlayDiv = null;
+			this._keyBindings = null;
+			this._actions = null;
+		},
+		/**
+		 * Gives focus to the text view.
+		 */
+		focus: function() {
+			/*
+			* Feature in Chrome. When focus is called in the clientDiv without
+			* setting selection the browser will set the selection to the first dom 
+			* element, which can be above the client area. When this happen the 
+			* browser also scrolls the window to show that element.
+			* The fix is to call _updateDOMSelection() before calling focus().
+			*/
+			this._updateDOMSelection();
+			if (isPad) {
+				this._textArea.focus();
+			} else {
+				if (isOpera) { this._clientDiv.blur(); }
+				this._clientDiv.focus();
+			}
+			/*
+			* Feature in Safari. When focus is called the browser selects the clientDiv
+			* itself. The fix is to call _updateDOMSelection() after calling focus().
+			*/
+			this._updateDOMSelection();
+		},
+		/**
+		 * Returns all action names defined in the text view.
+		 * <p>
+		 * There are two types of actions, the predefined actions of the view 
+		 * and the actions added by application code.
+		 * </p>
+		 * <p>
+		 * The predefined actions are:
+		 * <ul>
+		 *   <li>Navigation actions. These actions move the caret collapsing the selection.</li>
+		 *     <ul>
+		 *       <li>"lineUp" - moves the caret up by one line</li>
+		 *       <li>"lineDown" - moves the caret down by one line</li>
+		 *       <li>"lineStart" - moves the caret to beginning of the current line</li>
+		 *       <li>"lineEnd" - moves the caret to end of the current line </li>
+		 *       <li>"charPrevious" - moves the caret to the previous character</li>
+		 *       <li>"charNext" - moves the caret to the next character</li>
+		 *       <li>"pageUp" - moves the caret up by one page</li>
+		 *       <li>"pageDown" - moves the caret down by one page</li>
+		 *       <li>"wordPrevious" - moves the caret to the previous word</li>
+		 *       <li>"wordNext" - moves the caret to the next word</li>
+		 *       <li>"textStart" - moves the caret to the beginning of the document</li>
+		 *       <li>"textEnd" - moves the caret to the end of the document</li>
+		 *     </ul>
+		 *   <li>Selection actions. These actions move the caret extending the selection.</li>
+		 *     <ul>
+		 *       <li>"selectLineUp" - moves the caret up by one line</li>
+		 *       <li>"selectLineDown" - moves the caret down by one line</li>
+		 *       <li>"selectLineStart" - moves the caret to beginning of the current line</li>
+		 *       <li>"selectLineEnd" - moves the caret to end of the current line </li>
+		 *       <li>"selectCharPrevious" - moves the caret to the previous character</li>
+		 *       <li>"selectCharNext" - moves the caret to the next character</li>
+		 *       <li>"selectPageUp" - moves the caret up by one page</li>
+		 *       <li>"selectPageDown" - moves the caret down by one page</li>
+		 *       <li>"selectWordPrevious" - moves the caret to the previous word</li>
+		 *       <li>"selectWordNext" - moves the caret to the next word</li>
+		 *       <li>"selectTextStart" - moves the caret to the beginning of the document</li>
+		 *       <li>"selectTextEnd" - moves the caret to the end of the document</li>
+		 *       <li>"selectAll" - selects the entire document</li>
+		 *     </ul>
+		 *   <li>Edit actions. These actions modify the text view text</li>
+		 *     <ul>
+		 *       <li>"deletePrevious" - deletes the character preceding the caret</li>
+		 *       <li>"deleteNext" - deletes the charecter following the caret</li>
+		 *       <li>"deleteWordPrevious" - deletes the word preceding the caret</li>
+		 *       <li>"deleteWordNext" - deletes the word following the caret</li>
+		 *       <li>"tab" - inserts a tab character at the caret</li>
+		 *       <li>"enter" - inserts a line delimiter at the caret</li>
+		 *     </ul>
+		 *   <li>Clipboard actions.</li>
+		 *     <ul>
+		 *       <li>"copy" - copies the selected text to the clipboard</li>
+		 *       <li>"cut" - copies the selected text to the clipboard and deletes the selection</li>
+		 *       <li>"paste" - replaces the selected text with the clipboard contents</li>
+		 *     </ul>
+		 * </ul>
+		 * </p>
+		 *
+		 * @param {Boolean} [defaultAction=false] whether or not the predefined actions are included.
+		 * @returns {String[]} an array of action names defined in the text view.
+		 *
+		 * @see #invokeAction
+		 * @see #setAction
+		 * @see #setKeyBinding
+		 * @see #getKeyBindings
+		 */
+		getActions: function (defaultAction) {
+			var result = [];
+			var actions = this._actions;
+			for (var i = 0; i < actions.length; i++) {
+				if (!defaultAction && actions[i].defaultHandler) { continue; }
+				result.push(actions[i].name);
+			}
+			return result;
+		},
+		/**
+		 * Returns the bottom index.
+		 * <p>
+		 * The bottom index is the line that is currently at the bottom of the view.  This
+		 * line may be partially visible depending on the vertical scroll of the view. The parameter
+		 * <code>fullyVisible</code> determines whether to return only fully visible lines. 
+		 * </p>
+		 *
+		 * @param {Boolean} [fullyVisible=false] if <code>true</code>, returns the index of the last fully visible line. This
+		 *    parameter is ignored if the view is not big enough to show one line.
+		 * @returns {Number} the index of the bottom line.
+		 *
+		 * @see #getTopIndex
+		 * @see #setTopIndex
+		 */
+		getBottomIndex: function(fullyVisible) {
+			return this._getBottomIndex(fullyVisible);
+		},
+		/**
+		 * Returns the bottom pixel.
+		 * <p>
+		 * The bottom pixel is the pixel position that is currently at
+		 * the bottom edge of the view.  This position is relative to the
+		 * beginning of the document.
+		 * </p>
+		 *
+		 * @returns {Number} the bottom pixel.
+		 *
+		 * @see #getTopPixel
+		 * @see #setTopPixel
+		 * @see #convert
+		 */
+		getBottomPixel: function() {
+			return this._getScroll().y + this._getClientHeight();
+		},
+		/**
+		 * Returns the caret offset relative to the start of the document.
+		 *
+		 * @returns the caret offset relative to the start of the document.
+		 *
+		 * @see #setCaretOffset
+		 * @see #setSelection
+		 * @see #getSelection
+		 */
+		getCaretOffset: function () {
+			var s = this._getSelection();
+			return s.getCaret();
+		},
+		/**
+		 * Returns the client area.
+		 * <p>
+		 * The client area is the portion in pixels of the document that is visible. The
+		 * client area position is relative to the beginning of the document.
+		 * </p>
+		 *
+		 * @returns the client area rectangle {x, y, width, height}.
+		 *
+		 * @see #getTopPixel
+		 * @see #getBottomPixel
+		 * @see #getHorizontalPixel
+		 * @see #convert
+		 */
+		getClientArea: function() {
+			var scroll = this._getScroll();
+			return {x: scroll.x, y: scroll.y, width: this._getClientWidth(), height: this._getClientHeight()};
+		},
+		/**
+		 * Returns the horizontal pixel.
+		 * <p>
+		 * The horizontal pixel is the pixel position that is currently at
+		 * the left edge of the view.  This position is relative to the
+		 * beginning of the document.
+		 * </p>
+		 *
+		 * @returns {Number} the horizontal pixel.
+		 *
+		 * @see #setHorizontalPixel
+		 * @see #convert
+		 */
+		getHorizontalPixel: function() {
+			return this._getScroll().x;
+		},
+		/**
+		 * Returns all the key bindings associated to the given action name.
+		 *
+		 * @param {String} name the action name.
+		 * @returns {orion.textview.KeyBinding[]} the array of key bindings associated to the given action name.
+		 *
+		 * @see #setKeyBinding
+		 * @see #setAction
+		 */
+		getKeyBindings: function (name) {
+			var result = [];
+			var keyBindings = this._keyBindings;
+			for (var i = 0; i < keyBindings.length; i++) {
+				if (keyBindings[i].name === name) {
+					result.push(keyBindings[i].keyBinding);
+				}
+			}
+			return result;
+		},
+		/**
+		 * Returns the line height for a given line index.  Returns the default line
+		 * height if the line index is not specified.
+		 *
+		 * @param {Number} [lineIndex] the line index.
+		 * @returns {Number} the height of the line in pixels.
+		 *
+		 * @see #getLinePixel
+		 */
+		getLineHeight: function(lineIndex) {
+			return this._getLineHeight();
+		},
+		/**
+		 * Returns the top pixel position of a given line index relative to the beginning
+		 * of the document.
+		 * <p>
+		 * Clamps out of range indices.
+		 * </p>
+		 *
+		 * @param {Number} lineIndex the line index.
+		 * @returns {Number} the pixel position of the line.
+		 *
+		 * @see #setTopPixel
+		 * @see #convert
+		 */
+		getLinePixel: function(lineIndex) {
+			lineIndex = Math.min(Math.max(0, lineIndex), this._model.getLineCount());
+			var lineHeight = this._getLineHeight();
+			return lineHeight * lineIndex;
+		},
+		/**
+		 * Returns the {x, y} pixel location of the top-left corner of the character
+		 * bounding box at the specified offset in the document.  The pixel location
+		 * is relative to the document.
+		 * <p>
+		 * Clamps out of range offsets.
+		 * </p>
+		 *
+		 * @param {Number} offset the character offset
+		 * @returns the {x, y} pixel location of the given offset.
+		 *
+		 * @see #getOffsetAtLocation
+		 * @see #convert
+		 */
+		getLocationAtOffset: function(offset) {
+			var model = this._model;
+			offset = Math.min(Math.max(0, offset), model.getCharCount());
+			var lineIndex = model.getLineAtOffset(offset);
+			var scroll = this._getScroll();
+			var viewRect = this._viewDiv.getBoundingClientRect();
+			var viewPad = this._getViewPadding();
+			var x = this._getOffsetToX(offset) + scroll.x - viewRect.left - viewPad.left;
+			var y = this.getLinePixel(lineIndex);
+			return {x: x, y: y};
+		},
+		/**
+		 * Returns the text model of the text view.
+		 *
+		 * @returns {orion.textview.TextModel} the text model of the view.
+		 */
+		getModel: function() {
+			return this._model;
+		},
+		/**
+		 * Returns the character offset nearest to the given pixel location.  The
+		 * pixel location is relative to the document.
+		 *
+		 * @param x the x of the location
+		 * @param y the y of the location
+		 * @returns the character offset at the given location.
+		 *
+		 * @see #getLocationAtOffset
+		 */
+		getOffsetAtLocation: function(x, y) {
+			var model = this._model;
+			var scroll = this._getScroll();
+			var viewRect = this._viewDiv.getBoundingClientRect();
+			var viewPad = this._getViewPadding();
+			var lineIndex = this._getYToLine(y - scroll.y);
+			x += -scroll.x + viewRect.left + viewPad.left;
+			var offset = this._getXToOffset(lineIndex, x);
+			return offset;
+		},
+		/**
+		 * Returns the text view selection.
+		 * <p>
+		 * The selection is defined by a start and end character offset relative to the
+		 * document. The character at end offset is not included in the selection.
+		 * </p>
+		 * 
+		 * @returns {orion.textview.Selection} the view selection
+		 *
+		 * @see #setSelection
+		 */
+		getSelection: function () {
+			var s = this._getSelection();
+			return {start: s.start, end: s.end};
+		},
+		/**
+		 * Returns the text for the given range.
+		 * <p>
+		 * The text does not include the character at the end offset.
+		 * </p>
+		 *
+		 * @param {Number} [start=0] the start offset of text range.
+		 * @param {Number} [end=char count] the end offset of text range.
+		 *
+		 * @see #setText
+		 */
+		getText: function(start, end) {
+			var model = this._model;
+			return model.getText(start, end);
+		},
+		/**
+		 * Returns the top index.
+		 * <p>
+		 * The top index is the line that is currently at the top of the view.  This
+		 * line may be partially visible depending on the vertical scroll of the view. The parameter
+		 * <code>fullyVisible</code> determines whether to return only fully visible lines. 
+		 * </p>
+		 *
+		 * @param {Boolean} [fullyVisible=false] if <code>true</code>, returns the index of the first fully visible line. This
+		 *    parameter is ignored if the view is not big enough to show one line.
+		 * @returns {Number} the index of the top line.
+		 *
+		 * @see #getBottomIndex
+		 * @see #setTopIndex
+		 */
+		getTopIndex: function(fullyVisible) {
+			return this._getTopIndex(fullyVisible);
+		},
+		/**
+		 * Returns the top pixel.
+		 * <p>
+		 * The top pixel is the pixel position that is currently at
+		 * the top edge of the view.  This position is relative to the
+		 * beginning of the document.
+		 * </p>
+		 *
+		 * @returns {Number} the top pixel.
+		 *
+		 * @see #getBottomPixel
+		 * @see #setTopPixel
+		 * @see #convert
+		 */
+		getTopPixel: function() {
+			return this._getScroll().y;
+		},
+		/**
+		 * Executes the action handler associated with the given name.
+		 * <p>
+		 * The application defined action takes precedence over predefined actions unless
+		 * the <code>defaultAction</code> paramater is <code>true</code>.
+		 * </p>
+		 * <p>
+		 * If the application defined action returns <code>false</code>, the text view predefined
+		 * action is executed if present.
+		 * </p>
+		 *
+		 * @param {String} name the action name.
+		 * @param {Boolean} [defaultAction] whether to always execute the predefined action.
+		 * @returns {Boolean} <code>true</code> if the action was executed.
+		 *
+		 * @see #setAction
+		 * @see #getActions
+		 */
+		invokeAction: function (name, defaultAction) {
+			var actions = this._actions;
+			for (var i = 0; i < actions.length; i++) {
+				var a = actions[i];
+				if (a.name && a.name === name) {
+					if (!defaultAction && a.userHandler) {
+						if (a.userHandler()) { return; }
+					}
+					if (a.defaultHandler) { return a.defaultHandler(); }
+					return false;
+				}
+			}
+			return false;
+		},
+		/** 
+		 * @class This is the event sent when the user right clicks or otherwise invokes the context menu of the view. 
+		 * <p> 
+		 * <b>See:</b><br/> 
+		 * {@link orion.textview.TextView}<br/> 
+		 * {@link orion.textview.TextView#event:onContextMenu} 
+		 * </p> 
+		 * 
+		 * @name orion.textview.ContextMenuEvent 
+		 * 
+		 * @property {Number} x The pointer location on the x axis, relative to the document the user is editing. 
+		 * @property {Number} y The pointer location on the y axis, relative to the document the user is editing. 
+		 * @property {Number} screenX The pointer location on the x axis, relative to the screen. This is copied from the DOM contextmenu event.screenX property. 
+		 * @property {Number} screenY The pointer location on the y axis, relative to the screen. This is copied from the DOM contextmenu event.screenY property. 
+		 */ 
+		/** 
+		 * This event is sent when the user invokes the view context menu. 
+		 * 
+		 * @event 
+		 * @param {orion.textview.ContextMenuEvent} contextMenuEvent the event 
+		 */ 
+		onContextMenu: function(contextMenuEvent) { 
+		  this._eventTable.sendEvent("ContextMenu", contextMenuEvent); 
+		}, 
+		/**
+		 * @class This is the event sent when the text view is destroyed.
+		 * <p>
+		 * <b>See:</b><br/>
+		 * {@link orion.textview.TextView}<br/>
+		 * {@link orion.textview.TextView#event:onDestroy}
+		 * </p>
+		 * @name orion.textview.DestroyEvent
+		 */
+		/**
+		 * This event is sent when the text view has been destroyed.
+		 *
+		 * @event
+		 * @param {orion.textview.DestroyEvent} destroyEvent the event
+		 *
+		 * @see #destroy
+		 */
+		onDestroy: function(destroyEvent) {
+			this._eventTable.sendEvent("Destroy", destroyEvent);
+		},
+		/**
+		 * @class This object is used to define style information for the text view.
+		 * <p>
+		 * <b>See:</b><br/>
+		 * {@link orion.textview.TextView}<br/>
+		 * {@link orion.textview.TextView#event:onLineStyle}
+		 * </p>		 
+		 * @name orion.textview.Style
+		 * 
+		 * @property {String} styleClass A CSS class name.
+		 * @property {Object} style An object with CSS properties.
+		 */
+		/**
+		 * @class This object is used to style range.
+		 * <p>
+		 * <b>See:</b><br/>
+		 * {@link orion.textview.TextView}<br/>
+		 * {@link orion.textview.TextView#event:onLineStyle}
+		 * </p>		 
+		 * @name orion.textview.StyleRange
+		 * 
+		 * @property {Number} start The start character offset, relative to the document, where the style should be applied.
+		 * @property {Number} end The end character offset (exclusive), relative to the document, where the style should be applied.
+		 * @property {orion.textview.Style} style The style for the range.
+		 */
+		/**
+		 * @class This is the event sent when the text view needs the style information for a line.
+		 * <p>
+		 * <b>See:</b><br/>
+		 * {@link orion.textview.TextView}<br/>
+		 * {@link orion.textview.TextView#event:onLineStyle}
+		 * </p>		 
+		 * @name orion.textview.LineStyleEvent
+		 * 
+		 * @property {Number} lineIndex The line index.
+		 * @property {String} lineText The line text.
+		 * @property {Number} lineStart The character offset, relative to document, of the first character in the line.
+		 * @property {orion.textview.Style} style The style for the entire line (output argument).
+		 * @property {orion.textview.StyleRange[]} ranges An array of style ranges for the line (output argument).		 
+		 */
+		/**
+		 * This event is sent when the text view needs the style information for a line.
+		 *
+		 * @event
+		 * @param {orion.textview.LineStyleEvent} lineStyleEvent the event
+		 */
+		onLineStyle: function(lineStyleEvent) {
+			this._eventTable.sendEvent("LineStyle", lineStyleEvent);
+		},
+		/**
+		 * @class This is the event sent when the text in the model has changed.
+		 * <p>
+		 * <b>See:</b><br/>
+		 * {@link orion.textview.TextView}<br/>
+		 * {@link orion.textview.TextView#event:onModelChanged}<br/>
+		 * {@link orion.textview.TextModel#onChanged}
+		 * </p>
+		 * @name orion.textview.ModelChangedEvent
+		 * 
+		 * @property {Number} start The character offset in the model where the change has occurred.
+		 * @property {Number} removedCharCount The number of characters removed from the model.
+		 * @property {Number} addedCharCount The number of characters added to the model.
+		 * @property {Number} removedLineCount The number of lines removed from the model.
+		 * @property {Number} addedLineCount The number of lines added to the model.
+		 */
+		/**
+		 * This event is sent when the text in the model has changed.
+		 *
+		 * @event
+		 * @param {orion.textview.ModelChangingEvent} modelChangingEvent the event
+		 */
+		onModelChanged: function(modelChangedEvent) {
+			this._eventTable.sendEvent("ModelChanged", modelChangedEvent);
+		},
+		/**
+		 * @class This is the event sent when the text in the model is about to change.
+		 * <p>
+		 * <b>See:</b><br/>
+		 * {@link orion.textview.TextView}<br/>
+		 * {@link orion.textview.TextView#event:onModelChanging}<br/>
+		 * {@link orion.textview.TextModel#onChanging}
+		 * </p>
+		 * @name orion.textview.ModelChangingEvent
+		 * 
+		 * @property {String} text The text that is about to be inserted in the model.
+		 * @property {Number} start The character offset in the model where the change will occur.
+		 * @property {Number} removedCharCount The number of characters being removed from the model.
+		 * @property {Number} addedCharCount The number of characters being added to the model.
+		 * @property {Number} removedLineCount The number of lines being removed from the model.
+		 * @property {Number} addedLineCount The number of lines being added to the model.
+		 */
+		/**
+		 * This event is sent when the text in the model is about to change.
+		 *
+		 * @event
+		 * @param {orion.textview.ModelChangingEvent} modelChangingEvent the event
+		 */
+		onModelChanging: function(modelChangingEvent) {
+			this._eventTable.sendEvent("ModelChanging", modelChangingEvent);
+		},
+		/**
+		 * @class This is the event sent when the text is modified by the text view.
+		 * <p>
+		 * <b>See:</b><br/>
+		 * {@link orion.textview.TextView}<br/>
+		 * {@link orion.textview.TextView#event:onModify}
+		 * </p>
+		 * @name orion.textview.ModifyEvent
+		 */
+		/**
+		 * This event is sent when the text view has changed text in the model.
+		 * <p>
+		 * If the text is changed directly through the model API, this event
+		 * is not sent.
+		 * </p>
+		 *
+		 * @event
+		 * @param {orion.textview.ModifyEvent} modifyEvent the event
+		 */
+		onModify: function(modifyEvent) {
+			this._eventTable.sendEvent("Modify", modifyEvent);
+		},
+		/**
+		 * @class This is the event sent when the selection changes in the text view.
+		 * <p>
+		 * <b>See:</b><br/>
+		 * {@link orion.textview.TextView}<br/>
+		 * {@link orion.textview.TextView#event:onSelection}
+		 * </p>		 
+		 * @name orion.textview.SelectionEvent
+		 * 
+		 * @property {orion.textview.Selection} oldValue The old selection.
+		 * @property {orion.textview.Selection} newValue The new selection.
+		 */
+		/**
+		 * This event is sent when the text view selection has changed.
+		 *
+		 * @event
+		 * @param {orion.textview.SelectionEvent} selectionEvent the event
+		 */
+		onSelection: function(selectionEvent) {
+			this._eventTable.sendEvent("Selection", selectionEvent);
+		},
+		/**
+		 * @class This is the event sent when the text view scrolls.
+		 * <p>
+		 * <b>See:</b><br/>
+		 * {@link orion.textview.TextView}<br/>
+		 * {@link orion.textview.TextView#event:onScroll}
+		 * </p>		 
+		 * @name orion.textview.ScrollEvent
+		 * 
+		 * @property oldValue The old scroll {x,y}.
+		 * @property newValue The new scroll {x,y}.
+		 */
+		/**
+		 * This event is sent when the text view scrolls vertically or horizontally.
+		 *
+		 * @event
+		 * @param {orion.textview.ScrollEvent} scrollEvent the event
+		 */
+		onScroll: function(scrollEvent) {
+			this._eventTable.sendEvent("Scroll", scrollEvent);
+		},
+		/**
+		 * @class This is the event sent when the text is about to be modified by the text view.
+		 * <p>
+		 * <b>See:</b><br/>
+		 * {@link orion.textview.TextView}<br/>
+		 * {@link orion.textview.TextView#event:onVerify}
+		 * </p>
+		 * @name orion.textview.VerifyEvent
+		 * 
+		 * @property {String} text The text being inserted.
+		 * @property {Number} start The start offset of the text range to be replaced.
+		 * @property {Number} end The end offset (exclusive) of the text range to be replaced.
+		 */
+		/**
+		 * This event is sent when the text view is about to change text in the model.
+		 * <p>
+		 * If the text is changed directly through the model API, this event
+		 * is not sent.
+		 * </p>
+		 * <p>
+		 * Listeners are allowed to change these parameters. Setting text to null
+		 * or undefined stops the change.
+		 * </p>
+		 *
+		 * @event
+		 * @param {orion.textview.VerifyEvent} verifyEvent the event
+		 */
+		onVerify: function(verifyEvent) {
+			this._eventTable.sendEvent("Verify", verifyEvent);
+		},
+		/**
+		 * Redraws the text in the given line range.
+		 * <p>
+		 * The line at the end index is not redrawn.
+		 * </p>
+		 *
+		 * @param {Number} [startLine=0] the start line
+		 * @param {Number} [endLine=line count] the end line
+		 */
+		redrawLines: function(startLine, endLine, ruler) {
+			if (startLine === undefined) { startLine = 0; }
+			if (endLine === undefined) { endLine = this._model.getLineCount(); }
+			if (startLine === endLine) { return; }
+			var div = this._clientDiv;
+			if (ruler) {
+				var location = ruler.getLocation();//"left" or "right"
+				var divRuler = location === "left" ? this._leftDiv : this._rightDiv;
+				var cells = divRuler.firstChild.rows[0].cells;
+				for (var i = 0; i < cells.length; i++) {
+					if (cells[i].firstChild._ruler === ruler) {
+						div = cells[i].firstChild;
+						break;
+					}
+				}
+			}
+			if (ruler) {
+				div.rulerChanged = true;
+			}
+			if (!ruler || ruler.getOverview() === "page") {
+				var child = div.firstChild;
+				while (child) {
+					var lineIndex = child.lineIndex;
+					if (startLine <= lineIndex && lineIndex < endLine) {
+						child.lineChanged = true;
+					}
+					child = child.nextSibling;
+				}
+			}
+			if (!ruler) {
+				if (startLine <= this._maxLineIndex && this._maxLineIndex < endLine) {
+					this._maxLineIndex = -1;
+					this._maxLineWidth = 0;
+				}
+			}
+			this._queueUpdatePage();
+		},
+		/**
+		 * Redraws the text in the given range.
+		 * <p>
+		 * The character at the end offset is not redrawn.
+		 * </p>
+		 *
+		 * @param {Number} [start=0] the start offset of text range
+		 * @param {Number} [end=char count] the end offset of text range
+		 */
+		redrawRange: function(start, end) {
+			var model = this._model;
+			if (start === undefined) { start = 0; }
+			if (end === undefined) { end = model.getCharCount(); }
+			if (start === end) { return; }
+			var startLine = model.getLineAtOffset(start);
+			var endLine = model.getLineAtOffset(Math.max(0, end - 1)) + 1;
+			this.redrawLines(startLine, endLine);
+		},
+		/**
+		 * Removes an event listener from the text view.
+		 * <p>
+		 * All the parameters must be the same ones used to add the listener.
+		 * </p>
+		 * 
+		 * @param {String} type the event type.
+		 * @param {Object} context the context of the function.
+		 * @param {Function} func the function that will be executed when the event happens. 
+		 * @param {Object} [data] optional data passed to the function.
+		 * 
+		 * @see #addEventListener
+		 */
+		removeEventListener: function(type, context, func, data) {
+			this._eventTable.removeEventListener(type, context, func, data);
+		},
+		/**
+		 * Removes a ruler from the text view.
+		 *
+		 * @param {orion.textview.Ruler} ruler the ruler.
+		 */
+		removeRuler: function (ruler) {
+			ruler.setView(null);
+			var side = ruler.getLocation();
+			var rulerParent = side === "left" ? this._leftDiv : this._rightDiv;
+			var row = rulerParent.firstChild.rows[0];
+			var cells = row.cells;
+			for (var index = 0; index < cells.length; index++) {
+				var cell = cells[index];
+				if (cell.firstChild._ruler === ruler) { break; }
+			}
+			if (index === cells.length) { return; }
+			row.cells[index]._ruler = undefined;
+			row.deleteCell(index);
+			this._updatePage();
+		},
+		/**
+		 * Associates an application defined handler to an action name.
+		 * <p>
+		 * If the action name is a predefined action, the given handler executes before
+		 * the default action handler.  If the given handler returns <code>true</code>, the
+		 * default action handler is not called.
+		 * </p>
+		 *
+		 * @param {String} name the action name.
+		 * @param {Function} handler the action handler.
+		 *
+		 * @see #getActions
+		 * @see #invokeAction
+		 */
+		setAction: function(name, handler) {
+			if (!name) { return; }
+			var actions = this._actions;
+			for (var i = 0; i < actions.length; i++) {
+				var a = actions[i];
+				if (a.name === name) {
+					a.userHandler = handler;
+					return;
+				}
+			}
+			actions.push({name: name, userHandler: handler});
+		},
+		/**
+		 * Associates a key binding with the given action name. Any previous
+		 * association with the specified key binding is overwriten. If the
+		 * action name is <code>null</code>, the association is removed.
+		 * 
+		 * @param {orion.textview.KeyBinding} keyBinding the key binding
+		 * @param {String} name the action
+		 */
+		setKeyBinding: function(keyBinding, name) {
+			var keyBindings = this._keyBindings;
+			for (var i = 0; i < keyBindings.length; i++) {
+				var kb = keyBindings[i]; 
+				if (kb.keyBinding.equals(keyBinding)) {
+					if (name) {
+						kb.name = name;
+					} else {
+						if (kb.predefined) {
+							kb.name = null;
+						} else {
+							var oldName = kb.name; 
+							keyBindings.splice(i, 1);
+							var index = 0;
+							while (index < keyBindings.length && oldName !== keyBindings[index].name) {
+								index++;
+							}
+							if (index === keyBindings.length) {
+								/* <p>
+								 * Removing all the key bindings associated to an user action will cause
+								 * the user action to be removed. TextView predefined actions are never
+								 * removed (so they can be reinstalled in the future). 
+								 * </p>
+								 */
+								var actions = this._actions;
+								for (var j = 0; j < actions.length; j++) {
+									if (actions[j].name === oldName) {
+										if (!actions[j].defaultHandler) {
+											actions.splice(j, 1);
+										}
+									}
+								}
+							}
+						}
+					}
+					return;
+				}
+			}
+			if (name) {
+				keyBindings.push({keyBinding: keyBinding, name: name});
+			}
+		},
+		/**
+		 * Sets the caret offset relative to the start of the document.
+		 *
+		 * @param {Number} caret the caret offset relative to the start of the document.
+		 * @param {Boolean} [show=true] if <code>true</coce>, the view will scroll if needed to show the caret location.
+		 *
+		 * @see #getCaretOffset
+		 * @see #setSelection
+		 * @see #getSelection
+		 */
+		setCaretOffset: function(offset, show) {
+			var charCount = this._model.getCharCount();
+			offset = Math.max(0, Math.min (offset, charCount));
+			var selection = new Selection(offset, offset, false);
+			this._setSelection (selection, show === undefined || show);
+		},
+		/**
+		 * Sets the horizontal pixel.
+		 * <p>
+		 * The horizontal pixel is the pixel position that is currently at
+		 * the left edge of the view.  This position is relative to the
+		 * beginning of the document.
+		 * </p>
+		 *
+		 * @param {Number} pixel the horizontal pixel.
+		 *
+		 * @see #getHorizontalPixel
+		 * @see #convert
+		 */
+		setHorizontalPixel: function(pixel) {
+			pixel = Math.max(0, pixel);
+			this._scrollView(pixel - this._getScroll().x, 0);
+		},
+		/**
+		 * Sets the text model of the text view.
+		 *
+		 * @param {orion.textview.TextModel} model the text model of the view.
+		 */
+		setModel: function(model) {
+			if (!model) { return; }
+			this._model.removeListener(this._modelListener);
+			var oldLineCount = this._model.getLineCount();
+			var oldCharCount = this._model.getCharCount();
+			var newLineCount = model.getLineCount();
+			var newCharCount = model.getCharCount();
+			var newText = model.getText();
+			var e = {
+				text: newText,
+				start: 0,
+				removedCharCount: oldCharCount,
+				addedCharCount: newCharCount,
+				removedLineCount: oldLineCount,
+				addedLineCount: newLineCount
+			};
+			this.onModelChanging(e); 
+			this.redrawRange();
+			this._model = model;
+			e = {
+				start: 0,
+				removedCharCount: oldCharCount,
+				addedCharCount: newCharCount,
+				removedLineCount: oldLineCount,
+				addedLineCount: newLineCount
+			};
+			this.onModelChanged(e); 
+			this._model.addListener(this._modelListener);
+			this.redrawRange();
+		},
+		/**
+		 * Sets the text view selection.
+		 * <p>
+		 * The selection is defined by a start and end character offset relative to the
+		 * document. The character at end offset is not included in the selection.
+		 * </p>
+		 * <p>
+		 * The caret is always placed at the end offset. The start offset can be
+		 * greater than the end offset to place the caret at the beginning of the
+		 * selection.
+		 * </p>
+		 * <p>
+		 * Clamps out of range offsets.
+		 * </p>
+		 * 
+		 * @param {Number} start the start offset of the selection
+		 * @param {Number} end the end offset of the selection
+		 * @param {Boolean} [show=true] if <code>true</coce>, the view will scroll if needed to show the caret location.
+		 *
+		 * @see #getSelection
+		 */
+		setSelection: function (start, end, show) {
+			var caret = start > end;
+			if (caret) {
+				var tmp = start;
+				start = end;
+				end = tmp;
+			}
+			var charCount = this._model.getCharCount();
+			start = Math.max(0, Math.min (start, charCount));
+			end = Math.max(0, Math.min (end, charCount));
+			var selection = new Selection(start, end, caret);
+			this._setSelection(selection, show === undefined || show);
+		},
+		/**
+		 * Replaces the text in the given range with the given text.
+		 * <p>
+		 * The character at the end offset is not replaced.
+		 * </p>
+		 * <p>
+		 * When both <code>start</code> and <code>end</code> parameters
+		 * are not specified, the text view places the caret at the beginning
+		 * of the document and scrolls to make it visible.
+		 * </p>
+		 *
+		 * @param {String} text the new text.
+		 * @param {Number} [start=0] the start offset of text range.
+		 * @param {Number} [end=char count] the end offset of text range.
+		 *
+		 * @see #getText
+		 */
+		setText: function (text, start, end) {
+			var reset = start === undefined && end === undefined;
+			if (start === undefined) { start = 0; }
+			if (end === undefined) { end = this._model.getCharCount(); }
+			this._modifyContent({text: text, start: start, end: end, _code: true}, !reset);
+			if (reset) {
+				this._columnX = -1;
+				this._setSelection(new Selection (0, 0, false), true);
+				
+				/*
+				* Bug in Firefox.  For some reason, the caret does not show after the
+				* view is refreshed.  The fix is to toggle the contentEditable state and
+				* force the clientDiv to loose and receive focus if the it is focused.
+				*/
+				if (isFirefox) {
+					var hasFocus = this._hasFocus;
+					var clientDiv = this._clientDiv;
+					if (hasFocus) { clientDiv.blur(); }
+					clientDiv.contentEditable = false;
+					clientDiv.contentEditable = true;
+					if (hasFocus) { clientDiv.focus(); }
+				}
+			}
+		},
+		/**
+		 * Sets the top index.
+		 * <p>
+		 * The top index is the line that is currently at the top of the text view.  This
+		 * line may be partially visible depending on the vertical scroll of the view.
+		 * </p>
+		 *
+		 * @param {Number} topIndex the index of the top line.
+		 *
+		 * @see #getBottomIndex
+		 * @see #getTopIndex
+		 */
+		setTopIndex: function(topIndex) {
+			var model = this._model;
+			if (model.getCharCount() === 0) {
+				return;
+			}
+			var lineCount = model.getLineCount();
+			var lineHeight = this._getLineHeight();
+			var pageSize = Math.max(1, Math.min(lineCount, Math.floor(this._getClientHeight () / lineHeight)));
+			if (topIndex < 0) {
+				topIndex = 0;
+			} else if (topIndex > lineCount - pageSize) {
+				topIndex = lineCount - pageSize;
+			}
+			var pixel = topIndex * lineHeight - this._getScroll().y;
+			this._scrollView(0, pixel);
+		},
+		/**
+		 * Sets the top pixel.
+		 * <p>
+		 * The top pixel is the pixel position that is currently at
+		 * the top edge of the view.  This position is relative to the
+		 * beginning of the document.
+		 * </p>
+		 *
+		 * @param {Number} pixel the top pixel.
+		 *
+		 * @see #getBottomPixel
+		 * @see #getTopPixel
+		 * @see #convert
+		 */
+		setTopPixel: function(pixel) {
+			var lineHeight = this._getLineHeight();
+			var clientHeight = this._getClientHeight();
+			var lineCount = this._model.getLineCount();
+			pixel = Math.min(Math.max(0, pixel), lineHeight * lineCount - clientHeight);
+			this._scrollView(0, pixel - this._getScroll().y);
+		},
+		/**
+		 * Scrolls the selection into view if needed.
+		 *
+		 * @see #getSelection
+		 * @see #setSelection
+		 */
+		showSelection: function() {
+			return this._showCaret();
+		},
+		
+		/**************************************** Event handlers *********************************/
+		_handleBodyMouseDown: function (e) {
+			if (!e) { e = window.event; }
+			/*
+			 * Prevent clicks outside of the view from taking focus 
+			 * away the view. Note that in Firefox and Opera clicking on the 
+			 * scrollbar also take focus from the view. Other browsers
+			 * do not have this problem and stopping the click over the 
+			 * scrollbar for them causes mouse capture problems.
+			 */
+			var topNode = isOpera ? this._clientDiv : this._overlayDiv || this._viewDiv;
+			
+			var temp = e.target ? e.target : e.srcElement;
+			while (temp) {
+				if (topNode === temp) {
+					return;
+				}
+				temp = temp.parentNode;
+			}
+			if (e.preventDefault) { e.preventDefault(); }
+			if (e.stopPropagation){ e.stopPropagation(); }
+			if (!isW3CEvents) {
+				/* In IE 8 is not possible to prevent the default handler from running
+				*  during mouse down event using usual API. The workaround is to use
+				*  setCapture/releaseCapture. 
+				*/ 
+				topNode.setCapture();
+				setTimeout(function() { topNode.releaseCapture(); }, 0);
+			}
+		},
+		_handleBlur: function (e) {
+			if (!e) { e = window.event; }
+			this._hasFocus = false;
+			/*
+			* Bug in IE 8 and earlier. For some reason when text is deselected
+			* the overflow selection at the end of some lines does not get redrawn.
+			* The fix is to create a DOM element in the body to force a redraw.
+			*/
+			if (isIE < 9) {
+				if (!this._getSelection().isEmpty()) {
+					var document = this._frameDocument;
+					var child = document.createElement("DIV");
+					var body = document.body;
+					body.appendChild(child);
+					body.removeChild(child);
+				}
+			}
+			if (isFirefox || isIE) {
+				if (this._selDiv1) {
+					var color = isIE ? "transparent" : "#AFAFAF";
+					this._selDiv1.style.background = color;
+					this._selDiv2.style.background = color;
+					this._selDiv3.style.background = color;
+				}
+			}
+		},
+		_handleContextMenu: function (e) {
+			if (!e) { e = window.event; }
+			var scroll = this._getScroll(); 
+			var viewRect = this._viewDiv.getBoundingClientRect(); 
+			var viewPad = this._getViewPadding(); 
+			var x = e.clientX + scroll.x - viewRect.left - viewPad.left; 
+			var y = e.clientY + scroll.y - viewRect.top - viewPad.top; 
+			this.onContextMenu({x: x, y: y, screenX: e.screenX, screenY: e.screenY}); 
+			if (e.preventDefault) { e.preventDefault(); }
+			return false;
+		},
+		_handleCopy: function (e) {
+			if (this._ignoreCopy) { return; }
+			if (!e) { e = window.event; }
+			if (this._doCopy(e)) {
+				if (e.preventDefault) { e.preventDefault(); }
+				return false;
+			}
+		},
+		_handleCut: function (e) {
+			if (!e) { e = window.event; }
+			if (this._doCut(e)) {
+				if (e.preventDefault) { e.preventDefault(); }
+				return false;
+			}
+		},
+		_handleDataModified: function(e) {
+			this._startIME();
+		},
+		_handleDblclick: function (e) {
+			if (!e) { e = window.event; }
+			var time = e.timeStamp ? e.timeStamp : new Date().getTime();
+			this._lastMouseTime = time;
+			if (this._clickCount !== 2) {
+				this._clickCount = 2;
+				this._handleMouse(e);
+			}
+		},
+		_handleDragStart: function (e) {
+			if (!e) { e = window.event; }
+			if (e.preventDefault) { e.preventDefault(); }
+			return false;
+		},
+		_handleDragOver: function (e) {
+			if (!e) { e = window.event; }
+			e.dataTransfer.dropEffect = "none";
+			if (e.preventDefault) { e.preventDefault(); }
+			return false;
+		},
+		_handleDrop: function (e) {
+			if (!e) { e = window.event; }
+			if (e.preventDefault) { e.preventDefault(); }
+			return false;
+		},
+		_handleDocFocus: function (e) {
+			if (!e) { e = window.event; }
+			this._clientDiv.focus();
+		},
+		_handleFocus: function (e) {
+			if (!e) { e = window.event; }
+			this._hasFocus = true;
+			/*
+			* Feature in IE.  The selection is not restored when the
+			* view gets focus and the caret is always placed at the
+			* beginning of the document.  The fix is to update the DOM
+			* selection during the focus event.
+			*/
+			if (isIE) {
+				this._updateDOMSelection();
+			}
+			if (isFirefox || isIE) {
+				if (this._selDiv1) {
+					var color = this._hightlightRGB;
+					this._selDiv1.style.background = color;
+					this._selDiv2.style.background = color;
+					this._selDiv3.style.background = color;
+				}
+			}
+		},
+		_handleKeyDown: function (e) {
+			if (!e) { e = window.event; }
+			if (isPad) {
+				if (e.keyCode === 8) {
+					this._doBackspace({});
+					e.preventDefault();
+				}
+				return;
+			}
+			if (e.keyCode === 229) {
+				if (this.readonly) {
+					if (e.preventDefault) { e.preventDefault(); }
+					return false;
+				}
+				this._startIME();
+			} else {
+				this._commitIME();
+			}
+			/*
+			* Feature in Firefox. When a key is held down the browser sends 
+			* right number of keypress events but only one keydown. This is
+			* unexpected and causes the view to only execute an action
+			* just one time. The fix is to ignore the keydown event and 
+			* execute the actions from the keypress handler.
+			* Note: This only happens on the Mac and Linux (Firefox 3.6).
+			*
+			* Feature in Opera.  Opera sends keypress events even for non-printable
+			* keys.  The fix is to handle actions in keypress instead of keydown.
+			*/
+			if (((isMac || isLinux) && isFirefox < 4) || isOpera) {
+				this._keyDownEvent = e;
+				return true;
+			}
+			
+			if (this._doAction(e)) {
+				if (e.preventDefault) {
+					e.preventDefault(); 
+				} else {
+					e.cancelBubble = true;
+					e.returnValue = false;
+					e.keyCode = 0;
+				}
+				return false;
+			}
+		},
+		_handleKeyPress: function (e) {
+			if (!e) { e = window.event; }
+			/*
+			* Feature in Embedded WebKit.  Embedded WekKit on Mac runs in compatibility mode and
+			* generates key press events for these Unicode values (Function keys).  This does not
+			* happen in Safari or Chrome.  The fix is to ignore these key events.
+			*/
+			if (isMac && isWebkit) {
+				if ((0xF700 <= e.keyCode && e.keyCode <= 0xF7FF) || e.keyCode === 13 || e.keyCode === 8) {
+					if (e.preventDefault) { e.preventDefault(); }
+					return false;
+				}
+			}
+			if (((isMac || isLinux) && isFirefox < 4) || isOpera) {
+				if (this._doAction(this._keyDownEvent)) {
+					if (e.preventDefault) { e.preventDefault(); }
+					return false;
+				}
+			}
+			var ctrlKey = isMac ? e.metaKey : e.ctrlKey;
+			if (e.charCode !== undefined) {
+				if (ctrlKey) {
+					switch (e.charCode) {
+						/*
+						* In Firefox and Safari if ctrl+v, ctrl+c ctrl+x is canceled
+						* the clipboard events are not sent. The fix to allow
+						* the browser to handles these key events.
+						*/
+						case 99://c
+						case 118://v
+						case 120://x
+							return true;
+					}
+				}
+			}
+			var ignore = false;
+			if (isMac) {
+				if (e.ctrlKey || e.metaKey) { ignore = true; }
+			} else {
+				if (isFirefox) {
+					//Firefox clears the state mask when ALT GR generates input
+					if (e.ctrlKey || e.altKey) { ignore = true; }
+				} else {
+					//IE and Chrome only send ALT GR when input is generated
+					if (e.ctrlKey ^ e.altKey) { ignore = true; }
+				}
+			}
+			if (!ignore) {
+				var key = isOpera ? e.which : (e.charCode !== undefined ? e.charCode : e.keyCode);
+				if (key !== 0) {
+					this._doContent(String.fromCharCode (key));
+					if (e.preventDefault) { e.preventDefault(); }
+					return false;
+				}
+			}
+		},
+		_handleKeyUp: function (e) {
+			if (!e) { e = window.event; }
+			
+			// don't commit for space (it happens during JP composition)  
+			if (e.keyCode === 13) {
+				this._commitIME();
+			}
+		},
+		_handleMouse: function (e) {
+			var target = this._frameWindow;
+			if (isIE) { target = this._clientDiv; }
+			if (this._overlayDiv) {
+				var self = this;
+				setTimeout(function () {
+					self.focus();
+				}, 0);
+			}
+			if (this._clickCount === 1) {
+				this._setGrab(target);
+				this._setSelectionTo(e.clientX, e.clientY, e.shiftKey);
+			} else {
+				/*
+				* Feature in IE8 and older, the sequence of events in the IE8 event model
+				* for a doule-click is:
+				*
+				*	down
+				*	up
+				*	up
+				*	dblclick
+				*
+				* Given that the mouse down/up events are not balanced, it is not possible to
+				* grab on mouse down and ungrab on mouse up.  The fix is to grab on the first
+				* mouse down and ungrab on mouse move when the button 1 is not set.
+				*/
+				if (isW3CEvents) { this._setGrab(target); }
+				
+				this._doubleClickSelection = null;
+				this._setSelectionTo(e.clientX, e.clientY, e.shiftKey);
+				this._doubleClickSelection = this._getSelection();
+			}
+		},
+		_handleMouseDown: function (e) {
+			if (!e) { e = window.event; }
+			var left = e.which ? e.button === 0 : e.button === 1;
+			this._commitIME();
+			if (left) {
+				this._isMouseDown = true;
+				var deltaX = Math.abs(this._lastMouseX - e.clientX);
+				var deltaY = Math.abs(this._lastMouseY - e.clientY);
+				var time = e.timeStamp ? e.timeStamp : new Date().getTime();  
+				if ((time - this._lastMouseTime) <= this._clickTime && deltaX <= this._clickDist && deltaY <= this._clickDist) {
+					this._clickCount++;
+				} else {
+					this._clickCount = 1;
+				}
+				this._lastMouseX = e.clientX;
+				this._lastMouseY = e.clientY;
+				this._lastMouseTime = time;
+				this._handleMouse(e);
+				if (isOpera) {
+						if (!this._hasFocus) {
+							this.focus();
+						}
+						e.preventDefault();
+				}
+			}
+		},
+		_handleMouseMove: function (e) {
+			if (!e) { e = window.event; }
+			/*
+			* Feature in IE8 and older, the sequence of events in the IE8 event model
+			* for a doule-click is:
+			*
+			*	down
+			*	up
+			*	up
+			*	dblclick
+			*
+			* Given that the mouse down/up events are not balanced, it is not possible to
+			* grab on mouse down and ungrab on mouse up.  The fix is to grab on the first
+			* mouse down and ungrab on mouse move when the button 1 is not set.
+			*
+			* In order to detect double-click and drag gestures, it is necessary to send
+			* a mouse down event from mouse move when the button is still down and isMouseDown
+			* flag is not set.
+			*/
+			if (!isW3CEvents) {
+				if (e.button === 0) {
+					this._setGrab(null);
+					return true;
+				}
+				if (!this._isMouseDown && e.button === 1 && (this._clickCount & 1) !== 0) {
+					this._clickCount = 2;
+					return this._handleMouse(e, this._clickCount);
+				}
+			}
+			
+			var x = e.clientX;
+			var y = e.clientY;
+			var viewPad = this._getViewPadding();
+			var viewRect = this._viewDiv.getBoundingClientRect();
+			var width = this._getClientWidth (), height = this._getClientHeight();
+			var leftEdge = viewRect.left + viewPad.left;
+			var topEdge = viewRect.top + viewPad.top;
+			var rightEdge = viewRect.left + viewPad.left + width;
+			var bottomEdge = viewRect.top + viewPad.top + height;
+			var model = this._model;
+			var caretLine = model.getLineAtOffset(this._getSelection().getCaret());
+			if (y < topEdge && caretLine !== 0) {
+				this._doAutoScroll("up", x, y - topEdge);
+			} else if (y > bottomEdge && caretLine !== model.getLineCount() - 1) {
+				this._doAutoScroll("down", x, y - bottomEdge);
+			} else if (x < leftEdge) {
+				this._doAutoScroll("left", x - leftEdge, y);
+			} else if (x > rightEdge) {
+				this._doAutoScroll("right", x - rightEdge, y);
+			} else {
+				this._endAutoScroll();
+				this._setSelectionTo(x, y, true);
+				/*
+				* Feature in IE. IE does redraw the selection background right
+				* away after the selection changes because of mouse move events.
+				* The fix is to call getBoundingClientRect() on the
+				* body element to force the selection to be redraw. Some how
+				* calling this method forces a redraw.
+				*/
+				if (isIE) {
+					var body = this._frameDocument.body;
+					body.getBoundingClientRect();
+				}
+			}
+		},
+		_handleMouseUp: function (e) {
+			if (!e) { e = window.event; }
+			this._endAutoScroll();
+			var left = e.which ? e.button === 0 : e.button === 1;
+			if (left) {
+				this._isMouseDown=false;
+				
+				/*
+				* Feature in IE8 and older, the sequence of events in the IE8 event model
+				* for a doule-click is:
+				*
+				*	down
+				*	up
+				*	up
+				*	dblclick
+				*
+				* Given that the mouse down/up events are not balanced, it is not possible to
+				* grab on mouse down and ungrab on mouse up.  The fix is to grab on the first
+				* mouse down and ungrab on mouse move when the button 1 is not set.
+				*/
+				if (isW3CEvents) { this._setGrab(null); }
+			}
+		},
+		_handleMouseWheel: function (e) {
+			if (!e) { e = window.event; }
+			var lineHeight = this._getLineHeight();
+			var pixelX = 0, pixelY = 0;
+			// Note: On the Mac the correct behaviour is to scroll by pixel.
+			if (isFirefox) {
+				var pixel;
+				if (isMac) {
+					pixel = e.detail * 3;
+				} else {
+					var limit = 256;
+					pixel = Math.max(-limit, Math.min(limit, e.detail)) * lineHeight;
+				}
+				if (e.axis === e.HORIZONTAL_AXIS) {
+					pixelX = pixel;
+				} else {
+					pixelY = pixel;
+				}
+			} else {
+				//Webkit
+				if (isMac) {
+					/*
+					* In Safari, the wheel delta is a multiple of 120. In order to
+					* convert delta to pixel values, it is necessary to divide delta
+					* by 40.
+					*
+					* In Chrome, the wheel delta depends on the type of the mouse. In
+					* general, it is the pixel value for Mac mice and track pads, but
+					* it is a multiple of 120 for other mice. There is no presise
+					* way to determine if it is pixel value or a multiple of 120.
+					* 
+					* Note that the current approach does not calculate the correct
+					* pixel value for Mac mice when the delta is a multiple of 120.
+					*/
+					var denominatorX = 40, denominatorY = 40;
+					if (isChrome) {
+						if (e.wheelDeltaX % 120 !== 0) { denominatorX = 1; }
+						if (e.wheelDeltaY % 120 !== 0) { denominatorY = 1; }
+					}
+					pixelX = -e.wheelDeltaX / denominatorX;
+					if (-1 < pixelX && pixelX < 0) { pixelX = -1; }
+					if (0 < pixelX && pixelX < 1) { pixelX = 1; }
+					pixelY = -e.wheelDeltaY / denominatorY;
+					if (-1 < pixelY && pixelY < 0) { pixelY = -1; }
+					if (0 < pixelY && pixelY < 1) { pixelY = 1; }
+				} else {
+					pixelX = -e.wheelDeltaX;
+					var linesToScroll = 8;
+					pixelY = (-e.wheelDeltaY / 120 * linesToScroll) * lineHeight;
+				}
+			}
+			/* 
+			* Feature in Safari. If the event target is removed from the DOM 
+			* safari stops smooth scrolling. The fix is keep the element target
+			* in the DOM and remove it on a later time. 
+			*
+			* Note: Using a timer is not a solution, because the timeout needs to
+			* be at least as long as the gesture (which is too long).
+			*/
+			if (isSafari) {
+				var lineDiv = e.target;
+				while (lineDiv && lineDiv.lineIndex === undefined) {
+					lineDiv = lineDiv.parentNode;
+				}
+				this._mouseWheelLine = lineDiv;
+			}
+			var oldScroll = this._getScroll();
+			this._scrollView(pixelX, pixelY);
+			var newScroll = this._getScroll();
+			if (isSafari) { this._mouseWheelLine = null; }
+			if (oldScroll.x !== newScroll.x || oldScroll.y !== newScroll.y) {
+				if (e.preventDefault) { e.preventDefault(); }
+				return false;
+			}
+		},
+		_handlePaste: function (e) {
+			if (this._ignorePaste) { return; }
+			if (!e) { e = window.event; }
+			if (this._doPaste(e)) {
+				if (isIE) {
+					/*
+					 * Bug in IE,  
+					 */
+					var self = this;
+					setTimeout(function() {self._updateDOMSelection();}, 0);
+				}
+				if (e.preventDefault) { e.preventDefault(); }
+				return false;
+			}
+		},
+		_handleResize: function (e) {
+			if (!e) { e = window.event; }
+			var element = this._frameDocument.documentElement;
+			var newWidth = element.clientWidth;
+			var newHeight = element.clientHeight;
+			if (this._frameWidth !== newWidth || this._frameHeight !== newHeight) {
+				this._frameWidth = newWidth;
+				this._frameHeight = newHeight;
+				this._updatePage();
+			}
+		},
+		_handleRulerEvent: function (e) {
+			if (!e) { e = window.event; }
+			var target = e.target ? e.target : e.srcElement;
+			var lineIndex = target.lineIndex;
+			var element = target;
+			while (element && !element._ruler) {
+				if (lineIndex === undefined && element.lineIndex !== undefined) {
+					lineIndex = element.lineIndex;
+				}
+				element = element.parentNode;
+			}
+			var ruler = element ? element._ruler : null;
+			if (isPad && lineIndex === undefined && ruler && ruler.getOverview() === "document") {
+				var buttonHeight = 17;
+				var clientHeight = this._getClientHeight ();
+				var lineHeight = this._getLineHeight ();
+				var viewPad = this._getViewPadding();
+				var trackHeight = clientHeight + viewPad.top + viewPad.bottom - 2 * buttonHeight;
+				var pixels = this._model.getLineCount () * lineHeight;
+				this.setTopPixel(Math.floor((e.clientY - buttonHeight - lineHeight) * pixels / trackHeight));
+			}
+			if (ruler) {
+				switch (e.type) {
+					case "click":
+						if (ruler.onClick) { ruler.onClick(lineIndex, e); }
+						break;
+					case "dblclick": 
+						if (ruler.onDblClick) { ruler.onDblClick(lineIndex, e); }
+						break;
+				}
+			}
+		},
+		_handleScroll: function () {
+			this._doScroll(this._getScroll());
+		},
+		_handleSelectStart: function (e) {
+			if (!e) { e = window.event; }
+			if (this._ignoreSelect) {
+				if (e && e.preventDefault) { e.preventDefault(); }
+				return false;
+			}
+		},
+		_handleInput: function (e) {
+			var textArea = this._textArea;
+			this._doContent(textArea.value);
+			textArea.selectionStart = textArea.selectionEnd = 0;
+			textArea.value = "";
+			e.preventDefault();
+		},
+		_handleTextInput: function (e) {
+			this._doContent(e.data);
+			e.preventDefault();
+		},
+		_touchConvert: function (touch) {
+			var rect = this._frame.getBoundingClientRect();
+			var body = this._parentDocument.body;
+			return {left: touch.clientX - rect.left - body.scrollLeft, top: touch.clientY - rect.top - body.scrollTop};
+		},
+		_handleTouchStart: function (e) {
+			var touches = e.touches, touch, pt, sel;
+			this._touchMoved = false;
+			this._touchStartScroll = undefined;
+			if (touches.length === 1) {
+				touch = touches[0];
+				var pageX = touch.pageX;
+				var pageY = touch.pageY;
+				this._touchStartX = pageX;
+				this._touchStartY = pageY;
+				this._touchStartTime = e.timeStamp;
+				this._touchStartScroll = this._getScroll();
+				sel = this._getSelection();
+				pt = this._touchConvert(touches[0]);
+				this._touchGesture = "none";
+				if (!sel.isEmpty()) {
+					if (this._hitOffset(sel.end, pt.left, pt.top)) {
+						this._touchGesture = "extendEnd";
+					} else if (this._hitOffset(sel.start, pt.left, pt.top)) {
+						this._touchGesture = "extendStart";
+					}
+				}
+				if (this._touchGesture === "none") {
+					var textArea = this._textArea;
+					textArea.value = "";
+					textArea.style.left = "-1000px";
+					textArea.style.top = "-1000px";
+					textArea.style.width = "3000px";
+					textArea.style.height = "3000px";
+					var self = this;
+					var f = function() {
+						self._touchTimeout = null;
+						self._clickCount = 1;
+						self._setSelectionTo(pt.left, pt.top, false);
+					};
+					this._touchTimeout = setTimeout(f, 200);
+				}
+			} else if (touches.length === 2) {
+				this._touchGesture = "select";
+				if (this._touchTimeout) {
+					clearTimeout(this._touchTimeout);
+					this._touchTimeout = null;
+				}
+				pt = this._touchConvert(touches[0]);
+				var offset1 = this._getXToOffset(this._getYToLine(pt.top), pt.left);
+				pt = this._touchConvert(touches[1]);
+				var offset2 = this._getXToOffset(this._getYToLine(pt.top), pt.left);
+				sel = this._getSelection();
+				sel.setCaret(offset1);
+				sel.extend(offset2);
+				this._setSelection(sel, true, true);
+			}
+			//Cannot prevent to show maginifier
+//			e.preventDefault();
+		},
+		_handleTouchMove: function (e) {
+			this._touchMoved = true;
+			var touches = e.touches, pt, sel;
+			if (touches.length === 1) {
+				var touch = touches[0];
+				var pageX = touch.pageX;
+				var pageY = touch.pageY;
+				var deltaX = this._touchStartX - pageX;
+				var deltaY = this._touchStartY - pageY;
+				pt = this._touchConvert(touch);
+				sel = this._getSelection();
+				if (this._touchTimeout) {
+					clearTimeout(this._touchTimeout);
+					this._touchTimeout = null;
+				}
+				if (this._touchGesture === "none") {
+					if ((e.timeStamp - this._touchStartTime) < 200 && (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5)) {
+						this._touchGesture = "scroll";
+					} else {
+						this._touchGesture = "caret";
+					}
+				}
+				if (this._touchGesture === "select") {
+					if (this._hitOffset(sel.end, pt.left, pt.top)) {
+						this._touchGesture = "extendEnd";
+					} else if (this._hitOffset(sel.start, pt.left, pt.top)) {
+						this._touchGesture = "extendStart";
+					} else {
+						this._touchGesture = "caret";
+					}
+				}
+				switch (this._touchGesture) {
+					case "scroll":
+						this._touchStartX = pageX;
+						this._touchStartY = pageY;
+						this._scrollView(deltaX, deltaY);
+						break;
+					case "extendStart":
+					case "extendEnd":
+						this._clickCount = 1;
+						var lineIndex = this._getYToLine(pt.top);
+						var offset = this._getXToOffset(lineIndex, pt.left);
+						sel.setCaret(this._touchGesture === "extendStart" ? sel.end : sel.start);
+						sel.extend(offset);
+						if (offset >= sel.end && this._touchGesture === "extendStart") {
+							this._touchGesture = "extendEnd";
+						}
+						if (offset <= sel.start && this._touchGesture === "extendEnd") {
+							this._touchGesture = "extendStart";
+						}
+						this._setSelection(sel, true, true);
+						break;
+					case "caret":
+						this._setSelectionTo(pt.left, pt.top, false);
+						break;
+				}
+			} else if (touches.length === 2) {
+				pt = this._touchConvert(touches[0]);
+				var offset1 = this._getXToOffset(this._getYToLine(pt.top), pt.left);
+				pt = this._touchConvert(touches[1]);
+				var offset2 = this._getXToOffset(this._getYToLine(pt.top), pt.left);
+				sel = this._getSelection();
+				sel.setCaret(offset1);
+				sel.extend(offset2);
+				this._setSelection(sel, true, true);
+			}
+			e.preventDefault();
+		},
+		_handleTouchEnd: function (e) {
+			if (!this._touchMoved) {
+				if (e.touches.length === 0 && e.changedTouches.length === 1 && this._touchTimeout) {
+					clearTimeout(this._touchTimeout);
+					this._touchTimeout = null;
+					var touch = e.changedTouches[0];
+					this._clickCount = 1;
+					var pt = this._touchConvert(touch);
+					this._setSelectionTo(pt.left, pt.top, false);
+				}
+			}
+			if (e.touches.length === 0) {
+				var self = this;
+				setTimeout(function() {
+					var selection = self._getSelection();
+					var text = self._model.getText(selection.start, selection.end);
+					var textArea = self._textArea;
+					textArea.value = text;
+					textArea.selectionStart = 0;
+					textArea.selectionEnd = text.length;
+					if (!selection.isEmpty()) {
+						var touchRect = self._touchDiv.getBoundingClientRect();
+						var bounds = self._getOffsetBounds(selection.start);
+						textArea.style.left = (touchRect.width / 2) + "px";
+						textArea.style.top = ((bounds.top > 40 ? bounds.top - 30 : bounds.top + 30)) + "px";
+					}
+				}, 0);
+			}
+			e.preventDefault();
+		},
+
+		/************************************ Actions ******************************************/
+		_doAction: function (e) {
+			var keyBindings = this._keyBindings;
+			for (var i = 0; i < keyBindings.length; i++) {
+				var kb = keyBindings[i];
+				if (kb.keyBinding.match(e)) {
+					if (kb.name) {
+						var actions = this._actions;
+						for (var j = 0; j < actions.length; j++) {
+							var a = actions[j];
+							if (a.name === kb.name) {
+								if (a.userHandler) {
+									if (!a.userHandler()) {
+										if (a.defaultHandler) {
+											a.defaultHandler();
+										} else {
+											return false;
+										}
+									}
+								} else if (a.defaultHandler) {
+									a.defaultHandler();
+								}
+								break;
+							}
+						}
+					}
+					return true;
+				}
+			}
+			return false;
+		},
+		_doBackspace: function (args) {
+			var selection = this._getSelection();
+			if (selection.isEmpty()) {
+				var model = this._model;
+				var caret = selection.getCaret();
+				var lineIndex = model.getLineAtOffset(caret);
+				if (caret === model.getLineStart(lineIndex)) {
+					if (lineIndex > 0) {
+						selection.extend(model.getLineEnd(lineIndex - 1));
+					}
+				} else {
+					selection.extend(this._getOffset(caret, args.unit, -1));
+				}
+			}
+			this._modifyContent({text: "", start: selection.start, end: selection.end}, true);
+			return true;
+		},
+		_doContent: function (text) {
+			var selection = this._getSelection();
+			this._modifyContent({text: text, start: selection.start, end: selection.end, _ignoreDOMSelection: true}, true);
+		},
+		_doCopy: function (e) {
+			var selection = this._getSelection();
+			if (!selection.isEmpty()) {
+				var text = this._model.getText(selection.start, selection.end);
+				return this._setClipboardText(text, e);
+			}
+			return true;
+		},
+		_doCursorNext: function (args) {
+			if (!args.select) {
+				if (this._clearSelection("next")) { return true; }
+			}
+			var model = this._model;
+			var selection = this._getSelection();
+			var caret = selection.getCaret();
+			var lineIndex = model.getLineAtOffset(caret);
+			if (caret === model.getLineEnd(lineIndex)) {
+				if (lineIndex + 1 < model.getLineCount()) {
+					selection.extend(model.getLineStart(lineIndex + 1));
+				}
+			} else {
+				selection.extend(this._getOffset(caret, args.unit, 1));
+			}
+			if (!args.select) { selection.collapse(); }
+			this._setSelection(selection, true);
+			return true;
+		},
+		_doCursorPrevious: function (args) {
+			if (!args.select) {
+				if (this._clearSelection("previous")) { return true; }
+			}
+			var model = this._model;
+			var selection = this._getSelection();
+			var caret = selection.getCaret();
+			var lineIndex = model.getLineAtOffset(caret);
+			if (caret === model.getLineStart(lineIndex)) {
+				if (lineIndex > 0) {
+					selection.extend(model.getLineEnd(lineIndex - 1));
+				}
+			} else {
+				selection.extend(this._getOffset(caret, args.unit, -1));
+			}
+			if (!args.select) { selection.collapse(); }
+			this._setSelection(selection, true);
+			return true;
+		},
+		_doCut: function (e) {
+			var selection = this._getSelection();
+			if (!selection.isEmpty()) {
+				var text = this._model.getText(selection.start, selection.end);
+				this._doContent("");
+				return this._setClipboardText(text, e);
+			}
+			return true;
+		},
+		_doDelete: function (args) {
+			var selection = this._getSelection();
+			if (selection.isEmpty()) {
+				var model = this._model;
+				var caret = selection.getCaret();
+				var lineIndex = model.getLineAtOffset(caret);
+				if (caret === model.getLineEnd (lineIndex)) {
+					if (lineIndex + 1 < model.getLineCount()) {
+						selection.extend(model.getLineStart(lineIndex + 1));
+					}
+				} else {
+					selection.extend(this._getOffset(caret, args.unit, 1));
+				}
+			}
+			this._modifyContent({text: "", start: selection.start, end: selection.end}, true);
+			return true;
+		},
+		_doEnd: function (args) {
+			var selection = this._getSelection();
+			var model = this._model;
+			if (args.ctrl) {
+				selection.extend(model.getCharCount());
+			} else {
+				var lineIndex = model.getLineAtOffset(selection.getCaret());
+				selection.extend(model.getLineEnd(lineIndex)); 
+			}
+			if (!args.select) { selection.collapse(); }
+			this._setSelection(selection, true);
+			return true;
+		},
+		_doEnter: function (args) {
+			var model = this._model;
+			this._doContent(model.getLineDelimiter()); 
+			return true;
+		},
+		_doHome: function (args) {
+			var selection = this._getSelection();
+			var model = this._model;
+			if (args.ctrl) {
+				selection.extend(0);
+			} else {
+				var lineIndex = model.getLineAtOffset(selection.getCaret());
+				selection.extend(model.getLineStart(lineIndex)); 
+			}
+			if (!args.select) { selection.collapse(); }
+			this._setSelection(selection, true);
+			return true;
+		},
+		_doLineDown: function (args) {
+			var model = this._model;
+			var selection = this._getSelection();
+			var caret = selection.getCaret();
+			var lineIndex = model.getLineAtOffset(caret);
+			if (lineIndex + 1 < model.getLineCount()) {
+				var x = this._columnX;
+				if (x === -1 || args.select) {
+					x = this._getOffsetToX(caret);
+				}
+				selection.extend(this._getXToOffset(lineIndex + 1, x));
+				if (!args.select) { selection.collapse(); }
+				this._setSelection(selection, true, true);
+				this._columnX = x;//fix x by scrolling
+			}
+			return true;
+		},
+		_doLineUp: function (args) {
+			var model = this._model;
+			var selection = this._getSelection();
+			var caret = selection.getCaret();
+			var lineIndex = model.getLineAtOffset(caret);
+			if (lineIndex > 0) {
+				var x = this._columnX;
+				if (x === -1 || args.select) {
+					x = this._getOffsetToX(caret);
+				}
+				selection.extend(this._getXToOffset(lineIndex - 1, x));
+				if (!args.select) { selection.collapse(); }
+				this._setSelection(selection, true, true);
+				this._columnX = x;//fix x by scrolling
+			}
+			return true;
+		},
+		_doPageDown: function (args) {
+			var model = this._model;
+			var selection = this._getSelection();
+			var caret = selection.getCaret();
+			var caretLine = model.getLineAtOffset(caret);
+			var lineCount = model.getLineCount();
+			if (caretLine < lineCount - 1) {
+				var clientHeight = this._getClientHeight();
+				var lineHeight = this._getLineHeight();
+				var lines = Math.floor(clientHeight / lineHeight);
+				var scrollLines = Math.min(lineCount - caretLine - 1, lines);
+				scrollLines = Math.max(1, scrollLines);
+				var x = this._columnX;
+				if (x === -1 || args.select) {
+					x = this._getOffsetToX(caret);
+				}
+				selection.extend(this._getXToOffset(caretLine + scrollLines, x));
+				if (!args.select) { selection.collapse(); }
+				this._setSelection(selection, false, false);
+				
+				var verticalMaximum = lineCount * lineHeight;
+				var verticalScrollOffset = this._getScroll().y;
+				var scrollOffset = verticalScrollOffset + scrollLines * lineHeight;
+				if (scrollOffset + clientHeight > verticalMaximum) {
+					scrollOffset = verticalMaximum - clientHeight;
+				} 
+				if (scrollOffset > verticalScrollOffset) {
+					this._scrollView(0, scrollOffset - verticalScrollOffset);
+				} else {
+					this._updateDOMSelection();
+				}
+				this._columnX = x;//fix x by scrolling
+			}
+			return true;
+		},
+		_doPageUp: function (args) {
+			var model = this._model;
+			var selection = this._getSelection();
+			var caret = selection.getCaret();
+			var caretLine = model.getLineAtOffset(caret);
+			if (caretLine > 0) {
+				var clientHeight = this._getClientHeight();
+				var lineHeight = this._getLineHeight();
+				var lines = Math.floor(clientHeight / lineHeight);
+				var scrollLines = Math.max(1, Math.min(caretLine, lines));
+				var x = this._columnX;
+				if (x === -1 || args.select) {
+					x = this._getOffsetToX(caret);
+				}
+				selection.extend(this._getXToOffset(caretLine - scrollLines, x));
+				if (!args.select) { selection.collapse(); }
+				this._setSelection(selection, false, false);
+				
+				var verticalScrollOffset = this._getScroll().y;
+				var scrollOffset = Math.max(0, verticalScrollOffset - scrollLines * lineHeight);
+				if (scrollOffset < verticalScrollOffset) {
+					this._scrollView(0, scrollOffset - verticalScrollOffset);
+				} else {
+					this._updateDOMSelection();
+				}
+				this._columnX = x;//fix x by scrolling
+			}
+			return true;
+		},
+		_doPaste: function(e) {
+			var text = this._getClipboardText(e);
+			if (text) {
+				this._doContent(text);
+			}
+			return text !== null;
+		},
+		_doScroll: function (scroll) {
+			var oldX = this._hScroll;
+			var oldY = this._vScroll;
+			if (oldX !== scroll.x || oldY !== scroll.y) {
+				this._hScroll = scroll.x;
+				this._vScroll = scroll.y;
+				this._commitIME();
+				this._updatePage();
+				var e = {
+					oldValue: {x: oldX, y: oldY},
+					newValue: scroll
+				};
+				this.onScroll(e);
+			}
+		},
+		_doSelectAll: function (args) {
+			var model = this._model;
+			var selection = this._getSelection();
+			selection.setCaret(0);
+			selection.extend(model.getCharCount());
+			this._setSelection(selection, false);
+			return true;
+		},
+		_doTab: function (args) {
+			this._doContent("\t"); 
+			return true;
+		},
+		
+		/************************************ Internals ******************************************/
+		_applyStyle: function(style, node) {
+			if (!style) {
+				return;
+			}
+			if (style.styleClass) {
+				node.className = style.styleClass;
+			}
+			var properties = style.style;
+			if (properties) {
+				for (var s in properties) {
+					if (properties.hasOwnProperty(s)) {
+						node.style[s] = properties[s];
+					}
+				}
+			}
+		},
+		_autoScroll: function () {
+			var selection = this._getSelection();
+			var line;
+			var x = this._autoScrollX;
+			if (this._autoScrollDir === "up" || this._autoScrollDir === "down") {
+				var scroll = this._autoScrollY / this._getLineHeight();
+				scroll = scroll < 0 ? Math.floor(scroll) : Math.ceil(scroll);
+				line = this._model.getLineAtOffset(selection.getCaret());
+				line = Math.max(0, Math.min(this._model.getLineCount() - 1, line + scroll));
+			} else if (this._autoScrollDir === "left" || this._autoScrollDir === "right") {
+				line = this._getYToLine(this._autoScrollY);
+				x += this._getOffsetToX(selection.getCaret());
+			}
+			selection.extend(this._getXToOffset(line, x));
+			this._setSelection(selection, true);
+		},
+		_autoScrollTimer: function () {
+			this._autoScroll();
+			var self = this;
+			this._autoScrollTimerID = setTimeout(function () {self._autoScrollTimer();}, this._AUTO_SCROLL_RATE);
+		},
+		_calculateLineHeight: function() {
+			var parent = this._clientDiv;
+			var document = this._frameDocument;
+			var c = " ";
+			var line = document.createElement("DIV");
+			line.style.position = "fixed";
+			line.style.left = "-1000px";
+			var span1 = document.createElement("SPAN");
+			span1.appendChild(document.createTextNode(c));
+			line.appendChild(span1);
+			var span2 = document.createElement("SPAN");
+			span2.style.fontStyle = "italic";
+			span2.appendChild(document.createTextNode(c));
+			line.appendChild(span2);
+			var span3 = document.createElement("SPAN");
+			span3.style.fontWeight = "bold";
+			span3.appendChild(document.createTextNode(c));
+			line.appendChild(span3);
+			var span4 = document.createElement("SPAN");
+			span4.style.fontWeight = "bold";
+			span4.style.fontStyle = "italic";
+			span4.appendChild(document.createTextNode(c));
+			line.appendChild(span4);
+			parent.appendChild(line);
+			var spanRect1 = span1.getBoundingClientRect();
+			var spanRect2 = span2.getBoundingClientRect();
+			var spanRect3 = span3.getBoundingClientRect();
+			var spanRect4 = span4.getBoundingClientRect();
+			var h1 = spanRect1.bottom - spanRect1.top;
+			var h2 = spanRect2.bottom - spanRect2.top;
+			var h3 = spanRect3.bottom - spanRect3.top;
+			var h4 = spanRect4.bottom - spanRect4.top;
+			var fontStyle = 0;
+			var lineHeight = h1;
+			if (h2 > h1) {
+				lineHeight = h2;
+				fontStyle = 1;
+			}
+			if (h3 > h2) {
+				lineHeight = h3;
+				fontStyle = 2;
+			}
+			if (h4 > h3) {
+				lineHeight = h4;
+				fontStyle = 3;
+			}
+			this._largestFontStyle = fontStyle;
+			parent.removeChild(line);
+			return lineHeight;
+		},
+		_calculatePadding: function() {
+			var document = this._frameDocument;
+			var parent = this._clientDiv;
+			var pad = this._getPadding(this._viewDiv);
+			var div1 = document.createElement("DIV");
+			div1.style.position = "fixed";
+			div1.style.left = "-1000px";
+			div1.style.paddingLeft = pad.left + "px";
+			div1.style.paddingTop = pad.top + "px";
+			div1.style.paddingRight = pad.right + "px";
+			div1.style.paddingBottom = pad.bottom + "px";
+			div1.style.width = "100px";
+			div1.style.height = "100px";
+			var div2 = document.createElement("DIV");
+			div2.style.width = "100%";
+			div2.style.height = "100%";
+			div1.appendChild(div2);
+			parent.appendChild(div1);
+			var rect1 = div1.getBoundingClientRect();
+			var rect2 = div2.getBoundingClientRect();
+			parent.removeChild(div1);
+			pad = {
+				left: rect2.left - rect1.left,
+				top: rect2.top - rect1.top,
+				right: rect1.right - rect2.right,
+				bottom: rect1.bottom - rect2.bottom
+			};
+			return pad;
+		},
+		_clearSelection: function (direction) {
+			var selection = this._getSelection();
+			if (selection.isEmpty()) { return false; }
+			if (direction === "next") {
+				selection.start = selection.end;
+			} else {
+				selection.end = selection.start;
+			}
+			this._setSelection(selection, true);
+			return true;
+		},
+		_commitIME: function () {
+			if (this._imeOffset === -1) { return; }
+			// make the state of the IME match the state the view expects it be in
+			// when the view commits the text and IME also need to be committed
+			// this can be accomplished by changing the focus around
+			this._scrollDiv.focus();
+			this._clientDiv.focus();
+			
+			var model = this._model;
+			var lineIndex = model.getLineAtOffset(this._imeOffset);
+			var lineStart = model.getLineStart(lineIndex);
+			var newText = this._getDOMText(lineIndex);
+			var oldText = model.getLine(lineIndex);
+			var start = this._imeOffset - lineStart;
+			var end = start + newText.length - oldText.length;
+			if (start !== end) {
+				var insertText = newText.substring(start, end);
+				this._doContent(insertText);
+			}
+			this._imeOffset = -1;
+		},
+		_convertDelimiter: function (text, addTextFunc, addDelimiterFunc) {
+				var cr = 0, lf = 0, index = 0, length = text.length;
+				while (index < length) {
+					if (cr !== -1 && cr <= index) { cr = text.indexOf("\r", index); }
+					if (lf !== -1 && lf <= index) { lf = text.indexOf("\n", index); }
+					var start = index, end;
+					if (lf === -1 && cr === -1) {
+						addTextFunc(text.substring(index));
+						break;
+					}
+					if (cr !== -1 && lf !== -1) {
+						if (cr + 1 === lf) {
+							end = cr;
+							index = lf + 1;
+						} else {
+							end = cr < lf ? cr : lf;
+							index = (cr < lf ? cr : lf) + 1;
+						}
+					} else if (cr !== -1) {
+						end = cr;
+						index = cr + 1;
+					} else {
+						end = lf;
+						index = lf + 1;
+					}
+					addTextFunc(text.substring(start, end));
+					addDelimiterFunc();
+				}
+		},
+		_createActions: function () {
+			var KeyBinding = orion.textview.KeyBinding;
+			//no duplicate keybindings
+			var bindings = this._keyBindings = [];
+
+			// Cursor Navigation
+			bindings.push({name: "lineUp",		keyBinding: new KeyBinding(38), predefined: true});
+			bindings.push({name: "lineDown",	keyBinding: new KeyBinding(40), predefined: true});
+			bindings.push({name: "charPrevious",	keyBinding: new KeyBinding(37), predefined: true});
+			bindings.push({name: "charNext",	keyBinding: new KeyBinding(39), predefined: true});
+			bindings.push({name: "pageUp",		keyBinding: new KeyBinding(33), predefined: true});
+			bindings.push({name: "pageDown",	keyBinding: new KeyBinding(34), predefined: true});
+			if (isMac) {
+				bindings.push({name: "lineStart",	keyBinding: new KeyBinding(37, true), predefined: true});
+				bindings.push({name: "lineEnd",		keyBinding: new KeyBinding(39, true), predefined: true});
+				bindings.push({name: "wordPrevious",	keyBinding: new KeyBinding(37, null, null, true), predefined: true});
+				bindings.push({name: "wordNext",	keyBinding: new KeyBinding(39, null, null, true), predefined: true});
+				bindings.push({name: "textStart",	keyBinding: new KeyBinding(36), predefined: true});
+				bindings.push({name: "textEnd",		keyBinding: new KeyBinding(35), predefined: true});
+				bindings.push({name: "textStart",	keyBinding: new KeyBinding(38, true), predefined: true});
+				bindings.push({name: "textEnd",		keyBinding: new KeyBinding(40, true), predefined: true});
+			} else {
+				bindings.push({name: "lineStart",	keyBinding: new KeyBinding(36), predefined: true});
+				bindings.push({name: "lineEnd",		keyBinding: new KeyBinding(35), predefined: true});
+				bindings.push({name: "wordPrevious",	keyBinding: new KeyBinding(37, true), predefined: true});
+				bindings.push({name: "wordNext",	keyBinding: new KeyBinding(39, true), predefined: true});
+				bindings.push({name: "textStart",	keyBinding: new KeyBinding(36, true), predefined: true});
+				bindings.push({name: "textEnd",		keyBinding: new KeyBinding(35, true), predefined: true});
+			}
+
+			// Select Cursor Navigation
+			bindings.push({name: "selectLineUp",		keyBinding: new KeyBinding(38, null, true), predefined: true});
+			bindings.push({name: "selectLineDown",		keyBinding: new KeyBinding(40, null, true), predefined: true});
+			bindings.push({name: "selectCharPrevious",	keyBinding: new KeyBinding(37, null, true), predefined: true});
+			bindings.push({name: "selectCharNext",		keyBinding: new KeyBinding(39, null, true), predefined: true});
+			bindings.push({name: "selectPageUp",		keyBinding: new KeyBinding(33, null, true), predefined: true});
+			bindings.push({name: "selectPageDown",		keyBinding: new KeyBinding(34, null, true), predefined: true});
+			if (isMac) {
+				bindings.push({name: "selectLineStart",	keyBinding: new KeyBinding(37, true, true), predefined: true});
+				bindings.push({name: "selectLineEnd",		keyBinding: new KeyBinding(39, true, true), predefined: true});
+				bindings.push({name: "selectWordPrevious",	keyBinding: new KeyBinding(37, null, true, true), predefined: true});
+				bindings.push({name: "selectWordNext",	keyBinding: new KeyBinding(39, null, true, true), predefined: true});
+				bindings.push({name: "selectTextStart",	keyBinding: new KeyBinding(36, null, true), predefined: true});
+				bindings.push({name: "selectTextEnd",		keyBinding: new KeyBinding(35, null, true), predefined: true});
+				bindings.push({name: "selectTextStart",	keyBinding: new KeyBinding(38, true, true), predefined: true});
+				bindings.push({name: "selectTextEnd",		keyBinding: new KeyBinding(40, true, true), predefined: true});
+			} else {
+				bindings.push({name: "selectLineStart",		keyBinding: new KeyBinding(36, null, true), predefined: true});
+				bindings.push({name: "selectLineEnd",		keyBinding: new KeyBinding(35, null, true), predefined: true});
+				bindings.push({name: "selectWordPrevious",	keyBinding: new KeyBinding(37, true, true), predefined: true});
+				bindings.push({name: "selectWordNext",		keyBinding: new KeyBinding(39, true, true), predefined: true});
+				bindings.push({name: "selectTextStart",		keyBinding: new KeyBinding(36, true, true), predefined: true});
+				bindings.push({name: "selectTextEnd",		keyBinding: new KeyBinding(35, true, true), predefined: true});
+			}
+
+			//Misc
+			bindings.push({name: "deletePrevious",		keyBinding: new KeyBinding(8), predefined: true});
+			bindings.push({name: "deletePrevious",		keyBinding: new KeyBinding(8, null, true), predefined: true});
+			bindings.push({name: "deleteNext",		keyBinding: new KeyBinding(46), predefined: true});
+			bindings.push({name: "deleteWordPrevious",	keyBinding: new KeyBinding(8, true), predefined: true});
+			bindings.push({name: "deleteWordPrevious",	keyBinding: new KeyBinding(8, true, true), predefined: true});
+			bindings.push({name: "deleteWordNext",		keyBinding: new KeyBinding(46, true), predefined: true});
+			bindings.push({name: "tab",			keyBinding: new KeyBinding(9), predefined: true});
+			bindings.push({name: "enter",			keyBinding: new KeyBinding(13), predefined: true});
+			bindings.push({name: "enter",			keyBinding: new KeyBinding(13, null, true), predefined: true});
+			bindings.push({name: "selectAll",		keyBinding: new KeyBinding('a', true), predefined: true});
+			if (isMac) {
+				bindings.push({name: "deleteNext",		keyBinding: new KeyBinding(46, null, true), predefined: true});
+				bindings.push({name: "deleteWordPrevious",	keyBinding: new KeyBinding(8, null, null, true), predefined: true});
+				bindings.push({name: "deleteWordNext",		keyBinding: new KeyBinding(46, null, null, true), predefined: true});
+			}
+				
+			/*
+			* Feature in IE/Chrome: prevent ctrl+'u', ctrl+'i', and ctrl+'b' from applying styles to the text.
+			*
+			* Note that Chrome applies the styles on the Mac with Ctrl instead of Cmd.
+			*/
+			var isMacChrome = isMac && isChrome;
+			bindings.push({name: null, keyBinding: new KeyBinding('u', !isMacChrome, false, false, isMacChrome), predefined: true});
+			bindings.push({name: null, keyBinding: new KeyBinding('i', !isMacChrome, false, false, isMacChrome), predefined: true});
+			bindings.push({name: null, keyBinding: new KeyBinding('b', !isMacChrome, false, false, isMacChrome), predefined: true});
+
+			if (isFirefox) {
+				bindings.push({name: "copy", keyBinding: new KeyBinding(45, true), predefined: true});
+				bindings.push({name: "paste", keyBinding: new KeyBinding(45, null, true), predefined: true});
+				bindings.push({name: "cut", keyBinding: new KeyBinding(46, null, true), predefined: true});
+			}
+
+			//1 to 1, no duplicates
+			var self = this;
+			this._actions = [
+				{name: "lineUp",		defaultHandler: function() {return self._doLineUp({select: false});}},
+				{name: "lineDown",		defaultHandler: function() {return self._doLineDown({select: false});}},
+				{name: "lineStart",		defaultHandler: function() {return self._doHome({select: false, ctrl:false});}},
+				{name: "lineEnd",		defaultHandler: function() {return self._doEnd({select: false, ctrl:false});}},
+				{name: "charPrevious",		defaultHandler: function() {return self._doCursorPrevious({select: false, unit:"character"});}},
+				{name: "charNext",		defaultHandler: function() {return self._doCursorNext({select: false, unit:"character"});}},
+				{name: "pageUp",		defaultHandler: function() {return self._doPageUp({select: false});}},
+				{name: "pageDown",		defaultHandler: function() {return self._doPageDown({select: false});}},
+				{name: "wordPrevious",		defaultHandler: function() {return self._doCursorPrevious({select: false, unit:"word"});}},
+				{name: "wordNext",		defaultHandler: function() {return self._doCursorNext({select: false, unit:"word"});}},
+				{name: "textStart",		defaultHandler: function() {return self._doHome({select: false, ctrl:true});}},
+				{name: "textEnd",		defaultHandler: function() {return self._doEnd({select: false, ctrl:true});}},
+				
+				{name: "selectLineUp",		defaultHandler: function() {return self._doLineUp({select: true});}},
+				{name: "selectLineDown",	defaultHandler: function() {return self._doLineDown({select: true});}},
+				{name: "selectLineStart",	defaultHandler: function() {return self._doHome({select: true, ctrl:false});}},
+				{name: "selectLineEnd",		defaultHandler: function() {return self._doEnd({select: true, ctrl:false});}},
+				{name: "selectCharPrevious",	defaultHandler: function() {return self._doCursorPrevious({select: true, unit:"character"});}},
+				{name: "selectCharNext",	defaultHandler: function() {return self._doCursorNext({select: true, unit:"character"});}},
+				{name: "selectPageUp",		defaultHandler: function() {return self._doPageUp({select: true});}},
+				{name: "selectPageDown",	defaultHandler: function() {return self._doPageDown({select: true});}},
+				{name: "selectWordPrevious",	defaultHandler: function() {return self._doCursorPrevious({select: true, unit:"word"});}},
+				{name: "selectWordNext",	defaultHandler: function() {return self._doCursorNext({select: true, unit:"word"});}},
+				{name: "selectTextStart",	defaultHandler: function() {return self._doHome({select: true, ctrl:true});}},
+				{name: "selectTextEnd",		defaultHandler: function() {return self._doEnd({select: true, ctrl:true});}},
+				
+				{name: "deletePrevious",	defaultHandler: function() {return self._doBackspace({unit:"character"});}},
+				{name: "deleteNext",		defaultHandler: function() {return self._doDelete({unit:"character"});}},
+				{name: "deleteWordPrevious",	defaultHandler: function() {return self._doBackspace({unit:"word"});}},
+				{name: "deleteWordNext",	defaultHandler: function() {return self._doDelete({unit:"word"});}},
+				{name: "tab",			defaultHandler: function() {return self._doTab();}},
+				{name: "enter",			defaultHandler: function() {return self._doEnter();}},
+				{name: "selectAll",		defaultHandler: function() {return self._doSelectAll();}},
+				{name: "copy",			defaultHandler: function() {return self._doCopy();}},
+				{name: "cut",			defaultHandler: function() {return self._doCut();}},
+				{name: "paste",			defaultHandler: function() {return self._doPaste();}}
+			];
+		},
+		_createLine: function(parent, sibling, document, lineIndex, model) {
+			var lineText = model.getLine(lineIndex);
+			var lineStart = model.getLineStart(lineIndex);
+			var e = {lineIndex: lineIndex, lineText: lineText, lineStart: lineStart};
+			this.onLineStyle(e);
+			var child = document.createElement("DIV");
+			child.lineIndex = lineIndex;
+			this._applyStyle(e.style, child);
+			if (lineText.length !== 0) {
+				var start = 0;
+				var tabSize = this._tabSize;
+				if (tabSize && tabSize !== 8) {
+					var tabIndex = lineText.indexOf("\t"), ignoreChars = 0;
+					while (tabIndex !== -1) {
+						this._createRange(child, document, e.ranges, start, tabIndex, lineText, lineStart);
+						var spacesCount = tabSize - ((tabIndex + ignoreChars) % tabSize);
+						var spaces = "\u00A0";
+						for (var i = 1; i < spacesCount; i++) {
+							spaces += " ";
+						}
+						var tabSpan = document.createElement("SPAN");
+						tabSpan.appendChild(document.createTextNode(spaces));
+						tabSpan.ignoreChars = spacesCount - 1;
+						ignoreChars += tabSpan.ignoreChars;
+						if (e.ranges) {
+							for (var j = 0; j < e.ranges.length; j++) {
+								var range = e.ranges[j];
+								var styleStart = range.start - lineStart;
+								var styleEnd = range.end - lineStart;
+								if (styleStart > tabIndex) { break; } 
+								if (styleStart <= tabIndex && tabIndex < styleEnd) {
+									this._applyStyle(range.style, tabSpan);
+									break;
+								}
+							}
+						} 
+						child.appendChild(tabSpan);
+						start = tabIndex + 1;
+						tabIndex = lineText.indexOf("\t", start);
+					}
+				}
+				this._createRange(child, document, e.ranges, start, lineText.length, lineText, lineStart);
+			}
+			
+			/*
+			* Firefox, Opera and IE9 do not extend the selection at the end of the line
+			* when the line is fully selected. The fix is to add an extra space at the end
+			* of the line.
+			*
+			* Note: the height of a div with only an empty span is zero.  The fix is
+			* the add a extra zero-width non-break space to preserve the default
+			* height in the line div. In Chrome this character shows a glyph, so the
+			* zero-width non-joiner character is used instead.
+			*
+			* Note: in order to support bold and italic fonts with fixed line
+			* height all lines need to have at least one span with the largest
+			* font.
+			*/
+			var span = document.createElement("SPAN");
+			span.ignoreChars = 1;
+			if ((this._largestFontStyle & 1) !== 0) {
+				span.style.fontStyle = "italic";
+			}
+			if ((this._largestFontStyle & 2) !== 0) {
+				span.style.fontWeight = "bold";
+			}
+			var fullSelection = this._fullSelection;
+			var extendSelection = !fullSelection && (isFirefox || isOpera || isIE >= 9);
+			var c = extendSelection ? " " : (isWebkit || isFirefox ? "\u200C" : "\uFEFF");
+			span.appendChild(document.createTextNode(c));
+			child.appendChild(span);
+			
+			parent.insertBefore(child, sibling);
+			return child;
+		},
+		_createRange: function(parent, document, ranges, start, end, text, lineStart) {
+			if (start >= end) { return; }
+			var span;
+			if (ranges) {
+				for (var i = 0; i < ranges.length; i++) {
+					var range = ranges[i];
+					if (range.end <= lineStart + start) { continue; }
+					var styleStart = Math.max(lineStart + start, range.start) - lineStart;
+					if (styleStart >= end) { break; }
+					var styleEnd = Math.min(lineStart + end, range.end) - lineStart;
+					if (styleStart < styleEnd) {
+						styleStart = Math.max(start, styleStart);
+						styleEnd = Math.min(end, styleEnd);
+						if (start < styleStart) {
+							span = document.createElement("SPAN");
+							span.appendChild(document.createTextNode(text.substring(start, styleStart)));
+							parent.appendChild(span);
+						}
+						span = document.createElement("SPAN");
+						span.appendChild(document.createTextNode(text.substring(styleStart, styleEnd)));
+						this._applyStyle(range.style, span);
+						parent.appendChild(span);
+						start = styleEnd;
+					}
+				}
+			}
+			if (start < end) {
+				span = document.createElement("SPAN");
+				span.appendChild(document.createTextNode(text.substring(start, end)));
+				parent.appendChild(span);
+			}
+		},
+		_doAutoScroll: function (direction, x, y) {
+			this._autoScrollDir = direction;
+			this._autoScrollX = x;
+			this._autoScrollY = y;
+			if (!this._autoScrollTimerID) {
+				this._autoScrollTimer();
+			}
+		},
+		_endAutoScroll: function () {
+			if (this._autoScrollTimerID) { clearTimeout(this._autoScrollTimerID); }
+			this._autoScrollDir = undefined;
+			this._autoScrollTimerID = undefined;
+		},
+		_getBoundsAtOffset: function (offset) {
+			var model = this._model;
+			var document = this._frameDocument;
+			var clientDiv = this._clientDiv;
+			var lineIndex = model.getLineAtOffset(offset);
+			var dummy;
+			var child = this._getLineNode(lineIndex);
+			if (!child) {
+				child = dummy = this._createLine(clientDiv, null, document, lineIndex, model);
+			}
+			var result = null;
+			if (offset < model.getLineEnd(lineIndex)) {
+				var lineOffset = model.getLineStart(lineIndex);
+				var lineChild = child.firstChild;
+				while (lineChild) {
+					var textNode = lineChild.firstChild;
+					var nodeLength = textNode.length; 
+					if (lineChild.ignoreChars) {
+						nodeLength -= lineChild.ignoreChars;
+					}
+					if (lineOffset + nodeLength > offset) {
+						var index = offset - lineOffset;
+						var range;
+						if (isRangeRects) {
+							range = document.createRange();
+							range.setStart(textNode, index);
+							range.setEnd(textNode, index + 1);
+							result = range.getBoundingClientRect();
+						} else if (isIE) {
+							range = document.body.createTextRange();
+							range.moveToElementText(lineChild);
+							range.collapse();
+							range.moveEnd("character", index + 1);
+							range.moveStart("character", index);
+							result = range.getBoundingClientRect();
+						} else {
+							var text = textNode.data;
+							lineChild.removeChild(textNode);
+							lineChild.appendChild(document.createTextNode(text.substring(0, index)));
+							var span = document.createElement("SPAN");
+							span.appendChild(document.createTextNode(text.substring(index, index + 1)));
+							lineChild.appendChild(span);
+							lineChild.appendChild(document.createTextNode(text.substring(index + 1)));
+							result = span.getBoundingClientRect();
+							lineChild.innerHTML = "";
+							lineChild.appendChild(textNode);
+							if (!dummy) {
+								/*
+								 * Removing the element node that holds the selection start or end
+								 * causes the selection to be lost. The fix is to detect this case
+								 * and restore the selection. 
+								 */
+								var s = this._getSelection();
+								if ((lineOffset <= s.start && s.start < lineOffset + nodeLength) ||  (lineOffset <= s.end && s.end < lineOffset + nodeLength)) {
+									this._updateDOMSelection();
+								}
+							}
+						}
+						if (isIE) {
+							var logicalXDPI = window.screen.logicalXDPI;
+							var deviceXDPI = window.screen.deviceXDPI;
+							result.left = result.left * logicalXDPI / deviceXDPI;
+							result.right = result.right * logicalXDPI / deviceXDPI;
+						}
+						break;
+					}
+					lineOffset += nodeLength;
+					lineChild = lineChild.nextSibling;
+				}
+			}
+			if (!result) {
+				var rect = this._getLineBoundingClientRect(child);
+				result = {left: rect.right, right: rect.right};
+			}
+			if (dummy) { clientDiv.removeChild(dummy); }
+			return result;
+		},
+		_getBottomIndex: function (fullyVisible) {
+			var child = this._bottomChild;
+			if (fullyVisible && this._getClientHeight() > this._getLineHeight()) {
+				var rect = child.getBoundingClientRect();
+				var clientRect = this._clientDiv.getBoundingClientRect();
+				if (rect.bottom > clientRect.bottom) {
+					child = this._getLinePrevious(child) || child;
+				}
+			}
+			return child.lineIndex;
+		},
+		_getFrameHeight: function() {
+			return this._frameDocument.documentElement.clientHeight;
+		},
+		_getFrameWidth: function() {
+			return this._frameDocument.documentElement.clientWidth;
+		},
+		_getClientHeight: function() {
+			var viewPad = this._getViewPadding();
+			return Math.max(0, this._viewDiv.clientHeight - viewPad.top - viewPad.bottom);
+		},
+		_getClientWidth: function() {
+			var viewPad = this._getViewPadding();
+			return Math.max(0, this._viewDiv.clientWidth - viewPad.left - viewPad.right);
+		},
+		_getClipboardText: function (event) {
+			var delimiter = this._model.getLineDelimiter();
+			var clipboadText, text;
+			if (this._frameWindow.clipboardData) {
+				//IE
+				clipboadText = [];
+				text = this._frameWindow.clipboardData.getData("Text");
+				this._convertDelimiter(text, function(t) {clipboadText.push(t);}, function() {clipboadText.push(delimiter);});
+				return clipboadText.join("");
+			}
+			if (isFirefox) {
+				var window = this._frameWindow;
+				var document = this._frameDocument;
+				var child = document.createElement("PRE");
+				child.style.position = "fixed";
+				child.style.left = "-1000px";
+				child.appendChild(document.createTextNode(" "));
+				this._clientDiv.appendChild(child);
+				var range = document.createRange();
+				range.selectNodeContents(child);
+				var sel = window.getSelection();
+				if (sel.rangeCount > 0) { sel.removeAllRanges(); }
+				sel.addRange(range);
+				var self = this;
+				var cleanup = function() {
+					self._updateDOMSelection();
+					self._clientDiv.removeChild(child);
+				};
+				var _getText = function() {
+					/*
+					* Use the selection anchor to determine the end of the pasted text as it is possible that
+					* some browsers (like Firefox) add extra elements (<BR>) after the pasted text.
+					*/
+					var endNode = null;
+					if (sel.anchorNode.nodeType !== child.TEXT_NODE) {
+						endNode = sel.anchorNode.childNodes[sel.anchorOffset];
+					}
+					var text = [];
+					var getNodeText = function(node) {
+						var nodeChild = node.firstChild;
+						while (nodeChild && nodeChild !== endNode) {
+							if (nodeChild.nodeType === child.TEXT_NODE) {
+								text.push(nodeChild !== sel.anchorNode ? nodeChild.data : nodeChild.data.substring(0, sel.anchorOffset));
+							} else if (nodeChild.tagName === "BR") {
+								text.push(delimiter); 
+							} else {
+								getNodeText(nodeChild);
+							}
+							nodeChild = nodeChild.nextSibling;
+						}
+					};
+					getNodeText(child);
+					cleanup();
+					return text.join("");
+				};
+				
+				/* Try execCommand first. Works on firefox with clipboard permission. */
+				var result = false;
+				this._ignorePaste = true;
+				try {
+					result = document.execCommand("paste", false, null);
+				} catch (ex) {}
+				this._ignorePaste = false;
+				if (!result) {
+					/*
+					* Try native paste in DOM, works for firefox during the paste event.
+					*/
+					if (event) {
+						setTimeout(function() {
+							var text = _getText();
+							if (text) { self._doContent(text); }
+						}, 0);
+						return null;
+					} else {
+						/* no event and no clipboard permission, paste can't be performed */
+						cleanup();
+						return "";
+					}
+				}
+				return _getText();
+			}
+			//webkit
+			if (event && event.clipboardData) {
+				/*
+				* Webkit (Chrome/Safari) allows getData during the paste event
+				* Note: setData is not allowed, not even during copy/cut event
+				*/
+				clipboadText = [];
+				text = event.clipboardData.getData("text/plain");
+				this._convertDelimiter(text, function(t) {clipboadText.push(t);}, function() {clipboadText.push(delimiter);});
+				return clipboadText.join("");
+			} else {
+				//TODO try paste using extension (Chrome only)
+			}
+			return "";
+		},
+		_getDOMText: function(lineIndex) {
+			var child = this._getLineNode(lineIndex);
+			var lineChild = child.firstChild;
+			var text = "";
+			while (lineChild) {
+				var textNode = lineChild.firstChild;
+				while (textNode) {
+					if (lineChild.ignoreChars) {
+						for (var i = 0; i < textNode.length; i++) {
+							var ch = textNode.data.substring(i, i + 1);
+							if (ch !== " ") {
+								text += ch;
+							}
+						}
+					} else {
+						text += textNode.data;
+					}
+					textNode = textNode.nextSibling;
+				}
+				lineChild = lineChild.nextSibling;
+			}
+			return text;
+		},
+		_getViewPadding: function() {
+			return this._viewPadding;
+		},
+		_getLineBoundingClientRect: function (child) {
+			var rect = child.getBoundingClientRect();
+			var lastChild = child.lastChild;
+			//Remove any artificial trailing whitespace in the line
+			while (lastChild && lastChild.ignoreChars === lastChild.firstChild.length) {
+				lastChild = lastChild.previousSibling;
+			}
+			if (!lastChild) {
+				return {left: rect.left, top: rect.top, right: rect.left, bottom: rect.bottom};
+			}
+			var lastRect = lastChild.getBoundingClientRect();
+			return {left: rect.left, top: rect.top, right: lastRect.right, bottom: rect.bottom};
+		},
+		_getLineHeight: function() {
+			return this._lineHeight;
+		},
+		_getLineNode: function (lineIndex) {
+			var clientDiv = this._clientDiv;
+			var child = clientDiv.firstChild;
+			while (child) {
+				if (lineIndex === child.lineIndex) {
+					return child;
+				}
+				child = child.nextSibling;
+			}
+			return undefined;
+		},
+		_getLineNext: function (lineNode) {
+			var node = lineNode ? lineNode.nextSibling : this._clientDiv.firstChild;
+			while (node && node.lineIndex === -1) {
+				node = node.nextSibling;
+			}
+			return node;
+		},
+		_getLinePrevious: function (lineNode) {
+			var node = lineNode ? lineNode.previousSibling : this._clientDiv.lastChild;
+			while (node && node.lineIndex === -1) {
+				node = node.previousSibling;
+			}
+			return node;
+		},
+		_getOffset: function (offset, unit, direction) {
+			if (unit === "wordend") {
+				return this._getOffset_W3C(offset, unit, direction);
+			}
+			return isIE ? this._getOffset_IE(offset, unit, direction) : this._getOffset_W3C(offset, unit, direction);
+		},
+		_getOffset_W3C: function (offset, unit, direction) {
+			function _isPunctuation(c) {
+				return (33 <= c && c <= 47) || (58 <= c && c <= 64) || (91 <= c && c <= 94) || c === 96 || (123 <= c && c <= 126);
+			}
+			function _isWhitespace(c) {
+				return c === 32 || c === 9;
+			}
+			if (unit === "word" || unit === "wordend") {
+				var model = this._model;
+				var lineIndex = model.getLineAtOffset(offset);
+				var lineText = model.getLine(lineIndex);
+				var lineStart = model.getLineStart(lineIndex);
+				var lineEnd = model.getLineEnd(lineIndex);
+				var lineLength = lineText.length;
+				var offsetInLine = offset - lineStart;
+				
+				
+				var c, previousPunctuation, previousLetterOrDigit, punctuation, letterOrDigit;
+				if (direction > 0) {
+					if (offsetInLine === lineLength) { return lineEnd; }
+					c = lineText.charCodeAt(offsetInLine);
+					previousPunctuation = _isPunctuation(c); 
+					previousLetterOrDigit = !previousPunctuation && !_isWhitespace(c);
+					offsetInLine++;
+					while (offsetInLine < lineLength) {
+						c = lineText.charCodeAt(offsetInLine);
+						punctuation = _isPunctuation(c);
+						if (unit === "wordend") {
+							if (!punctuation && previousPunctuation) { break; }
+						} else {
+							if (punctuation && !previousPunctuation) { break; }
+						}
+						letterOrDigit  = !punctuation && !_isWhitespace(c);
+						if (unit === "wordend") {
+							if (!letterOrDigit && previousLetterOrDigit) { break; }
+						} else {
+							if (letterOrDigit && !previousLetterOrDigit) { break; }
+						}
+						previousLetterOrDigit = letterOrDigit;
+						previousPunctuation = punctuation;
+						offsetInLine++;
+					}
+				} else {
+					if (offsetInLine === 0) { return lineStart; }
+					offsetInLine--;
+					c = lineText.charCodeAt(offsetInLine);
+					previousPunctuation = _isPunctuation(c); 
+					previousLetterOrDigit = !previousPunctuation && !_isWhitespace(c);
+					while (0 < offsetInLine) {
+						c = lineText.charCodeAt(offsetInLine - 1);
+						punctuation = _isPunctuation(c);
+						if (unit === "wordend") {
+							if (punctuation && !previousPunctuation) { break; }
+						} else {
+							if (!punctuation && previousPunctuation) { break; }
+						}
+						letterOrDigit  = !punctuation && !_isWhitespace(c);
+						if (unit === "wordend") {
+							if (letterOrDigit && !previousLetterOrDigit) { break; }
+						} else {
+							if (!letterOrDigit && previousLetterOrDigit) { break; }
+						}
+						previousLetterOrDigit = letterOrDigit;
+						previousPunctuation = punctuation;
+						offsetInLine--;
+					}
+				}
+				return lineStart + offsetInLine;
+			}
+			return offset + direction;
+		},
+		_getOffset_IE: function (offset, unit, direction) {
+			var document = this._frameDocument;
+			var model = this._model;
+			var lineIndex = model.getLineAtOffset(offset);
+			var clientDiv = this._clientDiv;
+			var dummy;
+			var child = this._getLineNode(lineIndex);
+			if (!child) {
+				child = dummy = this._createLine(clientDiv, null, document, lineIndex, model);
+			}
+			var result = 0, range, length;
+			var lineOffset = model.getLineStart(lineIndex);
+			if (offset === model.getLineEnd(lineIndex)) {
+				range = document.body.createTextRange();
+				range.moveToElementText(child.lastChild);
+				length = range.text.length;
+				range.moveEnd(unit, direction);
+				result = offset + range.text.length - length;
+			} else if (offset === lineOffset && direction < 0) {
+				result = lineOffset;
+			} else {
+				var lineChild = child.firstChild;
+				while (lineChild) {
+					var textNode = lineChild.firstChild;
+					var nodeLength = textNode.length;
+					if (lineChild.ignoreChars) {
+						nodeLength -= lineChild.ignoreChars;
+					}
+					if (lineOffset + nodeLength > offset) {
+						range = document.body.createTextRange();
+						if (offset === lineOffset && direction < 0) {
+							range.moveToElementText(lineChild.previousSibling);
+						} else {
+							range.moveToElementText(lineChild);
+							range.collapse();
+							range.moveEnd("character", offset - lineOffset);
+						}
+						length = range.text.length;
+						range.moveEnd(unit, direction);
+						result = offset + range.text.length - length;
+						break;
+					}
+					lineOffset = nodeLength + lineOffset;
+					lineChild = lineChild.nextSibling;
+				}
+			}
+			if (dummy) { clientDiv.removeChild(dummy); }
+			return result;
+		},
+		_getOffsetToX: function (offset) {
+			return this._getBoundsAtOffset(offset).left;
+		},
+		_getPadding: function (node) {
+			var left,top,right,bottom;
+			if (node.currentStyle) {
+				left = node.currentStyle.paddingLeft;
+				top = node.currentStyle.paddingTop;
+				right = node.currentStyle.paddingRight;
+				bottom = node.currentStyle.paddingBottom;
+			} else if (this._frameWindow.getComputedStyle) {
+				var style = this._frameWindow.getComputedStyle(node, null);
+				left = style.getPropertyValue("padding-left");
+				top = style.getPropertyValue("padding-top");
+				right = style.getPropertyValue("padding-right");
+				bottom = style.getPropertyValue("padding-bottom");
+			}
+			return {
+					left: parseInt(left, 10), 
+					top: parseInt(top, 10),
+					right: parseInt(right, 10),
+					bottom: parseInt(bottom, 10)
+			};
+		},
+		_getScroll: function() {
+			var viewDiv = this._viewDiv;
+			return {x: viewDiv.scrollLeft, y: viewDiv.scrollTop};
+		},
+		_getSelection: function () {
+			return this._selection.clone();
+		},
+		_getTopIndex: function (fullyVisible) {
+			var child = this._topChild;
+			if (fullyVisible && this._getClientHeight() > this._getLineHeight()) {
+				var rect = child.getBoundingClientRect();
+				var viewPad = this._getViewPadding();
+				var viewRect = this._viewDiv.getBoundingClientRect();
+				if (rect.top < viewRect.top + viewPad.top) {
+					child = this._getLineNext(child) || child;
+				}
+			}
+			return child.lineIndex;
+		},
+		_getXToOffset: function (lineIndex, x) {
+			var model = this._model;
+			var lineStart = model.getLineStart(lineIndex);
+			var lineEnd = model.getLineEnd(lineIndex);
+			if (lineStart === lineEnd) {
+				return lineStart;
+			}
+			var document = this._frameDocument;
+			var clientDiv = this._clientDiv;
+			var dummy;
+			var child = this._getLineNode(lineIndex);
+			if (!child) {
+				child = dummy = this._createLine(clientDiv, null, document, lineIndex, model);
+			}
+			var lineRect = this._getLineBoundingClientRect(child);
+			if (x < lineRect.left) { x = lineRect.left; }
+			if (x > lineRect.right) { x = lineRect.right; }
+			/*
+			* Bug in IE 8 and earlier. The coordinates of getClientRects() are relative to
+			* the browser window.  The fix is to convert to the frame window before using it. 
+			*/
+			var deltaX = 0, rects;
+			if (isIE < 9) {
+				rects = child.getClientRects();
+				var minLeft = rects[0].left;
+				for (var i=1; i<rects.length; i++) {
+					minLeft = Math.min(rects[i].left, minLeft);
+				}
+				deltaX = minLeft - lineRect.left;
+			}
+			var scrollX = this._getScroll().x;
+			function _getClientRects(element) {
+				var rects, newRects, i, r;
+				if (!element._rectsCache) {
+					rects = element.getClientRects();
+					newRects = [rects.length];
+					for (i = 0; i<rects.length; i++) {
+						r = rects[i];
+						newRects[i] = {left: r.left - deltaX + scrollX, top: r.top, right: r.right - deltaX + scrollX, bottom: r.bottom};
+					}
+					element._rectsCache = newRects; 
+				}
+				rects = element._rectsCache;
+				newRects = [rects.length];
+				for (i = 0; i<rects.length; i++) {
+					r = rects[i];
+					newRects[i] = {left: r.left - scrollX, top: r.top, right: r.right - scrollX, bottom: r.bottom};
+				}
+				return newRects;
+			}
+			var logicalXDPI = isIE ? window.screen.logicalXDPI : 1;
+			var deviceXDPI = isIE ? window.screen.deviceXDPI : 1;
+			var offset = lineStart;
+			var lineChild = child.firstChild;
+			done:
+			while (lineChild) {
+				var textNode = lineChild.firstChild;
+				var nodeLength = textNode.length;
+				if (lineChild.ignoreChars) {
+					nodeLength -= lineChild.ignoreChars;
+				}
+				rects = _getClientRects(lineChild);
+				for (var j = 0; j < rects.length; j++) {
+					var rect = rects[j];
+					if (rect.left <= x && x < rect.right) {
+						var range, start, end;
+						if (isIE || isRangeRects) {
+							range = isRangeRects ? document.createRange() : document.body.createTextRange();
+							var high = nodeLength;
+							var low = -1;
+							while ((high - low) > 1) {
+								var mid = Math.floor((high + low) / 2);
+								start = low + 1;
+								end = mid === nodeLength - 1 && lineChild.ignoreChars ? textNode.length : mid + 1;
+								if (isRangeRects) {
+									range.setStart(textNode, start);
+									range.setEnd(textNode, end);
+								} else {
+									range.moveToElementText(lineChild);
+									range.move("character", start);
+									range.moveEnd("character", end - start);
+								}
+								rects = range.getClientRects();
+								var found = false;
+								for (var k = 0; k < rects.length; k++) {
+									rect = rects[k];
+									var rangeLeft = rect.left * logicalXDPI / deviceXDPI - deltaX;
+									var rangeRight = rect.right * logicalXDPI / deviceXDPI - deltaX;
+									if (rangeLeft <= x && x < rangeRight) {
+										found = true;
+										break;
+									}
+								}
+								if (found) {
+									high = mid;
+								} else {
+									low = mid;
+								}
+							}
+							offset += high;
+							start = high;
+							end = high === nodeLength - 1 && lineChild.ignoreChars ? textNode.length : high + 1;
+							if (isRangeRects) {
+								range.setStart(textNode, start);
+								range.setEnd(textNode, end);
+							} else {
+								range.moveToElementText(lineChild);
+								range.move("character", start);
+								range.moveEnd("character", end - start);
+							}
+							rect = range.getClientRects()[0];
+							//TODO test for character trailing (wrong for bidi)
+							if (x > ((rect.left * logicalXDPI / deviceXDPI - deltaX) + ((rect.right - rect.left) * logicalXDPI / deviceXDPI / 2))) {
+								offset++;
+							}
+						} else {
+							var newText = [];
+							for (var q = 0; q < nodeLength; q++) {
+								newText.push("<span>");
+								if (q === nodeLength - 1) {
+									newText.push(textNode.data.substring(q));
+								} else {
+									newText.push(textNode.data.substring(q, q + 1));
+								}
+								newText.push("</span>");
+							}
+							lineChild.innerHTML = newText.join("");
+							var rangeChild = lineChild.firstChild;
+							while (rangeChild) {
+								rect = rangeChild.getBoundingClientRect();
+								if (rect.left <= x && x < rect.right) {
+									//TODO test for character trailing (wrong for bidi)
+									if (x > rect.left + (rect.right - rect.left) / 2) {
+										offset++;
+									}
+									break;
+								}
+								offset++;
+								rangeChild = rangeChild.nextSibling;
+							}
+							if (!dummy) {
+								lineChild.innerHTML = "";
+								lineChild.appendChild(textNode);
+								/*
+								 * Removing the element node that holds the selection start or end
+								 * causes the selection to be lost. The fix is to detect this case
+								 * and restore the selection. 
+								 */
+								var s = this._getSelection();
+								if ((offset <= s.start && s.start < offset + nodeLength) || (offset <= s.end && s.end < offset + nodeLength)) {
+									this._updateDOMSelection();
+								}
+							}
+						}
+						break done;
+					}
+				}
+				offset += nodeLength;
+				lineChild = lineChild.nextSibling;
+			}
+			if (dummy) { clientDiv.removeChild(dummy); }
+			return Math.min(lineEnd, Math.max(lineStart, offset));
+		},
+		_getYToLine: function (y) {
+			var viewPad = this._getViewPadding();
+			var viewRect = this._viewDiv.getBoundingClientRect();
+			y -= viewRect.top + viewPad.top;
+			var lineHeight = this._getLineHeight();
+			var lineIndex = Math.floor((y + this._getScroll().y) / lineHeight);
+			var lineCount = this._model.getLineCount();
+			return Math.max(0, Math.min(lineCount - 1, lineIndex));
+		},
+		_getOffsetBounds: function(offset) {
+			var model = this._model;
+			var lineIndex = model.getLineAtOffset(offset);
+			var lineHeight = this._getLineHeight();
+			var scroll = this._getScroll();
+			var viewPad = this._getViewPadding();
+			var viewRect = this._viewDiv.getBoundingClientRect();
+			var bounds = this._getBoundsAtOffset(offset);
+			var left = bounds.left;
+			var right = bounds.right;
+			var top = (lineIndex * lineHeight) - scroll.y + viewRect.top + viewPad.top;
+			var bottom = top + lineHeight;
+			return {left: left, top: top, right: right, bottom: bottom};
+		},
+		_hitOffset: function (offset, x, y) {
+			var bounds = this._getOffsetBounds(offset);
+			var left = bounds.left;
+			var right = bounds.right;
+			var top = bounds.top;
+			var bottom = bounds.bottom;
+			var area = 20;
+			left -= area;
+			top -= area;
+			right += area;
+			bottom += area;
+			return (left <= x && x <= right && top <= y && y <= bottom);
+		},
+		_hookEvents: function() {
+			var self = this;
+			this._modelListener = {
+				/** @private */
+				onChanging: function(newText, start, removedCharCount, addedCharCount, removedLineCount, addedLineCount) {
+					self._onModelChanging(newText, start, removedCharCount, addedCharCount, removedLineCount, addedLineCount);
+				},
+				/** @private */
+				onChanged: function(start, removedCharCount, addedCharCount, removedLineCount, addedLineCount) {
+					self._onModelChanged(start, removedCharCount, addedCharCount, removedLineCount, addedLineCount);
+				}
+			};
+			this._model.addListener(this._modelListener);
+			
+			this._mouseMoveClosure = function(e) { return self._handleMouseMove(e);};
+			this._mouseUpClosure = function(e) { return self._handleMouseUp(e);};
+			
+			var clientDiv = this._clientDiv;
+			var viewDiv = this._viewDiv;
+			var body = this._frameDocument.body; 
+			var handlers = this._handlers = [];
+			var resizeNode = isIE < 9 ? this._frame : this._frameWindow;
+			var focusNode = isPad ? this._textArea : (isIE ||  isFirefox ? this._clientDiv: this._frameWindow);
+			handlers.push({target: resizeNode, type: "resize", handler: function(e) { return self._handleResize(e);}});
+			handlers.push({target: focusNode, type: "blur", handler: function(e) { return self._handleBlur(e);}});
+			handlers.push({target: focusNode, type: "focus", handler: function(e) { return self._handleFocus(e);}});
+			handlers.push({target: viewDiv, type: "scroll", handler: function(e) { return self._handleScroll(e);}});
+			if (isPad) {
+				var touchDiv = this._touchDiv;
+				var textArea = this._textArea;
+				handlers.push({target: textArea, type: "keydown", handler: function(e) { return self._handleKeyDown(e);}});
+				handlers.push({target: textArea, type: "input", handler: function(e) { return self._handleInput(e); }});
+				handlers.push({target: textArea, type: "textInput", handler: function(e) { return self._handleTextInput(e); }});
+				handlers.push({target: touchDiv, type: "touchstart", handler: function(e) { return self._handleTouchStart(e); }});
+				handlers.push({target: touchDiv, type: "touchmove", handler: function(e) { return self._handleTouchMove(e); }});
+				handlers.push({target: touchDiv, type: "touchend", handler: function(e) { return self._handleTouchEnd(e); }});
+			} else {
+				var topNode = this._overlayDiv || this._clientDiv;
+				handlers.push({target: clientDiv, type: "keydown", handler: function(e) { return self._handleKeyDown(e);}});
+				handlers.push({target: clientDiv, type: "keypress", handler: function(e) { return self._handleKeyPress(e);}});
+				handlers.push({target: clientDiv, type: "keyup", handler: function(e) { return self._handleKeyUp(e);}});
+				handlers.push({target: clientDiv, type: "selectstart", handler: function(e) { return self._handleSelectStart(e);}});
+				handlers.push({target: clientDiv, type: "contextmenu", handler: function(e) { return self._handleContextMenu(e);}});
+				handlers.push({target: clientDiv, type: "copy", handler: function(e) { return self._handleCopy(e);}});
+				handlers.push({target: clientDiv, type: "cut", handler: function(e) { return self._handleCut(e);}});
+				handlers.push({target: clientDiv, type: "paste", handler: function(e) { return self._handlePaste(e);}});
+				handlers.push({target: topNode, type: "mousedown", handler: function(e) { return self._handleMouseDown(e);}});
+				handlers.push({target: body, type: "mousedown", handler: function(e) { return self._handleBodyMouseDown(e);}});
+				handlers.push({target: topNode, type: "dragstart", handler: function(e) { return self._handleDragStart(e);}});
+				handlers.push({target: topNode, type: "dragover", handler: function(e) { return self._handleDragOver(e);}});
+				handlers.push({target: topNode, type: "drop", handler: function(e) { return self._handleDrop(e);}});
+				if (isIE) {
+					handlers.push({target: this._frameDocument, type: "activate", handler: function(e) { return self._handleDocFocus(e); }});
+				}
+				if (isFirefox) {
+					handlers.push({target: this._frameDocument, type: "focus", handler: function(e) { return self._handleDocFocus(e); }});
+				}
+				if (!isIE && !isOpera) {
+					var wheelEvent = isFirefox ? "DOMMouseScroll" : "mousewheel";
+					handlers.push({target: this._viewDiv, type: wheelEvent, handler: function(e) { return self._handleMouseWheel(e); }});
+				}
+				if (isFirefox && !isWindows) {
+					handlers.push({target: this._clientDiv, type: "DOMCharacterDataModified", handler: function (e) { return self._handleDataModified(e); }});
+				}
+				if (this._overlayDiv) {
+					handlers.push({target: this._overlayDiv, type: "contextmenu", handler: function(e) { return self._handleContextMenu(e); }});
+				}
+				if (!isW3CEvents) {
+					handlers.push({target: this._clientDiv, type: "dblclick", handler: function(e) { return self._handleDblclick(e); }});
+				}
+			}
+			for (var i=0; i<handlers.length; i++) {
+				var h = handlers[i];
+				addHandler(h.target, h.type, h.handler, h.capture);
+			}
+		},
+		_init: function(options) {
+			var parent = options.parent;
+			if (typeof(parent) === "string") {
+				parent = window.document.getElementById(parent);
+			}
+			if (!parent) { throw "no parent"; }
+			this._parent = parent;
+			this._model = options.model ? options.model : new orion.textview.TextModel();
+			this.readonly = options.readonly === true;
+			this._selection = new Selection (0, 0, false);
+			this._eventTable = new EventTable();
+			this._maxLineWidth = 0;
+			this._maxLineIndex = -1;
+			this._ignoreSelect = true;
+			this._columnX = -1;
+
+			/* Auto scroll */
+			this._autoScrollX = null;
+			this._autoScrollY = null;
+			this._autoScrollTimerID = null;
+			this._AUTO_SCROLL_RATE = 50;
+			this._grabControl = null;
+			this._moseMoveClosure  = null;
+			this._mouseUpClosure = null;
+			
+			/* Double click */
+			this._lastMouseX = 0;
+			this._lastMouseY = 0;
+			this._lastMouseTime = 0;
+			this._clickCount = 0;
+			this._clickTime = 250;
+			this._clickDist = 5;
+			this._isMouseDown = false;
+			this._doubleClickSelection = null;
+			
+			/* Scroll */
+			this._hScroll = 0;
+			this._vScroll = 0;
+
+			/* IME */
+			this._imeOffset = -1;
+			
+			/* Create elements */
+			while (parent.hasChildNodes()) { parent.removeChild(parent.lastChild); }
+			var parentDocument = parent.document || parent.ownerDocument;
+			this._parentDocument = parentDocument;
+			var frame = parentDocument.createElement("IFRAME");
+			this._frame = frame;
+			frame.frameBorder = "0px";//for IE, needs to be set before the frame is added to the parent
+			frame.style.width = "100%";
+			frame.style.height = "100%";
+			frame.scrolling = "no";
+			frame.style.border = "0px";
+			parent.appendChild(frame);
+
+			var html = [];
+			html.push("<!DOCTYPE html>");
+			html.push("<html>");
+			html.push("<head>");
+			if (isIE < 9) {
+				html.push("<meta http-equiv='X-UA-Compatible' content='IE=EmulateIE7'/>");
+			}
+			html.push("<style>");
+			html.push(".viewContainer {font-family: monospace; font-size: 10pt;}");
+			html.push(".view {padding: 1px 2px;}");
+			html.push(".viewContent {}");
+			html.push("</style>");
+			if (options.stylesheet) {
+				var stylesheet = typeof(options.stylesheet) === "string" ? [options.stylesheet] : options.stylesheet;
+				for (var i = 0; i < stylesheet.length; i++) {
+					try {
+						//Force CSS to be loaded synchronously so lineHeight can be calculated
+						var objXml = new XMLHttpRequest();
+						if (objXml.overrideMimeType) {
+							objXml.overrideMimeType("text/css");
+						}
+						objXml.open("GET", stylesheet[i], false);
+						objXml.send(null);
+						html.push("<style>");
+						html.push(objXml.responseText);
+						html.push("</style>");
+					} catch (e) {
+						html.push("<link rel='stylesheet' type='text/css' href='");
+						html.push(stylesheet[i]);
+						html.push("'></link>");
+					}
+				}
+			}
+			html.push("</head>");
+			html.push("<body spellcheck='false'></body>");
+			html.push("</html>");
+
+			var frameWindow = frame.contentWindow;
+			this._frameWindow = frameWindow;
+			var document = frameWindow.document;
+			this._frameDocument = document;
+			document.open();
+			document.write(html.join(""));
+			document.close();
+			
+			var body = document.body;
+			body.className = "viewContainer";
+			body.style.margin = "0px";
+			body.style.borderWidth = "0px";
+			body.style.padding = "0px";
+			
+			if (isPad) {
+				var touchDiv = parentDocument.createElement("DIV");
+				this._touchDiv = touchDiv;
+				touchDiv.style.position = "absolute";
+				touchDiv.style.border = "0px";
+				touchDiv.style.padding = "0px";
+				touchDiv.style.margin = "0px";
+				touchDiv.style.zIndex = "2";
+				touchDiv.style.overflow = "hidden";
+				touchDiv.style.background="transparent";
+				touchDiv.style.WebkitUserSelect = "none";
+				parent.appendChild(touchDiv);
+
+				var textArea = parentDocument.createElement("TEXTAREA");
+				this._textArea = textArea;
+				textArea.style.position = "absolute";
+				textArea.style.whiteSpace = "pre";
+				textArea.style.left = "-1000px";
+				textArea.tabIndex = 1;
+				textArea.autocapitalize = false;
+				textArea.autocorrect = false;
+				textArea.className = "viewContainer";
+				textArea.style.background = "transparent";
+				textArea.style.color = "transparent";
+				textArea.style.border = "0px";
+				textArea.style.padding = "0px";
+				textArea.style.margin = "0px";
+				textArea.style.borderRadius = "0px";
+				textArea.style.WebkitAppearance = "none";
+				textArea.style.WebkitTapHighlightColor = "transparent";
+				touchDiv.appendChild(textArea);
+			}
+
+			var viewDiv = document.createElement("DIV");
+			viewDiv.className = "view";
+			this._viewDiv = viewDiv;
+			viewDiv.id = "viewDiv";
+			viewDiv.tabIndex = -1;
+			viewDiv.style.overflow = "auto";
+			viewDiv.style.position = "absolute";
+			viewDiv.style.top = "0px";
+			viewDiv.style.borderWidth = "0px";
+			viewDiv.style.margin = "0px";
+			viewDiv.style.MozOutline = "none";
+			viewDiv.style.outline = "none";
+			body.appendChild(viewDiv);
+				
+			var scrollDiv = document.createElement("DIV");
+			this._scrollDiv = scrollDiv;
+			scrollDiv.id = "scrollDiv";
+			scrollDiv.style.margin = "0px";
+			scrollDiv.style.borderWidth = "0px";
+			scrollDiv.style.padding = "0px";
+			viewDiv.appendChild(scrollDiv);
+
+			this._fullSelection = options.fullSelection === undefined || options.fullSelection;
+			/* 
+			* Bug in IE 8. For some reason, during scrolling IE does not reflow the elements
+			* that are used to compute the location for the selection divs. This causes the
+			* divs to be placed at the wrong location. The fix is to disabled full selection for IE8.
+			*/
+			if (isIE < 9) {
+				this._fullSelection = false;
+			}
+			if (isPad || (this._fullSelection && !isWebkit)) {
+				this._hightlightRGB = "Highlight";
+				var selDiv1 = document.createElement("DIV");
+				this._selDiv1 = selDiv1;
+				selDiv1.id = "selDiv1";
+				selDiv1.style.position = "fixed";
+				selDiv1.style.borderWidth = "0px";
+				selDiv1.style.margin = "0px";
+				selDiv1.style.padding = "0px";
+				selDiv1.style.MozOutline = "none";
+				selDiv1.style.outline = "none";
+				selDiv1.style.background = this._hightlightRGB;
+				selDiv1.style.width="0px";
+				selDiv1.style.height="0px";
+				scrollDiv.appendChild(selDiv1);
+				var selDiv2 = document.createElement("DIV");
+				this._selDiv2 = selDiv2;
+				selDiv2.id = "selDiv2";
+				selDiv2.style.position = "fixed";
+				selDiv2.style.borderWidth = "0px";
+				selDiv2.style.margin = "0px";
+				selDiv2.style.padding = "0px";
+				selDiv2.style.MozOutline = "none";
+				selDiv2.style.outline = "none";
+				selDiv2.style.background = this._hightlightRGB;
+				selDiv2.style.width="0px";
+				selDiv2.style.height="0px";
+				scrollDiv.appendChild(selDiv2);
+				var selDiv3 = document.createElement("DIV");
+				this._selDiv3 = selDiv3;
+				selDiv3.id = "selDiv3";
+				selDiv3.style.position = "fixed";
+				selDiv3.style.borderWidth = "0px";
+				selDiv3.style.margin = "0px";
+				selDiv3.style.padding = "0px";
+				selDiv3.style.MozOutline = "none";
+				selDiv3.style.outline = "none";
+				selDiv3.style.background = this._hightlightRGB;
+				selDiv3.style.width="0px";
+				selDiv3.style.height="0px";
+				scrollDiv.appendChild(selDiv3);
+				
+				/*
+				* Bug in Firefox. The Highlight color is mapped to list selection
+				* background instead of the text selection background.  The fix
+				* is to map known colors using a table or fallback to light blue.
+				*/
+				if (isFirefox && isMac) {
+					var style = frameWindow.getComputedStyle(selDiv3, null);
+					var rgb = style.getPropertyValue("background-color");
+					switch (rgb) {
+						case "rgb(119, 141, 168)": rgb = "rgb(199, 208, 218)"; break;
+						case "rgb(127, 127, 127)": rgb = "rgb(198, 198, 198)"; break;
+						case "rgb(255, 193, 31)": rgb = "rgb(250, 236, 115)"; break;
+						case "rgb(243, 70, 72)": rgb = "rgb(255, 176, 139)"; break;
+						case "rgb(255, 138, 34)": rgb = "rgb(255, 209, 129)"; break;
+						case "rgb(102, 197, 71)": rgb = "rgb(194, 249, 144)"; break;
+						case "rgb(140, 78, 184)": rgb = "rgb(232, 184, 255)"; break;
+						default: rgb = "rgb(180, 213, 255)"; break;
+					}
+					this._hightlightRGB = rgb;
+					selDiv1.style.background = rgb;
+					selDiv2.style.background = rgb;
+					selDiv3.style.background = rgb;
+					var styleSheet = document.styleSheets[0];
+					styleSheet.insertRule("::-moz-selection {background: " + rgb + "; }", 0);
+				}
+			}
+
+			var clientDiv = document.createElement("DIV");
+			clientDiv.className = "viewContent";
+			this._clientDiv = clientDiv;
+			clientDiv.id = "clientDiv";
+			clientDiv.style.whiteSpace = "pre";
+			clientDiv.style.position = "fixed";
+			clientDiv.style.borderWidth = "0px";
+			clientDiv.style.margin = "0px";
+			clientDiv.style.padding = "0px";
+			clientDiv.style.MozOutline = "none";
+			clientDiv.style.outline = "none";
+			if (isPad) {
+				clientDiv.style.WebkitTapHighlightColor = "transparent";
+			}
+			scrollDiv.appendChild(clientDiv);
+
+			if (isFirefox) {
+				var overlayDiv = document.createElement("DIV");
+				this._overlayDiv = overlayDiv;
+				overlayDiv.id = "overlayDiv";
+				overlayDiv.style.position = clientDiv.style.position;
+				overlayDiv.style.borderWidth = clientDiv.style.borderWidth;
+				overlayDiv.style.margin = clientDiv.style.margin;
+				overlayDiv.style.padding = clientDiv.style.padding;
+				overlayDiv.style.cursor = "text";
+				overlayDiv.style.zIndex = "1";
+				scrollDiv.appendChild(overlayDiv);
+			}
+			if (!isPad) {
+				clientDiv.contentEditable = "true";
+			}
+			this._lineHeight = this._calculateLineHeight();
+			this._viewPadding = this._calculatePadding();
+			if (isIE) {
+				body.style.lineHeight = this._lineHeight + "px";
+			}
+			if (options.tabSize) {
+				if (isOpera) {
+					clientDiv.style.OTabSize = options.tabSize+"";
+				} else if (isFirefox >= 4) {
+					clientDiv.style.MozTabSize = options.tabSize+"";
+				} else if (options.tabSize !== 8) {
+					this._tabSize = options.tabSize;
+				}
+			}
+			this._createActions();
+			this._hookEvents();
+		},
+		_modifyContent: function(e, updateCaret) {
+			if (this.readonly && !e._code) {
+				return;
+			}
+
+			this.onVerify(e);
+
+			if (e.text === null || e.text === undefined) { return; }
+			
+			var model = this._model;
+			if (e._ignoreDOMSelection) { this._ignoreDOMSelection = true; }
+			model.setText (e.text, e.start, e.end);
+			if (e._ignoreDOMSelection) { this._ignoreDOMSelection = false; }
+			
+			if (updateCaret) {
+				var selection = this._getSelection ();
+				selection.setCaret(e.start + e.text.length);
+				this._setSelection(selection, true);
+			}
+			this.onModify({});
+		},
+		_onModelChanged: function(start, removedCharCount, addedCharCount, removedLineCount, addedLineCount) {
+			var e = {
+				start: start,
+				removedCharCount: removedCharCount,
+				addedCharCount: addedCharCount,
+				removedLineCount: removedLineCount,
+				addedLineCount: addedLineCount
+			};
+			this.onModelChanged(e);
+			
+			var selection = this._getSelection();
+			if (selection.end > start) {
+				if (selection.end > start && selection.start < start + removedCharCount) {
+					// selection intersects replaced text. set caret behind text change
+					selection.setCaret(start + addedCharCount);
+				} else {
+					// move selection to keep same text selected
+					selection.start +=  addedCharCount - removedCharCount;
+					selection.end +=  addedCharCount - removedCharCount;
+				}
+				this._setSelection(selection, false, false);
+			}
+			
+			var model = this._model;
+			var startLine = model.getLineAtOffset(start);
+			var child = this._getLineNext();
+			while (child) {
+				var lineIndex = child.lineIndex;
+				if (startLine <= lineIndex && lineIndex <= startLine + removedLineCount) {
+					child.lineChanged = true;
+				}
+				if (lineIndex > startLine + removedLineCount) {
+					child.lineIndex = lineIndex + addedLineCount - removedLineCount;
+				}
+				child = this._getLineNext(child);
+			}
+			if (startLine <= this._maxLineIndex && this._maxLineIndex <= startLine + removedLineCount) {
+				this._maxLineIndex = -1;
+				this._maxLineWidth = 0;
+			}
+			this._updatePage();
+		},
+		_onModelChanging: function(newText, start, removedCharCount, addedCharCount, removedLineCount, addedLineCount) {
+			var e = {
+				text: newText,
+				start: start,
+				removedCharCount: removedCharCount,
+				addedCharCount: addedCharCount,
+				removedLineCount: removedLineCount,
+				addedLineCount: addedLineCount
+			};
+			this.onModelChanging(e);
+		},
+		_queueUpdatePage: function() {
+			if (this._updateTimer) { return; }
+			var self = this;
+			this._updateTimer = setTimeout(function() { 
+				self._updateTimer = null;
+				self._updatePage();
+			}, 0);
+		},
+		_resizeTouchDiv: function() {
+			var viewRect = this._viewDiv.getBoundingClientRect();
+			var parentRect = this._frame.getBoundingClientRect();
+			var temp = this._frame;
+			while (temp) {
+				if (temp.style && temp.style.top) { break; }
+				temp = temp.parentNode;
+			}
+			var parentTop = parentRect.top;
+			if (temp) {
+				parentTop -= temp.getBoundingClientRect().top;
+			} else {
+				parentTop += this._parentDocument.body.scrollTop;
+			}
+			temp = this._frame;
+			while (temp) {
+				if (temp.style && temp.style.left) { break; }
+				temp = temp.parentNode;
+			}
+			var parentLeft = parentRect.left;
+			if (temp) {
+				parentLeft -= temp.getBoundingClientRect().left;
+			} else {
+				parentLeft += this._parentDocument.body.scrollLeft;
+			}
+			var touchDiv = this._touchDiv;
+			touchDiv.style.left = (parentLeft + viewRect.left) + "px";
+			touchDiv.style.top = (parentTop + viewRect.top) + "px";
+			touchDiv.style.width = viewRect.width + "px";
+			touchDiv.style.height = viewRect.height + "px";
+		},
+		_scrollView: function (pixelX, pixelY) {
+			/*
+			* Always set _ensureCaretVisible to false so that the view does not scroll
+			* to show the caret when scrollView is not called from showCaret().
+			*/
+			this._ensureCaretVisible = false;
+			
+			/*
+			* Scrolling is done only by setting the scrollLeft and scrollTop fields in the
+			* view div. This causes an updatePage from the scroll event. In some browsers 
+			* this event is asynchromous and forcing update page to run synchronously
+			* (by calling doScroll) leads to redraw problems. On Chrome 11, the view 
+			* stops redrawing at times when holding PageDown/PageUp key.
+			* On Firefox 4 for Linux, the view redraws the first page when holding 
+			* PageDown/PageUp key, but it will not redraw again until the key is released.
+			*/
+			var viewDiv = this._viewDiv;
+			if (pixelX) { viewDiv.scrollLeft += pixelX; }
+			if (pixelY) { viewDiv.scrollTop += pixelY; }
+		},
+		_setClipboardText: function (text, event) {
+			var clipboardText;
+			if (this._frameWindow.clipboardData) {
+				//IE
+				clipboardText = [];
+				this._convertDelimiter(text, function(t) {clipboardText.push(t);}, function() {clipboardText.push(platformDelimiter);});
+				return this._frameWindow.clipboardData.setData("Text", clipboardText.join(""));
+			}
+			/* Feature in Chrome, clipboardData.setData is no-op on Chrome even though it returns true */
+			if (isChrome || isFirefox || !event) {
+				var window = this._frameWindow;
+				var document = this._frameDocument;
+				var child = document.createElement("PRE");
+				child.style.position = "fixed";
+				child.style.left = "-1000px";
+				this._convertDelimiter(text, 
+					function(t) {
+						child.appendChild(document.createTextNode(t));
+					}, 
+					function() {
+						child.appendChild(document.createElement("BR"));
+					}
+				);
+				child.appendChild(document.createTextNode(" "));
+				this._clientDiv.appendChild(child);
+				var range = document.createRange();
+				range.setStart(child.firstChild, 0);
+				range.setEndBefore(child.lastChild);
+				var sel = window.getSelection();
+				if (sel.rangeCount > 0) { sel.removeAllRanges(); }
+				sel.addRange(range);
+				var self = this;
+				var cleanup = function() {
+					self._clientDiv.removeChild(child);
+					self._updateDOMSelection();
+				};
+				var result = false;
+				/* 
+				* Try execCommand first, it works on firefox with clipboard permission,
+				* chrome 5, safari 4.
+				*/
+				this._ignoreCopy = true;
+				try {
+					result = document.execCommand("copy", false, null);
+				} catch (e) {}
+				this._ignoreCopy = false;
+				if (!result) {
+					if (event) {
+						setTimeout(cleanup, 0);
+						return false;
+					}
+				}
+				/* no event and no permission, copy can not be done */
+				cleanup();
+				return true;
+			}
+			if (event && event.clipboardData) {
+				//webkit
+				clipboardText = [];
+				this._convertDelimiter(text, function(t) {clipboardText.push(t);}, function() {clipboardText.push(platformDelimiter);});
+				return event.clipboardData.setData("text/plain", clipboardText.join("")); 
+			}
+		},
+		_setDOMSelection: function (startNode, startOffset, endNode, endOffset) {
+			var window = this._frameWindow;
+			var document = this._frameDocument;
+			var startLineNode, startLineOffset, endLineNode, endLineOffset;
+			var offset = 0;
+			var lineChild = startNode.firstChild;
+			var node, nodeLength, model = this._model;
+			var startLineEnd = model.getLine(startNode.lineIndex).length;
+			while (lineChild) {
+				node = lineChild.firstChild;
+				nodeLength = node.length;
+				if (lineChild.ignoreChars) {
+					nodeLength -= lineChild.ignoreChars;
+				}
+				if (offset + nodeLength > startOffset || offset + nodeLength >= startLineEnd) {
+					startLineNode = node;
+					startLineOffset = startOffset - offset;
+					if (lineChild.ignoreChars && nodeLength > 0 && startLineOffset === nodeLength) {
+						startLineOffset += lineChild.ignoreChars; 
+					}
+					break;
+				}
+				offset += nodeLength;
+				lineChild = lineChild.nextSibling;
+			}
+			offset = 0;
+			lineChild = endNode.firstChild;
+			var endLineEnd = this._model.getLine(endNode.lineIndex).length;
+			while (lineChild) {
+				node = lineChild.firstChild;
+				nodeLength = node.length;
+				if (lineChild.ignoreChars) {
+					nodeLength -= lineChild.ignoreChars;
+				}
+				if (nodeLength + offset > endOffset || offset + nodeLength >= endLineEnd) {
+					endLineNode = node;
+					endLineOffset = endOffset - offset;
+					if (lineChild.ignoreChars && nodeLength > 0 && endLineOffset === nodeLength) {
+						endLineOffset += lineChild.ignoreChars; 
+					}
+					break;
+				}
+				offset += nodeLength;
+				lineChild = lineChild.nextSibling;
+			}
+			
+			this._setDOMFullSelection(startNode, startOffset, startLineEnd, endNode, endOffset, endLineEnd);
+			if (isPad) { return; }
+
+			var range;
+			if (window.getSelection) {
+				//W3C
+				range = document.createRange();
+				range.setStart(startLineNode, startLineOffset);
+				range.setEnd(endLineNode, endLineOffset);
+				var sel = window.getSelection();
+				this._ignoreSelect = false;
+				if (sel.rangeCount > 0) { sel.removeAllRanges(); }
+				sel.addRange(range);
+				this._ignoreSelect = true;
+			} else if (document.selection) {
+				//IE < 9
+				var body = document.body;
+
+				/*
+				* Bug in IE. For some reason when text is deselected the overflow
+				* selection at the end of some lines does not get redrawn.  The
+				* fix is to create a DOM element in the body to force a redraw.
+				*/
+				var child = document.createElement("DIV");
+				body.appendChild(child);
+				body.removeChild(child);
+				
+				range = body.createTextRange();
+				range.moveToElementText(startLineNode.parentNode);
+				range.moveStart("character", startLineOffset);
+				var endRange = body.createTextRange();
+				endRange.moveToElementText(endLineNode.parentNode);
+				endRange.moveStart("character", endLineOffset);
+				range.setEndPoint("EndToStart", endRange);
+				this._ignoreSelect = false;
+				range.select();
+				this._ignoreSelect = true;
+			}
+		},
+		_setDOMFullSelection: function(startNode, startOffset, startLineEnd, endNode, endOffset, endLineEnd) {
+			var model = this._model;
+			if (this._selDiv1) {
+				var startLineBounds, l;
+				startLineBounds = this._getLineBoundingClientRect(startNode);
+				if (startOffset === 0) {
+					l = startLineBounds.left;
+				} else {
+					if (startOffset >= startLineEnd) {
+						l = startLineBounds.right;
+					} else {
+						this._ignoreDOMSelection = true;
+						l = this._getBoundsAtOffset(model.getLineStart(startNode.lineIndex) + startOffset).left;
+						this._ignoreDOMSelection = false;
+					}
+				}
+				var textArea = this._textArea;
+				if (textArea) {
+					textArea.selectionStart = textArea.selectionEnd = 0;
+					var rect = this._frame.getBoundingClientRect();
+					var touchRect = this._touchDiv.getBoundingClientRect();
+					var viewBounds = this._viewDiv.getBoundingClientRect();
+					if (!(viewBounds.left <= l && l <= viewBounds.left + viewBounds.width &&
+						viewBounds.top <= startLineBounds.top && startLineBounds.top <= viewBounds.top + viewBounds.height) ||
+						!(startNode === endNode && startOffset === endOffset))
+					{
+						textArea.style.left = "-1000px";
+					} else {
+						textArea.style.left = (l - 4 + rect.left - touchRect.left) + "px";
+					}
+					textArea.style.top = (startLineBounds.top + rect.top - touchRect.top) + "px";
+					textArea.style.width = "6px";
+					textArea.style.height = (startLineBounds.bottom - startLineBounds.top) + "px";
+				}
+			
+				var selDiv = this._selDiv1;
+				selDiv.style.width = "0px";
+				selDiv.style.height = "0px";
+				selDiv = this._selDiv2;
+				selDiv.style.width = "0px";
+				selDiv.style.height = "0px";
+				selDiv = this._selDiv3;
+				selDiv.style.width = "0px";
+				selDiv.style.height = "0px";
+				if (!(startNode === endNode && startOffset === endOffset)) {
+					var handleWidth = isPad ? 2 : 0;
+					var handleBorder = handleWidth + "px blue solid";
+					var viewPad = this._getViewPadding();
+					var clientRect = this._clientDiv.getBoundingClientRect();
+					var viewRect = this._viewDiv.getBoundingClientRect();
+					var left = viewRect.left + viewPad.left;
+					var right = clientRect.right;
+					var top = viewRect.top + viewPad.top;
+					var bottom = clientRect.bottom;
+					var r;
+					var endLineBounds = this._getLineBoundingClientRect(endNode);
+					if (endOffset === 0) {
+						r = endLineBounds.left;
+					} else {
+						if (endOffset >= endLineEnd) {
+							r = endLineBounds.right;
+						} else {
+							this._ignoreDOMSelection = true;
+							r = this._getBoundsAtOffset(model.getLineStart(endNode.lineIndex) + endOffset).left;
+							this._ignoreDOMSelection = false;
+						}
+					}
+					var sel1Div = this._selDiv1;
+					var sel1Left = Math.min(right, Math.max(left, l));
+					var sel1Top = Math.min(bottom, Math.max(top, startLineBounds.top));
+					var sel1Right = right;
+					var sel1Bottom = Math.min(bottom, Math.max(top, startLineBounds.bottom));
+					sel1Div.style.left = sel1Left + "px";
+					sel1Div.style.top = sel1Top + "px";
+					sel1Div.style.width = Math.max(0, sel1Right - sel1Left) + "px";
+					sel1Div.style.height = Math.max(0, sel1Bottom - sel1Top) + (isPad ? 1 : 0) + "px";
+					if (isPad) {
+						sel1Div.style.borderLeft = handleBorder;
+						sel1Div.style.borderRight = "0px";
+					}
+					if (startNode === endNode) {
+						sel1Right = Math.min(r, right);
+						sel1Div.style.width = Math.max(0, sel1Right - sel1Left - handleWidth * 2) + "px";
+						if (isPad) {
+							sel1Div.style.borderRight = handleBorder;
+						}
+					} else {
+						var sel3Left = left;
+						var sel3Top = Math.min(bottom, Math.max(top, endLineBounds.top));
+						var sel3Right = Math.min(right, Math.max(left, r));
+						var sel3Bottom = Math.min(bottom, Math.max(top, endLineBounds.bottom));
+						var sel3Div = this._selDiv3;
+						sel3Div.style.left = sel3Left + "px";
+						sel3Div.style.top = sel3Top + "px";
+						sel3Div.style.width = Math.max(0, sel3Right - sel3Left - handleWidth) + "px";
+						sel3Div.style.height = Math.max(0, sel3Bottom - sel3Top) + "px";
+						if (isPad) {
+							sel3Div.style.borderRight = handleBorder;
+						}
+						if (sel3Top - sel1Bottom > 0) {
+							var sel2Div = this._selDiv2;
+							sel2Div.style.left = left + "px";
+							sel2Div.style.top = sel1Bottom + "px";
+							sel2Div.style.width = Math.max(0, right - left) + "px";
+							sel2Div.style.height = Math.max(0, sel3Top - sel1Bottom) + (isPad ? 1 : 0) + "px";
+						}
+					}
+				}
+			}
+		},
+		_setGrab: function (target) {
+			if (target === this._grabControl) { return; }
+			if (target) {
+				addHandler(target, "mousemove", this._mouseMoveClosure);
+				addHandler(target, "mouseup", this._mouseUpClosure);
+				if (target.setCapture) { target.setCapture(); }
+				this._grabControl = target;
+			} else {
+				removeHandler(this._grabControl, "mousemove", this._mouseMoveClosure);
+				removeHandler(this._grabControl, "mouseup", this._mouseUpClosure);
+				if (this._grabControl.releaseCapture) { this._grabControl.releaseCapture(); }
+				this._grabControl = null;
+			}
+		},
+		_setSelection: function (selection, scroll, update) {
+			if (selection) {
+				this._columnX = -1;
+				if (update === undefined) { update = true; }
+				var oldSelection = this._selection; 
+				if (!oldSelection.equals(selection)) {
+					this._selection = selection;
+					var e = {
+						oldValue: {start:oldSelection.start, end:oldSelection.end},
+						newValue: {start:selection.start, end:selection.end}
+					};
+					this.onSelection(e);
+				}
+				/* 
+				* Always showCaret(), even when the selection is not changing, to ensure the
+				* caret is visible. Note that some views do not scroll to show the caret during
+				* keyboard navigation when the selection does not chanage. For example, line down
+				* when the caret is already at the last line.
+				*/
+				if (scroll) { update = !this._showCaret(); }
+				
+				/* 
+				* Sometimes the browser changes the selection 
+				* as result of method calls or "leaked" events. 
+				* The fix is to set the visual selection even
+				* when the logical selection is not changed.
+				*/
+				if (update) { this._updateDOMSelection(); }
+			}
+		},
+		_setSelectionTo: function (x,y,extent) {
+			var model = this._model, offset;
+			var selection = this._getSelection();
+			var lineIndex = this._getYToLine(y);
+			if (this._clickCount === 1) {
+				offset = this._getXToOffset(lineIndex, x);
+				selection.extend(offset);
+				if (!extent) { selection.collapse(); }
+			} else {
+				var word = (this._clickCount & 1) === 0;
+				var start, end;
+				if (word) {
+					offset = this._getXToOffset(lineIndex, x);
+					if (this._doubleClickSelection) {
+						if (offset >= this._doubleClickSelection.start) {
+							start = this._doubleClickSelection.start;
+							end = this._getOffset(offset, "wordend", +1);
+						} else {
+							start = this._getOffset(offset, "word", -1);
+							end = this._doubleClickSelection.end;
+						}
+					} else {
+						start = this._getOffset(offset, "word", -1);
+						end = this._getOffset(start, "wordend", +1);
+					}
+				} else {
+					if (this._doubleClickSelection) {
+						var doubleClickLine = model.getLineAtOffset(this._doubleClickSelection.start);
+						if (lineIndex >= doubleClickLine) {
+							start = model.getLineStart(doubleClickLine);
+							end = model.getLineEnd(lineIndex);
+						} else {
+							start = model.getLineStart(lineIndex);
+							end = model.getLineEnd(doubleClickLine);
+						}
+					} else {
+						start = model.getLineStart(lineIndex);
+						end = model.getLineEnd(lineIndex);
+					}
+				}
+				selection.setCaret(start);
+				selection.extend(end);
+			} 
+			this._setSelection(selection, true, true);
+		},
+		_showCaret: function () {
+			var model = this._model;
+			var selection = this._getSelection();
+			var scroll = this._getScroll();
+			var caret = selection.getCaret();
+			var start = selection.start;
+			var end = selection.end;
+			var startLine = model.getLineAtOffset(start); 
+			var endLine = model.getLineAtOffset(end);
+			var endInclusive = Math.max(Math.max(start, model.getLineStart(endLine)), end - 1);
+			var viewPad = this._getViewPadding();
+			
+			var clientWidth = this._getClientWidth();
+			var leftEdge = viewPad.left;
+			var rightEdge = viewPad.left + clientWidth;
+			var bounds = this._getBoundsAtOffset(caret === start ? start : endInclusive);
+			var left = bounds.left;
+			var right = bounds.right;
+			var minScroll = clientWidth / 4;
+			if (!selection.isEmpty() && startLine === endLine) {
+				bounds = this._getBoundsAtOffset(caret === end ? start : endInclusive);
+				var selectionWidth = caret === start ? bounds.right - left : right - bounds.left;
+				if ((clientWidth - minScroll) > selectionWidth) {
+					if (left > bounds.left) { left = bounds.left; }
+					if (right < bounds.right) { right = bounds.right; }
+				}
+			}
+			var viewRect = this._viewDiv.getBoundingClientRect(); 
+			left -= viewRect.left;
+			right -= viewRect.left;
+			var pixelX = 0;
+			if (left < leftEdge) {
+				pixelX = Math.min(left - leftEdge, -minScroll);
+			}
+			if (right > rightEdge) {
+				var maxScroll = this._scrollDiv.scrollWidth - scroll.x - clientWidth;
+				pixelX = Math.min(maxScroll,  Math.max(right - rightEdge, minScroll));
+			}
+
+			var pixelY = 0;
+			var topIndex = this._getTopIndex(true);
+			var bottomIndex = this._getBottomIndex(true);
+			var caretLine = model.getLineAtOffset(caret);
+			var clientHeight = this._getClientHeight();
+			if (!(topIndex <= caretLine && caretLine <= bottomIndex)) {
+				var lineHeight = this._getLineHeight();
+				var selectionHeight = (endLine - startLine) * lineHeight;
+				pixelY = caretLine * lineHeight;
+				pixelY -= scroll.y;
+				if (pixelY + lineHeight > clientHeight) {
+					pixelY -= clientHeight - lineHeight;
+					if (caret === start && start !== end) {
+						pixelY += Math.min(clientHeight - lineHeight, selectionHeight);
+					}
+				} else {
+					if (caret === end) {
+						pixelY -= Math.min (clientHeight - lineHeight, selectionHeight);
+					}
+				}
+			}
+
+			if (pixelX !== 0 || pixelY !== 0) {
+				this._scrollView (pixelX, pixelY);
+				/*
+				* When the view scrolls it is possible that one of the scrollbars can show over the caret.
+				* Depending on the browser scrolling can be synchronous (Safari), in which case the change 
+				* can be detected before showCaret() returns. When scrolling is asynchronous (most browsers), 
+				* the detection is done during the next update page.
+				*/
+				if (clientHeight !== this._getClientHeight() || clientWidth !== this._getClientWidth()) {
+					this._showCaret();
+				} else {
+					this._ensureCaretVisible = true;
+				}
+				return true;
+			}
+			return false;
+		},
+		_startIME: function () {
+			if (this._imeOffset !== -1) { return; }
+			var selection = this._getSelection();
+			if (!selection.isEmpty()) {
+				this._modifyContent({text: "", start: selection.start, end: selection.end}, true);
+			}
+			this._imeOffset = selection.start;
+		},
+		_unhookEvents: function() {
+			this._model.removeListener(this._modelListener);
+			this._modelListener = null;
+
+			this._mouseMoveClosure = null;
+			this._mouseUpClosure = null;
+
+			for (var i=0; i<this._handlers.length; i++) {
+				var h = this._handlers[i];
+				removeHandler(h.target, h.type, h.handler);
+			}
+			this._handlers = null;
+		},
+		_updateDOMSelection: function () {
+			if (this._ignoreDOMSelection) { return; }
+			var selection = this._getSelection();
+			var model = this._model;
+			var startLine = model.getLineAtOffset(selection.start);
+			var endLine = model.getLineAtOffset(selection.end);
+			var firstNode = this._getLineNext();
+			/*
+			* Bug in Firefox. For some reason, after a update page sometimes the 
+			* firstChild returns null incorrectly. The fix is to ignore show selection.
+			*/
+			if (!firstNode) { return; }
+			var lastNode = this._getLinePrevious();
+			
+			var topNode, bottomNode, topOffset, bottomOffset;
+			if (startLine < firstNode.lineIndex) {
+				topNode = firstNode;
+				topOffset = 0;
+			} else if (startLine > lastNode.lineIndex) {
+				topNode = lastNode;
+				topOffset = 0;
+			} else {
+				topNode = this._getLineNode(startLine);
+				topOffset = selection.start - model.getLineStart(startLine);
+			}
+
+			if (endLine < firstNode.lineIndex) {
+				bottomNode = firstNode;
+				bottomOffset = 0;
+			} else if (endLine > lastNode.lineIndex) {
+				bottomNode = lastNode;
+				bottomOffset = 0;
+			} else {
+				bottomNode = this._getLineNode(endLine);
+				bottomOffset = selection.end - model.getLineStart(endLine);
+			}
+			this._setDOMSelection(topNode, topOffset, bottomNode, bottomOffset);
+		},
+		_updatePage: function() {
+			if (this._updateTimer) { 
+				clearTimeout(this._updateTimer);
+				this._updateTimer = null;
+			}
+			var document = this._frameDocument;
+			var frameWidth = this._getFrameWidth();
+			var frameHeight = this._getFrameHeight();
+			document.body.style.width = frameWidth + "px";
+			document.body.style.height = frameHeight + "px";
+			
+			var viewDiv = this._viewDiv;
+			var clientDiv = this._clientDiv;
+			var viewPad = this._getViewPadding();
+			
+			/* Update view height in order to have client height computed */
+			viewDiv.style.height = Math.max(0, (frameHeight - viewPad.top - viewPad.bottom)) + "px";
+			
+			var model = this._model;
+			var lineHeight = this._getLineHeight();
+			var scrollY = this._getScroll().y;
+			var firstLine = Math.max(0, scrollY) / lineHeight;
+			var topIndex = Math.floor(firstLine);
+			var lineStart = Math.max(0, topIndex - 1);
+			var top = Math.round((firstLine - lineStart) * lineHeight);
+			var lineCount = model.getLineCount();
+			var clientHeight = this._getClientHeight();
+			var partialY = Math.round((firstLine - topIndex) * lineHeight);
+			var linesPerPage = Math.floor((clientHeight + partialY) / lineHeight);
+			var bottomIndex = Math.min(topIndex + linesPerPage, lineCount - 1);
+			var lineEnd = Math.min(bottomIndex + 1, lineCount - 1);
+			this._partialY = partialY;
+			
+			var lineIndex, lineWidth;
+			var child = clientDiv.firstChild;
+			while (child) {
+				lineIndex = child.lineIndex;
+				var nextChild = child.nextSibling;
+				if (!(lineStart <= lineIndex && lineIndex <= lineEnd) || child.lineChanged || child.lineIndex === -1) {
+					if (this._mouseWheelLine === child) {
+						child.style.display = "none";
+						child.lineIndex = -1;
+					} else {
+						clientDiv.removeChild(child);
+					}
+				}
+				child = nextChild;
+			}
+
+			child = this._getLineNext();
+			var frag = document.createDocumentFragment();
+			for (lineIndex=lineStart; lineIndex<=lineEnd; lineIndex++) {
+				if (!child || child.lineIndex > lineIndex) {
+					this._createLine(frag, null, document, lineIndex, model);
+				} else {
+					if (frag.firstChild) {
+						clientDiv.insertBefore(frag, child);
+						frag = document.createDocumentFragment();
+					}
+					child = this._getLineNext(child);
+				}
+			}
+			if (frag.firstChild) { clientDiv.insertBefore(frag, child); }
+
+			/*
+			* Feature in WekKit. Webkit limits the width of the lines
+			* computed below to the width of the client div.  This causes
+			* the lines to be wrapped even though "pre" is set.  The fix
+			* is to set the width of the client div to a larger number
+			* before computing the lines width.  Note that this value is
+			* reset to the appropriate value further down.
+			*/ 
+			if (isWebkit) {
+				clientDiv.style.width = (0x7FFFF).toString() + "px";
+			}
+
+			child = this._getLineNext();
+			while (child) {
+				lineWidth = child.lineWidth;
+				if (lineWidth === undefined) {
+					var rect = this._getLineBoundingClientRect(child);
+					lineWidth = child.lineWidth = rect.right - rect.left;
+				}
+				if (lineWidth >= this._maxLineWidth) {
+					this._maxLineWidth = lineWidth;
+					this._maxLineIndex = child.lineIndex;
+				}
+				if (child.lineIndex === topIndex) { this._topChild = child; }
+				if (child.lineIndex === bottomIndex) { this._bottomChild = child; }
+				child = this._getLineNext(child);
+			}
+
+			// Update rulers
+			this._updateRuler(this._leftDiv, topIndex, bottomIndex);
+			this._updateRuler(this._rightDiv, topIndex, bottomIndex);
+			
+			var leftWidth = this._leftDiv ? this._leftDiv.scrollWidth : 0;
+			var rightWidth = this._rightDiv ? this._rightDiv.scrollWidth : 0;
+			viewDiv.style.left = leftWidth + "px";
+			viewDiv.style.width = Math.max(0, frameWidth - leftWidth - rightWidth - viewPad.left - viewPad.right) + "px";
+			if (this._rightDiv) {
+				this._rightDiv.style.left = (frameWidth - rightWidth) + "px"; 
+			}
+			
+			var scrollDiv = this._scrollDiv;
+			/* Need to set the height first in order for the width to consider the vertical scrollbar */
+			var scrollHeight = lineCount * lineHeight;
+			scrollDiv.style.height = scrollHeight + "px";
+			var clientWidth = this._getClientWidth();
+			var width = Math.max(this._maxLineWidth, clientWidth);
+			/*
+			* Except by IE 8 and earlier, all other browsers are not allocating enough space for the right padding 
+			* in the scrollbar. It is possible this a bug since all other paddings are considered.
+			*/
+			var scrollWidth = width;
+			if (!isIE || isIE >= 9) { width += viewPad.right; }
+			scrollDiv.style.width = width + "px";
+
+			// Get the left scroll after setting the width of the scrollDiv as this can change the horizontal scroll offset.
+			var scroll = this._getScroll();
+			var left = scroll.x;
+			var clipLeft = left;
+			var clipTop = top;
+			var clipRight = left + clientWidth;
+			var clipBottom = top + clientHeight;
+			if (clipLeft === 0) { clipLeft -= viewPad.left; }
+			if (clipTop === 0) { clipTop -= viewPad.top; }
+			if (clipRight === scrollWidth) { clipRight += viewPad.right; }
+			if (scroll.y + clientHeight === scrollHeight) { clipBottom += viewPad.bottom; }
+			clientDiv.style.clip = "rect(" + clipTop + "px," + clipRight + "px," + clipBottom + "px," + clipLeft + "px)";
+			clientDiv.style.left = (-left + leftWidth + viewPad.left) + "px";
+			clientDiv.style.top = (-top + viewPad.top) + "px";
+			clientDiv.style.width = (isWebkit ? scrollWidth : clientWidth + left) + "px";
+			clientDiv.style.height = (clientHeight + top) + "px";
+			var overlayDiv = this._overlayDiv;
+			if (overlayDiv) {
+				overlayDiv.style.clip = clientDiv.style.clip;
+				overlayDiv.style.left = clientDiv.style.left;
+				overlayDiv.style.top = clientDiv.style.top;
+				overlayDiv.style.width = clientDiv.style.width;
+				overlayDiv.style.height = clientDiv.style.height;
+			}
+			function _updateRulerSize(divRuler) {
+				if (!divRuler) { return; }
+				var rulerHeight = clientHeight + viewPad.top + viewPad.bottom;
+				var cells = divRuler.firstChild.rows[0].cells;
+				for (var i = 0; i < cells.length; i++) {
+					var div = cells[i].firstChild;
+					var offset = lineHeight;
+					if (div._ruler.getOverview() === "page") { offset += partialY; }
+					div.style.top = -offset + "px";
+					div.style.height = (rulerHeight + offset) + "px";
+					div = div.nextSibling;
+				}
+				divRuler.style.height = rulerHeight + "px";
+			}
+			_updateRulerSize(this._leftDiv);
+			_updateRulerSize(this._rightDiv);
+			if (isPad) {
+				var self = this;
+				setTimeout(function() {self._resizeTouchDiv();}, 0);
+			}
+			this._updateDOMSelection();
+
+			/*
+			* If the client height changed during the update page it means that scrollbar has either been shown or hidden.
+			* When this happens update page has to run again to ensure that the top and bottom lines div are correct.
+			* 
+			* Note: On IE, updateDOMSelection() has to be called before getting the new client height because it
+			* forces the client area to be recomputed.
+			*/
+			var ensureCaretVisible = this._ensureCaretVisible;
+			this._ensureCaretVisible = false;
+			if (clientHeight !== this._getClientHeight()) {
+				this._updatePage();
+				if (ensureCaretVisible) {
+					this._showCaret();
+				}
+			}
+		},
+		_updateRuler: function (divRuler, topIndex, bottomIndex) {
+			if (!divRuler) { return; }
+			var cells = divRuler.firstChild.rows[0].cells;
+			var lineHeight = this._getLineHeight();
+			var parentDocument = this._frameDocument;
+			var viewPad = this._getViewPadding();
+			for (var i = 0; i < cells.length; i++) {
+				var div = cells[i].firstChild;
+				var ruler = div._ruler, style;
+				if (div.rulerChanged) {
+					this._applyStyle(ruler.getStyle(), div);
+				}
+				
+				var widthDiv;
+				var child = div.firstChild;
+				if (child) {
+					widthDiv = child;
+					child = child.nextSibling;
+				} else {
+					widthDiv = parentDocument.createElement("DIV");
+					widthDiv.style.visibility = "hidden";
+					div.appendChild(widthDiv);
+				}
+				var lineIndex;
+				if (div.rulerChanged) {
+					if (widthDiv) {
+						lineIndex = -1;
+						this._applyStyle(ruler.getStyle(lineIndex), widthDiv);
+						widthDiv.innerHTML = ruler.getHTML(lineIndex);
+						widthDiv.lineIndex = lineIndex;
+						widthDiv.style.height = (lineHeight + viewPad.top) + "px";
+					}
+				}
+
+				var overview = ruler.getOverview(), lineDiv, frag;
+				if (overview === "page") {
+					while (child) {
+						lineIndex = child.lineIndex;
+						var nextChild = child.nextSibling;
+						if (!(topIndex <= lineIndex && lineIndex <= bottomIndex) || child.lineChanged) {
+							div.removeChild(child);
+						}
+						child = nextChild;
+					}
+					child = div.firstChild.nextSibling;
+					frag = document.createDocumentFragment();
+					for (lineIndex=topIndex; lineIndex<=bottomIndex; lineIndex++) {
+						if (!child || child.lineIndex > lineIndex) {
+							lineDiv = parentDocument.createElement("DIV");
+							this._applyStyle(ruler.getStyle(lineIndex), lineDiv);
+							lineDiv.innerHTML = ruler.getHTML(lineIndex);
+							lineDiv.lineIndex = lineIndex;
+							lineDiv.style.height = lineHeight + "px";
+							frag.appendChild(lineDiv);
+						} else {
+							if (frag.firstChild) {
+								div.insertBefore(frag, child);
+								frag = document.createDocumentFragment();
+							}
+							if (child) {
+								child = child.nextSibling;
+							}
+						}
+					}
+					if (frag.firstChild) { div.insertBefore(frag, child); }
+				} else {
+					var buttonHeight = 17;
+					var clientHeight = this._getClientHeight ();
+					var trackHeight = clientHeight + viewPad.top + viewPad.bottom - 2 * buttonHeight;
+					var lineCount = this._model.getLineCount ();
+					var divHeight = trackHeight / lineCount;
+					if (div.rulerChanged) {
+						var count = div.childNodes.length;
+						while (count > 1) {
+							div.removeChild(div.lastChild);
+							count--;
+						}
+						var lines = ruler.getAnnotations ();
+						frag = document.createDocumentFragment();
+						for (var j = 0; j < lines.length; j++) {
+							lineIndex = lines[j];
+							lineDiv = parentDocument.createElement("DIV");
+							this._applyStyle(ruler.getStyle(lineIndex), lineDiv);
+							lineDiv.style.position = "absolute";
+							lineDiv.style.top = buttonHeight + lineHeight + Math.floor(lineIndex * divHeight) + "px";
+							lineDiv.innerHTML = ruler.getHTML(lineIndex);
+							lineDiv.lineIndex = lineIndex;
+							frag.appendChild(lineDiv);
+						}
+						div.appendChild(frag);
+					} else if (div._oldTrackHeight !== trackHeight) {
+						lineDiv = div.firstChild ? div.firstChild.nextSibling : null;
+						while (lineDiv) {
+							lineDiv.style.top = buttonHeight + lineHeight + Math.floor(lineDiv.lineIndex * divHeight) + "px";
+							lineDiv = lineDiv.nextSibling;
+						}
+					}
+					div._oldTrackHeight = trackHeight;
+				}
+				div.rulerChanged = false;
+				div = div.nextSibling;
+			}
+		}
+	};//end prototype
+	
+	return TextView;
+}());
+
+if (typeof window !== "undefined" && typeof window.define !== "undefined") {
+	define(['orion/textview/textModel', 'orion/textview/keyBinding'], function() {
+		return orion.textview;
+	});
+}
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/textview/textview.css b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/textview/textview.css
new file mode 100644
index 0000000..510648c
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/textview/textview.css
@@ -0,0 +1,11 @@
+.view {
+	background-color: white;
+}
+
+.viewContainer {
+	font-family: monospace;
+	font-size: 10pt;
+}
+
+.viewContent {
+}
\ No newline at end of file
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/textview/undoStack.js b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/textview/undoStack.js
new file mode 100644
index 0000000..e56a870
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/orion/orion/textview/undoStack.js
@@ -0,0 +1,339 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials are made 
+ * available under the terms of the Eclipse Public License v1.0 
+ * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
+ * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
+ * 
+ * Contributors: IBM Corporation - initial API and implementation
+ ******************************************************************************/
+
+/*global window define */
+
+/**
+ * @namespace The global container for Orion APIs.
+ */ 
+var orion = orion || {};
+orion.textview = orion.textview || {};
+
+/**
+ * Constructs a new UndoStack on a text view.
+ *
+ * @param {orion.textview.TextView} view the text view for the undo stack.
+ * @param {Number} [size=100] the size for the undo stack.
+ *
+ * @name orion.textview.UndoStack
+ * @class The UndoStack is used to record the history of a text model associated to an view. Every
+ * change to the model is added to stack, allowing the application to undo and redo these changes.
+ *
+ * <p>
+ * <b>See:</b><br/>
+ * {@link orion.textview.TextView}<br/>
+ * </p>
+ */
+orion.textview.UndoStack = (function() {
+	/** 
+	 * Constructs a new Change object.
+	 * 
+	 * @class 
+	 * @name orion.textview.Change
+	 * @private
+	 */
+	var Change = (function() {
+		function Change(offset, text, previousText) {
+			this.offset = offset;
+			this.text = text;
+			this.previousText = previousText;
+		}
+		Change.prototype = {
+			undo: function (view, select) {
+				this._doUndoRedo(this.offset, this.previousText, this.text, view, select);
+			},
+			redo: function (view, select) {
+				this._doUndoRedo(this.offset, this.text, this.previousText, view, select);
+			},
+			_doUndoRedo: function(offset, text, previousText, view, select) {
+				view.setText(text, offset, offset + previousText.length);
+				if (select) {
+					view.setSelection(offset, offset + text.length);
+				}
+			}
+		};
+		return Change;
+	}());
+
+	/** 
+	 * Constructs a new CompoundChange object.
+	 * 
+	 * @class 
+	 * @name orion.textview.CompoundChange
+	 * @private
+	 */
+	var CompoundChange = (function() {
+		function CompoundChange (selection, caret) {
+			this.selection = selection;
+			this.caret = caret;
+			this.changes = [];
+		}
+		CompoundChange.prototype = {
+			add: function (change) {
+				this.changes.push(change);
+			},
+			undo: function (view, select) {
+				for (var i=this.changes.length - 1; i >= 0; i--) {
+					this.changes[i].undo(view, false);
+				}
+				if (select) {
+					var start = this.selection.start;
+					var end = this.selection.end;
+					view.setSelection(this.caret ? start : end, this.caret ? end : start);
+				}
+			},
+			redo: function (view, select) {
+				for (var i = 0; i < this.changes.length; i++) {
+					this.changes[i].redo(view, false);
+				}
+				if (select) {
+					var start = this.selection.start;
+					var end = this.selection.end;
+					view.setSelection(this.caret ? start : end, this.caret ? end : start);
+				}
+			}
+		};
+		return CompoundChange;
+	}());
+
+	/** @private */
+	function UndoStack (view, size) {
+		this.view = view;
+		this.size = size !== undefined ? size : 100;
+		this.reset();
+		view.addEventListener("ModelChanging", this, this._onModelChanging);
+		view.addEventListener("Destroy", this, this._onDestroy);
+	}
+	UndoStack.prototype = /** @lends orion.textview.UndoStack.prototype */ {
+		/**
+		 * Adds a change to the stack.
+		 * 
+		 * @param change the change to add.
+		 * @param {Number} change.offset the offset of the change
+		 * @param {String} change.text the new text of the change
+		 * @param {String} change.previousText the previous text of the change
+		 */
+		add: function (change) {
+			if (this.compoundChange) {
+				this.compoundChange.add(change);
+			} else {
+				var length = this.stack.length;
+				this.stack.splice(this.index, length-this.index, change);
+				this.index++;
+				if (this.stack.length > this.size) {
+					this.stack.shift();
+					this.index--;
+					this.cleanIndex--;
+				}
+			}
+		},
+		/** 
+		 * Marks the current state of the stack as clean.
+		 *
+		 * <p>
+		 * This function is typically called when the content of view associated with the stack is saved.
+		 * </p>
+		 *
+		 * @see #isClean
+		 */
+		markClean: function() {
+			this.endCompoundChange();
+			this._commitUndo();
+			this.cleanIndex = this.index;
+		},
+		/**
+		 * Returns true if current state of stack is the same
+		 * as the state when markClean() was called.
+		 *
+		 * <p>
+		 * For example, the application calls markClean(), then calls undo() four times and redo() four times.
+		 * At this point isClean() returns true.  
+		 * </p>
+		 * <p>
+		 * This function is typically called to determine if the content of the view associated with the stack
+		 * has changed since the last time it was saved.
+		 * </p>
+		 *
+		 * @return {Boolean} returns if the state is the same as the state when markClean() was called.
+		 *
+		 * @see #markClean
+		 */
+		isClean: function() {
+			return this.cleanIndex === this.getSize().undo;
+		},
+		/**
+		 * Returns true if there is at least one change to undo.
+		 *
+		 * @return {Boolean} returns true if there is at least one change to undo.
+		 *
+		 * @see #canRedo
+		 * @see #undo
+		 */
+		canUndo: function() {
+			return this.getSize().undo > 0;
+		},
+		/**
+		 * Returns true if there is at least one change to redo.
+		 *
+		 * @return {Boolean} returns true if there is at least one change to redo.
+		 *
+		 * @see #canUndo
+		 * @see #redo
+		 */
+		canRedo: function() {
+			return this.getSize().redo > 0;
+		},
+		/**
+		 * Finishes a compound change.
+		 *
+		 * @see #startCompoundChange
+		 */
+		endCompoundChange: function() {
+			this.compoundChange = undefined;
+		},
+		/**
+		 * Returns the sizes of the stack.
+		 *
+		 * @return {object} a object where object.undo is the number of changes that can be un-done, 
+		 *  and object.redo is the number of changes that can be re-done.
+		 *
+		 * @see #canUndo
+		 * @see #canRedo
+		 */
+		getSize: function() {
+			var index = this.index;
+			var length = this.stack.length;
+			if (this._undoStart !== undefined) {
+				index++;
+			}
+			return {undo: index, redo: (length - index)};
+		},
+		/**
+		 * Undo the last change in the stack.
+		 *
+		 * @return {Boolean} returns true if a change was un-done.
+		 *
+		 * @see #redo
+		 * @see #canUndo
+		 */
+		undo: function() {
+			this._commitUndo();
+			if (this.index <= 0) {
+				return false;
+			}
+			var change = this.stack[--this.index];
+			this._ignoreUndo = true;
+			change.undo(this.view, true);
+			this._ignoreUndo = false;
+			return true;
+		},
+		/**
+		 * Redo the last change in the stack.
+		 *
+		 * @return {Boolean} returns true if a change was re-done.
+		 *
+		 * @see #undo
+		 * @see #canRedo
+		 */
+		redo: function() {
+			this._commitUndo();
+			if (this.index >= this.stack.length) {
+				return false;
+			}
+			var change = this.stack[this.index++];
+			this._ignoreUndo = true;
+			change.redo(this.view, true);
+			this._ignoreUndo = false;
+			return true;
+		},
+		/**
+		 * Reset the stack to its original state. All changes in the stack are thrown away.
+		 */
+		reset: function() {
+			this.index = this.cleanIndex = 0;
+			this.stack = [];
+			this._undoStart = undefined;
+			this._undoText = "";
+			this._ignoreUndo = false;
+			this._compoundChange = undefined;
+		},
+		/**
+		 * Starts a compound change. 
+		 * <p>
+		 * All changes added to stack from the time startCompoundChange() is called
+		 * to the time that endCompoundChange() is called are compound on one change that can be un-done or re-done
+		 * with one single call to undo() or redo().
+		 * </p>
+		 *
+		 * @see #endCompoundChange
+		 */
+		startCompoundChange: function() {
+			var change = new CompoundChange(this.view.getSelection(), this.view.getCaretOffset());
+			this.add(change);
+			this.compoundChange = change;
+		},
+		_commitUndo: function () {
+			if (this._undoStart !== undefined) {
+				if (this._undoStart < 0) {
+					this.add(new Change(-this._undoStart, "", this._undoText, ""));
+				} else {
+					this.add(new Change(this._undoStart, this._undoText, ""));
+				}
+				this._undoStart = undefined;
+				this._undoText = "";
+			}
+		},
+		_onDestroy: function() {
+			this.view.removeEventListener("ModelChanging", this, this._onModelChanging);
+			this.view.removeEventListener("Destroy", this, this._onDestroy);
+		},
+		_onModelChanging: function(e) {
+			var newText = e.text;
+			var start = e.start;
+			var removedCharCount = e.removedCharCount;
+			var addedCharCount = e.addedCharCount;
+			if (this._ignoreUndo) {
+				return;
+			}
+			if (this._undoStart !== undefined && 
+				!((addedCharCount === 1 && removedCharCount === 0 && start === this._undoStart + this._undoText.length) ||
+					(addedCharCount === 0 && removedCharCount === 1 && (((start + 1) === -this._undoStart) || (start === -this._undoStart)))))
+			{
+				this._commitUndo();
+			}
+			if (!this.compoundChange) {
+				if (addedCharCount === 1 && removedCharCount === 0) {
+					if (this._undoStart === undefined) {
+						this._undoStart = start;
+					}
+					this._undoText = this._undoText + newText;
+					return;
+				} else if (addedCharCount === 0 && removedCharCount === 1) {
+					var deleting = this._undoText.length > 0 && -this._undoStart === start;
+					this._undoStart = -start;
+					if (deleting) {
+						this._undoText = this._undoText + this.view.getText(start, start + removedCharCount);
+					} else {
+						this._undoText = this.view.getText(start, start + removedCharCount) + this._undoText;
+					}
+					return;
+				}
+			}
+			this.add(new Change(start, newText, this.view.getText(start, start + removedCharCount)));
+		}
+	};
+	return UndoStack;
+}());
+
+if (typeof window !== "undefined" && typeof window.define !== "undefined") {
+	define([], function() {
+		return orion.textview;
+	});
+}
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/plugin.xml b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/plugin.xml
new file mode 100644
index 0000000..0b837ee
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/plugin.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.4"?>
+<plugin>
+
+   <extension
+         point="org.eclipse.ui.editors">
+      <editor
+            class="org.eclipse.e4.examples.webintegration.orion.editor.plugin.Editor"
+            default="true"
+            extensions="js"
+            icon="orion-old/images/silk/page.png"
+            id="org.eclipse.e4.examples.webintegration.plugins.editors.orion"
+            name="Embedded Orion Editor">
+      </editor>
+   </extension>
+</plugin>
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/src/org/eclipse/e4/examples/webintegration/orion/editor/plugin/Editor.java b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/src/org/eclipse/e4/examples/webintegration/orion/editor/plugin/Editor.java
new file mode 100644
index 0000000..b55626f
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/src/org/eclipse/e4/examples/webintegration/orion/editor/plugin/Editor.java
@@ -0,0 +1,227 @@
+/*******************************************************************************
+ * Copyright (c) 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Dean Roberts, IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.e4.examples.webintegration.orion.editor.plugin;
+
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.net.URL;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.action.IStatusLineManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.jface.action.StatusLineContributionItem;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.SWTException;
+import org.eclipse.swt.browser.Browser;
+import org.eclipse.swt.browser.BrowserFunction;
+import org.eclipse.swt.browser.LocationEvent;
+import org.eclipse.swt.browser.LocationListener;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorSite;
+import org.eclipse.ui.IFileEditorInput;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.part.EditorPart;
+
+
+/**
+ * Example of hosting an editor in a BrowserWidget while
+ * implementing interesting parts of the Eclipse Workbench 
+ * editor life cycle and properties:
+ *      1) Opening local content
+ *      2) Dirty indicator
+ *      3) Saving
+ *      4) Eclipse Status Bar (cursor position)
+ *      
+ * Probably the most interesting part of this example is how
+ * the web application is implemented to allow the application
+ * to run in either a Browser or the Workbench with maximum integration
+ * using minimal code changes.
+ * 
+ * The particular editor we embed is the Orion editor from http://eclipse.org/orion/
+ * 
+ * This example is discussed at http://deanoneclipse.wordpress.com
+ */
+public class Editor extends EditorPart {
+	private Browser browser;
+	private boolean isDirty = false;
+	private EditorService editorService;
+	private IStatusLineManager statusLineManager;
+	private StatusLineContributionItem position;
+	private StatusLineContributionItem keyMode;
+	private StatusLineContributionItem writeMode;
+
+	// Create the editor widgets
+	public void createPartControl(Composite parent) {
+		// Use the system's default browser
+		browser = new Browser(parent, SWT.NONE);
+		
+		URL resource = Editor.class.getClassLoader().getResource("orion/examples/editor/embeddededitor.html");
+		try {
+			URL resolved = FileLocator.resolve(resource);
+			String qualifiedPath = resolved.toExternalForm();
+			browser.setUrl(qualifiedPath);
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+		
+		// Add a listener to register and unregister browser functions when pages load
+		browser.addLocationListener(getLocationListener());
+		
+		// Create Eclipse status line contributions
+		createStatusLine();
+	}
+	
+	/**
+	 * Register our browser functions when the editor web page is loaded, unregister the browser
+	 * functions when any other page is loaded.
+	 */
+	private LocationListener getLocationListener() {
+		return new LocationListener() {
+			public void changing(LocationEvent event) {
+				// Do nothing before the page loads
+			}
+			
+			public void changed(LocationEvent event) {
+				if (!event.top) return;
+
+				if (event.location.contains("embeddededitor.html")) {
+					// Register browser functions that allow JavaScript to call into the Workbench
+					registerBrowserFunctions();
+				} else {
+					unregisterBrowserFunctions();
+				}
+			}
+		};
+	}
+
+	// Register browser functions that allow JavaScript to call into the Workbench
+	private void registerBrowserFunctions() {
+		editorService = new EditorService(browser, EditorService.EDITOR_SERVICE_HANDLER , this);
+	}
+
+	private void unregisterBrowserFunctions() {
+		if (editorService != null && editorService instanceof BrowserFunction) {
+			editorService.dispose();
+		}
+	}
+	
+	/**
+	 * This method is part of the Eclipse editor save framework and will be called
+	 * by Eclipse when a Save action is invoked (tool bar, menu, Eclipse key binding etc.)
+	 * 
+	 * This implementation will call into the web application requesting a save, since
+	 * only the web application knows what it means to save itself
+	 *  
+	 * IMPORTANT:
+	 * 
+	 * This mechanism is synchronous, while saving in a web world is typically asynchronous.  
+	 * Eclipse has an asynchronous saving mechanism through ISavelable.  This example will
+	 * be updated shortly to use that mechanism.
+	 */
+	public void doSave(IProgressMonitor monitor) {
+			try {
+				Object resultObj = browser.evaluate(EditorService.JAVA_SCRIPT_SAVE_FUNCTION);
+
+				// If the call to the web application returns false, indicating the save failed, cancel the operation
+				if (!(resultObj instanceof Boolean && (Boolean) resultObj)) {
+					monitor.setCanceled(true);
+				}
+			} catch (SWTException e) {
+				// Either the script caused a javascript error or returned an unsupported type
+				e.printStackTrace();
+				monitor.setCanceled(true);
+			}
+	}
+	
+	
+	/**
+	 * This method is called by the web application's editor service to perform the local save.
+	 * @param newContents The new contents to save as a string 
+	 * @return true if the save was successful, false otherwise
+	 */
+	protected boolean performSave(String newContents) {
+		if (getEditorInput() instanceof IFileEditorInput) {
+			IFileEditorInput input = (IFileEditorInput) getEditorInput();
+			ByteArrayInputStream inputStream = new ByteArrayInputStream(newContents.getBytes());
+			try {
+				input.getFile().setContents(inputStream, IFile.KEEP_HISTORY, null);
+				return true;
+			} catch (CoreException e) {
+				// Save failed
+				e.printStackTrace();
+			}
+		}
+		return false;
+	}
+	
+	public boolean isDirty() {
+		return isDirty;
+	}
+	
+	// Set by the web application's editor service when the dirty state changes
+	protected void setDirty(boolean newValue) {
+		if (isDirty != newValue) {
+			isDirty = newValue;
+			firePropertyChange(PROP_DIRTY);
+		}
+	}
+	
+	// Set by the web application's editor service when the cursor position changes
+	protected void setPositionStatus(String text) {
+		position.setText(text);
+	}
+	
+	/**
+	 * Initialize the editor.
+	 */
+	public void init(IEditorSite site, IEditorInput input) throws PartInitException {
+		setSite(site);
+		setInput(input);
+		setPartName(input.getName());
+	}
+
+	public boolean isSaveAsAllowed() {
+		return false;
+	}
+
+	public void doSaveAs() {
+	}
+	
+	private void createStatusLine() {
+		statusLineManager = getEditorSite().getActionBars().getStatusLineManager();
+		position = new StatusLineContributionItem("position", 15);
+		keyMode = new StatusLineContributionItem("keyMode", 15);
+		writeMode = new StatusLineContributionItem("writeMode", 15);
+		statusLineManager.add(writeMode);
+		statusLineManager.add(new Separator());
+		statusLineManager.add(keyMode);
+		statusLineManager.add(new Separator());
+		statusLineManager.add(position);
+		
+		// Set the initial cursor position
+		position.setText("0 : 0");
+
+		// writeMode and keyMode values are faked at the moment since the Orion editor
+		// does not return actual values for these properties.
+		writeMode.setText("Writable");
+		keyMode.setText("Smart Insert");
+	}
+	
+	public void setFocus() {
+		// Nothing to do but contractually obligated to override this abstract method.
+	}
+}
diff --git a/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/src/org/eclipse/e4/examples/webintegration/orion/editor/plugin/EditorService.java b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/src/org/eclipse/e4/examples/webintegration/orion/editor/plugin/EditorService.java
new file mode 100644
index 0000000..a107798
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration.orion.editor.plugin/src/org/eclipse/e4/examples/webintegration/orion/editor/plugin/EditorService.java
@@ -0,0 +1,213 @@
+/*******************************************************************************
+ * Copyright (c) 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Dean Roberts, IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.e4.examples.webintegration.orion.editor.plugin;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.core.resources.IStorage;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.swt.browser.Browser;
+import org.eclipse.swt.browser.BrowserFunction;
+import org.eclipse.ui.IFileEditorInput;
+import org.eclipse.ui.IStorageEditorInput;
+
+/**
+ * This class implements the call in point for a web application.
+ * Through this single BrowserFunction a web application can request
+ * various actions.
+ * 
+ * An alternate implementation could have a a single BrowserFunction subclass
+ * for each action a web application could call.  But it was unclear that
+ * such an implementation adds any value. 
+ */
+public class EditorService extends BrowserFunction {
+	// Action constants.  Same values used by JavaScript
+	private static final int DIRTY_CHANGED = 1;
+	private static final int GET_CONTENT_NAME = 2;
+	private static final int GET_INITIAL_CONTENT = 3;
+	private static final int SAVE = 4;
+	private static final int STATUS_CHANGED = 5;
+
+	// Name of the JavaScript variable containing the editor service
+	public static final String EDITOR_SERVICE_MAP = "editorService";
+	
+	// Name of the JavaScript function for the editor service handler
+	public static final String EDITOR_SERVICE_HANDLER = "editorServiceHandler";
+	
+	// Name of JavaScript save function
+	public static final String JAVA_SCRIPT_SAVE_FUNCTION = "return " + EDITOR_SERVICE_MAP + ".save()";
+	
+	Editor editor;
+	
+	public EditorService(Browser browser, String name, Editor editor) {
+		super(browser, name);
+		this.editor = editor;
+	}
+	
+	/**
+	 * This is the single function that is invoked by the JavaScript program.
+	 * By specification of our implementation there is always one or more arguments
+	 * and the first argument is the action id.
+	 * Subsequent arguments, if present, are arguments for the given action.
+	 */
+	public Object function(Object[] arguments) {
+		super.function(arguments);
+		
+		if (arguments.length == 0 || !(arguments[0] instanceof Double)) {
+			return null;
+		}
+
+		int action = ((Double) arguments[0]).intValue();
+		switch (action) {
+			case DIRTY_CHANGED:
+				return doDirtyChanged(arguments);
+				
+			case GET_CONTENT_NAME:
+				return doGetContentName(arguments);
+
+			case GET_INITIAL_CONTENT:
+				return doGetInitialContent(arguments);
+
+			case SAVE:
+				return doSave(arguments);
+				
+			case STATUS_CHANGED:
+				return doStatusChanged(arguments);
+				
+			default:
+				return null;
+		}
+	}
+
+	// Actions
+
+	/**
+	 * Return the initial content for the editor.  Return an empty string on any error
+	 */
+	private Object doGetInitialContent(Object[] arguments) {
+		if (editor.getEditorInput() instanceof IStorageEditorInput) {
+			IStorageEditorInput input = (IStorageEditorInput) editor.getEditorInput();
+
+			BufferedInputStream inputStream = null;
+			
+			
+			try {
+				IStorage storage = input.getStorage();
+				inputStream = new BufferedInputStream(storage.getContents());
+				String contents = readInputStream(inputStream);
+				return contents;
+			} catch (CoreException e) {
+				e.printStackTrace();
+			} catch (IOException e) {
+				e.printStackTrace();
+			} finally {
+				if (inputStream != null) {
+					try {
+						inputStream.close();
+					} catch (IOException e) {
+						e.printStackTrace();
+					}
+				}
+			}
+		}
+		return "";
+	}
+	
+	/**
+	 * Return the name of the file being edited.  Return an empty string on any error
+	 */
+	private Object doGetContentName(Object[] arguments) {
+		if (editor.getEditorInput() instanceof IFileEditorInput) {
+			IFileEditorInput input = (IFileEditorInput) editor.getEditorInput();
+			return input.getName();
+		}
+		return "";
+	}
+
+	/**
+	 * This method is called by the JavaScript editor whenever its dirty state changes.
+	 * Set the dirty status of the Eclipse editor so the framework displays the
+	 * appropriate dirty marker and save actions are enabled appropriately.
+	 * 
+	 * Return the dirtyState after the event is processed
+	 */
+
+	private Object doDirtyChanged(Object[] arguments) {
+		if (arguments.length == 2 && (arguments[1] instanceof Boolean)) {
+			editor.setDirty((Boolean) arguments[1]);
+		}
+		return editor.isDirty();
+	}
+	
+	/**
+	 * JavaScript has requested a save.  Call the Eclipse editors save method.
+	 * By contract, the JavaScript passes the new contents as an argument. 
+	 * @param arguments
+	 * @return
+	 */
+	private Object doSave(Object[] arguments) {
+		boolean result = false;
+		if (arguments.length == 2 && (arguments[1] instanceof String)) {
+			String newContents = (String) arguments[1];
+			result = editor.performSave(newContents);
+		}
+		return result;
+	}
+	
+	/**
+	 * Update Eclipse status line.
+	 * Currently we dig the cursor position out of the string that the Orion editor sends us.
+	 * Clearly we would prefer Orion API that could just send us the position info ... but this is, after all,
+	 * a web integration example and not an Orion example :-)
+	 */
+	private boolean doStatusChanged(Object[] arguments) {
+		if (arguments.length != 2 || !(arguments[1] instanceof String)) {
+			return false;
+		}
+		
+		String[] position = parsePosition((String) arguments[1]);
+		editor.setPositionStatus(position[0] + " : " + position[1]);
+		return true;
+	}
+	
+	// Boring utility methods
+
+	// Read all the bytes from an InputStream and return them as a String
+	private String readInputStream(InputStream inputStream) throws IOException {
+		ByteArrayOutputStream buffer = null;
+		buffer = new ByteArrayOutputStream();
+		byte[] bytes = new byte[1024];
+		int bytesRead = 0;
+		while ((bytesRead = inputStream.read(bytes)) != -1) {
+			buffer.write(bytes, 0, bytesRead);
+		}
+		return buffer.toString();
+	}
+	
+	// Really sleazy "parsing" code for "Line x : Col y".  Lots of error cases ignored
+	private String[] parsePosition(String message) {
+		int start = message.indexOf("Line ") + "Line ".length();
+		int end = message.indexOf(' ', start);
+		String line = message.substring(start, end);
+		
+		start = message.indexOf("Col ") + "Col ".length();
+		end = message.indexOf(' ', start);
+		if (end == -1) {
+			end = message.length();
+		}
+		
+		String col = message.substring(start, end);
+		return new String[] {line, col};
+	}
+}
diff --git a/examples/org.eclipse.e4.examples.webintegration/.classpath b/examples/org.eclipse.e4.examples.webintegration/.classpath
new file mode 100755
index 0000000..2d1a430
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<classpath>

+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>

+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>

+	<classpathentry kind="src" path="src"/>

+	<classpathentry kind="output" path="bin"/>

+</classpath>

diff --git a/examples/org.eclipse.e4.examples.webintegration/.project b/examples/org.eclipse.e4.examples.webintegration/.project
new file mode 100755
index 0000000..4de1f9c
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.e4.examples.webintegration</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/examples/org.eclipse.e4.examples.webintegration/.settings/org.eclipse.jdt.core.prefs b/examples/org.eclipse.e4.examples.webintegration/.settings/org.eclipse.jdt.core.prefs
new file mode 100755
index 0000000..d91c5b6
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,8 @@
+#Tue Apr 05 13:17:59 EDT 2011

+eclipse.preferences.version=1

+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled

+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5

+org.eclipse.jdt.core.compiler.compliance=1.5

+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error

+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error

+org.eclipse.jdt.core.compiler.source=1.5

diff --git a/examples/org.eclipse.e4.examples.webintegration/META-INF/MANIFEST.MF b/examples/org.eclipse.e4.examples.webintegration/META-INF/MANIFEST.MF
new file mode 100755
index 0000000..d624fa2
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration/META-INF/MANIFEST.MF
@@ -0,0 +1,10 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Web Application <-> Workbench Interactions
+Bundle-SymbolicName: org.eclipse.e4.examples.webintegration;singleton:=true
+Bundle-Version: 0.9.0.qualifier
+Require-Bundle: org.eclipse.ui,
+ org.eclipse.core.runtime
+Bundle-RequiredExecutionEnvironment: J2SE-1.5
+Bundle-ActivationPolicy: lazy
+Bundle-Vendor: Eclipse.org
diff --git a/examples/org.eclipse.e4.examples.webintegration/Web UI Integration Examples.launch b/examples/org.eclipse.e4.examples.webintegration/Web UI Integration Examples.launch
new file mode 100644
index 0000000..7294cac
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration/Web UI Integration Examples.launch
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.pde.ui.RuntimeWorkbench">
+<booleanAttribute key="append.args" value="true"/>
+<stringAttribute key="application" value="WebAppExamples.application"/>
+<booleanAttribute key="askclear" value="true"/>
+<booleanAttribute key="automaticAdd" value="false"/>
+<booleanAttribute key="automaticValidate" value="false"/>
+<stringAttribute key="bootstrap" value=""/>
+<stringAttribute key="checked" value="[NONE]"/>
+<booleanAttribute key="clearConfig" value="false"/>
+<booleanAttribute key="clearws" value="false"/>
+<booleanAttribute key="clearwslog" value="false"/>
+<stringAttribute key="configLocation" value="${workspace_loc}/.metadata/.plugins/org.eclipse.pde.core/Web UI Integration Examples"/>
+<booleanAttribute key="default" value="false"/>
+<booleanAttribute key="includeOptional" value="false"/>
+<stringAttribute key="location" value="${workspace_loc}/../runtime-WebUIIntegrationExamples"/>
+<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog"/>
+<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xms40m -Xmx384m"/>
+<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${workspace_loc:org.eclipse.e4.examples.webintegration}"/>
+<booleanAttribute key="pde.generated.config" value="false"/>
+<stringAttribute key="pde.version" value="3.3"/>
+<stringAttribute key="product" value="EarlyUpdateTest.product"/>
+<stringAttribute key="selected_target_plugins" value="com.ibm.icu@default:default,javax.servlet@default:default,org.eclipse.core.commands@default:default,org.eclipse.core.contenttype@default:default,org.eclipse.core.databinding.observable@default:default,org.eclipse.core.databinding.property@default:default,org.eclipse.core.databinding@default:default,org.eclipse.core.expressions@default:default,org.eclipse.core.filesystem@default:default,org.eclipse.core.jobs@default:default,org.eclipse.core.runtime@default:true,org.eclipse.core.variables@default:default,org.eclipse.equinox.app@default:default,org.eclipse.equinox.common@2:true,org.eclipse.equinox.preferences@default:default,org.eclipse.equinox.registry@default:default,org.eclipse.equinox.security@default:default,org.eclipse.help@default:default,org.eclipse.jface.databinding@default:default,org.eclipse.jface@default:default,org.eclipse.osgi.services@default:default,org.eclipse.osgi@-1:true,org.eclipse.swt.cocoa.macosx.x86_64@default:default,org.eclipse.swt@default:default,org.eclipse.ui.workbench@default:default,org.eclipse.ui@default:default"/>
+<stringAttribute key="selected_workspace_plugins" value="WebAppExamples@default:default"/>
+<booleanAttribute key="show_selected_only" value="false"/>
+<stringAttribute key="timestamp" value="1303824967114"/>
+<booleanAttribute key="tracing" value="false"/>
+<booleanAttribute key="useCustomFeatures" value="false"/>
+<booleanAttribute key="useDefaultConfig" value="true"/>
+<booleanAttribute key="useDefaultConfigArea" value="true"/>
+<booleanAttribute key="useProduct" value="false"/>
+<booleanAttribute key="usefeatures" value="false"/>
+</launchConfiguration>
diff --git a/examples/org.eclipse.e4.examples.webintegration/build.properties b/examples/org.eclipse.e4.examples.webintegration/build.properties
new file mode 100755
index 0000000..e4a04bb
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration/build.properties
@@ -0,0 +1,6 @@
+source.. = src/

+output.. = bin/

+bin.includes = plugin.xml,\

+               META-INF/,\

+               .,\

+               static/

diff --git a/examples/org.eclipse.e4.examples.webintegration/plugin.xml b/examples/org.eclipse.e4.examples.webintegration/plugin.xml
new file mode 100755
index 0000000..e5d254a
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration/plugin.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<?eclipse version="3.4"?>

+<plugin>

+

+	<extension

+		id="application"

+		point="org.eclipse.core.runtime.applications">

+		<application>

+			<run

+				class="org.eclipse.e4.examples.webintegration.application.Application">

+			</run>

+		</application>

+	</extension>

+   

+	<!-- Add Perspectives.  One for each example -->

+	<extension point="org.eclipse.ui.perspectives">

+		<perspective

+			name="Link Intercept"

+			class="org.eclipse.e4.examples.webintegration.application.Perspective"

+			id="WebUI.link.intercept.example.perspective">

+		</perspective>

+   </extension>

+   

+	<extension

+	point="org.eclipse.ui.views">

+      

+	<!-- Begin Link Intercept Views -->

+	<!-- View with browser -->

+	<view

+		class="org.eclipse.e4.examples.webintegration.links.BrowserView"

+		id="WebUI.link.intercept.browser.view"

+		name="Link Intercept"

+		restorable="true">

+	</view>

+

+	<!-- View invoked by intercepted link -->

+	<view

+		class="org.eclipse.e4.examples.webintegration.links.LinkView"

+		id="url.link.1"

+		name="Link View"

+		restorable="true">

+	</view>

+	<!-- End Link Intercept Views -->

+

+	<!-- View for displaying instructions about the example -->	

+	<view

+		class="org.eclipse.e4.examples.webintegration.application.InstructionsView"

+		id="WebUI.instructions.view"

+		name="Instructions"

+		restorable="true">

+	</view>

+   </extension>

+   

+   <extension point="org.eclipse.ui.perspectiveExtensions">

+   

+	<!-- Add all perspectives to main part of perspective shortcut menu -->

+	<perspectiveExtension targetID="*">

+		<perspectiveShortcut id="WebUI.link.intercept.example.perspective"/>

+	</perspectiveExtension>

+      

+    <!-- Link Intercept example perspective definition !-->

+    <perspectiveExtension targetID="WebUI.link.intercept.example.perspective">

+		 <view

+		       id="WebUI.link.intercept.browser.view"

+		       minimized="false"

+		       ratio="0.10"

+		       relationship="top"

+		       relative="org.eclipse.ui.editorss">

+		 </view>

+		 

+		 <view

+			id="WebUI.instructions.view"

+			minimized="false"

+			ratio="0.60"

+            relationship="bottom"

+            relative="WebUI.link.intercept.browser.view">

+        </view>

+      </perspectiveExtension>

+   </extension>

+</plugin>

diff --git a/examples/org.eclipse.e4.examples.webintegration/src/org/eclipse/e4/examples/webintegration/application/Application.java b/examples/org.eclipse.e4.examples.webintegration/src/org/eclipse/e4/examples/webintegration/application/Application.java
new file mode 100755
index 0000000..b23b497
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration/src/org/eclipse/e4/examples/webintegration/application/Application.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Dean Roberts, IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.e4.examples.webintegration.application;
+
+import org.eclipse.equinox.app.IApplication;
+import org.eclipse.equinox.app.IApplicationContext;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.PlatformUI;
+
+/**
+ * This is boiler plate code created by the New Plugin Wizard and does
+ * not have anything interesting in here in relation to the WebUI Integration examples
+ * 
+ * This class controls all aspects of the application's execution
+ */
+public class Application implements IApplication {
+
+	/* (non-Javadoc)
+	 * @see org.eclipse.equinox.app.IApplication#start(org.eclipse.equinox.app.IApplicationContext)
+	 */
+	public Object start(IApplicationContext context) throws Exception {
+		Display display = PlatformUI.createDisplay();
+		try {
+			int returnCode = PlatformUI.createAndRunWorkbench(display, new ApplicationWorkbenchAdvisor());
+			if (returnCode == PlatformUI.RETURN_RESTART)
+				return IApplication.EXIT_RESTART;
+			else
+				return IApplication.EXIT_OK;
+		} finally {
+			display.dispose();
+		}
+		
+	}
+
+	/* (non-Javadoc)
+	 * @see org.eclipse.equinox.app.IApplication#stop()
+	 */
+	public void stop() {
+		if (!PlatformUI.isWorkbenchRunning())
+			return;
+		final IWorkbench workbench = PlatformUI.getWorkbench();
+		final Display display = workbench.getDisplay();
+		display.syncExec(new Runnable() {
+			public void run() {
+				if (!display.isDisposed())
+					workbench.close();
+			}
+		});
+	}
+}
diff --git a/examples/org.eclipse.e4.examples.webintegration/src/org/eclipse/e4/examples/webintegration/application/ApplicationActionBarAdvisor.java b/examples/org.eclipse.e4.examples.webintegration/src/org/eclipse/e4/examples/webintegration/application/ApplicationActionBarAdvisor.java
new file mode 100755
index 0000000..d3bb20d
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration/src/org/eclipse/e4/examples/webintegration/application/ApplicationActionBarAdvisor.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Dean Roberts, IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.e4.examples.webintegration.application;
+
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.application.ActionBarAdvisor;
+import org.eclipse.ui.application.IActionBarConfigurer;
+
+/**
+ * This is boiler plate code created by the New Plugin Wizard and does
+ * not have anything interesting in here in relation to the WebUI Integration examples
+ */
+public class ApplicationActionBarAdvisor extends ActionBarAdvisor {
+
+    public ApplicationActionBarAdvisor(IActionBarConfigurer configurer) {
+        super(configurer);
+    }
+
+    protected void makeActions(IWorkbenchWindow window) {
+    }
+
+    protected void fillMenuBar(IMenuManager menuBar) {
+    }
+    
+}
diff --git a/examples/org.eclipse.e4.examples.webintegration/src/org/eclipse/e4/examples/webintegration/application/ApplicationWorkbenchAdvisor.java b/examples/org.eclipse.e4.examples.webintegration/src/org/eclipse/e4/examples/webintegration/application/ApplicationWorkbenchAdvisor.java
new file mode 100755
index 0000000..d3b9bf8
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration/src/org/eclipse/e4/examples/webintegration/application/ApplicationWorkbenchAdvisor.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Dean Roberts, IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.e4.examples.webintegration.application;
+
+import org.eclipse.ui.application.IWorkbenchWindowConfigurer;
+import org.eclipse.ui.application.WorkbenchAdvisor;
+import org.eclipse.ui.application.WorkbenchWindowAdvisor;
+
+/**
+ * This is boiler plate code created by the New Plug-in Wizard and does
+ * not have anything interesting in here in relation to the WebUI Integration examples
+ */
+public class ApplicationWorkbenchAdvisor extends WorkbenchAdvisor {
+
+	private static final String PERSPECTIVE_ID = "WebUI.link.intercept.example.perspective"; //$NON-NLS-1$
+
+    public WorkbenchWindowAdvisor createWorkbenchWindowAdvisor(IWorkbenchWindowConfigurer configurer) {
+        return new ApplicationWorkbenchWindowAdvisor(configurer);
+    }
+
+	public String getInitialWindowPerspectiveId() {
+		return PERSPECTIVE_ID;
+	}
+}
diff --git a/examples/org.eclipse.e4.examples.webintegration/src/org/eclipse/e4/examples/webintegration/application/ApplicationWorkbenchWindowAdvisor.java b/examples/org.eclipse.e4.examples.webintegration/src/org/eclipse/e4/examples/webintegration/application/ApplicationWorkbenchWindowAdvisor.java
new file mode 100755
index 0000000..a3f67fe
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration/src/org/eclipse/e4/examples/webintegration/application/ApplicationWorkbenchWindowAdvisor.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Dean Roberts, IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.e4.examples.webintegration.application;
+
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.ui.application.ActionBarAdvisor;
+import org.eclipse.ui.application.IActionBarConfigurer;
+import org.eclipse.ui.application.IWorkbenchWindowConfigurer;
+import org.eclipse.ui.application.WorkbenchWindowAdvisor;
+
+/**
+ * This is boiler plate code created by the New Plugin Wizard and does
+ * not have anything interesting in here in relation to the WebUI Integration examples
+ */
+public class ApplicationWorkbenchWindowAdvisor extends WorkbenchWindowAdvisor {
+
+    public ApplicationWorkbenchWindowAdvisor(IWorkbenchWindowConfigurer configurer) {
+        super(configurer);
+    }
+
+    public ActionBarAdvisor createActionBarAdvisor(IActionBarConfigurer configurer) {
+        return new ApplicationActionBarAdvisor(configurer);
+    }
+    
+    public void preWindowOpen() {
+        IWorkbenchWindowConfigurer configurer = getWindowConfigurer();
+        configurer.setInitialSize(new Point(800, 1000));
+        configurer.setShowCoolBar(false);
+        configurer.setShowStatusLine(false);
+        configurer.setTitle("Web Application <-> Workbench Interactions"); //$NON-NLS-1$
+        configurer.setShowPerspectiveBar(true);
+        configurer.setShowStatusLine(true);
+    }
+}
diff --git a/examples/org.eclipse.e4.examples.webintegration/src/org/eclipse/e4/examples/webintegration/application/InstructionsView.java b/examples/org.eclipse.e4.examples.webintegration/src/org/eclipse/e4/examples/webintegration/application/InstructionsView.java
new file mode 100755
index 0000000..ba65651
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration/src/org/eclipse/e4/examples/webintegration/application/InstructionsView.java
@@ -0,0 +1,49 @@
+/*******************************************************************************

+ * Copyright (c) 2011 IBM Corporation and others.

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License v1.0

+ * which accompanies this distribution, and is available at

+ * http://www.eclipse.org/legal/epl-v10.html

+ *

+ * Contributors:

+ *     Dean Roberts, IBM Corporation - initial API and implementation

+ *******************************************************************************/

+

+package org.eclipse.e4.examples.webintegration.application;

+

+import java.io.File;

+import java.net.MalformedURLException;

+

+import org.eclipse.swt.SWT;

+import org.eclipse.swt.browser.Browser;

+import org.eclipse.swt.widgets.Composite;

+import org.eclipse.ui.IPerspectiveDescriptor;

+import org.eclipse.ui.part.ViewPart;

+

+/**

+ * This view provides an area for displaying instructions germane 

+ * to each example.  The view itself is not interesting in regards

+ * to integrating WebUIs with an Eclipse Workbench 

+ */

+public class InstructionsView extends ViewPart {

+

+	private Browser browser;

+	

+	public void createPartControl(Composite parent) {

+		IPerspectiveDescriptor descriptor = getSite().getPage().getPerspective();

+		String instructionLocation = Perspective.getInstructionLocation(descriptor.getLabel());

+		browser = new Browser(parent, SWT.NONE);

+

+		String qualifiedPath = "";

+		try {

+			qualifiedPath = new File(instructionLocation).toURL().toExternalForm();

+			browser.setUrl(qualifiedPath);

+		} catch (MalformedURLException e) {

+			e.printStackTrace();

+		}

+	}

+

+	public void setFocus() {

+		// Not used in this example

+	}

+}

diff --git a/examples/org.eclipse.e4.examples.webintegration/src/org/eclipse/e4/examples/webintegration/application/Perspective.java b/examples/org.eclipse.e4.examples.webintegration/src/org/eclipse/e4/examples/webintegration/application/Perspective.java
new file mode 100755
index 0000000..7c8d05e
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration/src/org/eclipse/e4/examples/webintegration/application/Perspective.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Dean Roberts, IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.e4.examples.webintegration.application;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.ui.IPageLayout;
+import org.eclipse.ui.IPerspectiveFactory;
+
+/**
+ * This Perspective is part of an example project that contains a set of simple examples
+ * for accomplishing better integration between WebUIs and the Eclipse Workbench.
+ * 
+ * Each example topic will have its own instance of this Perspective.  The implementation will
+ * be in its one example.* package.  Look there for the interesting implementation details.
+ * 
+ * The code contained in this class is generic and is not particularly illustrative of the
+ * examples themselves.
+ */
+public class Perspective implements IPerspectiveFactory {
+
+	// Some public URLs that may be interesting for the various examples
+	public static final String gmailURL = "https://mail.google.com/mail/?hl=en&shva=1#inbox";
+	public static final String pcFinancialURL = "https://www.txn.banking.pcfinancial.ca/a/banking/accounts/accountSummary.ams";
+	public static final String pcFinancialURL2 = "https://www.pcfinancial.ca/";
+	public static final String yahooURL = "https://mail.yahoo.com";
+	private static final Map<String, String> instructionURLMap = new HashMap<String, String>();
+	
+	// Initialize the instructions for each example
+	{
+		instructionURLMap.put("default", "static/default.html");
+		instructionURLMap.put("Link Intercept", "static/link.intercept.example.instructions.html");
+	}
+	
+	public void createInitialLayout(IPageLayout layout) {
+		layout.setEditorAreaVisible(false);
+	}
+
+	// Return the appropriate instructions for each example
+	public static String getInstructionLocation(String label) {
+		
+		String result = instructionURLMap.get(label);
+		if (result == null) {
+			result = instructionURLMap.get("default");
+		}
+		return result;
+	}
+}
diff --git a/examples/org.eclipse.e4.examples.webintegration/src/org/eclipse/e4/examples/webintegration/links/BrowserView.java b/examples/org.eclipse.e4.examples.webintegration/src/org/eclipse/e4/examples/webintegration/links/BrowserView.java
new file mode 100755
index 0000000..b29aada
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration/src/org/eclipse/e4/examples/webintegration/links/BrowserView.java
@@ -0,0 +1,106 @@
+/*******************************************************************************

+ * Copyright (c) 2011 IBM Corporation and others.

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License v1.0

+ * which accompanies this distribution, and is available at

+ * http://www.eclipse.org/legal/epl-v10.html

+ *

+ * Contributors:

+ *     Dean Roberts, IBM Corporation - initial API and implementation

+ *******************************************************************************/

+

+package org.eclipse.e4.examples.webintegration.links;

+

+

+import org.eclipse.e4.examples.webintegration.application.Perspective;

+import org.eclipse.swt.SWT;

+import org.eclipse.swt.browser.Browser;

+import org.eclipse.swt.browser.LocationEvent;

+import org.eclipse.swt.browser.LocationListener;

+import org.eclipse.swt.widgets.Composite;

+import org.eclipse.ui.IViewPart;

+import org.eclipse.ui.PartInitException;

+import org.eclipse.ui.PlatformUI;

+import org.eclipse.ui.actions.NewWizardAction;

+import org.eclipse.ui.part.ViewPart;

+

+

+/**

+ * Example of a BrowserWidget that can incercept links and perform Eclipse Workbench

+ * actions as desired.

+ * 

+ * This example is discussed at http://deanoneclipse.wordpress.com

+ */

+public class BrowserView extends ViewPart {

+

+	private Browser browser;

+

+	public void createPartControl(Composite parent) {

+		browser = new Browser(parent, SWT.NONE);

+		browser.setUrl(Perspective.gmailURL);

+		

+		// Hooks the link intercept code

+		browser.addLocationListener(new LinkInterceptListener());

+	}

+

+	/**

+	 * Implement a LocationListener to intercept links and decide what to do.

+	 */

+	private class LinkInterceptListener implements LocationListener {

+		// method called when the user clicks a link but before the link is opened.

+		public void changing(LocationEvent event) {

+			try {

+				// Call user code to process link as desired and return

+				// true if the link should be opened in place.

+				boolean shouldOpenLinkInPlace = !openView(event.location);

+				

+				// Setting event.doit to false prevents the link from opening in place

+				event.doit = shouldOpenLinkInPlace;

+			} catch (PartInitException e) {

+				e.printStackTrace();

+			}

+		}

+		

+		// method called after the link has been opened in place.

+		public void changed(LocationEvent event) {

+			// Not used in this example

+		}

+	}

+

+	/**

+	 * User code:

+	 * 

+	 * Examine the link and determine if we wish to intercept it.  Perform appropriate actions for intercepted links, do

+	 * nothing for links we want to be opened in place (default behaviour)

+	 * 

+	 * Return true if we intercepted the link.  Return false if we did not intercept the link and expect the browser to

+	 * open the link in place.

+	 */

+	private boolean openView(String location) throws PartInitException {

+		

+		/**

+		 * Certainly the if/else-if construct could be replaced with a more elegant lookup mechanism. 

+		 */

+		

+		// Open a view

+		if (location.equals("http://www.google.com/intl/en_CA/mobile/mail/#utm_source=en_CA-cpp-g4mc-gmhp&utm_medium=cpp&utm_campaign=en_CA")) {

+			IViewPart newView = getViewSite().getPage().showView("url.link.1");

+			((LinkView) newView).setURL(location);

+			

+			return true;

+		// Open a wizard

+		} else if (location.contains("/accounts/recovery")) {

+			NewWizardAction action = new NewWizardAction(PlatformUI.getWorkbench().getActiveWorkbenchWindow());

+			action.run();

+			

+			return true;

+		}

+		

+		// Do not intercept link.  Allow browser widget to open link in place

+		return false;

+	}

+

+	public void setFocus() {

+		// Not important for our example.

+	}

+}
\ No newline at end of file
diff --git a/examples/org.eclipse.e4.examples.webintegration/src/org/eclipse/e4/examples/webintegration/links/LinkView.java b/examples/org.eclipse.e4.examples.webintegration/src/org/eclipse/e4/examples/webintegration/links/LinkView.java
new file mode 100755
index 0000000..65f682d
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration/src/org/eclipse/e4/examples/webintegration/links/LinkView.java
@@ -0,0 +1,37 @@
+/*******************************************************************************

+ * Copyright (c) 2011 IBM Corporation and others.

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License v1.0

+ * which accompanies this distribution, and is available at

+ * http://www.eclipse.org/legal/epl-v10.html

+ *

+ * Contributors:

+ *     Dean Roberts, IBM Corporation - initial API and implementation

+ *******************************************************************************/

+

+package org.eclipse.e4.examples.webintegration.links;

+

+import org.eclipse.swt.SWT;

+import org.eclipse.swt.browser.Browser;

+import org.eclipse.swt.widgets.Composite;

+import org.eclipse.ui.part.ViewPart;

+

+/**

+ * A simple view to be opened by BrowserView when certain links are intercepted

+ */

+public class LinkView extends ViewPart {

+

+	private Browser browser;

+

+	public void createPartControl(Composite parent) {

+		browser = new Browser(parent, SWT.NONE);

+	}

+

+	public void setFocus() {

+	}

+

+	public void setURL(String location) {

+		browser.setUrl(location);

+	}

+

+}

diff --git a/examples/org.eclipse.e4.examples.webintegration/static/default.html b/examples/org.eclipse.e4.examples.webintegration/static/default.html
new file mode 100755
index 0000000..148d979
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration/static/default.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

+<html>

+<head>

+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">

+<title>Default Instructions</title>

+<link rel="stylesheet" type="text/css" href="style.css" />

+</head>

+<body>

+<h1>

+The example implementor should have provided a description here.

+</h1>

+</body>

+</html>
\ No newline at end of file
diff --git a/examples/org.eclipse.e4.examples.webintegration/static/link.intercept.example.instructions.html b/examples/org.eclipse.e4.examples.webintegration/static/link.intercept.example.instructions.html
new file mode 100755
index 0000000..3cbbe80
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration/static/link.intercept.example.instructions.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

+<html>

+<head>

+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">

+<title>Link Intercept Example Instructions</title>

+<link rel="stylesheet" type="text/css" href="style.css" />

+</head>

+<body>

+<h1>

+Description

+</h1>

+<p>

+This example demonstrates intercepting HTML links.  Once intercepted the view implementor can decide which actions to take.  Opening an Eclipse view or wizard for example.

+</p>

+<p>

+While this implementation does not require any changes to the HTML, care should be taken to define a consistent naming pattern for links to aid in the link to action mapping in the workbench code.

+</p>

+<h1>

+What to See

+</h1>

+<p>

+Various links in the browser window above do special things.

+</p>

+<ul>

+<li><a href="#null">Can't access your account?</a> opens a <b>wizard</b>. 

+<li><a href="#null">Learn more</a> opens a new <b>view</b>.

+<li>All other links open in the target browser.  You can use the back action from the context menu to return from one of these.

+</ul>

+</body>

+</html>
\ No newline at end of file
diff --git a/examples/org.eclipse.e4.examples.webintegration/static/style.css b/examples/org.eclipse.e4.examples.webintegration/static/style.css
new file mode 100755
index 0000000..118e1bc
--- /dev/null
+++ b/examples/org.eclipse.e4.examples.webintegration/static/style.css
@@ -0,0 +1,13 @@
+body

+{

+	font-family:Arial,Helvetica,sans-serif;

+}

+

+p 

+{

+	margin-left:20px;

+	font-size:0.875em;

+}

+

+h1 {font-size:1.875em;} /* 40px/16=2.5em */

+ul {font-size:0.875em;font-family:Arial,Helvetica,sans-serif;}
\ No newline at end of file