]> git.wincent.com - wikitext.git/commitdiff
Fix list indentation
authorWincent Colaiuta <win@wincent.com>
Fri, 8 Feb 2008 20:05:43 +0000 (21:05 +0100)
committerWincent Colaiuta <win@wincent.com>
Fri, 8 Feb 2008 20:05:43 +0000 (21:05 +0100)
Quick a tricky one, this one, because the LI token is the only one which
varies its behaviour depending on the context.

For example, when it is just a standard list item containing some text
we want it to behave like a P token (ie. when we dedent we don't want to
emit any spaces). On the other hand, if the LI contains a nested list
then it takes on block-like properties and we do want to emit spaces
when dedenting (ie. like blockquote).

We implementing this by introducing another imaginary token,
NESTED_LIST, which is present when a nested list is present and guides
the dedentation process accordingly.

Signed-off-by: Wincent Colaiuta <win@wincent.com>
ext/parser.c
ext/token.c
ext/token.h
spec/ul_spec.rb

index 93ce26ef7877f7f9e4b0ad09a5b958d48f520ec4..a369bb36266f3214d96e0f59f42505ba097bd948 100644 (file)
@@ -253,6 +253,16 @@ void _Wikitext_pop_from_stack(parser_t *parser, VALUE target)
             rb_str_append(target, parser->line_ending);
             break;
 
+        case NESTED_LIST:
+            // next token to pop will be a LI
+            // LI is an interesting token because sometimes we want it to behave like P (ie. do a non-emitting indent)
+            // and other times we want it to behave like BLOCKQUOTE (ie. when it has a nested list inside)
+            // hence this hack: we do an emitting dedent on behalf of the LI that we know must be coming
+            // and then when we pop the actual LI itself (below) we do the standard non-emitting indent
+            _Wikitext_dedent(parser, Qtrue);    // we really only want to emit the spaces
+            parser->current_indent += 2;        // we don't want to decrement the actual indent level, so put it back
+            break;
+
         case LI:
             rb_str_cat(target, li_end, sizeof(li_end) - 1);
             rb_str_append(target, parser->line_ending);
@@ -1249,6 +1259,7 @@ VALUE Wikitext_parser_parse(int argc, VALUE *argv, VALUE self)
                 // count number of tokens in line and scope stacks
                 i = line->count;
                 j = scope->count;
+                k = i;
 
                 // list tokens can be nested so look ahead for any more which might affect the decision to push or pop
                 for (;;)
@@ -1257,9 +1268,15 @@ VALUE Wikitext_parser_parse(int argc, VALUE *argv, VALUE self)
                     if (type == OL || type == UL)
                     {
                         token = NULL;
+                        if (i - k >= 2)                     // already seen at least one OL or UL
+                        {
+                            ary_push(line, NESTED_LIST);    // which means this is a nested list
+                            i += 3;
+                        }
+                        else
+                            i += 2;
                         ary_push(line, type);
                         ary_push(line, LI);
-                        i += 2;
 
                         // want to compare line with scope but can only do so if scope has enough items on it
                         if (j >= i)
@@ -1300,18 +1317,18 @@ VALUE Wikitext_parser_parse(int argc, VALUE *argv, VALUE self)
                     NEXT_TOKEN();
                 }
 
-                // TODO: consider adding indentation here... wouldn't be too hard...
+                // will emit
                 if (type == OL || type == UL)
                 {
                     // if LI is at the top of a stack this is the start of a nested list
                     if (j > 0 && ary_entry(scope, -1) == LI)
-                        // so we should precede it with a CRLF
+                    {
+                        // so we should precede it with a CRLF, and indicate that it's a nested list
                         rb_str_append(output, line_ending);
-                }
+                        ary_push(scope, NESTED_LIST);
+                    }
 
-                // emit
-                if (type == OL || type == UL)
-                {
+                    // emit
                     _Wikitext_indent(parser);
                     if (type == OL)
                         rb_str_cat(output, ol_start, sizeof(ol_start) - 1);
index 49d0e081963cb0801181b1361c0d6683f80cbfa2..0ce7fd853f74fdcb1f0458a5bbd555410b6a0d41 100644 (file)
@@ -28,6 +28,7 @@ VALUE Wikitext_parser_token_types(VALUE self)
     SET_TOKEN_TYPE(NO_TOKEN);
     SET_TOKEN_TYPE(P);
     SET_TOKEN_TYPE(LI);
+    SET_TOKEN_TYPE(NESTED_LIST);
     SET_TOKEN_TYPE(PRE);
     SET_TOKEN_TYPE(NO_WIKI_START);
     SET_TOKEN_TYPE(NO_WIKI_END);
index ae96c0f962e4e6090f1c9de73f77090407ed9056..1b2f78684227bc848568718b1317cdb2b9c5dcd5 100644 (file)
@@ -34,6 +34,7 @@ enum token_types {
     NO_TOKEN,
     P,              // imaginary token (never explicitly marked up)
     LI,             // imaginary token (never explicitly marked up)
+    NESTED_LIST,    // imaginary token (never explicitly marked up)
     PRE,
     NO_WIKI_START,
     NO_WIKI_END,
index 8ce8bd9439f5f97d8c3c09560f7647d57e057c8a..16c664ea5c49fab8ead4f88028bc4f4fc23a7e0a 100755 (executable)
@@ -22,16 +22,16 @@ describe Wikitext::Parser, 'parsing unordered lists' do
   end
 
   it 'should recognize a single item list' do
-    @parser.parse('*foo').should == "<ul>\n<li>foo</li>\n</ul>\n"
+    @parser.parse('*foo').should == "<ul>\n  <li>foo</li>\n</ul>\n"
   end
 
   it 'should allow and consume optional space after the last <ul> marker' do
-    @parser.parse('* foo').should == "<ul>\n<li>foo</li>\n</ul>\n"    # exactly one space consumed
-    @parser.parse('*  foo').should == "<ul>\n<li>foo</li>\n</ul>\n"   # multiple spaces consumed
+    @parser.parse('* foo').should == "<ul>\n  <li>foo</li>\n</ul>\n"    # exactly one space consumed
+    @parser.parse('*  foo').should == "<ul>\n  <li>foo</li>\n</ul>\n"   # multiple spaces consumed
   end
 
   it 'should consider a space after an <ul> marker to indicate that it will be the last marker' do
-    @parser.parse('* * foo').should == "<ul>\n<li>* foo</li>\n</ul>\n"
+    @parser.parse('* * foo').should == "<ul>\n  <li>* foo</li>\n</ul>\n"
   end
 
   it 'should only recognize <ul> markers if they or a direct ancestor start in the left column' do
@@ -39,77 +39,160 @@ describe Wikitext::Parser, 'parsing unordered lists' do
   end
 
   it 'should recognize <ul> markers nested inside blockquote blocks' do
-    @parser.parse('> * foo').should == "<blockquote>\n<ul>\n<li>foo</li>\n</ul>\n</blockquote>\n"
+    @parser.parse('> * foo').should == <<-END
+<blockquote>
+  <ul>
+    <li>foo</li>
+  </ul>
+</blockquote>
+END
   end
 
   it 'should display excess <ul> markers as literals' do
     #┬áthis provides feedback to the user
-    @parser.parse('** foo').should == "<ul>\n<li>* foo</li>\n</ul>\n"
-    @parser.parse('*** foo').should == "<ul>\n<li>** foo</li>\n</ul>\n"
+    @parser.parse('** foo').should == "<ul>\n  <li>* foo</li>\n</ul>\n"
+    @parser.parse('*** foo').should == "<ul>\n  <li>** foo</li>\n</ul>\n"
   end
 
   it 'should recognize a multi-item, single-level list' do
-    @parser.parse("* foo\n* bar").should == "<ul>\n<li>foo</li>\n<li>bar</li>\n</ul>\n"
+    @parser.parse("* foo\n* bar").should == <<-END
+<ul>
+  <li>foo</li>
+  <li>bar</li>
+</ul>
+END
   end
 
   it 'should recognize a multi-item, nested list (two levels)' do
-    @parser.parse("* foo\n** bar").should == "<ul>\n<li>foo\n<ul>\n<li>bar</li>\n</ul>\n</li>\n</ul>\n"
+    # indentation of nested lists is tricky
+    # the last </li> appears too far to the left
+    # the difficult is that sometimes li has to act like a block level element (like blockquote, does emit before dedent)
+    # and at other times it has to act like p (doesn't emit before dedent)
+    # so basically when nested we need to do an emitting dedent
+    # and when not we need to do a non-emitting one
+    @parser.parse("* foo\n** bar").should == <<-END
+<ul>
+  <li>foo
+    <ul>
+      <li>bar</li>
+    </ul>
+  </li>
+</ul>
+END
   end
 
   it 'should recognize a multi-item, nested list (three levels)' do
-    @parser.parse("* foo\n** bar\n*** baz").should == "<ul>\n<li>foo\n<ul>\n<li>bar\n<ul>\n<li>baz</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n"
+    @parser.parse("* foo\n** bar\n*** baz").should == <<-END
+<ul>
+  <li>foo
+    <ul>
+      <li>bar
+        <ul>
+          <li>baz</li>
+        </ul>
+      </li>
+    </ul>
+  </li>
+</ul>
+END
   end
 
   it 'should recognize lists in which nesting level increases and then is maintained' do
-    @parser.parse("* foo\n** bar\n** baz").should == "<ul>\n<li>foo\n<ul>\n<li>bar</li>\n<li>baz</li>\n</ul>\n</li>\n</ul>\n"
+    @parser.parse("* foo\n** bar\n** baz").should == <<-END
+<ul>
+  <li>foo
+    <ul>
+      <li>bar</li>
+      <li>baz</li>
+    </ul>
+  </li>
+</ul>
+END
   end
 
   it 'should recognize lists in which nesting level increases and then decreases' do
-    @parser.parse("* foo\n** bar\n* baz").should == "<ul>\n<li>foo\n<ul>\n<li>bar</li>\n</ul>\n</li>\n<li>baz</li>\n</ul>\n"
+    @parser.parse("* foo\n** bar\n* baz").should == <<-END
+<ul>
+  <li>foo
+    <ul>
+      <li>bar</li>
+    </ul>
+  </li>
+  <li>baz</li>
+</ul>
+END
   end
 
   it 'should be terminated by subsequent paragraph at the same level' do
-    @parser.parse("* foo\nbar").should == "<ul>\n<li>foo</li>\n</ul>\n<p>bar</p>\n"
+    @parser.parse("* foo\nbar").should == <<-END
+<ul>
+  <li>foo</li>
+</ul>
+<p>bar</p>
+END
   end
 
   it 'should be terminated by subsequent blockquote at the same level' do
-    @parser.parse("* foo\n> bar").should == "<ul>\n<li>foo</li>\n</ul>\n<blockquote>\n<p>bar</p>\n</blockquote>\n"
+    @parser.parse("* foo\n> bar").should == <<-END
+<ul>
+  <li>foo</li>
+</ul>
+<blockquote>
+  <p>bar</p>
+</blockquote>
+END
   end
 
   it 'should be terminated by subsequent heading at the same level' do
-    @parser.parse("* foo\n====== bar ======").should == "<ul>\n<li>foo</li>\n</ul>\n<h6>bar</h6>\n"
-    @parser.parse("* foo\n===== bar =====").should == "<ul>\n<li>foo</li>\n</ul>\n<h5>bar</h5>\n"
-    @parser.parse("* foo\n==== bar ====").should == "<ul>\n<li>foo</li>\n</ul>\n<h4>bar</h4>\n"
-    @parser.parse("* foo\n=== bar ===").should == "<ul>\n<li>foo</li>\n</ul>\n<h3>bar</h3>\n"
-    @parser.parse("* foo\n== bar ==").should == "<ul>\n<li>foo</li>\n</ul>\n<h2>bar</h2>\n"
-    @parser.parse("* foo\n= bar =").should == "<ul>\n<li>foo</li>\n</ul>\n<h1>bar</h1>\n"
+    @parser.parse("* foo\n====== bar ======").should == "<ul>\n  <li>foo</li>\n</ul>\n<h6>bar</h6>\n"
+    @parser.parse("* foo\n===== bar =====").should == "<ul>\n  <li>foo</li>\n</ul>\n<h5>bar</h5>\n"
+    @parser.parse("* foo\n==== bar ====").should == "<ul>\n  <li>foo</li>\n</ul>\n<h4>bar</h4>\n"
+    @parser.parse("* foo\n=== bar ===").should == "<ul>\n  <li>foo</li>\n</ul>\n<h3>bar</h3>\n"
+    @parser.parse("* foo\n== bar ==").should == "<ul>\n  <li>foo</li>\n</ul>\n<h2>bar</h2>\n"
+    @parser.parse("* foo\n= bar =").should == "<ul>\n  <li>foo</li>\n</ul>\n<h1>bar</h1>\n"
   end
 
   it 'should be terminated by subsequent <pre> block at the same level' do
-    @parser.parse("* foo\n bar").should == "<ul>\n<li>foo</li>\n</ul>\n<pre>bar</pre>\n"
+    @parser.parse("* foo\n bar").should == "<ul>\n  <li>foo</li>\n</ul>\n<pre>bar</pre>\n"
   end
 
   it 'should be terminated by subsequent ordered list at the same level' do
-    @parser.parse("* foo\n# bar").should == "<ul>\n<li>foo</li>\n</ul>\n<ol>\n<li>bar</li>\n</ol>\n"
+    @parser.parse("* foo\n# bar").should == <<-END
+<ul>
+  <li>foo</li>
+</ul>
+<ol>
+  <li>bar</li>
+</ol>
+END
   end
 
   it 'should recognize lists which contain nested ordered lists' do
-    @parser.parse("* foo\n*# bar").should == "<ul>\n<li>foo\n<ol>\n<li>bar</li>\n</ol>\n</li>\n</ul>\n"
-    input = <<INPUT
+    @parser.parse("* foo\n*# bar").should == <<-END
+<ul>
+  <li>foo
+    <ol>
+      <li>bar</li>
+    </ol>
+  </li>
+</ul>
+END
+
+    input = <<-END
 * foo
 *# bar
 *# baz
-INPUT
-    expected = <<EXPECTED
+END
+
+    @parser.parse(input).should == <<-END
 <ul>
-<li>foo
-<ol>
-<li>bar</li>
-<li>baz</li>
-</ol>
-</li>
+  <li>foo
+    <ol>
+      <li>bar</li>
+      <li>baz</li>
+    </ol>
+  </li>
 </ul>
-EXPECTED
-    @parser.parse(input).should == expected
+END
   end
 end