X-Git-Url: http://nitlanguage.org diff --git a/lib/markdown/markdown.nit b/lib/markdown/markdown.nit index 0d264cb..613e1a4 100644 --- a/lib/markdown/markdown.nit +++ b/lib/markdown/markdown.nit @@ -41,33 +41,39 @@ class MarkdownProcessor # # In normal markdown the following: # - # This is a paragraph - # * and this is not a list + # ~~~md + # This is a paragraph + # * and this is not a list + # ~~~ # # Will produce: # - #

This is a paragraph - # * and this is not a list

+ # ~~~html + #

This is a paragraph + # * and this is not a list

+ # ~~~ # - # When using extended mode this changes to: + # When using extended mode this changes to: # - #

This is a paragraph

- # + # ~~~html + #

This is a paragraph

+ # + # ~~~ # # * Fences code blocks # # If you don't want to indent your all your code with 4 spaces, # you can wrap your code in ``` ``` ``` or `~~~`. # - # Here's an example: + # Here's an example: # - # ``` - # fun test do - # print "Hello World!" - # end - # ``` + # ~~~md + # fun test do + # print "Hello World!" + # end + # ~~~ # # * Code blocks meta # @@ -76,43 +82,55 @@ class MarkdownProcessor # You can add an optional language identifier after the fence declaration to output # it in the HTML render. # - # ```nit - # import markdown + # ```nit + # import markdown # - # print "# Hello World!".md_to_html - # ``` + # print "# Hello World!".md_to_html + # ``` # # Becomes # - #
import markdown
+	# ~~~html
+	# 
import markdown
 	#
-	#		print "Hello World!".md_to_html
-	#		
+ # print "Hello World!".md_to_html + #
+ # ~~~ # # * Underscores (Emphasis) # # Underscores in the middle of a word like: # - # Con_cat_this + # ~~~md + # Con_cat_this + # ~~~ # - # normally produces this: + # normally produces this: # - #

Concatthis

+ # ~~~html + #

Concatthis

+ # ~~~ # # With extended mode they don't result in emphasis. # - #

Con_cat_this

+ # ~~~html + #

Con_cat_this

+ # ~~~ # # * Strikethrough # # Like in [GFM](https://help.github.com/articles/github-flavored-markdown), # strikethrought span is marked with `~~`. # - # ~~Mistaken text.~~ + # ~~~md + # ~~Mistaken text.~~ + # ~~~ # # becomes # - # Mistaken text. + # ~~~html + # Mistaken text. + # ~~~ var ext_mode = true init do self.emitter = new MarkdownEmitter(self) @@ -151,7 +169,7 @@ class MarkdownProcessor if c == '\n' then eol = true else if c == '\t' then - var np = pos + (4 - (pos.bin_and(3))) + var np = pos + (4 - (pos & 3)) while pos < np do value.add ' ' pos += 1 @@ -250,8 +268,10 @@ class MarkdownProcessor # # Markdown allows link refs to be defined over two lines: # - # [id]: http://example.com/longish/path/to/resource/here - # "Optional Title Here" + # ~~~md + # [id]: http://example.com/longish/path/to/resource/here + # "Optional Title Here" + # ~~~ # private var last_link_ref: nullable LinkRef = null @@ -376,7 +396,11 @@ class MarkdownProcessor c2 = ' ' end - var loc = text.pos_to_loc(pos) + var loc = new MDLocation( + current_loc.line_start, + current_loc.column_start + pos, + current_loc.line_start, + current_loc.column_start + pos) if c == '*' then if c1 == '*' then @@ -456,6 +480,12 @@ class MarkdownProcessor end return -1 end + + # Location used for next parsed token. + # + # This location can be changed by the emitter to adjust with `\n` found + # in the input. + private fun current_loc: MDLocation do return emitter.current_loc end # Emit output corresponding to blocks content. @@ -499,15 +529,19 @@ class MarkdownEmitter # Transform and emit mardown text fun emit_text(text: Text) do emit_text_until(text, 0, null) - # Transform and emit mardown text starting at `from` and + # Transform and emit mardown text starting at `start` and # until a token with the same type as `token` is found. - # Go until the end of text if `token` is null. + # Go until the end of `text` if `token` is null. fun emit_text_until(text: Text, start: Int, token: nullable Token): Int do var old_text = current_text var old_pos = current_pos current_text = text current_pos = start while current_pos < text.length do + if text[current_pos] == '\n' then + current_loc.line_start += 1 + current_loc.column_start = -current_pos + end var mt = processor.token_at(text, current_pos) if (token != null and not token isa TokenNone) and (mt.is_same_type(token) or @@ -550,6 +584,21 @@ class MarkdownEmitter return buffer_stack.last end + # Stacked locations. + private var loc_stack = new List[MDLocation] + + # Push a new MDLocation on the stack. + private fun push_loc(location: MDLocation) do loc_stack.add location + + # Pop the last buffer. + private fun pop_loc: MDLocation do return loc_stack.pop + + # Current output buffer. + private fun current_loc: MDLocation do + assert not loc_stack.is_empty + return loc_stack.last + end + # Append `e` to current buffer. fun add(e: Writable) do if e isa Text then @@ -597,6 +646,11 @@ interface Decorator # Kind of emitter used for decoration. type EMITTER: MarkdownEmitter + # Render a single plain char. + # + # Redefine this method to add special escaping for plain text. + fun add_char(v: EMITTER, c: Char) do v.addc c + # Render a ruler block. fun add_ruler(v: EMITTER, block: BlockRuler) is abstract @@ -880,6 +934,11 @@ class MDLocation var column_end: Int redef fun to_s do return "{line_start},{column_start}--{line_end},{column_end}" + + # Return a copy of `self`. + fun copy: MDLocation do + return new MDLocation(line_start, column_start, line_end, column_end) + end end # A block of markdown lines. @@ -1108,10 +1167,32 @@ abstract class Block fun emit_blocks(v: MarkdownEmitter) do var block = self.block.first_block while block != null do + v.push_loc(block.location) block.kind.emit(v) + v.pop_loc block = block.next end end + + # The raw content of the block as a multi-line string. + fun raw_content: String do + var infence = self isa BlockFence + var text = new FlatBuffer + var line = self.block.first_line + while line != null do + if not line.is_empty then + var str = line.value + if not infence and str.has_prefix(" ") then + text.append str.substring(4, str.length - line.trailing) + else + text.append str + end + end + text.append "\n" + line = line.next + end + return text.write_to_string + end end # A block without any markdown specificities. @@ -1152,6 +1233,9 @@ end class BlockCode super Block + # Any string found after fence token. + var meta: nullable Text + # Number of char to skip at the beginning of the line. # # Block code lines start at 4 spaces. @@ -1178,9 +1262,6 @@ end class BlockFence super BlockCode - # Any string found after fence token. - var meta: nullable Text - # Fence code lines start at 0 spaces. redef var line_start = 0 end @@ -1189,7 +1270,15 @@ end class BlockHeadline super Block - redef fun emit(v) do v.decorator.add_headline(v, self) + redef fun emit(v) do + var loc = block.location.copy + loc.column_start += start + v.push_loc(loc) + v.decorator.add_headline(v, self) + v.pop_loc + end + + private var start = 0 # Depth of the headline used to determine the headline level. var depth = 0 @@ -1218,6 +1307,7 @@ class BlockHeadline line.leading = 0 line.trailing = 0 end + self.start = start depth = level.min(6) end end @@ -1703,6 +1793,7 @@ class LineFence else block = v.current_block.split(v.current_block.last_line.as(not null)) end + block.remove_surrounding_empty_lines var meta = block.first_line.value.meta_from_fence block.kind = new BlockFence(block, meta) block.first_line.clear @@ -1772,7 +1863,7 @@ end # A markdown list line. # Mainly used to factorize code between ordered and unordered lists. -class LineList +abstract class LineList super Line redef fun process(v) do @@ -1852,7 +1943,7 @@ abstract class Token var char: Char # Output that token using `MarkdownEmitter::decorator`. - fun emit(v: MarkdownEmitter) do v.addc char + fun emit(v: MarkdownEmitter) do v.decorator.add_char(v, char) end # A token without a specific meaning. @@ -2036,6 +2127,7 @@ abstract class TokenLinkOrImage if pos == -1 then return -1 end end + if pos < start then return -1 if md[pos] != ')' then return -1 else if md[pos] == '[' then pos += 1 @@ -2474,24 +2566,6 @@ redef class Text return null end - # Init a `MDLocation` instance at `pos` in `self`. - private fun pos_to_loc(pos: Int): MDLocation do - assert pos <= length - var line = 1 - var col = 0 - var i = 0 - while i <= pos do - col += 1 - var c = self[i] - if c == '\n' then - line +=1 - col = 0 - end - i +=1 - end - return new MDLocation(line, col, line, col) - end - # Is `self` an unsafe HTML element? private fun is_html_unsafe: Bool do return html_unsafe_tags.has(self.write_to_string)