adding proposal choices to templates; fix undo with linked mode active
diff --git a/bundles/org.eclipse.orion.client.editor/web/examples/editor/embeddededitor.js b/bundles/org.eclipse.orion.client.editor/web/examples/editor/embeddededitor.js
index 4bd6aa7..67cc001 100644
--- a/bundles/org.eclipse.orion.client.editor/web/examples/editor/embeddededitor.js
+++ b/bundles/org.eclipse.orion.client.editor/web/examples/editor/embeddededitor.js
@@ -15,71 +15,12 @@
 /*jslint browser:true devel:true*/
 
 define([
-	"require", 
-	"orion/editor/textView",
-	"orion/keyBinding",
-	"examples/editor/textStyler",
-	"orion/editor/textMateStyler",
-	"orion/editor/htmlGrammar",
-	"orion/editor/editor",
-	"orion/editor/editorFeatures",
-	"orion/editor/contentAssist",
-	"orion/editor/jsTemplateContentAssist",
-	"orion/editor/cssContentAssist"],
-
-function(require, mTextView, mKeyBinding, mTextStyler, mTextMateStyler, mHtmlGrammar, mEditor, mEditorFeatures, mContentAssist, mJSTemplateContentAssist, mCSSContentAssist){
+	"orion/editor/edit",
+	"orion/keyBinding"
+],
+function(edit, mKeyBinding){
 	
-	var editorDomNode = document.getElementById("editor");
-	
-	var textViewFactory = function() {
-		return new mTextView.TextView({
-			parent: editorDomNode,
-			tabSize: 4
-		});
-	};
-
-	var contentAssist;
-	var contentAssistFactory = {
-		createContentAssistMode: function(editor) {
-			contentAssist = new mContentAssist.ContentAssist(editor.getTextView());
-			var contentAssistWidget = new mContentAssist.ContentAssistWidget(contentAssist);
-			return new mContentAssist.ContentAssistMode(contentAssist, contentAssistWidget);
-		}
-	};
-	var cssContentAssistProvider = new mCSSContentAssist.CssContentAssistProvider();
-	var jsTemplateContentAssistProvider = new mJSTemplateContentAssist.JSTemplateContentAssistProvider();
-	
-	// Canned highlighters for js, java, and css. Grammar-based highlighter for html
-	var syntaxHighlighter = {
-		styler: null, 
-		
-		highlight: function(fileName, editor) {
-			if (this.styler) {
-				this.styler.destroy();
-				this.styler = null;
-			}
-			if (fileName) {
-				var splits = fileName.split(".");
-				var extension = splits.pop().toLowerCase();
-				var textView = editor.getTextView();
-				var annotationModel = editor.getAnnotationModel();
-				if (splits.length > 0) {
-					switch(extension) {
-						case "js":
-						case "java":
-						case "css":
-							this.styler = new mTextStyler.TextStyler(textView, extension, annotationModel);
-							break;
-						case "html":
-							this.styler = new mTextMateStyler.TextMateStyler(textView, new mHtmlGrammar.HtmlGrammar());
-							break;
-					}
-				}
-			}
-		}
-	};
-	
-	var annotationFactory = new mEditorFeatures.AnnotationFactory();
+	var editorDomNode = document.getElementById("editor"); //$NON-NLS-0$
 
 	function save(editor) {
 		editor.setInput(null, null, null, true);
@@ -87,80 +28,42 @@
 			window.alert("Save hook.");
 		}, 0);
 	}
-	
-	var keyBindingFactory = function(editor, keyModeStack, undoStack, contentAssist) {
-		
-		// Create keybindings for generic editing
-		var genericBindings = new mEditorFeatures.TextActions(editor, undoStack);
-		keyModeStack.push(genericBindings);
-		
-		// Linked Mode
-		var linkedMode = new mEditorFeatures.LinkedMode(editor, undoStack);
-		keyModeStack.push(linkedMode);
-		
-		// create keybindings for source editing
-		var codeBindings = new mEditorFeatures.SourceCodeActions(editor, undoStack, contentAssist, linkedMode);
-		keyModeStack.push(codeBindings);
-		
-		// save binding
-		editor.getTextView().setKeyBinding(new mKeyBinding.KeyBinding("s", true), "save");
-		editor.getTextView().setAction("save", function(){
-				save(editor);
-				return true;
-		});
-		
-		// speaking of save...
-		document.getElementById("save").onclick = function() {save(editor);};
 
-	};
-		
-	var dirtyIndicator = "";
 	var status = "";
-	
+	var dirtyIndicator = "";
 	var statusReporter = function(message, isError) {
 		if (isError) {
 			status =  "ERROR: " + message;
 		} else {
 			status = message;
 		}
-		document.getElementById("status").textContent = dirtyIndicator + status;
+		document.getElementById("status").textContent = dirtyIndicator + status; //$NON-NLS-0$
 	};
 	
-	var editor = new mEditor.Editor({
-		textViewFactory: textViewFactory,
-		undoStackFactory: new mEditorFeatures.UndoFactory(),
-		annotationFactory: annotationFactory,
-		lineNumberRulerFactory: new mEditorFeatures.LineNumberRulerFactory(),
-		contentAssistFactory: contentAssistFactory,
-		keyBindingFactory: keyBindingFactory, 
-		statusReporter: statusReporter,
-		domNode: editorDomNode
+	var editor = edit({
+		parent: editorDomNode,
+		lang: "js", //$NON-NLS-0$
+		contents: "window.alert('this is some javascript code');", //$NON-NLS-0$  // try pasting in some real code
+		statusReporter: statusReporter
 	});
+	
+	// save binding
+	editor.getTextView().setKeyBinding(new mKeyBinding.KeyBinding("s", true), "save"); //$NON-NLS-1$ //$NON-NLS-0$
+	editor.getTextView().setAction("save", function(){ //$NON-NLS-0$
+			save(editor);
+			return true;
+	});
+	document.getElementById("save").onclick = function() {save(editor);}; //$NON-NLS-0$
 		
-	editor.addEventListener("DirtyChanged", function(evt) {
+	editor.addEventListener("DirtyChanged", function(evt) { //$NON-NLS-0$
 		if (editor.isDirty()) {
-			dirtyIndicator = "*";
+			dirtyIndicator = "*"; //$NON-NLS-0$
 		} else {
 			dirtyIndicator = "";
 		}
-		document.getElementById("status").textContent = dirtyIndicator + status;
+		statusReporter(dirtyIndicator + status);
 	});
 	
-	editor.installTextView();
-	// if there is a mechanism to change which file is being viewed, this code would be run each time it changed.
-	var contentName = "sample.js";  // for example, a file name, something the user recognizes as the content.
-	var initialContent = "window.alert('this is some javascript code');  // try pasting in some real code";
-	editor.setInput(contentName, null, initialContent);
-	syntaxHighlighter.highlight(contentName, editor);
-	contentAssist.addEventListener("Activating", function() {
-		if (/\.css$/.test(contentName)) {
-			contentAssist.setProviders([cssContentAssistProvider]);
-		} else if (/\.js$/.test(contentName)) {
-			contentAssist.setProviders([jsTemplateContentAssistProvider]);
-		}
-	});
-	// end of code to run when content changes.
-	
 	window.onbeforeunload = function() {
 		if (editor.isDirty()) {
 			 return "There are unsaved changes.";
diff --git a/bundles/org.eclipse.orion.client.editor/web/orion/editor/contentAssist.js b/bundles/org.eclipse.orion.client.editor/web/orion/editor/contentAssist.js
index dc1d4c7..62017e4 100644
--- a/bundles/org.eclipse.orion.client.editor/web/orion/editor/contentAssist.js
+++ b/bundles/org.eclipse.orion.client.editor/web/orion/editor/contentAssist.js
@@ -220,6 +220,7 @@
 			var self = this;

 			var offset = this.textView.getCaretOffset();

 			this._computeProposals(offset).then(function(proposals) {

+				if (!self.isActive()) { return; }

 				self.dispatchEvent({type: "ProposalsComputed", data: {proposals: proposals}}); //$NON-NLS-0$

 			});

 		},

@@ -556,7 +557,9 @@
 			this.isShowing = false;

 		},

 		position: function() {

-			var caretLocation = this.textView.getLocationAtOffset(this.textView.getCaretOffset());

+			var contentAssist = this.contentAssist;

+			var offset = contentAssist.offset !== undefined ? contentAssist.offset : this.textView.getCaretOffset();

+			var caretLocation = this.textView.getLocationAtOffset(offset);

 			caretLocation.y += this.textView.getLineHeight();

 			this.textView.convert(caretLocation, "document", "page"); //$NON-NLS-1$ //$NON-NLS-0$

 			this.parentNode.style.position = "fixed"; //$NON-NLS-0$

diff --git a/bundles/org.eclipse.orion.client.editor/web/orion/editor/contentassist.css b/bundles/org.eclipse.orion.client.editor/web/orion/editor/contentassist.css
index 7e3ab38..54a49e6 100644
--- a/bundles/org.eclipse.orion.client.editor/web/orion/editor/contentassist.css
+++ b/bundles/org.eclipse.orion.client.editor/web/orion/editor/contentassist.css
@@ -9,10 +9,14 @@
 	z-index:10;

 	cursor: default;

 	overflow: auto;

-	height: 150px;

-	width: 200px;

+	min-width: 70px;

+	min-height: 50px;

+	max-height: 150px;

+	max-width: 350px;

+	white-space: nowrap;

 }

 

 .contentassist .selected {

 	background-color: #EAF2FE;

+	font-weight: bold;

 }

diff --git a/bundles/org.eclipse.orion.client.editor/web/orion/editor/cssContentAssist.js b/bundles/org.eclipse.orion.client.editor/web/orion/editor/cssContentAssist.js
index fa8d437..911c1bb 100644
--- a/bundles/org.eclipse.orion.client.editor/web/orion/editor/cssContentAssist.js
+++ b/bundles/org.eclipse.orion.client.editor/web/orion/editor/cssContentAssist.js
@@ -15,6 +15,111 @@
 	'orion/editor/templates', //$NON-NLS-0$
 	'orion/editor/keywords' //$NON-NLS-0$
 ], function(mTemplates, mKeywords) {
+
+	var overflowValues = {
+		type: "link", //$NON-NLS-0$
+		values: ["visible", "hidden", "scroll", "auto", "no-display", "no-content"] //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+	};
+	var fontStyleValues = {
+		type: "link", //$NON-NLS-0$
+		values: ["italic", "normal", "oblique", "inherit"] //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+	};
+	var fontWeightValues = {
+		type: "link", //$NON-NLS-0$
+		values: [
+			"bold", "normal", "bolder", "lighter", //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+			"100", "200", "300", "400", "500", "600", //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+			"700", "800", "900", "inherit" //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+		]
+	};
+	var displayValues = {
+		type: "link", //$NON-NLS-0$
+		values: [
+			"none", "block", "box", "flex", "inline", "inline-block", "inline-flex", "inline-table", //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+			"list-item", "table", "table-caption", "table-cell", "table-column", "table-column-group", //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+			"table-footer-group", "table-header-group", "table-row", "table-row-group", "inherit" //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+		]
+	};
+	var visibilityValues = {
+		type: "link", //$NON-NLS-0$
+		values: ["hidden", "visible", "collapse", "inherit"] //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+	};
+	var positionValues = {
+		type: "link", //$NON-NLS-0$
+		values: ["absolute", "fixed", "relative", "static", "inherit"] //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+	};
+	var whitespaceValues = {
+		type: "link", //$NON-NLS-0$
+		values: ["pre", "pre-line", "pre-wrap", "nowrap", "normal", "inherit"] //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+	};
+	var wordwrapValues = {
+		type: "link", //$NON-NLS-0$
+		values: ["normal", "break-word"] //$NON-NLS-1$ //$NON-NLS-0$
+	};
+	var floatValues = {
+		type: "link", //$NON-NLS-0$
+		values: ["left", "right", "none", "inherit"] //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+	};
+	var borderStyles = {
+		type: "link", //$NON-NLS-0$
+		values: ["solid", "dashed", "dotted", "double", "groove", "ridge", "inset", "outset"] //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+	};
+	var widths = {
+		type: "link", //$NON-NLS-0$
+		values: []
+	};
+	for (var i=0; i<10; i++) {
+		widths.values.push(i.toString());
+	}
+	var colorValues = {
+		type: "link", //$NON-NLS-0$
+		values: [
+			"black", //$NON-NLS-0$
+			"white", //$NON-NLS-0$
+			"red", //$NON-NLS-0$
+			"green", //$NON-NLS-0$
+			"blue", //$NON-NLS-0$
+			"magenta", //$NON-NLS-0$
+			"yellow", //$NON-NLS-0$
+			"cyan", //$NON-NLS-0$
+			"grey", //$NON-NLS-0$
+			"darkred", //$NON-NLS-0$
+			"darkgreen", //$NON-NLS-0$
+			"darkblue", //$NON-NLS-0$
+			"darkmagenta", //$NON-NLS-0$
+			"darkcyan", //$NON-NLS-0$
+			"darkyellow", //$NON-NLS-0$
+			"darkgray", //$NON-NLS-0$
+			"lightgray" //$NON-NLS-0$
+		]
+	};
+	var cursorValues = {
+		type: "link", //$NON-NLS-0$
+		values: [
+			"auto", //$NON-NLS-0$
+			"crosshair", //$NON-NLS-0$
+			"default", //$NON-NLS-0$
+			"e-resize", //$NON-NLS-0$
+			"help", //$NON-NLS-0$
+			"move", //$NON-NLS-0$
+			"n-resize", //$NON-NLS-0$
+			"ne-resize", //$NON-NLS-0$
+			"nw-resize", //$NON-NLS-0$
+			"pointer", //$NON-NLS-0$
+			"progress", //$NON-NLS-0$
+			"s-resize", //$NON-NLS-0$
+			"se-resize", //$NON-NLS-0$
+			"sw-resize", //$NON-NLS-0$
+			"text", //$NON-NLS-0$
+			"w-resize", //$NON-NLS-0$
+			"wait", //$NON-NLS-0$
+			"inherit" //$NON-NLS-0$
+		]
+	};
+	
+	function fromJSON(o) {
+		return JSON.stringify(o).replace("}", "\\}"); //$NON-NLS-1$ //$NON-NLS-0$
+	}
 	
 	var templates = [
 		{
@@ -28,14 +133,9 @@
 			template: "#${id} {\n\t${cursor}\n}" //$NON-NLS-0$
 		},
 		{
-			prefix: "width", //$NON-NLS-0$
-			description: "width - width pixel style",
-			template: "width: ${value}px;" //$NON-NLS-0$
-		},
-		{
-			prefix: "height", //$NON-NLS-0$
-			description: "height - height pixel style",
-			template: "width: ${value}px;" //$NON-NLS-0$
+			prefix: "outline", //$NON-NLS-0$
+			description: "outline - outline style",
+			template: "outline: ${color:" + fromJSON(colorValues) + "} ${style:" + fromJSON(borderStyles) + "} ${width:" + fromJSON(widths) + "}px;" //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
 		},
 		{
 			prefix: "background-image", //$NON-NLS-0$
@@ -48,11 +148,88 @@
 			template: "url(\"${uri}\");" //$NON-NLS-0$
 		},
 		{
+			prefix: "rgb", //$NON-NLS-0$
+			description: "rgb - rgb color",
+			template: "rgb(${red},${green},${blue});" //$NON-NLS-0$
+		},
+		{
 			prefix: "@", //$NON-NLS-0$
 			description: "import - import style sheet",
 			template: "@import \"${uri}\";" //$NON-NLS-0$
 		}
 	];
+	var valuesProperties = [
+		{prop: "display", values: displayValues}, //$NON-NLS-0$
+		{prop: "overflow", values: overflowValues}, //$NON-NLS-0$
+		{prop: "overflow-x", values: overflowValues}, //$NON-NLS-0$
+		{prop: "overflow-y", values: overflowValues}, //$NON-NLS-0$
+		{prop: "float", values: floatValues}, //$NON-NLS-0$
+		{prop: "position", values: positionValues}, //$NON-NLS-0$
+		{prop: "cursor", values: cursorValues}, //$NON-NLS-0$
+		{prop: "color", values: colorValues}, //$NON-NLS-0$
+		{prop: "border-top-color", values: colorValues}, //$NON-NLS-0$
+		{prop: "border-bottom-color", values: colorValues}, //$NON-NLS-0$
+		{prop: "border-right-color", values: colorValues}, //$NON-NLS-0$
+		{prop: "border-left-color", values: colorValues}, //$NON-NLS-0$
+		{prop: "background-color", values: colorValues}, //$NON-NLS-0$
+		{prop: "font-style", values: fontStyleValues}, //$NON-NLS-0$
+		{prop: "font-weight", values: fontWeightValues}, //$NON-NLS-0$
+		{prop: "white-space", values: whitespaceValues}, //$NON-NLS-0$
+		{prop: "word-wrap", values: wordwrapValues}, //$NON-NLS-0$
+		{prop: "visibility", values: visibilityValues} //$NON-NLS-0$
+	];
+	var prop;
+	for (i=0; i<valuesProperties.length; i++) {
+		prop = valuesProperties[i];
+		templates.push({
+			prefix: prop.prop, //$NON-NLS-0$
+			description: prop.prop + " - " + prop.prop + " style",
+			template: prop.prop + ": ${value:" + fromJSON(prop.values) + "};" //$NON-NLS-1$ //$NON-NLS-0$
+		});
+	}	
+	var pixelProperties = [
+		"width", "height", "top", "bottom", "left", "right", //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+		"min-width", "min-height", "max-width", "max-height", //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+		"margin", "padding", "padding-left", "padding-right", //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+		"padding-top", "padding-bottom", "margin-left", "margin-top", //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+		"margin-bottom", "margin-right" //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+	];
+	for (i=0; i<pixelProperties.length; i++) {
+		prop = pixelProperties[i];
+		templates.push({
+			prefix: prop, //$NON-NLS-0$
+			description: prop + " - " + prop + " pixel style",
+			template: prop  + ": ${value}px;" //$NON-NLS-0$
+		});
+	}
+	var fourWidthsProperties = ["padding", "margin"]; //$NON-NLS-1$ //$NON-NLS-0$
+	for (i=0; i<fourWidthsProperties.length; i++) {
+		prop = fourWidthsProperties[i];
+		templates.push({
+			prefix: prop, //$NON-NLS-0$
+			description: prop + " - " + prop + " top right bottom left style",
+			template: prop  + ": ${top}px ${left}px ${bottom}px ${right}px;" //$NON-NLS-0$
+		});
+		templates.push({
+			prefix: prop, //$NON-NLS-0$
+			description: prop + " - " + prop + " top right,left bottom style",
+			template: prop  + ": ${top}px ${right_left}px ${bottom}px;" //$NON-NLS-0$
+		});
+		templates.push({
+			prefix: prop, //$NON-NLS-0$
+			description: prop + " - " + prop + " top,bottom right,left style",
+			template: prop  + ": ${top_bottom}px ${right_left}px" //$NON-NLS-0$
+		});
+	}
+	var borderProperties = ["border", "border-top", "border-bottom", "border-left", "border-right"]; //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+	for (i=0; i<borderProperties.length; i++) {
+		prop = borderProperties[i];
+		templates.push({
+			prefix: prop, //$NON-NLS-0$
+			description: prop + " - " + prop + " style",
+			template: prop + ": ${width:" + fromJSON(widths) + "}px ${style:" + fromJSON(borderStyles) + "} ${color:" + fromJSON(colorValues) + "};" //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+		});
+	}
 
 	/**
 	 * @name orion.contentAssist.CssContentAssistProvider
diff --git a/bundles/org.eclipse.orion.client.editor/web/orion/editor/edit.js b/bundles/org.eclipse.orion.client.editor/web/orion/editor/edit.js
index e8e931a..91b5032 100644
--- a/bundles/org.eclipse.orion.client.editor/web/orion/editor/edit.js
+++ b/bundles/org.eclipse.orion.client.editor/web/orion/editor/edit.js
@@ -265,7 +265,7 @@
 			keyModeStack.push(genericBindings);
 
 			// Linked Mode
-			var linkedMode = new mEditorFeatures.LinkedMode(editor, undoStack);
+			var linkedMode = new mEditorFeatures.LinkedMode(editor, undoStack, contentAssist);
 			keyModeStack.push(linkedMode);
 			
 			// create keybindings for source editing
diff --git a/bundles/org.eclipse.orion.client.editor/web/orion/editor/editorFeatures.js b/bundles/org.eclipse.orion.client.editor/web/orion/editor/editorFeatures.js
index 5244cf7..a12b23f 100644
--- a/bundles/org.eclipse.orion.client.editor/web/orion/editor/editorFeatures.js
+++ b/bundles/org.eclipse.orion.client.editor/web/orion/editor/editorFeatures.js
@@ -19,9 +19,10 @@
 	'orion/editor/annotations', //$NON-NLS-0$
 	'orion/editor/tooltip', //$NON-NLS-0$
 	'orion/editor/textDND', //$NON-NLS-0$
+	'orion/editor/templates', //$NON-NLS-0$
 	'orion/editor/regex', //$NON-NLS-0$
 	'orion/util' //$NON-NLS-0$
-], function(messages, mUndoStack, mKeyBinding, mRulers, mAnnotations, mTooltip, mTextDND, mRegex, util) {
+], function(messages, mUndoStack, mKeyBinding, mRulers, mAnnotations, mTooltip, mTextDND, mTemplates, mRegex, util) {
 
 	function UndoFactory() {
 	}
@@ -967,9 +968,10 @@
 		}
 	};
 	
-	function LinkedMode(editor, undoStack) {
+	function LinkedMode(editor, undoStack, contentAssist) {
 		this.editor = editor;
 		this.undoStack = undoStack;
+		this.contentAssist = contentAssist;
 		
 		this.linkedModeActive = false;
 		this.linkedModeModel = null;
@@ -981,24 +983,15 @@
 		 */
 		this.linkedModeListener = {
 		
-			onModelChanged: function(event) {
-				if (!this._viewEditing) {
-					this.cancel(true);
+			onActivating: function(event) {
+				if (this._groupContentAssistProvider) {
+					this.contentAssist.setProviders([this._groupContentAssistProvider]);
+					this.contentAssist.setProgress(null);
 				}
 			}.bind(this),
-			
-			onModify: function(event) {
-				this._viewEditing = false;
-			}.bind(this),
-			
+
 			onVerify: function(event) {
-				this._viewEditing = true;
 				if (this.ignoreVerify) { return; }
-				var start = event.start;
-				var addedCharCount = event.text.length;
-				var removedCharCount = event.end - event.start;
-				var end = start + removedCharCount;
-				var changeCount = addedCharCount - removedCharCount;
 				var sortedPositions = [];
 				var groups = this.linkedModeModel.groups, i;
 				for (i = 0; i < groups.length; i++) {
@@ -1018,7 +1011,7 @@
 				for (i = 0; i < sortedPositions.length; i++) {
 					group = sortedPositions[i].group;
 					position = sortedPositions[i].position;
-					if (position.offset <= start && end <= position.offset + position.length) {
+					if (position.offset <= event.start && event.end <= position.offset + position.length) {
 						deltaStart -= position.offset;
 						deltaEnd -= position.offset;
 						groupChanged = group;
@@ -1035,24 +1028,15 @@
 					} else {
 						this.startUndo();
 					}
-					this.ignoreVerify = true;
-					var textView = this.editor.getTextView();
-					for (i = sortedPositions.length - 1; i >= 0; i--) {
-						group = sortedPositions[i].group;
-						position = sortedPositions[i].position;
-						if (group === groupChanged) {
-							textView.setText(event.text, position.offset + deltaStart , position.offset + deltaEnd);
-						}
-					}
-					this.ignoreVerify = false;
-					event.text = null;
 					var deltaCount = 0, escapePositionDeltaCount;
+					var changeCount = event.text.length - (event.end - event.start);
 					for (i = 0; i < sortedPositions.length; ++i) {
 						group = sortedPositions[i].group;
 						position = sortedPositions[i].position;
 						if (escapePositionDeltaCount === undefined && this.linkedModeModel.escapePosition < position.offset) {
 							escapePositionDeltaCount = deltaCount;
 						}
+						position._oldOffset = position.offset;
 						if (group === groupChanged) {
 							position.offset += deltaCount;
 							position.length += changeCount;
@@ -1065,6 +1049,17 @@
 						escapePositionDeltaCount = deltaCount;
 					}
 					this.linkedModeModel.escapePosition += escapePositionDeltaCount;
+					this.ignoreVerify = true;
+					var textView = this.editor.getTextView();
+					for (i = sortedPositions.length - 1; i >= 0; i--) {
+						group = sortedPositions[i].group;
+						position = sortedPositions[i].position;
+						if (group === groupChanged) {
+							textView.setText(event.text, position._oldOffset + deltaStart , position._oldOffset + deltaEnd);
+						}
+					}
+					this.ignoreVerify = false;
+					event.text = null;
 					this._updateAnnotations();
 				} else {
 					// The change has been done outside of the positions, exit the Linked Mode
@@ -1095,12 +1090,21 @@
 			}
 			this.linkedModeActive = true;
 			this.linkedModeModel = linkedModeModel;
-			this.selectLinkedGroup(0);
+
+			if (this.undoStack) {
+				var self = this;
+				this.undoStack.startCompoundChange({
+					undo: function() {
+						self.cancel(true);
+					}
+				});
+				this.undoStack.endCompoundChange();
+			}
 
 			var textView = this.editor.getTextView();
 			textView.addEventListener("Verify", this.linkedModeListener.onVerify); //$NON-NLS-0$
-			textView.addEventListener("Modify", this.linkedModeListener.onModify); //$NON-NLS-0$
-			textView.addEventListener("ModelChanged", this.linkedModeListener.onModelChanged); //$NON-NLS-0$
+			var contentAssist = this.contentAssist;
+			contentAssist.addEventListener("Activating", this.linkedModeListener.onActivating); //$NON-NLS-0$
 
 			textView.setKeyBinding(new mKeyBinding.KeyBinding(9), "nextLinkedModePosition"); //$NON-NLS-0$
 			textView.setAction("nextLinkedModePosition", function() { //$NON-NLS-0$
@@ -1115,6 +1119,8 @@
 			}.bind(this));
 
 			this.editor.reportStatus(messages.linkedModeEntered, null, true);
+			
+			this.selectLinkedGroup(0);
 		},
 		_cloneModel: function() {
 			var model = this.linkedModeModel;
@@ -1189,8 +1195,9 @@
 			this.linkedModeActive = false;
 			var textView = this.editor.getTextView();
 			textView.removeEventListener("Verify", this.linkedModeListener.onVerify); //$NON-NLS-0$
-			textView.removeEventListener("Modify", this.linkedModeListener.onModify); //$NON-NLS-0$
-			textView.removeEventListener("ModelChanged", this.linkedModeListener.onModelChanged); //$NON-NLS-0$
+			var contentAssist = this.contentAssist;
+			contentAssist.removeEventListener("Activating", this.linkedModeListener.onActivating); //$NON-NLS-0$
+			contentAssist.offset = undefined;
 			textView.setKeyBinding(new mKeyBinding.KeyBinding(9), "tab"); //$NON-NLS-0$
 			textView.setKeyBinding(new mKeyBinding.KeyBinding(9, false, true), "shiftTab"); //$NON-NLS-0$
 			
@@ -1218,6 +1225,29 @@
 			var position = group.positions[0];
 			var textView = this.editor.getTextView();
 			textView.setSelection(position.offset, position.offset + position.length);
+			var contentAssist = this.contentAssist;
+			if (contentAssist) {
+				contentAssist.offset = undefined;
+				if (group.data && group.data.type === "link") { //$NON-NLS-0$
+					var provider = this._groupContentAssistProvider = new mTemplates.TemplateContentAssist(group.data.values);
+					provider.getPrefix = function() {
+						var selection = textView.getSelection();
+						if (selection.start === selection.end) {
+							var caretOffset = textView.getCaretOffset();
+							if (position.offset <= caretOffset && caretOffset <= position.offset + position.length) {
+								return textView.getText(position.offset, caretOffset);
+							}
+						}
+						return "";
+					};
+					contentAssist.offset = position.offset;
+					contentAssist.deactivate();
+					contentAssist.activate();
+				} else if (this._groupContentAssistProvider) {
+					this._groupContentAssistProvider = null;
+					contentAssist.deactivate();
+				}
+			}
 			this._updateAnnotations();
 		},
 		_updateAnnotations: function() {
diff --git a/bundles/org.eclipse.orion.client.editor/web/orion/editor/jsTemplateContentAssist.js b/bundles/org.eclipse.orion.client.editor/web/orion/editor/jsTemplateContentAssist.js
index 49ea14c..35514c8 100644
--- a/bundles/org.eclipse.orion.client.editor/web/orion/editor/jsTemplateContentAssist.js
+++ b/bundles/org.eclipse.orion.client.editor/web/orion/editor/jsTemplateContentAssist.js
@@ -35,6 +35,23 @@
 		return c;
 	}
 	
+	var typeofValues = {
+		type: "link", //$NON-NLS-0$
+		values: [
+			"undefined", //$NON-NLS-0$
+			"object", //$NON-NLS-0$
+			"boolean", //$NON-NLS-0$
+			"number", //$NON-NLS-0$
+			"string", //$NON-NLS-0$
+			"function", //$NON-NLS-0$
+			"xml" //$NON-NLS-0$
+		]
+	};
+	
+	function fromJSON(o) {
+		return JSON.stringify(o).replace("}", "\\}"); //$NON-NLS-1$ //$NON-NLS-0$
+	}
+	
 	var uninterestingChars = ":!@#$^&*.?<>"; //$NON-NLS-0$
 
 	var templates = [
@@ -56,7 +73,7 @@
 		{
 			prefix: "for", //$NON-NLS-0$
 			description: "for - iterate over array with local var",
-			template: "for (var ${i}=0; ${i}<${array}.length; ${i}++) {\n\tvar ${v} = ${array}[${i}];\n\t${cursor}\n}" //$NON-NLS-0$
+			template: "for (var ${i}=0; ${i}<${array}.length; ${i}++) {\n\tvar ${value} = ${array}[${i}];\n\t${cursor}\n}" //$NON-NLS-0$
 		},
 		{
 			prefix: "for", //$NON-NLS-0$
@@ -121,12 +138,12 @@
 		{
 			prefix: "typeof", //$NON-NLS-0$
 			description: "typeof - typeof statement",
-			template: "typeof ${var} = \"${type}\"" //$NON-NLS-0$
+			template: "typeof ${object} = \"${type:" + fromJSON(typeofValues) + "}\"" //$NON-NLS-1$ //$NON-NLS-0$
 		},
 		{
 			prefix: "instanceof", //$NON-NLS-0$
 			description: "instanceof - instanceof statement",
-			template: "${var} instanceof ${type}" //$NON-NLS-0$
+			template: "${object} instanceof ${type}" //$NON-NLS-0$
 		},
 		{
 			prefix: "with", //$NON-NLS-0$
diff --git a/bundles/org.eclipse.orion.client.editor/web/orion/editor/templates.js b/bundles/org.eclipse.orion.client.editor/web/orion/editor/templates.js
index b2e2b69..a938eca 100644
--- a/bundles/org.eclipse.orion.client.editor/web/orion/editor/templates.js
+++ b/bundles/org.eclipse.orion.client.editor/web/orion/editor/templates.js
@@ -42,29 +42,32 @@
 			if (context.indentation) {

 				delimiter += context.indentation;

 			}

-			var variables = this.variables;

-			variables[delimiterVar] = delimiter;

-			variables[tabVar] = context.tab !== undefined ? context.tab : "\t"; //$NON-NLS-0$

-			variables[cursorVar] = "";

+			var tab = context.tab !== undefined ? context.tab : "\t"; //$NON-NLS-0$

 			var delta = 0;

+			var variables = this.variables;

 			var segments = this.segments, proposal = [];

 			for (var i = 0; i < segments.length; i++) {

-				var segment = segments[i], variable = segment;

-				var substitution = variables[segment];

-				if (substitution !== undefined) {

-					segment = substitution;

-					switch (variable) {

+				var segment = segments[i];

+				var variable = variables[segment];

+				if (variable !== undefined) {

+					switch (segment) {

 						case tabVar:

+							segment = tab;

+							break;

 						case delimiterVar:

+							segment = delimiter;

 							break;

 						case cursorVar:

+							segment = "";

 							escapePosition = delta;

 							break;

 						default:

 							var g = groups[segment];

 							if (!g) {

-								g = groups[segment] = {positions: []};

+								g = groups[segment] = {data: variable.data, positions: []};

 							}

+							segment = variable.substitution;

+							if (g.data && g.data.values) { segment = g.data.values[0]; }

 							g.positions.push({

 								offset: startOffset + delta,

 								length: segment.length

@@ -99,21 +102,26 @@
 			var segments = [], variables = {}, segment, start = 0;

 			template = template.replace(/\n/g, delimiterVar);

 			template = template.replace(/\t/g, tabVar);

-			template.replace(/\$\{([^\}]+)\}/g, function(group, text, index) {

-				var variable = text;

-				var colon = variable.indexOf(":"); //$NON-NLS-0$

+			template.replace(/\$\{((?:[^\\}]+|\\.))*\}/g, function(group, text1, index) {

+				var text = group.substring(2,group.length-1);

+				var variable = group, substitution = text, data = null;

+				var colon = substitution.indexOf(":"); //$NON-NLS-0$

 				if (colon !== -1) {

-					variable = variable.substring(0, colon);

+					substitution = substitution.substring(0, colon);

+					variable = "${"+ substitution + "}"; //$NON-NLS-1$ //$NON-NLS-0$

+					data = JSON.parse(text.substring(colon + 1).replace("\\}", "}").trim()); //$NON-NLS-1$ //$NON-NLS-0$

 				}

-				var v = variables[group];

-				if (!v) {

-					v = variables[group] = variable;

+				var v = variables[variable];

+				if (!v) { v = variables[variable] = {}; }

+				v.substitution = substitution;

+				if (data) {

+					v.data = data;

 				}

 				segment = template.substring(start, index);

 				if (segment) { segments.push(segment); }

-				segments.push(group);

+				segments.push(variable);

 				start = index + group.length;

-				return variable;

+				return substitution;

 			});

 			segment = template.substring(start, template.length);

 			if (segment) { segments.push(segment); }

diff --git a/bundles/org.eclipse.orion.client.editor/web/orion/editor/undoStack.js b/bundles/org.eclipse.orion.client.editor/web/orion/editor/undoStack.js
index 1d7ab40..c0be8b4 100644
--- a/bundles/org.eclipse.orion.client.editor/web/orion/editor/undoStack.js
+++ b/bundles/org.eclipse.orion.client.editor/web/orion/editor/undoStack.js
@@ -29,10 +29,12 @@
 		/** @ignore */
 		undo: function (view, select) {
 			this._doUndoRedo(this.offset, this.previousText, this.text, view, select);
+			return true;
 		},
 		/** @ignore */
 		redo: function (view, select) {
 			this._doUndoRedo(this.offset, this.text, this.previousText, view, select);
+			return true;
 		},
 		_doUndoRedo: function(offset, text, previousText, view, select) {
 			var model = view.getModel();
@@ -94,6 +96,9 @@
 		},
 		/** @ignore */
 		undo: function (view, select) {
+			if (this.changes.length > 1) {
+				view.setRedraw(false);
+			}
 			for (var i=this.changes.length - 1; i >= 0; i--) {
 				this.changes[i].undo(view, false);
 			}
@@ -106,9 +111,16 @@
 			if (owner && owner.undo) {
 				owner.undo();
 			}
+			if (this.changes.length > 1) {
+				view.setRedraw(true);
+			}
+			return this.changes.length > 0;
 		},
 		/** @ignore */
 		redo: function (view, select) {
+			if (this.changes.length > 1) {
+				view.setRedraw(false);
+			}
 			for (var i = 0; i < this.changes.length; i++) {
 				this.changes[i].redo(view, false);
 			}
@@ -121,6 +133,10 @@
 			if (owner && owner.redo) {
 				owner.redo();
 			}
+			if (this.changes.length > 1) {
+				view.setRedraw(true);
+			}
+			return this.changes.length > 0;
 		},
 		/** @ignore */
 		start: function (view) {
@@ -283,14 +299,16 @@
 		 */
 		undo: function() {
 			this._commitUndo();
-			if (this.index <= 0) {
-				return false;
-			}
-			var change = this.stack[--this.index];
+			var change, result = false;
 			this._ignoreUndo = true;
-			change.undo(this.view, true);
+			do {
+				if (this.index <= 0) {
+					break;
+				}
+				change = this.stack[--this.index];
+			} while (!(result = change.undo(this.view, true)));
 			this._ignoreUndo = false;
-			return true;
+			return result;
 		},
 		/**
 		 * Redo the last change in the stack.
@@ -302,12 +320,14 @@
 		 */
 		redo: function() {
 			this._commitUndo();
-			if (this.index >= this.stack.length) {
-				return false;
-			}
-			var change = this.stack[this.index++];
+			var change, result = false;
 			this._ignoreUndo = true;
-			change.redo(this.view, true);
+			do {
+				if (this.index >= this.stack.length) {
+					break;
+				}
+				change = this.stack[this.index++];
+			} while (!(result = change.redo(this.view, true)));
 			this._ignoreUndo = false;
 			return true;
 		},
diff --git a/bundles/org.eclipse.orion.client.ui/web/css/ide.css b/bundles/org.eclipse.orion.client.ui/web/css/ide.css
index 4619432..7cef863 100644
--- a/bundles/org.eclipse.orion.client.ui/web/css/ide.css
+++ b/bundles/org.eclipse.orion.client.ui/web/css/ide.css
@@ -685,8 +685,11 @@
 	z-index:100;
 	cursor: default;
 	overflow: auto;
-	height: 150px;
-	width: 350px;
+	min-width: 70px;
+	min-height: 50px;
+	max-width: 350px;
+	max-height: 150px;
+	white-space: nowrap;
 }
 
 .contentassist:focus {
diff --git a/bundles/org.eclipse.orion.client.ui/web/edit/setup.js b/bundles/org.eclipse.orion.client.ui/web/edit/setup.js
index b71c77e..ebc841f 100644
--- a/bundles/org.eclipse.orion.client.ui/web/edit/setup.js
+++ b/bundles/org.eclipse.orion.client.ui/web/edit/setup.js
@@ -345,7 +345,7 @@
 		keyModeStack.push(genericBindings);
 		
 		// Linked Mode
-		var linkedMode = new mEditorFeatures.LinkedMode(editor, undoStack);
+		var linkedMode = new mEditorFeatures.LinkedMode(editor, undoStack, contentAssist);
 		keyModeStack.push(linkedMode);
 		
 		// create keybindings for source editing