Skip to content

Commit 487ac9c

Browse files
josevalimJosé Valim
authored andcommitted
Merge pull request #2924 from jw2013/italic
Fix broken IO.ANSI.Docs parser Conflicts: lib/elixir/lib/io/ansi/docs.ex
1 parent f0f65f8 commit 487ac9c

File tree

2 files changed

+130
-35
lines changed

2 files changed

+130
-35
lines changed

lib/elixir/lib/io/ansi/docs.ex

Lines changed: 71 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ defmodule IO.ANSI.Docs do
194194
lines
195195
|> Enum.join(" ")
196196
|> handle_links
197-
|> handle_inline(nil, [], [], options)
197+
|> handle_inline(true, nil, [], [], options)
198198
|> String.split(~r{\s})
199199
|> write_with_wrap(options[:width] - byte_size(indent), indent, no_wrap)
200200

@@ -262,7 +262,7 @@ defmodule IO.ANSI.Docs do
262262
col = col
263263
|> String.replace(~r/\\ \|/x, "|")
264264
|> handle_links
265-
|> handle_inline(nil, [], [], options)
265+
|> handle_inline(true, nil, [], [], options)
266266
{col, length_without_escape(col, 0)}
267267
end
268268

@@ -398,61 +398,102 @@ defmodule IO.ANSI.Docs do
398398
# ` does not require space in between
399399
@spaced [?_, ?*]
400400

401+
# Characters that can mark the beginning or the end of a word.
402+
# Only support the most common ones at this moment.
403+
@delimiters [?\s, ?', ?", ?!, ?@, ?#, ?$, ?%, ?^, ?&, ?-, ?+, ?(, ?), ?[, ?], ?{, ?}, ?<, ?>]
404+
401405
# Clauses for handling spaces
402-
defp handle_inline(<<?*, ?*, ?\s, rest :: binary>>, nil, buffer, acc, options) do
403-
handle_inline(rest, nil, [?\s, ?*, ?*|buffer], acc, options)
406+
defp handle_inline(<<?*, ?*, ?\s, rest :: binary>>, _line_starter, nil, buffer, acc, options) do
407+
handle_inline(rest, false, nil, [?\s, ?*, ?*|buffer], acc, options)
408+
end
409+
410+
defp handle_inline(<<mark, ?\s, rest :: binary>>, _line_starter, nil, buffer, acc, options) when mark in @spaced do
411+
handle_inline(rest, false, nil, [?\s, mark|buffer], acc, options)
412+
end
413+
414+
# Inline start
415+
defp handle_inline(<<delimiter, ?*, ?*, rest :: binary>>, _line_starter, nil, buffer, acc, options)
416+
when rest != "" and delimiter in @delimiters do
417+
handle_inline(rest, false, ?d, ["**"], [delimiter, Enum.reverse(buffer)|acc], options)
404418
end
405419

406-
defp handle_inline(<<mark, ?\s, rest :: binary>>, nil, buffer, acc, options) when mark in @spaced do
407-
handle_inline(rest, nil, [?\s, mark|buffer], acc, options)
420+
defp handle_inline(<<delimiter, mark, rest :: binary>>, _line_starter, nil, buffer, acc, options)
421+
when rest != "" and delimiter in @delimiters and mark in @single do
422+
handle_inline(rest, false, mark, [<<mark>>], [delimiter, Enum.reverse(buffer)|acc], options)
408423
end
409424

410-
defp handle_inline(<<?\s, ?*, ?*, rest :: binary>>, limit, buffer, acc, options) do
411-
handle_inline(rest, limit, [?*, ?*, ?\s|buffer], acc, options)
425+
defp handle_inline(<<?*, ?*, rest :: binary>>, true, nil, buffer, acc, options) do
426+
handle_inline(rest, false, ?d, ["**"], [Enum.reverse(buffer)|acc], options)
412427
end
413428

414-
defp handle_inline(<<?\s, mark, rest :: binary>>, limit, buffer, acc, options) when mark in @spaced do
415-
handle_inline(rest, limit, [mark, ?\s|buffer], acc, options)
429+
defp handle_inline(<<mark, rest :: binary>>, true, nil, buffer, acc, options) when mark in @single do
430+
handle_inline(rest, false, mark, [<<mark>>], [Enum.reverse(buffer)|acc], options)
431+
end
432+
433+
# Clauses for handling spaces
434+
defp handle_inline(<<?\s, ?*, ?*, rest :: binary>>, _line_starter, limit, buffer, acc, options) do
435+
handle_inline(rest, false, limit, [?*, ?*, ?\s|buffer], acc, options)
436+
end
437+
438+
defp handle_inline(<<?\s, mark, rest :: binary>>, _line_starter, limit, buffer, acc, options) when mark in @spaced do
439+
handle_inline(rest, false, limit, [mark, ?\s|buffer], acc, options)
416440
end
417441

418442
# Clauses for handling escape
419-
defp handle_inline(<<?\\, ?\\, rest :: binary>>, limit, buffer, acc, options) do
420-
handle_inline(rest, limit, [?\\|buffer], acc, options)
443+
defp handle_inline(<<?\\, ?\\, ?*, ?*, rest :: binary>>, _line_starter, nil, buffer, acc, options)
444+
when rest != "" do
445+
handle_inline(rest, false, ?d, ["**"], [?\\, Enum.reverse(buffer)|acc], options)
421446
end
422447

423-
defp handle_inline(<<?\\, ?*, ?*, rest :: binary>>, limit, buffer, acc, options) do
424-
handle_inline(rest, limit, [?*, ?*|buffer], acc, options)
448+
defp handle_inline(<<?\\, ?\\, mark, rest :: binary>>, _line_starter, nil, buffer, acc, options)
449+
when rest != "" and mark in @single do
450+
handle_inline(rest, false, mark, [<<mark>>], [?\\, Enum.reverse(buffer)|acc], options)
425451
end
426452

427-
# A escape is not valid inside `
428-
defp handle_inline(<<?\\, mark, rest :: binary>>, limit, buffer, acc, options)
429-
when mark in [?_, ?*, ?`] and not(mark == limit and mark == ?`) do
430-
handle_inline(rest, limit, [mark|buffer], acc, options)
453+
defp handle_inline(<<?\\, ?\\, rest :: binary>>, _line_starter, limit, buffer, acc, options) do
454+
handle_inline(rest, false, limit, [?\\|buffer], acc, options)
431455
end
432456

433-
# Inline start
434-
defp handle_inline(<<?*, ?*, rest :: binary>>, nil, buffer, acc, options) when rest != "" do
435-
handle_inline(rest, ?d, ["**"], [Enum.reverse(buffer)|acc], options)
457+
defp handle_inline(<<?\\, ?*, ?*, rest :: binary>>, _line_starter, limit, buffer, acc, options) do
458+
handle_inline(rest, false, limit, [?*, ?*|buffer], acc, options)
436459
end
437460

438-
defp handle_inline(<<mark, rest :: binary>>, nil, buffer, acc, options) when rest != "" and mark in @single do
439-
handle_inline(rest, mark, [<<mark>>], [Enum.reverse(buffer)|acc], options)
461+
# An escape is not valid inside `
462+
defp handle_inline(<<?\\, mark, rest :: binary>>, _line_starter, limit, buffer, acc, options)
463+
when mark in [?_, ?*, ?`] and not(mark == limit and mark == ?`) do
464+
handle_inline(rest, false, limit, [mark|buffer], acc, options)
440465
end
441466

442467
# Inline end
443-
defp handle_inline(<<?*, ?*, rest :: binary>>, ?d, buffer, acc, options) do
444-
handle_inline(rest, nil, [], [inline_buffer(buffer, options)|acc], options)
468+
defp handle_inline(<<?*, ?*, delimiter, rest :: binary>>, _line_starter, ?d, buffer, acc, options)
469+
when delimiter in @delimiters do
470+
handle_inline(<<delimiter, rest :: binary>>, false, nil, [], [inline_buffer(buffer, options)|acc], options)
471+
end
472+
473+
defp handle_inline(<<mark, delimiter, rest :: binary>>, _line_starter, mark, buffer, acc, options)
474+
when delimiter in @delimiters and mark in @single do
475+
handle_inline(<<delimiter, rest :: binary>>, false, nil, [], [inline_buffer(buffer, options)|acc], options)
476+
end
477+
478+
defp handle_inline(<<?\s, ?`, rest :: binary>>, _line_starter, ?`, buffer, acc, options) do
479+
handle_inline(rest, false, nil, [], [inline_buffer([?\s|buffer], options)|acc], options)
480+
end
481+
482+
defp handle_inline(<<?*, ?*, rest:: binary>>, _line_starter, ?d, buffer, acc, options)
483+
when rest in @delimiters or rest == "" do
484+
handle_inline(<<>>, false, nil, [], [inline_buffer(buffer, options)|acc], options)
445485
end
446486

447-
defp handle_inline(<<mark, rest :: binary>>, mark, buffer, acc, options) when mark in @single do
448-
handle_inline(rest, nil, [], [inline_buffer(buffer, options)|acc], options)
487+
defp handle_inline(<<mark, rest :: binary>>, _line_starter, mark, buffer, acc, options)
488+
when (rest in @delimiters or rest == "") and mark in @single do
489+
handle_inline(<<>>, false, nil, [], [inline_buffer(buffer, options)|acc], options)
449490
end
450491

451-
defp handle_inline(<<char, rest :: binary>>, mark, buffer, acc, options) do
452-
handle_inline(rest, mark, [char|buffer], acc, options)
492+
defp handle_inline(<<char, rest :: binary>>, _line_starter, mark, buffer, acc, options) do
493+
handle_inline(rest, false, mark, [char|buffer], acc, options)
453494
end
454495

455-
defp handle_inline(<<>>, _mark, buffer, acc, _options) do
496+
defp handle_inline(<<>>, _line_starter, _mark, buffer, acc, _options) do
456497
IO.iodata_to_binary Enum.reverse([Enum.reverse(buffer)|acc])
457498
end
458499

lib/elixir/test/elixir/io/ansi/docs_test.exs

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,28 @@ defmodule IO.ANSI.DocsTest do
127127
assert result == "\e[36mhello world\e[0m\n\e[0m"
128128
end
129129

130+
test "multiple stars/underscores/backticks work" do
131+
result = format("*hello world* *hello world*")
132+
assert result == "\e[1mhello world\e[0m \e[1mhello world\e[0m\n\e[0m"
133+
134+
result = format("_hello world_ _hello world_")
135+
assert result == "\e[4mhello world\e[0m \e[4mhello world\e[0m\n\e[0m"
136+
137+
result = format("`hello world` `hello world`")
138+
assert result == "\e[36mhello world\e[0m \e[36mhello world\e[0m\n\e[0m"
139+
end
140+
141+
test "multiple stars/underscores/backticks work when separated by other words" do
142+
result = format("*hello world* unit test *hello world*")
143+
assert result == "\e[1mhello world\e[0m unit test \e[1mhello world\e[0m\n\e[0m"
144+
145+
result = format("_hello world_ unit test _hello world_")
146+
assert result == "\e[4mhello world\e[0m unit test \e[4mhello world\e[0m\n\e[0m"
147+
148+
result = format("`hello world` unit test `hello world`")
149+
assert result == "\e[36mhello world\e[0m unit test \e[36mhello world\e[0m\n\e[0m"
150+
end
151+
130152
test "star/underscore preceeded by space doesn't get interpreted" do
131153
result = format("_unit _size")
132154
assert result == "_unit _size\n\e[0m"
@@ -138,6 +160,43 @@ defmodule IO.ANSI.DocsTest do
138160
assert result == "*unit *size\n\e[0m"
139161
end
140162

163+
test "star/underscore/backtick preceeded by non-space delimiters gets interpreted" do
164+
result = format("(`hello world`)")
165+
assert result == "(\e[36mhello world\e[0m)\n\e[0m"
166+
result = format("<`hello world`>")
167+
assert result == "<\e[36mhello world\e[0m>\n\e[0m"
168+
169+
result = format("(*hello world*)")
170+
assert result == "(\e[1mhello world\e[0m)\n\e[0m"
171+
result = format("@*hello world*@")
172+
assert result == "@\e[1mhello world\e[0m@\n\e[0m"
173+
174+
result = format("(_hello world_)")
175+
assert result == "(\e[4mhello world\e[0m)\n\e[0m"
176+
result = format("'_hello world_'")
177+
assert result == "'\e[4mhello world\e[0m'\n\e[0m"
178+
end
179+
180+
test "star/underscore/backtick starts/ends within a word doesn't get interpreted" do
181+
result = format("foo_bar, foo_bar_baz!")
182+
assert result == "foo_bar, foo_bar_baz!\n\e[0m"
183+
184+
result = format("_foo_bar")
185+
assert result == "_foo_bar\n\e[0m"
186+
187+
result = format("foo_bar_")
188+
assert result == "foo_bar_\n\e[0m"
189+
190+
result = format("foo*bar, foo*bar*baz!")
191+
assert result == "foo*bar, foo*bar*baz!\n\e[0m"
192+
193+
result = format("*foo*bar")
194+
assert result == "*foo*bar\n\e[0m"
195+
196+
result = format("foo*bar*")
197+
assert result == "foo*bar*\n\e[0m"
198+
end
199+
141200
test "backtick preceeded by space gets interpreted" do
142201
result = format("`unit `size")
143202
assert result == "\e[36munit \e[0msize\n\e[0m"
@@ -200,11 +259,6 @@ defmodule IO.ANSI.DocsTest do
200259
assert result == "\e[36m__world__\e[0m\n\e[0m"
201260
end
202261

203-
test "backtick works inside parenthesis" do
204-
result = format("(`hello world`)")
205-
assert result == "(\e[36mhello world\e[0m)\n\e[0m"
206-
end
207-
208262
test "escaping of underlines within links" do
209263
result = format("(http://en.wikipedia.org/wiki/ANSI_escape_code)")
210264
assert result == "(http://en.wikipedia.org/wiki/ANSI_escape_code)\n\e[0m"

0 commit comments

Comments
 (0)