Initial insertParagraph support
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Sun, 29 May 2011 13:07:20 -0600
changeset 181 82126988dd3f
parent 180 beea6b6c9d05
child 182 9447ac9f919c
Initial insertParagraph support

Quite broken in all sorts of ways, but I was carrying way too much
uncommitted code. Lots of rewriting still to come.
autoimplementation.html
editcommands.html
implementation.js
linebreaktest.html
preprocess
source.html
--- a/autoimplementation.html	Thu May 26 11:52:13 2011 -0600
+++ b/autoimplementation.html	Sun May 29 13:07:20 2011 -0600
@@ -698,6 +698,9 @@
 		['<h1>', '<pre>[foo]<br>bar</pre>'],
 		['<h1>', '<pre>foo<br>[bar]</pre>'],
 		['<h1>', '<pre>[foo<br>bar]</pre>'],
+
+		// Spec bug, to address later
+		['<h1>', '<p>[foo</p>bar]'],
 	],
 	hilitecolor: [
 		'foo[]bar',
@@ -743,6 +746,15 @@
 		'foo]bar[baz<p>extra',
 		'{<p><p> <p>foo</p>}<p>extra',
 		'foo[bar<i>baz]qoz</i>quz<p>extra',
+		'[]foo<p>extra',
+		'foo[]<p>extra',
+		'<p>[]foo<p>extra',
+		'<p>foo[]<p>extra',
+		'<p>{}<br>foo</p><p>extra',
+		'<p>foo<br>{}</p><p>extra',
+		'<span>{}<br>foo</span>bar<p>extra',
+		'<span>foo<br>{}</span>bar<p>extra',
+		'<p>foo</p>{}<p>bar</p>',
 
 		'<table><tbody><tr><td>foo<td>b[a]r<td>baz</table><p>extra',
 		'<table><tbody><tr data-start=1 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
@@ -1108,6 +1120,48 @@
 		'<ul style=color:red><li>foo<li>bar<li>[baz]</ul>',
 		'<ul style=text-indent:1em><li>foo<li>bar<li>[baz]</ul>',
 	],
+	insertparagraph: [
+		'foo[bar]baz',
+
+		'[]foo',
+		'foo[]',
+		'foo[]bar',
+		'<address>[]foo</address>',
+		'<address>foo[]</address>',
+		'<address>foo[]bar</address>',
+		'<div>[]foo</div>',
+		'<div>foo[]</div>',
+		'<div>foo[]bar</div>',
+		'<dl><dt>[]foo<dd>bar</dl>',
+		'<dl><dt>foo[]<dd>bar</dl>',
+		'<dl><dt>foo[]bar<dd>baz</dl>',
+		'<dl><dt>foo<dd>[]bar</dl>',
+		'<dl><dt>foo<dd>bar[]</dl>',
+		'<dl><dt>foo<dd>bar[]baz</dl>',
+		'<h1>[]foo</h1>',
+		'<h1>foo[]</h1>',
+		'<h1>foo[]bar</h1>',
+		'<ol><li>[]foo</ol>',
+		'<ol><li>foo[]</ol>',
+		'<ol><li>foo[]bar</ol>',
+		'<p>[]foo</p>',
+		'<p>foo[]</p>',
+		'<p>foo[]bar</p>',
+		'<pre>[]foo</pre>',
+		'<pre>foo[]</pre>',
+		'<pre>foo[]bar</pre>',
+
+		'<ol><li>foo<li>{}<br></ol>',
+
+		'<h1>foo[bar</h1><p>baz]quz</p>',
+		'<p>foo[bar</p><h1>baz]quz</h1>',
+		'<p>foo</p>{}',
+		'{}<p>foo</p>',
+		'<p>foo</p>{}<h1>bar</h1>',
+		'<h1>foo</h1>{}<p>bar</p>',
+		'<p>foo</p><h1>[bar]</h1><p>baz</p>',
+		'<p>foo</p>{<h1>bar</h1>}<p>baz</p>',
+	],
 	insertunorderedlist: [
 		'foo[]bar',
 		'<span>foo</span>{}<span>bar</span>',
@@ -2434,12 +2488,40 @@
  * visible.
  */
 function addBrackets(range) {
-	// Do the end first, so that if the range is collapsed, the start point
-	// will be inserted before the end point.
+	// Handle the collapsed case specially, to avoid confusingly getting the
+	// markers backwards in some cases
+	if (range.startContainer.nodeType == Node.TEXT_NODE) {
+		if (range.collapsed) {
+			range.startContainer.insertData(range.startOffset, "[]");
+		} else {
+			range.startContainer.insertData(range.startOffset, "[");
+		}
+	} else {
+		// As everyone knows, the only node types are Text and Element.
+		var marker = range.collapsed ? "{}" : "{";
+		if (range.startOffset != range.startContainer.childNodes.length
+		&& range.startContainer.childNodes[range.startOffset].nodeType == Node.TEXT_NODE) {
+			range.startContainer.childNodes[range.startOffset].insertData(0, marker);
+		} else if (range.startOffset != 0
+		&& range.startContainer.childNodes[range.startOffset - 1].nodeType == Node.TEXT_NODE) {
+			range.startContainer.childNodes[range.startOffset - 1].appendData(marker);
+		} else {
+			// Seems to serialize as I'd want even for tables . . . IE doesn't
+			// allow undefined to be passed as the second argument (it throws
+			// an exception), so we have to explicitly check the number of
+			// children and pass null.
+			range.startContainer.insertBefore(document.createTextNode(marker),
+				range.startContainer.childNodes.length == range.startOffset
+				? null
+				: range.startContainer.childNodes[range.startOffset]);
+		}
+	}
+	if (range.collapsed) {
+		return;
+	}
 	if (range.endContainer.nodeType == Node.TEXT_NODE) {
 		range.endContainer.insertData(range.endOffset, "]");
 	} else {
-		// As everyone knows, the only node types are Text and Element.
 		if (range.endOffset != range.endContainer.childNodes.length
 		&& range.endContainer.childNodes[range.endOffset].nodeType == Node.TEXT_NODE) {
 			range.endContainer.childNodes[range.endOffset].insertData(0, "}");
@@ -2447,31 +2529,11 @@
 		&& range.endContainer.childNodes[range.endOffset - 1].nodeType == Node.TEXT_NODE) {
 			range.endContainer.childNodes[range.endOffset - 1].appendData("}");
 		} else {
-			// Seems to serialize as I'd want even for tables . . . IE doesn't
-			// allow undefined to be passed as the second argument (it throws
-			// an exception), so we have to explicitly check the number of
-			// children and pass null.
 			range.endContainer.insertBefore(document.createTextNode("}"),
 				range.endContainer.childNodes.length == range.endOffset
 				? null
 				: range.endContainer.childNodes[range.endOffset]);
 		}
 	}
-	if (range.startContainer.nodeType == Node.TEXT_NODE) {
-		range.startContainer.insertData(range.startOffset, "[");
-	} else {
-		if (range.startOffset != range.startContainer.childNodes.length
-		&& range.startContainer.childNodes[range.startOffset].nodeType == Node.TEXT_NODE) {
-			range.startContainer.childNodes[range.startOffset].insertData(0, "{");
-		} else if (range.startOffset != 0
-		&& range.startContainer.childNodes[range.startOffset - 1].nodeType == Node.TEXT_NODE) {
-			range.startContainer.childNodes[range.startOffset - 1].appendData("{");
-		} else {
-		range.startContainer.insertBefore(document.createTextNode("{"),
-			range.startContainer.childNodes.length == range.startOffset
-			? null
-			: range.startContainer.childNodes[range.startOffset]);
-		}
-	}
 }
 </script>
--- a/editcommands.html	Thu May 26 11:52:13 2011 -0600
+++ b/editcommands.html	Sun May 29 13:07:20 2011 -0600
@@ -35,7 +35,7 @@
 <body class=draft>
 <div class=head id=head>
 <h1>HTML Editing Commands</h1>
-<h2 class="no-num no-toc" id=work-in-progress-&mdash;-last-update-26-may-2011>Work in Progress &mdash; Last Update 26 May 2011</h2>
+<h2 class="no-num no-toc" id=work-in-progress-&mdash;-last-update-29-may-2011>Work in Progress &mdash; Last Update 29 May 2011</h2>
 <dl>
  <dt>Editor
  <dd>Aryeh Gregor &lt;ayg+spec@aryeh.name&gt;
@@ -103,8 +103,9 @@
    <li><a href=#the-indent-command><span class=secno>7.7 </span>The <code title="">indent</code> command</a></li>
    <li><a href=#the-inserthorizontalrule-command><span class=secno>7.8 </span>The <code title="">insertHorizontalRule</code> command</a></li>
    <li><a href=#the-insertorderedlist-command><span class=secno>7.9 </span>The <code title="">insertOrderedList</code> command</a></li>
-   <li><a href=#the-insertunorderedlist-command><span class=secno>7.10 </span>The <code title="">insertUnorderedList</code> command</a></li>
-   <li><a href=#the-outdent-command><span class=secno>7.11 </span>The <code title="">outdent</code> command</a></ol></li>
+   <li><a href=#the-insertparagraph-command><span class=secno>7.10 </span>The <code title="">insertParagraph</code> command</a></li>
+   <li><a href=#the-insertunorderedlist-command><span class=secno>7.11 </span>The <code title="">insertUnorderedList</code> command</a></li>
+   <li><a href=#the-outdent-command><span class=secno>7.12 </span>The <code title="">outdent</code> command</a></ol></li>
  <li><a href=#miscellaneous-commands><span class=secno>8 </span>Miscellaneous commands</a>
   <ol>
    <li><a href=#the-stylewithcss-command><span class=secno>8.1 </span>The <code title="">styleWithCSS</code> command</a></li>
@@ -2698,9 +2699,16 @@
   attribute.
 </ul>
 
-<p>A <dfn id=single-line-container>single-line container</dfn> is an <a href=#html-element>HTML element</a> with
-<a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-element-local-name title=concept-element-local-name>local name</a> "address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "p", or
-"pre".
+<p>A <dfn id=non-list-single-line-container>non-list single-line container</dfn> is an <a href=#html-element>HTML element</a>
+with <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-element-local-name title=concept-element-local-name>local name</a> "address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "p",
+or "pre".
+
+<p>A <dfn id=single-line-container>single-line container</dfn> is either a <a href=#non-list-single-line-container>non-list single-line
+container</a>, or an <a href=#html-element>HTML element</a> with <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-element-local-name title=concept-element-local-name>local name</a> "li",
+"dt", or "dd".
+
+<p>The <dfn id=default-single-line-container-name>default single-line container name</dfn> is "p".
+<!-- Possibly to be made configurable later. -->
 
 
 <h3 id=assorted-block-formatting-command-algorithms><span class=secno>7.2 </span>Assorted block formatting command algorithms</h3>
@@ -2924,14 +2932,18 @@
     of <var title="">start node</var> and then set <var title="">start node</var> to its
     <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
 
-    <li>Otherwise, if <var title="">start offset</var> is equal to the
-    <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a> of <var title="">start node</var>, set <var title="">start offset</var> to one
-    plus the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> of <var title="">start node</var> and then set <var title="">start
-    node</var> to its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
-
-    <li>Otherwise, if the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">start node</var> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>
-    <var title="">start offset</var> and its <code class=external data-anolis-spec=domcore title=dom-Node-previousSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling>previousSibling</a></code> are both <a href=#inline-node title="inline node">inline nodes</a> and the <code class=external data-anolis-spec=domcore title=dom-Node-previousSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling>previousSibling</a></code> isn't a
-    <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code>, subtract one from <var title="">start offset</var>.
+    <li>Otherwise, if <var title="">start offset</var> is <var title="">start node</var>'s
+    <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a> and <var title="">start node</var>'s last <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> is an
+    <a href=#inline-node>inline node</a> that's not a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code>, subtract one from <var title="">start
+    offset</var>.
+    <!-- So if you have a collapsed selection at the end of a block, for
+    instance, it will extend backwards into a block. -->
+
+    <li>Otherwise, if <var title="">start node</var> has a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>
+    <var title="">start offset</var>, and that <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> and its <code class=external data-anolis-spec=domcore title=dom-Node-previousSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling>previousSibling</a></code> are
+    both <a href=#inline-node title="inline node">inline nodes</a> and the
+    <code class=external data-anolis-spec=domcore title=dom-Node-previousSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling>previousSibling</a></code> isn't a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code>, subtract one from <var title="">start
+    offset</var>.
     <!-- IE also includes <br> (at least for the purposes of the indent
     command), but this is unlikely to match user expectations. -->
 
@@ -2948,19 +2960,20 @@
   <li>Repeat the following steps:
 
   <ol>
-    <li>If <var title="">end offset</var> is 0, set <var title="">end offset</var> to the
-    <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> of <var title="">end node</var> and then set <var title="">end node</var> to its
-    <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
-
-    <li>Otherwise, if <var title="">end node</var> is a <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> or <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment>Comment</a></code> node or
-    <var title="">end offset</var> is equal to the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a> of <var title="">end
-    node</var>, set <var title="">end offset</var> to one plus the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> of <var title="">end
-    node</var> and then set <var title="">end node</var> to its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
-
-    <li>Otherwise, if the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">end node</var> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>
-    <var title="">end offset</var> and its <code class=external data-anolis-spec=domcore title=dom-Node-previousSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling>previousSibling</a></code> are both <a href=#inline-node title="inline node">inline nodes</a>, and the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">end
-    node</var> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">end offset</var> isn't a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code>, add one to
-    <var title="">end offset</var>.
+    <li>If <var title="">end node</var> is a <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> or <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment>Comment</a></code> node or <var title="">end
+    offset</var> is equal to the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a> of <var title="">end node</var>, set
+    <var title="">end offset</var> to one plus the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> of <var title="">end node</var> and
+    then set <var title="">end node</var> to its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
+
+    <li>Otherwise, if <var title="">end offset</var> is 0 and <var title="">end node</var>'s
+    first <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> is an <a href=#inline-node>inline node</a> that's not a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code>, add one
+    to <var title="">end offset</var>.
+
+    <li>Otherwise, if <var title="">end node</var> has a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>
+    <var title="">end offset</var>, and that <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> and its <code class=external data-anolis-spec=domcore title=dom-Node-previousSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling>previousSibling</a></code> are
+    both <a href=#inline-node title="inline node">inline nodes</a>, and the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of
+    <var title="">end node</var> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">end offset</var> isn't a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code>,
+    add one to <var title="">end offset</var>.
 
     <li>Otherwise, break from this loop.
   </ol>
@@ -3846,10 +3859,10 @@
   not empty:
 
   <ol>
-    <li>If the first member of <var title="">node list</var> is a <a href=#single-line-container>single-line
-    container</a>, <a href=#set-the-tag-name>set the tag name</a> of the first member of
-    <var title="">node list</var> to <var title="">value</var>, then remove the first member from
-    <var title="">node list</var> and continue this loop from the beginning.
+    <li>If the first member of <var title="">node list</var> is a <a href=#non-list-single-line-container>non-list
+    single-line container</a>, <a href=#set-the-tag-name>set the tag name</a> of the first
+    member of <var title="">node list</var> to <var title="">value</var>, then remove the first
+    member from <var title="">node list</var> and continue this loop from the beginning.
 
     <li>Let <var title="">sublist</var> be an empty list of <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>nodes</a>.
 
@@ -3859,7 +3872,7 @@
     <li>While <var title="">node list</var> is not empty, and the first member of
     <var title="">node list</var> is the <code class=external data-anolis-spec=domcore title=dom-Node-nextSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-nextsibling>nextSibling</a></code> of the last member of
     <var title="">sublist</var>, and the first member of <var title="">node list</var> is not a
-    <a href=#single-line-container>single-line container</a>, and the last member of
+    <a href=#non-list-single-line-container>non-list single-line container</a>, and the last member of
     <var title="">sublist</var> is not a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code>, remove the first member of <var title="">node
     list</var> and append it to <var title="">sublist</var>.
 
@@ -3872,8 +3885,8 @@
   <li>Otherwise, while <var title="">node list</var> is not empty:
 
   <ol>
-    <li>If the first member of <var title="">node list</var> is a <a href=#single-line-container>single-line
-    container</a>:
+    <li>If the first member of <var title="">node list</var> is a <a href=#non-list-single-line-container>non-list
+    single-line container</a>:
 
     <ol>
       <li>Let <var title="">sublist</var> be the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a> of the first member of
@@ -3896,7 +3909,7 @@
       <li>While <var title="">node list</var> is not empty, and the first member of
       <var title="">node list</var> is the <code class=external data-anolis-spec=domcore title=dom-Node-nextSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-nextsibling>nextSibling</a></code> of the last member of
       <var title="">sublist</var>, and the first member of <var title="">node list</var> is not a
-      <a href=#single-line-container>single-line container</a>, and the last member of
+      <a href=#non-list-single-line-container>non-list single-line container</a>, and the last member of
       <var title="">sublist</var> is not a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code>, remove the first member of <var title="">node
       list</var> and append it to <var title="">sublist</var>.
     </ol>
@@ -3964,6 +3977,13 @@
 <p class=XXX>Handle corner cases: endpoints are detached, documents, document
 fragments, html/body, head or things in head . . .
 
+<p class=XXX>Does not handle cases where block-extending the selection leaves
+it collapsed, like <code title="">&lt;p&gt;foo&lt;/p&gt;{}&lt;p&gt;bar&lt;/p&gt;</code>.
+
+<p class=XXX>The algorithm for preserving ranges doesn't work well here at all
+if the selection is collapsed.  The cursor can end up in the wrong place
+entirely.  Probably due to the wrap algorithm; that needs to be reexamined.
+
 <ol>
   <li>Let <var title="">items</var> be a list of all <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code>s that are
   <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#ancestor-container title="ancestor container">ancestor containers</a> of the <a href=#active-range>active range</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a>
@@ -3984,6 +4004,8 @@
   <code class=external data-anolis-spec=html title="the div element"><a href=http://www.whatwg.org/html/#the-div-element>div</a></code> or <code class=external data-anolis-spec=html title="the ol element"><a href=http://www.whatwg.org/html/#the-ol-element>ol</a></code> or <code class=external data-anolis-spec=html title="the ul element"><a href=http://www.whatwg.org/html/#the-ul-element>ul</a></code> and if no <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-ancestor title=concept-tree-ancestor>ancestor</a> of <var title="">node</var> is in
   <var title="">node list</var>, append <var title="">node</var> to <var title="">node list</var>.
 
+  <p class=XXX>Use "allowed child" definition here.
+
   <li>If the first member of <var title="">node list</var> is an <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> whose <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>
   is an <code class=external data-anolis-spec=html title="the ol element"><a href=http://www.whatwg.org/html/#the-ol-element>ol</a></code> or <code class=external data-anolis-spec=html title="the ul element"><a href=http://www.whatwg.org/html/#the-ul-element>ul</a></code>, and its <code class=external data-anolis-spec=domcore title=dom-Node-previousSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling>previousSibling</a></code> is an <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> as well,
   <a href=#normalize-sublists>normalize sublists</a> of its <code class=external data-anolis-spec=domcore title=dom-Node-previousSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling>previousSibling</a></code>.
@@ -4067,13 +4089,194 @@
 "ol".
 
 
-<h3 id=the-insertunorderedlist-command><span class=secno>7.10 </span><dfn>The <code title="">insertUnorderedList</code> command</dfn></h3>
+<h3 id=the-insertparagraph-command><span class=secno>7.10 </span><dfn>The <code title="">insertParagraph</code> command</dfn></h3>
+<!--
+There are three major behaviors here.  Firefox 5.0a2 behaves identically to
+execCommand("formatBlock", false, "p"), which is not really useful.  IE9
+actually just overwrites the selection with an empty paragraph element, which
+seems not very useful either.  Chrome 13 dev and Opera 11.10 behave basically
+the same as if the user hit the Return key.  This later behavior seems much
+more useful, even though it's horribly misnamed, so it's what I'll spec.
+
+Then, of course, we have several flavors of line-breaking behavior to choose
+from.  Firefox prefers <br>s, unless it's in the middle of a <p> or something.
+Opera and IE like <p>.  Chrome prefers <div>.  And there are lots of subtleties
+besides.  I go with IE/Opera-style behavior, as discussed in this thread:
+
+http://lists.whatwg.org/htdig.cgi/whatwg-whatwg.org/2011-May/031577.html
+-->
+
+<p><a href=#action>Action</a>:
+
+<p class=XXX>Under construction, probably makes no sense.
+
+<ol>
+  <li><a href=#delete-the-selection>Delete the selection</a>.
+
+  <li>Let <var title="">range</var> be the <a href=#active-range>active range</a>.
+
+  <li>Let <var title="">node</var> and <var title="">offset</var> be <var title="">range</var>'s
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a>.
+
+  <li>If <var title="">node</var> is a <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> node, and <var title="">offset</var> is neither 0
+  nor the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a> of <var title="">node</var>, call <code class=external data-anolis-spec=domcore title=dom-Text-splitText><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-text-splittext>splitText(<var title="">offset</var>)</a></code> on
+  <var title="">node</var>.
+
+  <li>If <var title="">node</var> is a <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> node and <var title="">offset</var> is its
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a>, set <var title="">offset</var> to one plus the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> of
+  <var title="">node</var>, then set <var title="">node</var> to its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
+
+  <li>If <var title="">node</var> is a <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> or <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment>Comment</a></code> node, set
+  <var title="">offset</var> to the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> of <var title="">node</var>, then set
+  <var title="">node</var> to its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
+
+  <li>Set <var title="">range</var>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a> to
+  (<var title="">node</var>, <var title="">offset</var>).
+
+  <li>If <var title="">node</var> has an <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>
+  <var title="">offset</var>, let <var title="">container</var> equal that <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a>.
+
+  <li>Otherwise, if <var title="">node</var> has an <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>
+  <var title="">offset</var> minus one, let <var title="">container</var> equal that <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a>.
+
+  <li>Otherwise, let <var title="">container</var> equal <var title="">node</var>.
+
+  <li>While <var title="">container</var> is not a <a href=#single-line-container>single-line container</a>,
+  and <var title="">container</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is <a href=#editable>editable</a> and <a href=#in-the-same-editing-host>in
+  the same editing host</a> as <var title="">node</var>, set <var title="">container</var> to
+  its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
+
+  <li>If <var title="">container</var> is not <a href=#editable>editable</a> or not <a href=#in-the-same-editing-host>in the
+  same editing host</a> as <var title="">node</var> or is not a <a href=#single-line-container>single-line
+  container</a>:
+  <!-- Add the default wrapper. -->
+
+  <ol>
+    <li><a href=#block-extend>Block-extend</a> <var title="">range</var>, and let <var title="">new
+    range</var> be the result.
+
+    <li>Let <var title="">node list</var> be a list of all <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a> of
+    <var title="">node</var> that are  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in <var title="">new range</var>.
+
+    <li>Let <var title="">tag</var> be the <a href=#default-single-line-container-name>default single-line container
+    name</a>.
+
+    <li>If <var title="">node list</var> is empty:
+
+    <ol>
+      <li>Set <var title="">container</var> to the result of calling
+      <code class=external data-anolis-spec=domcore title=dom-Document-createElement><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-createelement>createElement(<var title="">tag</var>)</a></code> on the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a>.
+
+      <li>Call <code class=external data-anolis-spec=domrange title=dom-Range-insertNode><a href=http://html5.org/specs/dom-range.html#dom-range-insertnode>insertNode(<var title="">container</var>)</a></code> on <var title="">range</var>.
+
+      <li>Call <code class=external data-anolis-spec=domcore title=dom-Document-createElement><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-createelement>createElement("br")</a></code> on the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a>, and append the
+      result as the last <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">container</var>.
+
+      <li>Set <var title="">range</var>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a> to
+      (<var title="">container</var>, 0).
+
+      <li>Abort these steps.
+    </ol>
+
+    <li><a href=#wrap>Wrap</a> <var title="">node list</var>, with <a href=#sibling-criteria>sibling
+    criteria</a> matching nothing and <a href=#new-parent-instructions>new parent instructions</a>
+    returning the result of calling <code class=external data-anolis-spec=domcore title=dom-Document-createElement><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-createelement>createElement(<var title="">tag</var>)</a></code> on the
+    <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a>.  Set <var title="">container</var> to the result.
+
+    <li>If <var title="">range</var>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> is not <var title="">container</var>, set
+    <var title="">range</var>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a> to
+    (<var title="">container</var>, 0).
+
+    <p class=XXX>This is a hack to work around range mutation rules not working
+    the way I'd like them to.  Should be fixed more sensibly.
+  </ol>
+
+  <li>If <var title="">container</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-element-local-name title=concept-element-local-name>local name</a> is "address" or "pre":
+  <!--
+  IE9 and Chrome 13 dev just break <pre> up into multiple <pre>s.  Firefox
+  5.0a2 and Opera 11.10 insert a <br> instead, treating it differently from
+  <p>.  The latter makes more sense.  What might make the most sense is to just
+  insert an actual newline character, though, since this is a pre after all
+  . . .
+
+  IE9 and Chrome 13 dev also break <address> up into multiple <address>es.
+  Firefox 5.0a2 inserts <br> instead.  Opera 11.10 nests <p>s inside.  I don't
+  like Opera's behavior, because it means we nest formatBlock candidates inside
+  one another, so I'll go with Firefox.
+  -->
+
+  <p class=XXX>Why don't we just insert a newline character if it's a pre?  We
+  don't really need to use br in that case.  Need to test: what do browsers do
+  if a pre element has newlines in it, do they convert to line breaks when you
+  make it no longer a pre?
+
+  <ol>
+    <li>Let <var title="">br</var> be the result of calling <code class=external data-anolis-spec=domcore title=dom-Document-createElement><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-createelement>createElement("br")</a></code> on
+    the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a>.
+
+    <li>Call <code class=external data-anolis-spec=domrange title=dom-Range-insertNode><a href=http://html5.org/specs/dom-range.html#dom-range-insertnode>insertNode(<var title="">br</var>)</a></code> on <var title="">range</var>.
+
+    <li>Increment <var title="">range</var>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a>
+    <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offsets</a>.
+
+    <li>If <var title="">br</var> is the last <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-descendant title=concept-tree-descendant>descendant</a> of <var title="">container</var>,
+    let <var title="">br</var> be the result of calling <code class=external data-anolis-spec=domcore title=dom-Document-createElement><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-createelement>createElement("br")</a></code> on the
+    <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a>, then call <code class=external data-anolis-spec=domrange title=dom-Range-insertNode><a href=http://html5.org/specs/dom-range.html#dom-range-insertnode>insertNode(<var title="">br</var>)</a></code> on
+    <var title="">range</var>.
+    <!-- Necessary because adding a br to the end of a block element does
+    nothing. -->
+
+    <li>Abort these steps.
+  </ol>
+
+  <li>Let <var title="">new container</var> be the result of calling <code class=external data-anolis-spec=domcore title=dom-Node-cloneNode><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-clonenode>cloneNode(false)</a></code> on
+  <var title="">container</var>.
+
+  <p class=XXX>Copies id's.
+
+  <li>Insert <var title="">new container</var> into the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> of
+  <var title="">container</var> immediately after <var title="">container</var>.
+
+  <li>Let <var title="">new line range</var> be a new <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> whose <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> is
+  the same as <var title="">range</var>'s, and whose <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a> is
+  (<var title="">container</var>, <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a> of <var title="">container</var>).
+
+  <li>Let <var title="">frag</var> be the result of calling <code class=external data-anolis-spec=domrange title=dom-Range-extractContents><a href=http://html5.org/specs/dom-range.html#dom-range-extractcontents>extractContents()</a></code> on <var title="">new line
+  range</var>.
+
+  <p class=XXX>This blows up any ranges (other than the selection, which we
+  reset), duplicates id's, and other bad stuff.  May or may not be the best
+  solution.  The intermediate fragment is also probably black-box detectable by
+  DOM mutation events, but I like to pretend those don't exist.
+
+  <li>Call <code class=external data-anolis-spec=domcore title=dom-Node-appendChild><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-appendchild>appendChild(<var title="">frag</var>)</a></code> on <var title="">new
+  container</var>.
+
+  <li>If <var title="">container</var> has no <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a>, call <code class=external data-anolis-spec=domcore title=dom-Document-createElement><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-createelement>createElement("br")</a></code>
+  on the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a>, and append the result as the last <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of
+  <var title="">container</var>.
+
+  <li>If <var title="">new container</var> has no <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a>, call
+  <code class=external data-anolis-spec=domcore title=dom-Document-createElement><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-createelement>createElement("br")</a></code> on the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a>, and append the result as the
+  last <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">new container</var>.
+
+  <p class=XXX>Needs to also happen if there are only comments/whitespace/etc.
+  Also, this can leave stray useless br's lying around.  Probably when the user
+  starts typing, we want to remove the br.  On the flip side, if the user
+  deletes all the contents of a paragraph, we likely need to add a br.
+
+  <li>Set the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> of <var title="">range</var> to (<var title="">new container</var>,
+  0).
+</ol>
+
+
+<h3 id=the-insertunorderedlist-command><span class=secno>7.11 </span><dfn>The <code title="">insertUnorderedList</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#toggle-lists>Toggle lists</a> with <var title="">tag name</var>
 "ul".
 
 
-<h3 id=the-outdent-command><span class=secno>7.11 </span><dfn>The <code title="">outdent</code> command</dfn></h3>
+<h3 id=the-outdent-command><span class=secno>7.12 </span><dfn>The <code title="">outdent</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>:
 
--- a/implementation.js	Thu May 26 11:52:13 2011 -0600
+++ b/implementation.js	Sun May 29 13:07:20 2011 -0600
@@ -3,6 +3,7 @@
 var htmlNamespace = "http://www.w3.org/1999/xhtml";
 
 var cssStylingFlag = false;
+var defaultSingleLineContainerName = "p";
 
 // Utility functions
 function nextNode(node) {
@@ -1910,17 +1911,19 @@
 			startOffset = getNodeIndex(startNode);
 			startNode = startNode.parentNode;
 
-		// "Otherwise, if start offset is equal to the length of start
-		// node, set start offset to one plus the index of start node and
-		// then set start node to its parent."
-		} else if (startOffset == getNodeLength(startNode)) {
-			startOffset = 1 + getNodeIndex(startNode);
-			startNode = startNode.parentNode;
-
-		// "Otherwise, if the child of start node with index start offset and
-		// its previousSibling are both inline nodes and the previousSibling
-		// isn't a br, subtract one from start offset."
-		} else if (isInlineNode(startNode.childNodes[startOffset])
+		// "Otherwise, if start offset is start node's length and start node's
+		// last child is an inline node that's not a br, subtract one from
+		// start offset."
+		} else if (startOffset == getNodeLength(startNode)
+		&& isInlineNode(startNode.lastChild)
+		&& !isHtmlElement(startNode.lastChild, "br")) {
+			startOffset--;
+
+		// "Otherwise, if start node has a child with index start offset, and
+		// that child and its previousSibling are both inline nodes and the
+		// previousSibling isn't a br, subtract one from start offset."
+		} else if (startOffset < startNode.childNodes.length
+		&& isInlineNode(startNode.childNodes[startOffset])
 		&& isInlineNode(startNode.childNodes[startOffset].previousSibling)
 		&& !isHtmlElement(startNode.childNodes[startOffset].previousSibling, "BR")) {
 			startOffset--;
@@ -1948,25 +1951,27 @@
 
 	// "Repeat the following steps:"
 	while (true) {
-		// "If end offset is 0, set end offset to the index of end node and
-		// then set end node to its parent."
-		if (endOffset == 0) {
-			endOffset = getNodeIndex(endNode);
-			endNode = endNode.parentNode;
-
-		// "Otherwise, if end node is a Text or Comment node or end offset
-		// is equal to the length of end node, set end offset to one plus
-		// the index of end node and then set end node to its parent."
-		} else if (endNode.nodeType == Node.TEXT_NODE
+		// "If end node is a Text or Comment node or end offset is equal to the
+		// length of end node, set end offset to one plus the index of end node
+		// and then set end node to its parent."
+		if (endNode.nodeType == Node.TEXT_NODE
 		|| endNode.nodeType == Node.COMMENT_NODE
 		|| endOffset == getNodeLength(endNode)) {
 			endOffset = 1 + getNodeIndex(endNode);
 			endNode = endNode.parentNode;
 
-		// "Otherwise, if the child of end node with index end offset and its
-		// previousSibling are both inline nodes, and the child of end node
-		// with index end offset isn't a br, add one to end offset."
-		} else if (isInlineNode(endNode.childNodes[endOffset])
+		// "Otherwise, if end offset is 0 and end node's first child is an
+		// inline node that's not a br, add one to end offset."
+		} else if (endOffset == 0
+		&& isInlineNode(endNode.firstChild)
+		&& !isHtmlElement(endNode.firstChild, "br")) {
+			endOffset++;
+
+		// "Otherwise, if end node has a child with index end offset, and that
+		// child and its previousSibling are both inline nodes, and the
+		// previousSibling isn't a br, add one to end offset."
+		} else if (endOffset < endNode.childNodes.length
+		&& isInlineNode(endNode.childNodes[endOffset])
 		&& isInlineNode(endNode.childNodes[endOffset].previousSibling)
 		&& !isHtmlElement(endNode.childNodes[endOffset], "BR")) {
 			endOffset++;
@@ -2800,9 +2805,6 @@
 		break;
 
 		case "formatblock":
-		var singleLineContainerNames = ["ADDRESS", "DIV", "H1", "H2", "H3",
-			"H4", "H5", "H6", "P", "PRE"];
-
 		// "If value begins with a "<" character and ends with a ">" character,
 		// remove the first and last characters from it."
 		if (/^<.*>$/.test(value)) {
@@ -2814,7 +2816,8 @@
 
 		// "If value is not "address", "div", "h1", "h2", "h3", "h4", "h5",
 		// "h6", "p", or "pre", then do nothing and abort these steps."
-		if (singleLineContainerNames.indexOf(value.toUpperCase()) == -1) {
+		if (["ADDRESS", "DIV", "H1", "H2", "H3", "H4", "H5", "H6", "P",
+		"PRE"].indexOf(value.toUpperCase()) == -1) {
 			return;
 		}
 
@@ -2864,11 +2867,11 @@
 		// "If value is "div" or "p", then while node list is not empty:"
 		if (value == "div" || value == "p") {
 			while (nodeList.length) {
-				// "If the first member of node list is a single-line
+				// "If the first member of node list is a non-list single-line
 				// container, set the tag name of the first member of node list
 				// to value, then remove the first member from node list and
 				// continue this loop from the beginning."
-				if (isHtmlElement(nodeList[0], singleLineContainerNames)) {
+				if (isNonListSingleLineContainer(nodeList[0])) {
 					setTagName(nodeList[0], value);
 					nodeList.shift();
 					continue;
@@ -2883,13 +2886,13 @@
 
 				// "While node list is not empty, and the first member of node
 				// list is the nextSibling of the last member of sublist, and
-				// the first member of node list is not a single-line
+				// the first member of node list is not a non-list single-line
 				// container, and the last member of sublist is not a br,
 				// remove the first member of node list and append it to
 				// sublist."
 				while (nodeList.length
 				&& nodeList[0] == sublist[sublist.length - 1].nextSibling
-				&& !isHtmlElement(nodeList[0], singleLineContainerNames)
+				&& !isNonListSingleLineContainer(nodeList[0])
 				&& !isHtmlElement(sublist[sublist.length - 1], "BR")) {
 					sublist.push(nodeList.shift());
 				}
@@ -2907,9 +2910,9 @@
 			while (nodeList.length) {
 				var sublist;
 
-				// "If the first member of node list is a single-line
+				// "If the first member of node list is a non-list single-line
 				// container:"
-				if (isHtmlElement(nodeList[0], singleLineContainerNames)) {
+				if (isNonListSingleLineContainer(nodeList[0])) {
 					// "Let sublist be the children of the first member of node
 					// list."
 					sublist = [].slice.call(nodeList[0].childNodes);
@@ -2933,12 +2936,12 @@
 					// "While node list is not empty, and the first member of
 					// node list is the nextSibling of the last member of
 					// sublist, and the first member of node list is not a
-					// single-line container, and the last member of sublist is
-					// not a br, remove the first member of node list and
-					// append it to sublist."
+					// non-list single-line container, and the last member of
+					// sublist is not a br, remove the first member of node
+					// list and append it to sublist."
 					while (nodeList.length
 					&& nodeList[0] == sublist[sublist.length - 1].nextSibling
-					&& !isHtmlElement(nodeList[0], singleLineContainerNames)
+					&& !isNonListSingleLineContainer(nodeList[0])
 					&& !isHtmlElement(sublist[sublist.length - 1], "BR")) {
 						sublist.push(nodeList.shift());
 					}
@@ -3127,6 +3130,196 @@
 		toggleLists(range, "ol");
 		break;
 
+		case "insertparagraph":
+		// "Delete the selection."
+		deleteSelection();
+
+		// "Let node and offset be range's start node and offset."
+		var node = range.startContainer;
+		var offset = range.startOffset;
+
+		// "If node is a Text node, and offset is neither 0 nor the length of
+		// node, call splitText(offset) on node."
+		if (node.nodeType == Node.TEXT_NODE
+		&& offset != 0
+		&& offset != getNodeLength(node)) {
+			node.splitText(offset);
+		}
+
+		// "If node is a Text node and offset is its length, set offset to one
+		// plus the index of node, then set node to its parent."
+		if (node.nodeType == Node.TEXT_NODE
+		&& offset == getNodeLength(node)) {
+			offset = 1 + getNodeIndex(node);
+			node = node.parentNode;
+		}
+
+		// "If node is a Text or Comment node, set offset to the index of node,
+		// then set node to its parent."
+		if (node.nodeType == Node.TEXT_NODE
+		|| node.nodeType == Node.COMMENT_NODE) {
+			offset = getNodeIndex(node);
+			node = node.parentNode;
+		}
+
+		// "Set range's start and end to (node, offset)."
+		range.setStart(node, offset);
+		range.setEnd(node, offset);
+
+		// "If node has an Element child of index offset, let container equal
+		// that child."
+		var container;
+		if (offset < node.childNodes.length
+		&& node.childNodes[offset].nodeType == Node.ELEMENT_NODE) {
+			container = node.childNodes[offset];
+
+		// "Otherwise, if node has an Element child of index offset minus one,
+		// let container equal that child."
+		} else if (offset > 0
+		&& offset - 1 < node.childNodes.length
+		&& node.childNodes[offset - 1].nodeType == Node.ELEMENT_NODE) {
+			container = node.childNodes[offset - 1];
+
+		// "Otherwise, let container equal node."
+		} else {
+			container = node;
+		}
+
+		// "While container is not a single-line container, and container's
+		// parent is editable and in the same editing host as node, set
+		// container to its parent."
+		while (!isSingleLineContainer(container)
+		&& isEditable(container.parentNode)
+		&& inSameEditingHost(node, container.parentNode)) {
+			container = container.parentNode;
+		}
+
+		// "If container is not editable or not in the same editing host as
+		// node or is not a single-line container:"
+		if (!isEditable(container)
+		|| !inSameEditingHost(container, node)
+		|| !isSingleLineContainer(container)) {
+			// "Block-extend range, and let new range be the result."
+			var newRange = blockExtendRange(range);
+
+			// "Let node list be a list of all children of node that are
+			// contained in new range."
+			var nodeList = [].filter.call(node.childNodes, function(child) { return isContained(child, newRange) });
+
+			// "Let tag be the default single-line container name."
+			var tag = defaultSingleLineContainerName;
+
+			// "If node list is empty:"
+			if (!nodeList.length) {
+				// "Set container to the result of calling createElement(tag)
+				// on the context object."
+				container = document.createElement(tag);
+
+				// "Call insertNode(container) on range."
+				range.insertNode(container);
+
+				// "Call createElement("br") on the context object, and append
+				// the result as the last child of container."
+				container.appendChild(document.createElement("br"));
+
+				// "Set range's start and end to (container, 0)."
+				range.setStart(container, 0);
+				range.setEnd(container, 0);
+
+				// "Abort these steps."
+				return;
+			}
+
+			// "Wrap node list, with sibling criteria matching nothing and new
+			// parent instructions returning the result of calling
+			// createElement(tag) on the context object.  Set container to the
+			// result."
+			container = wrap(nodeList,
+				function() { return false },
+				function() { return document.createElement(tag) }
+			);
+
+			// "If range's start node is not container, set range's start and
+			// end to (container, 0)."
+			if (range.startContainer != container) {
+				range.setStart(container, 0);
+				range.setEnd(container, 0);
+			}
+		}
+
+		// "If container's local name is "address" or "pre":"
+		if (container.tagName == "ADDRESS"
+		|| container.tagName == "PRE") {
+			// "Let br be the result of calling createElement("br") on the
+			// context object."
+			var br = document.createElement("br");
+
+			// "Call insertNode(br) on range."
+			//
+			// Work around browser bugs: some browsers select the inserted
+			// node, not per spec.
+			range.insertNode(br);
+			range.setEnd(range.startContainer, range.startOffset);
+
+			// "Increment range's start and end offsets."
+			//
+			// Incrementing the start will increment the end as well, because
+			// the range is collapsed.
+			range.setStart(range.startContainer, range.startOffset + 1);
+
+			// "If br is the last descendant of container, let br be the result
+			// of calling createElement("br") on the context object, then call
+			// insertNode(br) on range."
+			//
+			// Work around browser bugs again.
+			if (!isDescendant(nextNode(br), container)) {
+				range.insertNode(document.createElement("br"));
+				range.setEnd(range.startContainer, range.startOffset);
+			}
+
+			// "Abort these steps."
+			return;
+		}
+
+		// "Let new container be the result of calling cloneNode(false) on
+		// container."
+		var newContainer = container.cloneNode(false);
+
+		// "Insert new container into the parent of container immediately after
+		// container."
+		container.parentNode.insertBefore(newContainer, container.nextSibling);
+
+		// "Let new line range be a new range whose start is the same as
+		// range's, and whose end is (container, length of container)."
+		var newLineRange = document.createRange();
+		newLineRange.setStart(range.startContainer, range.startOffset);
+		newLineRange.setEnd(container, getNodeLength(container));
+
+		// "Let frag be the result of calling extractContents() on new line
+		// range."
+		var frag = newLineRange.extractContents();
+
+		// "Call appendChild(frag) on new container."
+		newContainer.appendChild(frag);
+
+		// "If container has no children, call createElement("br") on the
+		// context object, and append the result as the last child of
+		// container."
+		if (!container.hasChildNodes()) {
+			container.appendChild(document.createElement("br"));
+		}
+
+		// "If new container has no children, call createElement("br") on the
+		// context object, and append the result as the last child of new
+		// container."
+		if (!newContainer.hasChildNodes()) {
+			newContainer.appendChild(document.createElement("br"));
+		}
+
+		// "Set the start of range to (new container, 0)."
+		range.setStart(newContainer, 0);
+		break;
+
 		case "insertunorderedlist":
 		// "Toggle lists with tag name "ul"."
 		toggleLists(range, "ul");
@@ -3995,6 +4188,20 @@
 	return true;
 }
 
+// "A non-list single-line container is an HTML element with local name
+// "address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "p", or "pre"."
+function isNonListSingleLineContainer(node) {
+	return isHtmlElement(node, ["address", "div", "h1", "h2", "h3", "h4", "h5",
+		"h6", "p", "pre"]);
+}
+
+// "A single-line container is either a non-list single-line container, or an
+// HTML element with local name "li", "dt", or "dd"."
+function isSingleLineContainer(node) {
+	return isNonListSingleLineContainer(node)
+		|| isHtmlElement(node, ["li", "dt", "dd"]);
+}
+
 function myQueryCommandState(command) {
 	command = command.toLowerCase();
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/linebreaktest.html	Sun May 29 13:07:20 2011 -0600
@@ -0,0 +1,520 @@
+<!doctype html>
+<!-- This is going to be merged into autoimplementation.html at some point. -->
+<meta charset=utf-8>
+<title>Line-breaking tests</title>
+<style>
+body { font-family: serif }
+.yes { color: green }
+.no { color: red }
+.maybe { color: orange }
+.yes, .no, .maybe {
+	text-align: center;
+	vertical-align: middle;
+	font-size: 3em;
+	/* Somehow Opera doesn't render the X's if the font is serif, on my
+	 * machine. */
+	font-family: sans-serif;
+	border-color: black;
+}
+div.alert {
+	color: red;
+	font-weight: bold;
+}
+/* http://www.w3.org/Bugs/Public/show_bug.cgi?id=12154
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=589124
+ * https://bugs.webkit.org/show_bug.cgi?id=56400 */
+b, strong { font-weight: bold }
+.bold { font-weight: bold }
+.notbold { font-weight: normal }
+.underline { text-decoration: underline }
+.line-through { text-decoration: line-through }
+.underline-and-line-through { text-decoration: underline line-through }
+#purple { color: purple }
+body > div > table > tbody > tr > td > div:first-child {
+	padding-bottom: 0.2em;
+}
+body > div > table > tbody > tr > td > div:last-child {
+	padding-top: 0.2em;
+	border-top: 1px solid black;
+}
+/* https://bugs.webkit.org/show_bug.cgi?id=56670 */
+dfn { font-style: italic }
+/* Opera has weird default blockquote style */
+blockquote { margin: 1em 40px }
+/* Let the rendered HTML line up so it's easier to compare whitespace */
+body > div > table > tbody > tr > td { vertical-align: top }
+/* We don't want test cells to not wrap */
+listing, plaintext, pre, xmp { white-space: pre-wrap }
+img, video { width: 50px }
+body > div > table {
+	width: 100%;
+	table-layout: fixed;
+}
+body > div > table > tbody > tr > td,
+body > div > table > tbody > tr > th {
+	width: 50%;
+}
+/* For testing */
+ol ol { list-style-type: lower-alpha }
+ol ol ol { list-style-type: lower-roman }
+#overlay {
+	display: none;
+	position: fixed;
+	top: 0;
+	bottom: 0;
+	left: 0;
+	right: 0;
+	color: red;
+	background: yellow;
+	font-size: 4em;
+	font-weight: bold;
+	text-align: center;
+	padding: 2em;
+}
+</style>
+<p>Legend: {[ are the selection anchor, }] are the selection focus, {}
+represent an element boundary point, [] represent a text node boundary point.
+Syntax and some of the tests taken from <a
+href=http://www.browserscope.org/richtext2/test>Browserscope</a>.  data-start
+and data-end attributes also represent element boundary points, with the node
+being the element with the attribute and the offset given as the attribute
+value, for cases where HTML parsing doesn't allow text nodes.  Currently we
+don't really pay attention to reversed selections at all, so they might get
+displayed as forwards or such.
+
+<p><input type=button value="Clear cached results" onclick="clearCachedResults()">
+
+<div id=tests>
+	<input type=button value="Run tests" onclick="runTests()">
+	<table border=1><tr><th>Input<th>Output</table>
+	<p><label>New test input: <input></label> <input type=button value="Add test" onclick="addTest()">
+</div>
+
+<div id=overlay>Tap enter repeatedly until this annoying message
+disappears!  (But not too quickly.  And don't hit any other keys or click with
+the mouse anywhere, it will mess it up.)</div>
+
+<script src=implementation.js></script>
+<script>
+var tests = [
+	'foo[bar]baz',
+
+	'[]foo',
+	'foo[]',
+	'foo[]bar',
+	'<address>[]foo</address>',
+	'<address>foo[]</address>',
+	'<address>foo[]bar</address>',
+	'<div>[]foo</div>',
+	'<div>foo[]</div>',
+	'<div>foo[]bar</div>',
+	'<dl><dt>[]foo<dd>bar</dl>',
+	'<dl><dt>foo[]<dd>bar</dl>',
+	'<dl><dt>foo[]bar<dd>baz</dl>',
+	'<dl><dt>foo<dd>[]bar</dl>',
+	'<dl><dt>foo<dd>bar[]</dl>',
+	'<dl><dt>foo<dd>bar[]baz</dl>',
+	'<h1>[]foo</h1>',
+	'<h1>foo[]</h1>',
+	'<h1>foo[]bar</h1>',
+	'<ol><li>[]foo</ol>',
+	'<ol><li>foo[]</ol>',
+	'<ol><li>foo[]bar</ol>',
+	'<p>[]foo</p>',
+	'<p>foo[]</p>',
+	'<p>foo[]bar</p>',
+	'<pre>[]foo</pre>',
+	'<pre>foo[]</pre>',
+	'<pre>foo[]bar</pre>',
+
+	'<ol><li>foo<li>{}<br></ol>',
+
+	'<h1>foo[bar</h1><p>baz]quz</p>',
+	'<p>foo[bar</p><h1>baz]quz</h1>',
+	'<p>foo</p>{}',
+	'{}<p>foo</p>',
+	'<p>foo</p>{}<h1>bar</h1>',
+	'<h1>foo</h1>{}<p>bar</p>',
+	'<p>foo</p><h1>[bar]</h1><p>baz</p>',
+	'<p>foo</p>{<h1>bar</h1>}<p>baz</p>',
+
+	'foo<br>bar[]',
+	'foo[]<br>bar',
+	'foo<br>[]bar',
+];
+
+var testsRunning = false;
+
+function clearCachedResults() {
+	for (var key in localStorage) {
+		if (/^linebreaktest-/.test(key)) {
+			localStorage.removeItem(key);
+		}
+	}
+}
+
+function runTests() {
+	testsRunning = true;
+	var runTestsButton = document.querySelector("#tests input[type=button]");
+	runTestsButton.parentNode.removeChild(runTestsButton);
+
+	var addTestButton = document.querySelector("#tests input[type=button]");
+	var input = document.querySelector("#tests label input");
+	// This code actually focuses and clicks everything because for some
+	// reason, anything else doesn't work in IE9 . . .
+	input.value = tests[0];
+	input.focus();
+	addTestButton.click();
+}
+
+function addTest() {
+	var tr = doSetup();
+	var input = document.querySelector("#tests label input");
+	var test = input.value;
+	doInputCell(tr, test);
+	if (localStorage.getItem("linebreaktest-" + test) !== null) {
+		// Yay, I get to cheat
+		var browserCell = document.createElement("td");
+		tr.appendChild(browserCell);
+		browserCell.innerHTML = localStorage["linebreaktest-" + test];
+		runNextTest(test);
+	} else {
+		doBrowserCell(tr, test, function() {
+			runNextTest(test);
+		});
+	}
+}
+
+function runNextTest(test) {
+	doTearDown();
+	var input = document.querySelector("#tests label input");
+	if (!testsRunning) {
+		document.getElementById("overlay").style.display = "none";
+		return;
+	}
+	var idx = tests.indexOf(test);
+	if (idx != tests.lastIndexOf(test)) {
+		// Cheap and effective error reporting
+		document.body.textContent = "Duplicate test: " + test;
+	}
+	if (idx + 1 >= tests.length) {
+		document.getElementById("overlay").style.display = "none";
+		testsRunning = false;
+		input.value = "";
+		return;
+	}
+	input.value = tests[idx + 1];
+	input.focus();
+	addTest();
+}
+
+function doSetup() {
+	var table = document.querySelector("#tests table");
+
+	var tr = document.createElement("tr");
+	table.firstChild.appendChild(tr);
+
+	return tr;
+}
+
+function doInputCell(tr, test) {
+	var inputCell = document.createElement("td");
+	inputCell.innerHTML = "<div></div><div></div>";
+	inputCell.firstChild.innerHTML = test;
+	inputCell.lastChild.textContent = inputCell.firstChild.innerHTML;
+	tr.appendChild(inputCell);
+}
+
+function doBrowserCell(tr, test, callback) {
+	var browserCell = document.createElement("td");
+	tr.appendChild(browserCell);
+
+	try {
+		var points = setupCell(browserCell, test);
+
+		var testDiv = browserCell.firstChild;
+		// Work around weird Firefox bug:
+		// https://bugzilla.mozilla.org/show_bug.cgi?id=649138
+		document.body.appendChild(testDiv);
+		testDiv.onkeyup = function() {
+			continueBrowserCell(test, testDiv, browserCell);
+			callback();
+		};
+		testDiv.contentEditable = "true";
+		testDiv.spellcheck = false;
+		document.getElementById("overlay").style.display = "block";
+		testDiv.focus();
+		setSelection(points[0], points[1], points[2], points[3]);
+	} catch (e) {
+		browserCellException(e, testDiv, browserCell);
+		callback();
+	}
+}
+
+function continueBrowserCell(test, testDiv, browserCell) {
+	try {
+		testDiv.contentEditable = "inherit";
+		testDiv.removeAttribute("spellcheck");
+		var compareDiv1 = testDiv.cloneNode(true);
+
+		if (getSelection().rangeCount) {
+			addBrackets(getSelection().getRangeAt(0));
+		}
+		browserCell.insertBefore(testDiv, browserCell.firstChild);
+
+		if (!browserCell.childNodes.length == 2) {
+			throw "The cell didn't have two children.  Did something spill outside the test div?";
+		}
+
+		compareDiv1.normalize();
+		// Sigh, Gecko is crazy
+		var treeWalker = document.createTreeWalker(compareDiv1, NodeFilter.SHOW_ELEMENT, null, null);
+		while (treeWalker.nextNode()) {
+			var remove = [].filter.call(treeWalker.currentNode.attributes, function(attrib) {
+				return /^_moz_/.test(attrib.name) || attrib.value == "_moz";
+			});
+			for (var i = 0; i < remove.length; i++) {
+				treeWalker.currentNode.removeAttribute(remove[i].name);
+			}
+		}
+		var compareDiv2 = compareDiv1.cloneNode(false);
+		compareDiv2.innerHTML = compareDiv1.innerHTML;
+		if (!compareDiv1.isEqualNode(compareDiv2)
+		&& compareDiv1.innerHTML != compareDiv2.innerHTML) {
+			throw "DOM does not round-trip through serialization!  "
+				+ compareDiv1.innerHTML + " vs. " + compareDiv2.innerHTML;
+		}
+		if (!compareDiv1.isEqualNode(compareDiv2)) {
+			throw "DOM does not round-trip through serialization (although innerHTML is the same)!  "
+				+ testDiv.innerHTML;
+		}
+
+		browserCell.lastChild.textContent = browserCell.firstChild.innerHTML;
+	} catch (e) {
+		browserCellException(e, testDiv, browserCell);
+	}
+
+	localStorage["linebreaktest-" + test] = browserCell.innerHTML;
+}
+
+function browserCellException(e, testDiv, browserCell) {
+	if (testDiv) {
+		testDiv.contenteditable = "inherit";
+		testDiv.removeAttribute("spellcheck");
+	}
+	browserCell.lastChild.className = "alert";
+	browserCell.lastChild.textContent = "Exception: " + e;
+	if (typeof e == "object" && "stack" in e) {
+		specCell.lastChild.textContent += " (stack: " + e.stack + ")";
+	}
+	if (testDiv && testDiv.parentNode != browserCell) {
+		browserCell.insertBefore(testDiv, browserCell.firstChild);
+	}
+}
+
+function doTearDown(command) {
+	getSelection().removeAllRanges();
+}
+
+function setupCell(cell, test) {
+	cell.innerHTML = "<div></div><div></div>";
+
+	// A variety of checks to avoid simple errors.  Not foolproof, of course.
+	var re = /\{|\[|data-start/g;
+	var markers = [];
+	var marker;
+	while (marker = re.exec(test)) {
+		markers.push(marker);
+	}
+	if (markers.length != 1) {
+		throw "Need exactly one start marker ([ or { or data-start), found " + markers.length;
+	}
+
+	var re = /\}|\]|data-end/g;
+	var markers = [];
+	var marker;
+	while (marker = re.exec(test)) {
+		markers.push(marker);
+	}
+	if (markers.length != 1) {
+		throw "Need exactly one end marker (] or } or data-end), found " + markers.length;
+	}
+
+	var node = cell.firstChild;
+	node.innerHTML = test;
+
+	var startNode, startOffset, endNode, endOffset;
+
+	// For braces that don't lie inside text nodes, we can't just set
+	// innerHTML, because that might disturb the DOM.  For instance, if the
+	// brace is right before a <tr>, it could get moved outside the table
+	// entirely, which messes everything up pretty badly.  So we instead
+	// allow using data attributes: data-start and data-end on the start and
+	// end nodes, with a numeric value indicating the offset.  This format
+	// doesn't allow the parent div to be a start or end node, but in that case
+	// you can always use the curly braces.
+	if (node.querySelector("[data-start]")) {
+		startNode = node.querySelector("[data-start]");
+		startOffset = startNode.getAttribute("data-start");
+		startNode.removeAttribute("data-start");
+	}
+	if (node.querySelector("[data-end]")) {
+		endNode = node.querySelector("[data-end]");
+		endOffset = endNode.getAttribute("data-end");
+		endNode.removeAttribute("data-end");
+	}
+
+	var cur = node;
+	while (true) {
+		if (!cur || (cur != node && !(cur.compareDocumentPosition(node) & Node.DOCUMENT_POSITION_CONTAINS))) {
+			break;
+		}
+
+		if (cur.nodeType != Node.TEXT_NODE) {
+			cur = nextNode(cur);
+			continue;
+		}
+
+		var data = cur.data.replace(/\]/g, "");
+		var startIdx = data.indexOf("[");
+
+		data = cur.data.replace(/\[/g, "");
+		var endIdx = data.indexOf("]");
+
+		cur.data = cur.data.replace(/[\[\]]/g, "");
+
+		if (startIdx != -1) {
+			startNode = cur;
+			startOffset = startIdx;
+		}
+
+		if (endIdx != -1) {
+			endNode = cur;
+			endOffset = endIdx;
+		}
+
+		// These are only legal as the first or last
+		data = cur.data.replace(/\}/g, "");
+		var elStartIdx = data.indexOf("{");
+
+		data = cur.data.replace(/\{/g, "");
+		var elEndIdx = data.indexOf("}");
+
+		if (elStartIdx == 0) {
+			startNode = cur.parentNode;
+			startOffset = getNodeIndex(cur);
+		} else if (elStartIdx != -1) {
+			startNode = cur.parentNode;
+			startOffset = getNodeIndex(cur) + 1;
+		}
+		if (elEndIdx == 0) {
+			endNode = cur.parentNode;
+			endOffset = getNodeIndex(cur);
+		} else if (elEndIdx != -1) {
+			endNode = cur.parentNode;
+			endOffset = getNodeIndex(cur) + 1;
+		}
+
+		cur.data = cur.data.replace(/[{}]/g, "");
+		if (!cur.data.length) {
+			if (cur == startNode || cur == endNode) {
+				throw "You put a square bracket where there was no text node . . .";
+			}
+			var oldCur = cur;
+			cur = nextNode(cur);
+			oldCur.parentNode.removeChild(oldCur);
+		} else {
+			cur = nextNode(cur);
+		}
+	}
+
+	return [startNode, startOffset, endNode, endOffset];
+}
+
+function setSelection(startNode, startOffset, endNode, endOffset) {
+	if (navigator.userAgent.indexOf("Opera") != -1) {
+		var range = document.createRange();
+		range.setStart(startNode, startOffset);
+		range.setEnd(endNode, endOffset);
+		if (range.collapsed) {
+			range.setEnd(startNode, startOffset);
+		}
+		getSelection().removeAllRanges();
+		getSelection().addRange(range);
+	} else if ("extend" in getSelection()) {
+		// WebKit behaves unreasonably for collapse(), so do that manually.
+		/*
+		var range = document.createRange();
+		range.setStart(startNode, startOffset);
+		getSelection().removeAllRanges();
+		getSelection().addRange(range);
+		*/
+		getSelection().collapse(startNode, startOffset);
+		getSelection().extend(endNode, endOffset);
+	} else {
+		// IE9.  Selections have no direction, so we just make the selection
+		// always forwards.
+		var range;
+		if (getSelection().rangeCount) {
+			range = getSelection().getRangeAt(0);
+		} else {
+			range = document.createRange();
+		}
+		range.setStart(startNode, startOffset);
+		range.setEnd(endNode, endOffset);
+		if (range.collapsed) {
+			// Phooey, we got them backwards.
+			range.setEnd(startNode, startOffset);
+		}
+		if (!getSelection().rangeCount) {
+			getSelection().addRange(range);
+		}
+	}
+}
+
+/**
+ * Add brackets at the start and end points of the given range, so that they're
+ * visible.
+ */
+function addBrackets(range) {
+	// Do the end first, so that if the range is collapsed, the start point
+	// will be inserted before the end point.
+	if (range.endContainer.nodeType == Node.TEXT_NODE) {
+		range.endContainer.insertData(range.endOffset, "]");
+	} else {
+		// As everyone knows, the only node types are Text and Element.
+		if (range.endOffset != range.endContainer.childNodes.length
+		&& range.endContainer.childNodes[range.endOffset].nodeType == Node.TEXT_NODE) {
+			range.endContainer.childNodes[range.endOffset].insertData(0, "}");
+		} else if (range.endOffset != 0
+		&& range.endContainer.childNodes[range.endOffset - 1].nodeType == Node.TEXT_NODE) {
+			range.endContainer.childNodes[range.endOffset - 1].appendData("}");
+		} else {
+			// Seems to serialize as I'd want even for tables . . . IE doesn't
+			// allow undefined to be passed as the second argument (it throws
+			// an exception), so we have to explicitly check the number of
+			// children and pass null.
+			range.endContainer.insertBefore(document.createTextNode("}"),
+				range.endContainer.childNodes.length == range.endOffset
+				? null
+				: range.endContainer.childNodes[range.endOffset]);
+		}
+	}
+	if (range.startContainer.nodeType == Node.TEXT_NODE) {
+		range.startContainer.insertData(range.startOffset, "[");
+	} else {
+		if (range.startOffset != range.startContainer.childNodes.length
+		&& range.startContainer.childNodes[range.startOffset].nodeType == Node.TEXT_NODE) {
+			range.startContainer.childNodes[range.startOffset].insertData(0, "{");
+		} else if (range.startOffset != 0
+		&& range.startContainer.childNodes[range.startOffset - 1].nodeType == Node.TEXT_NODE) {
+			range.startContainer.childNodes[range.startOffset - 1].appendData("{");
+		} else {
+		range.startContainer.insertBefore(document.createTextNode("{"),
+			range.startContainer.childNodes.length == range.startOffset
+			? null
+			: range.startContainer.childNodes[range.startOffset]);
+		}
+	}
+}
+</script>
--- a/preprocess	Thu May 26 11:52:13 2011 -0600
+++ b/preprocess	Sun May 29 13:07:20 2011 -0600
@@ -4,6 +4,7 @@
 # <span data-anolis-spec=domcore title=concept-element-namespace>namespace</span>.
 # <var title> is also really pointless, although I should really get that fixed
 # in Anolis proper.
+import re
 
 replace = {
     'a': '<code data-anolis-spec=html title="the a element">a</code>',
@@ -89,12 +90,21 @@
     s = s.replace("[[" + key + "]]", replace[key])
     # Plurals
     s = s.replace("[[" + key + "s]]", replace[key].replace("</", "s</"))
+    s = s.replace("[[" + key + "es]]", replace[key].replace("</", "es</"))
     # Capitals
     capreplace = replace[key].split(">")
     capreplace[1] = capreplace[1].capitalize()
     capreplace = ">".join(capreplace)
     s = s.replace("[[" + key.capitalize() + "]]", capreplace)
 
+fnreplace = {
+    'createelement': '<code data-anolis-spec=domcore title=dom-Document-createElement>createElement(\\1)</code>',
+    'insertnode': '<code data-anolis-spec=domrange title=dom-Range-insertNode>insertNode(\\1)</code>',
+}
+
+for key in fnreplace:
+    s = re.sub(r"\[\[" + key + "\|([^]]*)\]\]", fnreplace[key], s)
+
 if "[[" in s:
     raise Exception("Something mistyped?  " + s[s.find("[["):s.rfind("]]") + 2])
 
--- a/source.html	Thu May 26 11:52:13 2011 -0600
+++ b/source.html	Sun May 29 13:07:20 2011 -0600
@@ -2700,9 +2700,16 @@
   attribute.
 </ul>
 
-<p>A <dfn>single-line container</dfn> is an <span>HTML element</span> with
-[[localname]] "address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "p", or
-"pre".
+<p>A <dfn>non-list single-line container</dfn> is an <span>HTML element</span>
+with [[localname]] "address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "p",
+or "pre".
+
+<p>A <dfn>single-line container</dfn> is either a <span>non-list single-line
+container</span>, or an <span>HTML element</span> with [[localname]] "li",
+"dt", or "dd".
+
+<p>The <dfn>default single-line container name</dfn> is "p".
+<!-- Possibly to be made configurable later. -->
 
 
 <h3>Assorted block formatting command algorithms</h3>
@@ -2933,15 +2940,18 @@
     of <var>start node</var> and then set <var>start node</var> to its
     [[parent]].
 
-    <li>Otherwise, if <var>start offset</var> is equal to the
-    [[nodelength]] of <var>start node</var>, set <var>start offset</var> to one
-    plus the [[index]] of <var>start node</var> and then set <var>start
-    node</var> to its [[parent]].
-
-    <li>Otherwise, if the [[child]] of <var>start node</var> with [[index]]
-    <var>start offset</var> and its [[previoussibling]] are both <span
-    title="inline node">inline nodes</span> and the [[previoussibling]] isn't a
-    [[br]], subtract one from <var>start offset</var>.
+    <li>Otherwise, if <var>start offset</var> is <var>start node</var>'s
+    [[nodelength]] and <var>start node</var>'s last [[child]] is an
+    <span>inline node</span> that's not a [[br]], subtract one from <var>start
+    offset</var>.
+    <!-- So if you have a collapsed selection at the end of a block, for
+    instance, it will extend backwards into a block. -->
+
+    <li>Otherwise, if <var>start node</var> has a [[child]] with [[index]]
+    <var>start offset</var>, and that [[child]] and its [[previoussibling]] are
+    both <span title="inline node">inline nodes</span> and the
+    [[previoussibling]] isn't a [[br]], subtract one from <var>start
+    offset</var>.
     <!-- IE also includes <br> (at least for the purposes of the indent
     command), but this is unlikely to match user expectations. -->
 
@@ -2958,20 +2968,20 @@
   <li>Repeat the following steps:
 
   <ol>
-    <li>If <var>end offset</var> is 0, set <var>end offset</var> to the
-    [[index]] of <var>end node</var> and then set <var>end node</var> to its
-    [[parent]].
-
-    <li>Otherwise, if <var>end node</var> is a [[text]] or [[comment]] node or
-    <var>end offset</var> is equal to the [[nodelength]] of <var>end
-    node</var>, set <var>end offset</var> to one plus the [[index]] of <var>end
-    node</var> and then set <var>end node</var> to its [[parent]].
-
-    <li>Otherwise, if the [[child]] of <var>end node</var> with [[index]]
-    <var>end offset</var> and its [[previoussibling]] are both <span
-    title="inline node">inline nodes</span>, and the [[child]] of <var>end
-    node</var> with [[index]] <var>end offset</var> isn't a [[br]], add one to
-    <var>end offset</var>.
+    <li>If <var>end node</var> is a [[text]] or [[comment]] node or <var>end
+    offset</var> is equal to the [[nodelength]] of <var>end node</var>, set
+    <var>end offset</var> to one plus the [[index]] of <var>end node</var> and
+    then set <var>end node</var> to its [[parent]].
+
+    <li>Otherwise, if <var>end offset</var> is 0 and <var>end node</var>'s
+    first [[child]] is an <span>inline node</span> that's not a [[br]], add one
+    to <var>end offset</var>.
+
+    <li>Otherwise, if <var>end node</var> has a [[child]] with [[index]]
+    <var>end offset</var>, and that [[child]] and its [[previoussibling]] are
+    both <span title="inline node">inline nodes</span>, and the [[child]] of
+    <var>end node</var> with [[index]] <var>end offset</var> isn't a [[br]],
+    add one to <var>end offset</var>.
 
     <li>Otherwise, break from this loop.
   </ol>
@@ -3872,10 +3882,10 @@
   not empty:
 
   <ol>
-    <li>If the first member of <var>node list</var> is a <span>single-line
-    container</span>, <span>set the tag name</span> of the first member of
-    <var>node list</var> to <var>value</var>, then remove the first member from
-    <var>node list</var> and continue this loop from the beginning.
+    <li>If the first member of <var>node list</var> is a <span>non-list
+    single-line container</span>, <span>set the tag name</span> of the first
+    member of <var>node list</var> to <var>value</var>, then remove the first
+    member from <var>node list</var> and continue this loop from the beginning.
 
     <li>Let <var>sublist</var> be an empty list of [[nodes]].
 
@@ -3885,7 +3895,7 @@
     <li>While <var>node list</var> is not empty, and the first member of
     <var>node list</var> is the [[nextsibling]] of the last member of
     <var>sublist</var>, and the first member of <var>node list</var> is not a
-    <span>single-line container</span>, and the last member of
+    <span>non-list single-line container</span>, and the last member of
     <var>sublist</var> is not a [[br]], remove the first member of <var>node
     list</var> and append it to <var>sublist</var>.
 
@@ -3899,8 +3909,8 @@
   <li>Otherwise, while <var>node list</var> is not empty:
 
   <ol>
-    <li>If the first member of <var>node list</var> is a <span>single-line
-    container</span>:
+    <li>If the first member of <var>node list</var> is a <span>non-list
+    single-line container</span>:
 
     <ol>
       <li>Let <var>sublist</var> be the [[children]] of the first member of
@@ -3923,7 +3933,7 @@
       <li>While <var>node list</var> is not empty, and the first member of
       <var>node list</var> is the [[nextsibling]] of the last member of
       <var>sublist</var>, and the first member of <var>node list</var> is not a
-      <span>single-line container</span>, and the last member of
+      <span>non-list single-line container</span>, and the last member of
       <var>sublist</var> is not a [[br]], remove the first member of <var>node
       list</var> and append it to <var>sublist</var>.
     </ol>
@@ -3993,6 +4003,13 @@
 <p class=XXX>Handle corner cases: endpoints are detached, documents, document
 fragments, html/body, head or things in head . . .
 
+<p class=XXX>Does not handle cases where block-extending the selection leaves
+it collapsed, like <code title>&lt;p>foo&lt;/p>{}&lt;p>bar&lt;/p></code>.
+
+<p class=XXX>The algorithm for preserving ranges doesn't work well here at all
+if the selection is collapsed.  The cursor can end up in the wrong place
+entirely.  Probably due to the wrap algorithm; that needs to be reexamined.
+
 <ol>
   <li>Let <var>items</var> be a list of all [[li]]s that are
   [[ancestorcontainers]] of the <span>active range</span>'s [[rangestart]]
@@ -4013,6 +4030,8 @@
   [[div]] or [[ol]] or [[ul]] and if no [[ancestor]] of <var>node</var> is in
   <var>node list</var>, append <var>node</var> to <var>node list</var>.
 
+  <p class=XXX>Use "allowed child" definition here.
+
   <li>If the first member of <var>node list</var> is an [[li]] whose [[parent]]
   is an [[ol]] or [[ul]], and its [[previoussibling]] is an [[li]] as well,
   <span>normalize sublists</span> of its [[previoussibling]].
@@ -4103,6 +4122,193 @@
 "ol".
 
 
+<h3><dfn>The <code title>insertParagraph</code> command</dfn></h3>
+<!--
+There are three major behaviors here.  Firefox 5.0a2 behaves identically to
+execCommand("formatBlock", false, "p"), which is not really useful.  IE9
+actually just overwrites the selection with an empty paragraph element, which
+seems not very useful either.  Chrome 13 dev and Opera 11.10 behave basically
+the same as if the user hit the Return key.  This later behavior seems much
+more useful, even though it's horribly misnamed, so it's what I'll spec.
+
+Then, of course, we have several flavors of line-breaking behavior to choose
+from.  Firefox prefers <br>s, unless it's in the middle of a <p> or something.
+Opera and IE like <p>.  Chrome prefers <div>.  And there are lots of subtleties
+besides.  I go with IE/Opera-style behavior, as discussed in this thread:
+
+http://lists.whatwg.org/htdig.cgi/whatwg-whatwg.org/2011-May/031577.html
+-->
+
+<p><span>Action</span>:
+
+<p class=XXX>Under construction, probably makes no sense.
+
+<ol>
+  <li><span>Delete the selection</span>.
+
+  <li>Let <var>range</var> be the <span>active range</span>.
+
+  <li>Let <var>node</var> and <var>offset</var> be <var>range</var>'s
+  [[rangestart]] [[bpnode]] and [[bpoffset]].
+
+  <li>If <var>node</var> is a [[text]] node, and <var>offset</var> is neither 0
+  nor the [[nodelength]] of <var>node</var>, call <code
+  data-anolis-spec=domcore
+  title=dom-Text-splitText>splitText(<var>offset</var>)</code> on
+  <var>node</var>.
+
+  <li>If <var>node</var> is a [[text]] node and <var>offset</var> is its
+  [[nodelength]], set <var>offset</var> to one plus the [[index]] of
+  <var>node</var>, then set <var>node</var> to its [[parent]].
+
+  <li>If <var>node</var> is a [[text]] or [[comment]] node, set
+  <var>offset</var> to the [[index]] of <var>node</var>, then set
+  <var>node</var> to its [[parent]].
+
+  <li>Set <var>range</var>'s [[rangestart]] and [[rangeend]] to
+  (<var>node</var>, <var>offset</var>).
+
+  <li>If <var>node</var> has an [[element]] [[child]] of [[index]]
+  <var>offset</var>, let <var>container</var> equal that [[child]].
+
+  <li>Otherwise, if <var>node</var> has an [[element]] [[child]] of [[index]]
+  <var>offset</var> minus one, let <var>container</var> equal that [[child]].
+
+  <li>Otherwise, let <var>container</var> equal <var>node</var>.
+
+  <li>While <var>container</var> is not a <span>single-line container</span>,
+  and <var>container</var>'s [[parent]] is <span>editable</span> and <span>in
+  the same editing host</span> as <var>node</var>, set <var>container</var> to
+  its [[parent]].
+
+  <li>If <var>container</var> is not <span>editable</span> or not <span>in the
+  same editing host</span> as <var>node</var> or is not a <span>single-line
+  container</span>:
+  <!-- Add the default wrapper. -->
+
+  <ol>
+    <li><span>Block-extend</span> <var>range</var>, and let <var>new
+    range</var> be the result.
+
+    <li>Let <var>node list</var> be a list of all [[children]] of
+    <var>node</var> that are  [[contained]] in <var>new range</var>.
+
+    <li>Let <var>tag</var> be the <span>default single-line container
+    name</span>.
+
+    <li>If <var>node list</var> is empty:
+
+    <ol>
+      <li>Set <var>container</var> to the result of calling
+      [[createelement|<var>tag</var>]] on the [[contextobject]].
+
+      <li>Call [[insertnode|<var>container</var>]] on <var>range</var>.
+
+      <li>Call [[createelement|"br"]] on the [[contextobject]], and append the
+      result as the last [[child]] of <var>container</var>.
+
+      <li>Set <var>range</var>'s [[rangestart]] and [[rangeend]] to
+      (<var>container</var>, 0).
+
+      <li>Abort these steps.
+    </ol>
+
+    <li><span>Wrap</span> <var>node list</var>, with <span>sibling
+    criteria</span> matching nothing and <span>new parent instructions</span>
+    returning the result of calling [[createelement|<var>tag</var>]] on the
+    [[contextobject]].  Set <var>container</var> to the result.
+
+    <li>If <var>range</var>'s [[startnode]] is not <var>container</var>, set
+    <var>range</var>'s [[rangestart]] and [[rangeend]] to
+    (<var>container</var>, 0).
+
+    <p class=XXX>This is a hack to work around range mutation rules not working
+    the way I'd like them to.  Should be fixed more sensibly.
+  </ol>
+
+  <li>If <var>container</var>'s [[localname]] is "address" or "pre":
+  <!--
+  IE9 and Chrome 13 dev just break <pre> up into multiple <pre>s.  Firefox
+  5.0a2 and Opera 11.10 insert a <br> instead, treating it differently from
+  <p>.  The latter makes more sense.  What might make the most sense is to just
+  insert an actual newline character, though, since this is a pre after all
+  . . .
+
+  IE9 and Chrome 13 dev also break <address> up into multiple <address>es.
+  Firefox 5.0a2 inserts <br> instead.  Opera 11.10 nests <p>s inside.  I don't
+  like Opera's behavior, because it means we nest formatBlock candidates inside
+  one another, so I'll go with Firefox.
+  -->
+
+  <p class=XXX>Why don't we just insert a newline character if it's a pre?  We
+  don't really need to use br in that case.  Need to test: what do browsers do
+  if a pre element has newlines in it, do they convert to line breaks when you
+  make it no longer a pre?
+
+  <ol>
+    <li>Let <var>br</var> be the result of calling [[createelement|"br"]] on
+    the [[contextobject]].
+
+    <li>Call [[insertnode|<var>br</var>]] on <var>range</var>.
+
+    <li>Increment <var>range</var>'s [[rangestart]] and [[rangeend]]
+    [[bpoffsets]].
+
+    <li>If <var>br</var> is the last [[descendant]] of <var>container</var>,
+    let <var>br</var> be the result of calling [[createelement|"br"]] on the
+    [[contextobject]], then call [[insertnode|<var>br</var>]] on
+    <var>range</var>.
+    <!-- Necessary because adding a br to the end of a block element does
+    nothing. -->
+
+    <li>Abort these steps.
+  </ol>
+
+  <li>Let <var>new container</var> be the result of calling <code
+  data-anolis-spec=domcore title=dom-Node-cloneNode>cloneNode(false)</code> on
+  <var>container</var>.
+
+  <p class=XXX>Copies id's.
+
+  <li>Insert <var>new container</var> into the [[parent]] of
+  <var>container</var> immediately after <var>container</var>.
+
+  <li>Let <var>new line range</var> be a new [[range]] whose [[rangestart]] is
+  the same as <var>range</var>'s, and whose [[rangeend]] is
+  (<var>container</var>, [[nodelength]] of <var>container</var>).
+
+  <li>Let <var>frag</var> be the result of calling <code
+  data-anolis-spec=domrange
+  title=dom-Range-extractContents>extractContents()</code> on <var>new line
+  range</var>.
+
+  <p class=XXX>This blows up any ranges (other than the selection, which we
+  reset), duplicates id's, and other bad stuff.  May or may not be the best
+  solution.  The intermediate fragment is also probably black-box detectable by
+  DOM mutation events, but I like to pretend those don't exist.
+
+  <li>Call <code data-anolis-spec=domcore
+  title=dom-Node-appendChild>appendChild(<var>frag</var>)</code> on <var>new
+  container</var>.
+
+  <li>If <var>container</var> has no [[children]], call [[createelement|"br"]]
+  on the [[contextobject]], and append the result as the last [[child]] of
+  <var>container</var>.
+
+  <li>If <var>new container</var> has no [[children]], call
+  [[createelement|"br"]] on the [[contextobject]], and append the result as the
+  last [[child]] of <var>new container</var>.
+
+  <p class=XXX>Needs to also happen if there are only comments/whitespace/etc.
+  Also, this can leave stray useless br's lying around.  Probably when the user
+  starts typing, we want to remove the br.  On the flip side, if the user
+  deletes all the contents of a paragraph, we likely need to add a br.
+
+  <li>Set the [[rangestart]] of <var>range</var> to (<var>new container</var>,
+  0).
+</ol>
+
+
 <h3><dfn>The <code title>insertUnorderedList</code> command</dfn></h3>
 
 <p><span>Action</span>: <span>Toggle lists</span> with <var>tag name</var>