Repository: zulip/zulip

Issue:
Add limited ordered list support to our markdown
Markdown has this cool ordered list feature where you can do this:
```
1. first item
1. second
1. third
```

And it'll auto-renumber them for you to 1/2/3.

We can't use existing markdown ordered lists implementations, because they will also turn a solo `2.` into a `1.`, which really sucks if you're splitting a list across multiple messages.  But I think we could do something where if you do several of the same number, it does turn it into an ordered list, counting from that number.  

https://zulip.readthedocs.io/en/latest/markdown.html is a helpful resource for anyone interested in working on this.


Discussion:
Hello @zulip/server-markdown members, this issue was labeled with the **area: markdown** label, so you may want to check it out!
@zulipbot claim
Welcome to Zulip, @wjkg! We just sent you an invite to collaborate on this repository at https://github.com/zulip/zulip/invitations. Please accept this invite in order to claim this issue and begin a fun, rewarding experience contributing to Zulip!

Here's some tips to get you off to a good start:
* Join me on the [Zulip developers' server](https://chat.zulip.org), to get help, chat about this issue, and meet the other developers.
* Sign the [Dropbox Contributor License Agreement](https://opensource.dropbox.com/cla/), so that Zulip can use your code.
* [Unwatch this repository](https://help.github.com/articles/unwatching-repositories/), so that you don't get 100 emails a day.

As you work on this issue, you'll also want to refer to the [Zulip code contribution guide](http://zulip.readthedocs.io/en/latest/index.html#code-contribution-guide), as well as the rest of the developer documentation on that site.


See you on the other side (that is, the pull request side)!
Hello @wjkg, you claimed this issue to work on it, but this issue and any referenced pull requests haven't been updated for 7 days. Are you still working on this issue?

If so, please update this issue by leaving a comment on this issue to let me know that you're still working on it. Otherwise, I'll automatically remove you from this issue in 3 days.

If you've decided to work on something else, simply comment `@zulipbot abandon` so that someone else can claim it and continue from where you left off.

Thank you for your valuable contributions to Zulip!
I'm still working on this issue.

Resolving patch:
diff --git a/zerver/lib/bugdown/__init__.py b/zerver/lib/bugdown/__init__.py
--- a/zerver/lib/bugdown/__init__.py
+++ b/zerver/lib/bugdown/__init__.py
@@ -1078,6 +1078,68 @@ def run(self, lines: List[Text]) -> List[Text]:
                 inserts += 1
         return copy
 
+class AutoNumberOListPreprocessor(markdown.preprocessors.Preprocessor):
+    """ Finds a sequence of lines numbered by the same number"""
+    RE = re.compile(r'^([ ]*)(\d+)\.[ ]+(.*)')
+    TAB_LENGTH = 2
+
+    def run(self, lines):
+        # type: (List[Text]) -> List[Text]
+        new_lines = []  # type: List[Text]
+        current_list = []  # type: List[Match[Text]]
+        current_indent = 0
+
+        for line in lines:
+            m = self.RE.match(line)
+
+            # Remember if this line is a continuation of already started list
+            is_next_item = (m and current_list
+                            and current_indent == len(m.group(1)) // self.TAB_LENGTH)
+
+            if not is_next_item:
+                # There is no more items in the list we were processing
+                new_lines.extend(self.renumber(current_list))
+                current_list = []
+
+            if not m:
+                # Ordinary line
+                new_lines.append(line)
+            elif is_next_item:
+                # Another list item
+                current_list.append(m)
+            else:
+                # First list item
+                current_list = [m]
+                current_indent = len(m.group(1)) // self.TAB_LENGTH
+
+        new_lines.extend(self.renumber(current_list))
+
+        return new_lines
+
+    def renumber(self, mlist):
+        # type: (List[Match[Text]]) -> List[Text]
+        if not mlist:
+            return []
+
+        start_number = int(mlist[0].group(2))
+
+        # Change numbers only if every one is the same
+        change_numbers = True
+        for m in mlist:
+            if int(m.group(2)) != start_number:
+                change_numbers = False
+                break
+
+        lines = []  # type: List[Text]
+        counter = start_number
+
+        for m in mlist:
+            number = str(counter) if change_numbers else m.group(2)
+            lines.append('%s%s. %s' % (m.group(1), number, m.group(3)))
+            counter += 1
+
+        return lines
+
 # Based on markdown.inlinepatterns.LinkPattern
 class LinkPattern(markdown.inlinepatterns.Pattern):
     """ Return a link element from the given match. """
@@ -1397,6 +1459,10 @@ def extendMarkdown(self, md: markdown.Markdown, md_globals: Dict[str, Any]) -> N
                              BugdownUListPreprocessor(md),
                              "_begin")
 
+        md.preprocessors.add('auto_number_olist',
+                             AutoNumberOListPreprocessor(md),
+                             "_begin")
+
         md.treeprocessors.add("inline_interesting_links", InlineInterestingLinkProcessor(md, self), "_end")
 
         if settings.CAMO_URI:


Test patch:
diff --git a/zerver/fixtures/markdown_test_cases.json b/zerver/fixtures/markdown_test_cases.json
--- a/zerver/fixtures/markdown_test_cases.json
+++ b/zerver/fixtures/markdown_test_cases.json
@@ -217,6 +217,24 @@
       "expected_output": "<p>1. A<br>\n 2. B</p>",
       "text_content": "1. A\n 2. B"
     },
+    {
+      "name": "auto_renumbered_list",
+      "input": "1. A\n1. B\n 1. C\n1. D",
+      "expected_output": "<p>1. A<br>\n2. B<br>\n 3. C<br>\n4. D</p>",
+      "text_content": "1. A\n2. B\n 3. C\n4. D"
+    },
+    {
+      "name": "auto_renumbered_list_from",
+      "input": "3. A\n3. B\n3. C\n3. D",
+      "expected_output": "<p>3. A<br>\n4. B<br>\n5. C<br>\n6. D</p>",
+      "text_content": "3. A\n4. B\n5. C\n6. D"
+    },
+    {
+      "name": "not_auto_renumbered_list",
+      "input": "1. A\n3. B\n 2. C\n1. D",
+      "expected_output": "<p>1. A<br>\n3. B<br>\n 2. C<br>\n1. D</p>",
+      "text_content": "1. A\n3. B\n 2. C\n1. D"
+    },
     {
       "name": "linkify_interference",
       "input": "link: xx, x xxxxx xx xxxx xx\n\n[xxxxx #xx](http://xxxxxxxxx:xxxx/xxx/xxxxxx%xxxxxx/xx/):**xxxxxxx**\n\nxxxxxxx xxxxx xxxx xxxxx:\n`xxxxxx`: xxxxxxx\n`xxxxxx`: xxxxx\n`xxxxxx`: xxxxx xxxxx",