From 49684395f76ce0734ad7edde245a6172681fce4e Mon Sep 17 00:00:00 2001 From: Sascha Wolf Date: Fri, 3 Jan 2020 10:38:09 +0100 Subject: [PATCH 01/14] IO.ANSI.Docs: Add failing tests for quote blocks --- lib/elixir/test/elixir/io/ansi/docs_test.exs | 30 ++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/lib/elixir/test/elixir/io/ansi/docs_test.exs b/lib/elixir/test/elixir/io/ansi/docs_test.exs index 6b20858cb63..f621f8cf463 100644 --- a/lib/elixir/test/elixir/io/ansi/docs_test.exs +++ b/lib/elixir/test/elixir/io/ansi/docs_test.exs @@ -57,6 +57,36 @@ defmodule IO.ANSI.DocsTest do assert result == "\e[33m### wibble\e[0m\n\e[0m\ntext\n\e[0m" end + test "single-line quote block is converted" do + result = format("line\n\n> normal *italics* `code`\n\nline2\n") + + assert result == + "line\n" <> + "\e[0m\n" <> + "+---------------------+\n" <> + "| normal \e[1mitalics\e[0m \e[36mcode\e[0m |\n" <> + "+---------------------+\e[0m\n" <> + "\e[0m\n" <> + "line2\n" <> + "\e[0m" + end + + test "multi-line quote block is converted" do + result = format("line\n\n> normal\n> *italics* \n> `code`\n\nline2\n") + + assert result == + "line\n" <> + "\e[0m\n" <> + "+---------+\n" <> + "| normal |\n" <> + "| \e[1mitalics\e[0m |\n" <> + "| \e[36mcode\e[0m |\n" <> + "+---------+\e[0m\n" <> + "\e[0m\n" <> + "line2\n" <> + "\e[0m" + end + test "code block is converted" do result = format("line\n\n code\n code2\n\nline2\n") assert result == "line\n\e[0m\n\e[36m code\n code2\e[0m\n\e[0m\nline2\n\e[0m" From 88f38541a57ffbac764cb6d91f3a86d80e4c93ce Mon Sep 17 00:00:00 2001 From: Sascha Wolf Date: Fri, 3 Jan 2020 12:03:40 +0100 Subject: [PATCH 02/14] IO.ANSI.Docs: Format quotes as ASCII boxes --- lib/elixir/lib/io/ansi/docs.ex | 59 ++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/lib/elixir/lib/io/ansi/docs.ex b/lib/elixir/lib/io/ansi/docs.ex index 4d9534271f0..94d468d5d56 100644 --- a/lib/elixir/lib/io/ansi/docs.ex +++ b/lib/elixir/lib/io/ansi/docs.ex @@ -14,6 +14,7 @@ defmodule IO.ANSI.Docs do * `:doc_code` - code blocks (cyan) * `:doc_headings` - h1, h2, h3, h4, h5, h6 headings (yellow) * `:doc_metadata` - documentation metadata keys (yellow) + * `:doc_quote` - quotes (light black, italic) * `:doc_inline_code` - inline code (cyan) * `:doc_table_heading` - the style for table headings * `:doc_title` - top level heading (reverse, yellow) @@ -31,6 +32,7 @@ defmodule IO.ANSI.Docs do doc_code: [:cyan], doc_headings: [:yellow], doc_metadata: [:yellow], + doc_quote: [], doc_inline_code: [:cyan], doc_table_heading: [:reverse], doc_title: [:reverse, :yellow], @@ -139,6 +141,11 @@ defmodule IO.ANSI.Docs do write_heading(heading, rest, text, indent, options) end + defp process(["> " <> line | rest], text, indent, options) do + write_text(text, indent, options) + process_quote(rest, [line], indent, options) + end + defp process(["" | rest], text, indent, options) do write_text(text, indent, options) process(rest, [], indent, options) @@ -183,6 +190,49 @@ defmodule IO.ANSI.Docs do process(rest, [], "", options) end + ## Quotes + + defp process_quote([], lines, indent, options) do + write_quote(lines, indent, options) + end + + defp process_quote(["> " <> line | rest], lines, indent, options) do + process_quote(rest, [line | lines], indent, options) + end + + defp process_quote(rest, lines, indent, options) do + write_quote(lines, indent, options) + process(rest, [], indent, options) + end + + defp write_quote(lines, indent, options) do + formatted_lines_with_length = + lines + |> Enum.map(&format_text(&1, options)) + |> Enum.map(&{&1, length_without_escape(&1, 0)}) + + {_line, max_length} = Enum.max_by(formatted_lines_with_length, fn {_, length} -> length end) + + quote_text = + formatted_lines_with_length + |> Enum.reverse() + |> Enum.map_join("\n#{indent}", fn {line, length} -> + padding = String.duplicate(" ", max_length - length) + + "| " <> line <> padding <> " |" + end) + + quote_box = indent <> "+-" <> String.duplicate("-", max_length) <> "-+" + + quote_block = + "#{quote_box}\n" <> + "#{quote_text}\n" <> + quote_box + + write(:doc_quote, quote_block, options) + newline_after_block() + end + ## Lists defp process_rest(stripped, rest, count, text, indent, options) do @@ -269,14 +319,19 @@ defmodule IO.ANSI.Docs do defp write_text(lines, indent, options, no_wrap) do lines |> Enum.join(" ") - |> handle_links - |> handle_inline(options) + |> format_text(options) |> String.split(@spaces) |> write_with_wrap(options[:width] - byte_size(indent), indent, no_wrap) unless no_wrap, do: newline_after_block() end + defp format_text(text, options) do + text + |> handle_links() + |> handle_inline(options) + end + ## Code blocks defp process_code([], code, indent, options) do From b8b4c8beb7e8de0898bfe9f1066c73854ac59b96 Mon Sep 17 00:00:00 2001 From: Sascha Wolf Date: Fri, 3 Jan 2020 12:15:49 +0100 Subject: [PATCH 03/14] IO.ANSI.Docs: Remove the `doc_quote` styling Having such a styling would clash with quote inline styling which is non-trivial to resolve. --- lib/elixir/lib/io/ansi/docs.ex | 6 +++--- lib/elixir/test/elixir/io/ansi/docs_test.exs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/elixir/lib/io/ansi/docs.ex b/lib/elixir/lib/io/ansi/docs.ex index 94d468d5d56..f9f0e136913 100644 --- a/lib/elixir/lib/io/ansi/docs.ex +++ b/lib/elixir/lib/io/ansi/docs.ex @@ -14,7 +14,6 @@ defmodule IO.ANSI.Docs do * `:doc_code` - code blocks (cyan) * `:doc_headings` - h1, h2, h3, h4, h5, h6 headings (yellow) * `:doc_metadata` - documentation metadata keys (yellow) - * `:doc_quote` - quotes (light black, italic) * `:doc_inline_code` - inline code (cyan) * `:doc_table_heading` - the style for table headings * `:doc_title` - top level heading (reverse, yellow) @@ -32,7 +31,6 @@ defmodule IO.ANSI.Docs do doc_code: [:cyan], doc_headings: [:yellow], doc_metadata: [:yellow], - doc_quote: [], doc_inline_code: [:cyan], doc_table_heading: [:reverse], doc_title: [:reverse, :yellow], @@ -229,7 +227,9 @@ defmodule IO.ANSI.Docs do "#{quote_text}\n" <> quote_box - write(:doc_quote, quote_block, options) + # There is no special style for quotes, as such we simply use IO.puts; + # a special quotes style would clash with inline styling (bold, italics etc.) + IO.puts(quote_block) newline_after_block() end diff --git a/lib/elixir/test/elixir/io/ansi/docs_test.exs b/lib/elixir/test/elixir/io/ansi/docs_test.exs index f621f8cf463..bda513ee6f9 100644 --- a/lib/elixir/test/elixir/io/ansi/docs_test.exs +++ b/lib/elixir/test/elixir/io/ansi/docs_test.exs @@ -65,7 +65,7 @@ defmodule IO.ANSI.DocsTest do "\e[0m\n" <> "+---------------------+\n" <> "| normal \e[1mitalics\e[0m \e[36mcode\e[0m |\n" <> - "+---------------------+\e[0m\n" <> + "+---------------------+\n" <> "\e[0m\n" <> "line2\n" <> "\e[0m" @@ -81,7 +81,7 @@ defmodule IO.ANSI.DocsTest do "| normal |\n" <> "| \e[1mitalics\e[0m |\n" <> "| \e[36mcode\e[0m |\n" <> - "+---------+\e[0m\n" <> + "+---------+\n" <> "\e[0m\n" <> "line2\n" <> "\e[0m" From 52726a656887bdfb6f1277d0180c43388244ca71 Mon Sep 17 00:00:00 2001 From: Sascha Wolf Date: Fri, 3 Jan 2020 12:18:32 +0100 Subject: [PATCH 04/14] IO.ANSI.Docs: Avoid `mapping` quote lines twice --- lib/elixir/lib/io/ansi/docs.ex | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/elixir/lib/io/ansi/docs.ex b/lib/elixir/lib/io/ansi/docs.ex index f9f0e136913..013a79fc077 100644 --- a/lib/elixir/lib/io/ansi/docs.ex +++ b/lib/elixir/lib/io/ansi/docs.ex @@ -205,9 +205,11 @@ defmodule IO.ANSI.Docs do defp write_quote(lines, indent, options) do formatted_lines_with_length = - lines - |> Enum.map(&format_text(&1, options)) - |> Enum.map(&{&1, length_without_escape(&1, 0)}) + Enum.map(lines, fn line -> + formatted_line = format_text(line, options) + + {formatted_line, length_without_escape(formatted_line, 0)} + end) {_line, max_length} = Enum.max_by(formatted_lines_with_length, fn {_, length} -> length end) From 8024880d7a88e6ad20a287fc173f3e49633b72f5 Mon Sep 17 00:00:00 2001 From: Sascha Wolf Date: Fri, 3 Jan 2020 13:45:34 +0100 Subject: [PATCH 05/14] IO.ANSI.Docs: Adjust tests to check for closer-to-markdown formatting Now the formatting uses leading `>` instead of boxing the quote: > first line > second line --- lib/elixir/test/elixir/io/ansi/docs_test.exs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/elixir/test/elixir/io/ansi/docs_test.exs b/lib/elixir/test/elixir/io/ansi/docs_test.exs index bda513ee6f9..bb2b121634a 100644 --- a/lib/elixir/test/elixir/io/ansi/docs_test.exs +++ b/lib/elixir/test/elixir/io/ansi/docs_test.exs @@ -63,9 +63,7 @@ defmodule IO.ANSI.DocsTest do assert result == "line\n" <> "\e[0m\n" <> - "+---------------------+\n" <> - "| normal \e[1mitalics\e[0m \e[36mcode\e[0m |\n" <> - "+---------------------+\n" <> + "> normal \e[1mitalics\e[0m \e[36mcode\e[0m\n" <> "\e[0m\n" <> "line2\n" <> "\e[0m" @@ -77,11 +75,9 @@ defmodule IO.ANSI.DocsTest do assert result == "line\n" <> "\e[0m\n" <> - "+---------+\n" <> - "| normal |\n" <> - "| \e[1mitalics\e[0m |\n" <> - "| \e[36mcode\e[0m |\n" <> - "+---------+\n" <> + "> normal\n" <> + "> \e[1mitalics\e[0m\n" <> + "> \e[36mcode\e[0m\n" <> "\e[0m\n" <> "line2\n" <> "\e[0m" From cba0b190481653307a7d5c852e5cf2df05fd6e87 Mon Sep 17 00:00:00 2001 From: Sascha Wolf Date: Fri, 3 Jan 2020 13:48:38 +0100 Subject: [PATCH 06/14] IO.ANSI.Docs: Format quotes as written in markdown --- lib/elixir/lib/io/ansi/docs.ex | 35 ++++---------------- lib/elixir/test/elixir/io/ansi/docs_test.exs | 8 ++--- 2 files changed, 11 insertions(+), 32 deletions(-) diff --git a/lib/elixir/lib/io/ansi/docs.ex b/lib/elixir/lib/io/ansi/docs.ex index 013a79fc077..a34124b8ef3 100644 --- a/lib/elixir/lib/io/ansi/docs.ex +++ b/lib/elixir/lib/io/ansi/docs.ex @@ -139,7 +139,7 @@ defmodule IO.ANSI.Docs do write_heading(heading, rest, text, indent, options) end - defp process(["> " <> line | rest], text, indent, options) do + defp process(["> " <> _ = line | rest], text, indent, options) do write_text(text, indent, options) process_quote(rest, [line], indent, options) end @@ -194,7 +194,7 @@ defmodule IO.ANSI.Docs do write_quote(lines, indent, options) end - defp process_quote(["> " <> line | rest], lines, indent, options) do + defp process_quote(["> " <> _ = line | rest], lines, indent, options) do process_quote(rest, [line | lines], indent, options) end @@ -204,34 +204,13 @@ defmodule IO.ANSI.Docs do end defp write_quote(lines, indent, options) do - formatted_lines_with_length = - Enum.map(lines, fn line -> - formatted_line = format_text(line, options) - - {formatted_line, length_without_escape(formatted_line, 0)} - end) - - {_line, max_length} = Enum.max_by(formatted_lines_with_length, fn {_, length} -> length end) - - quote_text = - formatted_lines_with_length - |> Enum.reverse() - |> Enum.map_join("\n#{indent}", fn {line, length} -> - padding = String.duplicate(" ", max_length - length) - - "| " <> line <> padding <> " |" - end) - - quote_box = indent <> "+-" <> String.duplicate("-", max_length) <> "-+" - - quote_block = - "#{quote_box}\n" <> - "#{quote_text}\n" <> - quote_box - + lines + |> Enum.reverse() + |> Enum.map_join("\n#{indent}", &format_text(&1, options)) # There is no special style for quotes, as such we simply use IO.puts; # a special quotes style would clash with inline styling (bold, italics etc.) - IO.puts(quote_block) + |> IO.puts() + newline_after_block() end diff --git a/lib/elixir/test/elixir/io/ansi/docs_test.exs b/lib/elixir/test/elixir/io/ansi/docs_test.exs index bb2b121634a..e57b3de50a1 100644 --- a/lib/elixir/test/elixir/io/ansi/docs_test.exs +++ b/lib/elixir/test/elixir/io/ansi/docs_test.exs @@ -63,7 +63,7 @@ defmodule IO.ANSI.DocsTest do assert result == "line\n" <> "\e[0m\n" <> - "> normal \e[1mitalics\e[0m \e[36mcode\e[0m\n" <> + "\e[90m> \e[0mnormal \e[1mitalics\e[0m \e[36mcode\e[0m\n" <> "\e[0m\n" <> "line2\n" <> "\e[0m" @@ -75,9 +75,9 @@ defmodule IO.ANSI.DocsTest do assert result == "line\n" <> "\e[0m\n" <> - "> normal\n" <> - "> \e[1mitalics\e[0m\n" <> - "> \e[36mcode\e[0m\n" <> + "\e[90m> \e[0mnormal\n" <> + "\e[90m> \e[0m\e[1mitalics\e[0m\n" <> + "\e[90m> \e[0m\e[36mcode\e[0m\n" <> "\e[0m\n" <> "line2\n" <> "\e[0m" From e127c286b4e6ea8ab1c6748ad2e86985f061b4e7 Mon Sep 17 00:00:00 2001 From: Sascha Wolf Date: Fri, 3 Jan 2020 14:01:12 +0100 Subject: [PATCH 07/14] IO.ANSI.Docs: Format quotes as in markdown with a leading `> ` To differentiate it from "regular" text the leading `> ` character is formatted according to the `doc_quote` setting which is set to light_black for the time being. --- lib/elixir/lib/io/ansi/docs.ex | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/elixir/lib/io/ansi/docs.ex b/lib/elixir/lib/io/ansi/docs.ex index a34124b8ef3..80e8e4e2967 100644 --- a/lib/elixir/lib/io/ansi/docs.ex +++ b/lib/elixir/lib/io/ansi/docs.ex @@ -14,6 +14,7 @@ defmodule IO.ANSI.Docs do * `:doc_code` - code blocks (cyan) * `:doc_headings` - h1, h2, h3, h4, h5, h6 headings (yellow) * `:doc_metadata` - documentation metadata keys (yellow) + * `:doc_quote` - leading quote character `> ` (light black) * `:doc_inline_code` - inline code (cyan) * `:doc_table_heading` - the style for table headings * `:doc_title` - top level heading (reverse, yellow) @@ -31,6 +32,7 @@ defmodule IO.ANSI.Docs do doc_code: [:cyan], doc_headings: [:yellow], doc_metadata: [:yellow], + doc_quote: [:light_black], doc_inline_code: [:cyan], doc_table_heading: [:reverse], doc_title: [:reverse, :yellow], @@ -139,7 +141,7 @@ defmodule IO.ANSI.Docs do write_heading(heading, rest, text, indent, options) end - defp process(["> " <> _ = line | rest], text, indent, options) do + defp process(["> " <> line | rest], text, indent, options) do write_text(text, indent, options) process_quote(rest, [line], indent, options) end @@ -194,7 +196,7 @@ defmodule IO.ANSI.Docs do write_quote(lines, indent, options) end - defp process_quote(["> " <> _ = line | rest], lines, indent, options) do + defp process_quote(["> " <> line | rest], lines, indent, options) do process_quote(rest, [line | lines], indent, options) end @@ -206,7 +208,9 @@ defmodule IO.ANSI.Docs do defp write_quote(lines, indent, options) do lines |> Enum.reverse() - |> Enum.map_join("\n#{indent}", &format_text(&1, options)) + |> Enum.map_join("\n#{indent}", fn line -> + [color(:doc_quote, options), "> ", IO.ANSI.reset(), format_text(line, options)] + end) # There is no special style for quotes, as such we simply use IO.puts; # a special quotes style would clash with inline styling (bold, italics etc.) |> IO.puts() From a5cba0379a008b4db0264900f4fbd8f9b79045db Mon Sep 17 00:00:00 2001 From: Sascha Wolf Date: Fri, 3 Jan 2020 14:45:32 +0100 Subject: [PATCH 08/14] IO.ANSI.Docs: Extract `wrap_text` from `write_with_wrap` --- lib/elixir/lib/io/ansi/docs.ex | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/elixir/lib/io/ansi/docs.ex b/lib/elixir/lib/io/ansi/docs.ex index 80e8e4e2967..0389433dbec 100644 --- a/lib/elixir/lib/io/ansi/docs.ex +++ b/lib/elixir/lib/io/ansi/docs.ex @@ -515,9 +515,23 @@ defmodule IO.ANSI.Docs do end defp write_with_wrap(words, available, indent, first) do + words + |> wrap_text(available, indent, first) + |> Enum.join("\n") + |> IO.puts() + end + + defp wrap_text(words, available, ident, first, wrapped_lines \\ []) + + defp wrap_text([], _available, _indent, _first, wrapped_lines) do + Enum.reverse(wrapped_lines) + end + + defp wrap_text(words, available, indent, first, wrapped_lines) do {words, rest} = take_words(words, available, []) - IO.puts(if(first, do: "", else: indent) <> Enum.join(words, " ")) - write_with_wrap(rest, available, indent, false) + line = if(first, do: "", else: indent) <> Enum.join(words, " ") + + wrap_text(rest, available, indent, false, [line | wrapped_lines]) end defp take_words([word | words], available, acc) do From 36febf30672510753312564e710f643d7a9fdc0e Mon Sep 17 00:00:00 2001 From: Sascha Wolf Date: Fri, 3 Jan 2020 14:55:48 +0100 Subject: [PATCH 09/14] IO.ANSI.Docs: Update tests to reflect usage of line wrapping --- lib/elixir/test/elixir/io/ansi/docs_test.exs | 51 +++++++++++++++++--- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/lib/elixir/test/elixir/io/ansi/docs_test.exs b/lib/elixir/test/elixir/io/ansi/docs_test.exs index e57b3de50a1..97ee7326288 100644 --- a/lib/elixir/test/elixir/io/ansi/docs_test.exs +++ b/lib/elixir/test/elixir/io/ansi/docs_test.exs @@ -57,8 +57,36 @@ defmodule IO.ANSI.DocsTest do assert result == "\e[33m### wibble\e[0m\n\e[0m\ntext\n\e[0m" end - test "single-line quote block is converted" do - result = format("line\n\n> normal *italics* `code`\n\nline2\n") + test "short single-line quote block is converted into single-line quote" do + result = + format( + "line\n" <> + "\n" <> + "> normal *italics* `code`\n" <> + "\n" <> + "line2\n" + ) + + assert result == + "line\n" <> + "\e[0m\n" <> + "\e[90m> \e[0mnormal \e[1mitalics\e[0m \e[36mcode\e[0m\n" <> + "\e[0m\n" <> + "line2\n" <> + "\e[0m" + end + + test "short multi-line quote block is converted into single-line quote" do + result = + format( + "line\n" <> + "\n" <> + "> normal\n" <> + "> *italics* \n" <> + "> `code`\n" <> + "\n" <> + "line2\n" + ) assert result == "line\n" <> @@ -69,15 +97,24 @@ defmodule IO.ANSI.DocsTest do "\e[0m" end - test "multi-line quote block is converted" do - result = format("line\n\n> normal\n> *italics* \n> `code`\n\nline2\n") + test "long multi-line quote block is converted into wrapped multi-line quote" do + result = + format( + "line\n" <> + "\n" <> + "> normal\n" <> + "> *italics*\n" <> + "> `code` \n" <> + "> some-extremly-long-word-which-can-not-possibly-fit-into-the-previous-line\n" <> + "\n" <> + "line2\n" + ) assert result == "line\n" <> "\e[0m\n" <> - "\e[90m> \e[0mnormal\n" <> - "\e[90m> \e[0m\e[1mitalics\e[0m\n" <> - "\e[90m> \e[0m\e[36mcode\e[0m\n" <> + "\e[90m> \e[0mnormal \e[1mitalics\e[0m \e[36mcode\e[0m\n" <> + "\e[90m> \e[0msome-extremly-long-word-which-can-not-possibly-fit-into-the-previous-line\n" <> "\e[0m\n" <> "line2\n" <> "\e[0m" From 1cce83da694574f37f5153d9b0494d5e12684f57 Mon Sep 17 00:00:00 2001 From: Sascha Wolf Date: Fri, 3 Jan 2020 14:56:10 +0100 Subject: [PATCH 10/14] IO.ANSI.Docs: Wrap lines of a quote using `wrap_text` --- lib/elixir/lib/io/ansi/docs.ex | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/elixir/lib/io/ansi/docs.ex b/lib/elixir/lib/io/ansi/docs.ex index 0389433dbec..b0180117804 100644 --- a/lib/elixir/lib/io/ansi/docs.ex +++ b/lib/elixir/lib/io/ansi/docs.ex @@ -208,11 +208,14 @@ defmodule IO.ANSI.Docs do defp write_quote(lines, indent, options) do lines |> Enum.reverse() - |> Enum.map_join("\n#{indent}", fn line -> - [color(:doc_quote, options), "> ", IO.ANSI.reset(), format_text(line, options)] + |> Enum.join(" ") + |> format_text(options) + |> String.split(@spaces) + # -2 for the leading `> ` + |> wrap_text(options[:width] - byte_size(indent) - 2, indent, false) + |> Enum.map_join("\n", fn line -> + [indent, color(:doc_quote, options), "> ", IO.ANSI.reset(), line] end) - # There is no special style for quotes, as such we simply use IO.puts; - # a special quotes style would clash with inline styling (bold, italics etc.) |> IO.puts() newline_after_block() From b4eff0da987d68acf1a7f3b5f1267e8c68d0a71e Mon Sep 17 00:00:00 2001 From: Sascha Wolf Date: Fri, 3 Jan 2020 15:42:06 +0100 Subject: [PATCH 11/14] IO.ANSI.Docs: Add a test for handling empty quote lines correctly --- lib/elixir/test/elixir/io/ansi/docs_test.exs | 26 ++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/lib/elixir/test/elixir/io/ansi/docs_test.exs b/lib/elixir/test/elixir/io/ansi/docs_test.exs index 97ee7326288..3e4f01b7819 100644 --- a/lib/elixir/test/elixir/io/ansi/docs_test.exs +++ b/lib/elixir/test/elixir/io/ansi/docs_test.exs @@ -120,6 +120,32 @@ defmodule IO.ANSI.DocsTest do "\e[0m" end + test "multi-line quote block containing empty lines is converted into wrapped multi-line quote" do + result = + format( + "line\n" <> + "\n" <> + "> normal\n" <> + "> *italics*\n" <> + ">\n" <> + "> `code` \n" <> + "> some-extremly-long-word-which-can-not-possibly-fit-into-the-previous-line\n" <> + "\n" <> + "line2\n" + ) + + assert result == + "line\n" <> + "\e[0m\n" <> + "\e[90m> \e[0mnormal \e[1mitalics\e[0m\n" <> + "\e[90m> \e[0m\n" <> + "\e[90m> \e[0m\e[36mcode\e[0m\n" <> + "\e[90m> \e[0msome-extremly-long-word-which-can-not-possibly-fit-into-the-previous-line\n" <> + "\e[0m\n" <> + "line2\n" <> + "\e[0m" + end + test "code block is converted" do result = format("line\n\n code\n code2\n\nline2\n") assert result == "line\n\e[0m\n\e[36m code\n code2\e[0m\n\e[0m\nline2\n\e[0m" From b3198900c9e4e9ab64d586cb06c9de5c37a17a10 Mon Sep 17 00:00:00 2001 From: Sascha Wolf Date: Fri, 3 Jan 2020 15:42:27 +0100 Subject: [PATCH 12/14] IO.ANSI.Docs: Properly handle empty quote lines --- lib/elixir/lib/io/ansi/docs.ex | 75 +++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/lib/elixir/lib/io/ansi/docs.ex b/lib/elixir/lib/io/ansi/docs.ex index b0180117804..9b8a0049262 100644 --- a/lib/elixir/lib/io/ansi/docs.ex +++ b/lib/elixir/lib/io/ansi/docs.ex @@ -75,7 +75,7 @@ defmodule IO.ANSI.Docs do {key, value}, _printed when is_binary(value) and key in @metadata_filter -> label = metadata_label(key, options) indent = String.duplicate(" ", length_without_escape(label, 0) + 1) - write_with_wrap([label | String.split(value, @spaces)], options[:width], indent, true) + write_with_wrap([label | String.split(value, @spaces)], options[:width], indent, true, "") {key, value}, _printed when is_boolean(value) and key in @metadata_filter -> IO.puts([metadata_label(key, options), ' ', to_string(value)]) @@ -141,9 +141,9 @@ defmodule IO.ANSI.Docs do write_heading(heading, rest, text, indent, options) end - defp process(["> " <> line | rest], text, indent, options) do + defp process([">" <> line | rest], text, indent, options) do write_text(text, indent, options) - process_quote(rest, [line], indent, options) + process_quote(rest, [line], indent, options, quote_prefix(options)) end defp process(["" | rest], text, indent, options) do @@ -192,35 +192,43 @@ defmodule IO.ANSI.Docs do ## Quotes - defp process_quote([], lines, indent, options) do - write_quote(lines, indent, options) + defp quote_prefix(options) do + IO.iodata_to_binary([color(:doc_quote, options), "> ", IO.ANSI.reset()]) end - defp process_quote(["> " <> line | rest], lines, indent, options) do - process_quote(rest, [line | lines], indent, options) + defp process_quote([], lines, indent, options, prefix) do + write_quote(lines, indent, options, false, prefix) end - defp process_quote(rest, lines, indent, options) do - write_quote(lines, indent, options) + defp process_quote([">", ">" <> line | rest], lines, indent, options, prefix) do + write_quote(lines, indent, options, true, prefix) + write_empty_quote_line(prefix) + process_quote(rest, [line], indent, options, prefix) + end + + defp process_quote([">" <> line | rest], lines, indent, options, prefix) do + process_quote(rest, [line | lines], indent, options, prefix) + end + + defp process_quote(rest, lines, indent, options, prefix) do + write_quote(lines, indent, options, false, prefix) process(rest, [], indent, options) end - defp write_quote(lines, indent, options) do + defp write_quote(lines, indent, options, no_wrap, prefix) do lines + |> Enum.map(&String.trim/1) |> Enum.reverse() - |> Enum.join(" ") - |> format_text(options) - |> String.split(@spaces) - # -2 for the leading `> ` - |> wrap_text(options[:width] - byte_size(indent) - 2, indent, false) - |> Enum.map_join("\n", fn line -> - [indent, color(:doc_quote, options), "> ", IO.ANSI.reset(), line] - end) - |> IO.puts() - - newline_after_block() + |> write_lines( + indent, + options, + no_wrap, + prefix + ) end + defp write_empty_quote_line(prefix), do: IO.puts(prefix) + ## Lists defp process_rest(stripped, rest, count, text, indent, options) do @@ -305,11 +313,15 @@ defmodule IO.ANSI.Docs do end defp write_text(lines, indent, options, no_wrap) do + write_lines(lines, indent, options, no_wrap, "") + end + + defp write_lines(lines, indent, options, no_wrap, prefix) do lines |> Enum.join(" ") |> format_text(options) |> String.split(@spaces) - |> write_with_wrap(options[:width] - byte_size(indent), indent, no_wrap) + |> write_with_wrap(options[:width] - byte_size(indent), indent, no_wrap, prefix) unless no_wrap, do: newline_after_block() end @@ -513,28 +525,27 @@ defmodule IO.ANSI.Docs do IO.puts([color(style, options), string, IO.ANSI.reset()]) end - defp write_with_wrap([], _available, _indent, _first) do + defp write_with_wrap([], _available, _indent, _first, _prefix) do :ok end - defp write_with_wrap(words, available, indent, first) do + defp write_with_wrap(words, available, indent, first, prefix) do words - |> wrap_text(available, indent, first) + |> wrap_text(available, indent, first, prefix, []) |> Enum.join("\n") |> IO.puts() end - defp wrap_text(words, available, ident, first, wrapped_lines \\ []) - - defp wrap_text([], _available, _indent, _first, wrapped_lines) do + defp wrap_text([], _available, _indent, _first, _prefix, wrapped_lines) do Enum.reverse(wrapped_lines) end - defp wrap_text(words, available, indent, first, wrapped_lines) do - {words, rest} = take_words(words, available, []) - line = if(first, do: "", else: indent) <> Enum.join(words, " ") + defp wrap_text(words, available, indent, first, prefix, wrapped_lines) do + prefix_length = length_without_escape(prefix, 0) + {words, rest} = take_words(words, available - prefix_length, []) + line = [if(first, do: "", else: indent), prefix, Enum.join(words, " ")] - wrap_text(rest, available, indent, false, [line | wrapped_lines]) + wrap_text(rest, available, indent, false, prefix, [line | wrapped_lines]) end defp take_words([word | words], available, acc) do From 497c29c23e9abd55c1d15326a126b52d4af234a5 Mon Sep 17 00:00:00 2001 From: Sascha Wolf Date: Fri, 3 Jan 2020 16:00:59 +0100 Subject: [PATCH 13/14] IO.ANSI.Docs: Use heredoc for the quote tests --- lib/elixir/test/elixir/io/ansi/docs_test.exs | 138 ++++++++++--------- 1 file changed, 73 insertions(+), 65 deletions(-) diff --git a/lib/elixir/test/elixir/io/ansi/docs_test.exs b/lib/elixir/test/elixir/io/ansi/docs_test.exs index 3e4f01b7819..3695dc60d8e 100644 --- a/lib/elixir/test/elixir/io/ansi/docs_test.exs +++ b/lib/elixir/test/elixir/io/ansi/docs_test.exs @@ -59,91 +59,99 @@ defmodule IO.ANSI.DocsTest do test "short single-line quote block is converted into single-line quote" do result = - format( - "line\n" <> - "\n" <> - "> normal *italics* `code`\n" <> - "\n" <> - "line2\n" - ) + format(""" + line + + > normal *italics* `code` + + line2 + """) assert result == - "line\n" <> - "\e[0m\n" <> - "\e[90m> \e[0mnormal \e[1mitalics\e[0m \e[36mcode\e[0m\n" <> - "\e[0m\n" <> - "line2\n" <> - "\e[0m" + """ + line + \e[0m + \e[90m> \e[0mnormal \e[1mitalics\e[0m \e[36mcode\e[0m + \e[0m + line2 + \e[0m\ + """ end test "short multi-line quote block is converted into single-line quote" do result = - format( - "line\n" <> - "\n" <> - "> normal\n" <> - "> *italics* \n" <> - "> `code`\n" <> - "\n" <> - "line2\n" - ) + format(""" + line + + > normal + > *italics* + > `code` + + line2 + """) assert result == - "line\n" <> - "\e[0m\n" <> - "\e[90m> \e[0mnormal \e[1mitalics\e[0m \e[36mcode\e[0m\n" <> - "\e[0m\n" <> - "line2\n" <> - "\e[0m" + """ + line + \e[0m + \e[90m> \e[0mnormal \e[1mitalics\e[0m \e[36mcode\e[0m + \e[0m + line2 + \e[0m\ + """ end test "long multi-line quote block is converted into wrapped multi-line quote" do result = - format( - "line\n" <> - "\n" <> - "> normal\n" <> - "> *italics*\n" <> - "> `code` \n" <> - "> some-extremly-long-word-which-can-not-possibly-fit-into-the-previous-line\n" <> - "\n" <> - "line2\n" - ) + format(""" + line + + > normal + > *italics* + > `code` + > some-extremly-long-word-which-can-not-possibly-fit-into-the-previous-line + + line2 + """) assert result == - "line\n" <> - "\e[0m\n" <> - "\e[90m> \e[0mnormal \e[1mitalics\e[0m \e[36mcode\e[0m\n" <> - "\e[90m> \e[0msome-extremly-long-word-which-can-not-possibly-fit-into-the-previous-line\n" <> - "\e[0m\n" <> - "line2\n" <> - "\e[0m" + """ + line + \e[0m + \e[90m> \e[0mnormal \e[1mitalics\e[0m \e[36mcode\e[0m + \e[90m> \e[0msome-extremly-long-word-which-can-not-possibly-fit-into-the-previous-line + \e[0m + line2 + \e[0m\ + """ end test "multi-line quote block containing empty lines is converted into wrapped multi-line quote" do result = - format( - "line\n" <> - "\n" <> - "> normal\n" <> - "> *italics*\n" <> - ">\n" <> - "> `code` \n" <> - "> some-extremly-long-word-which-can-not-possibly-fit-into-the-previous-line\n" <> - "\n" <> - "line2\n" - ) + format(""" + line + + > normal + > *italics* + > + > `code` + > some-extremly-long-word-which-can-not-possibly-fit-into-the-previous-line + + line2 + """) assert result == - "line\n" <> - "\e[0m\n" <> - "\e[90m> \e[0mnormal \e[1mitalics\e[0m\n" <> - "\e[90m> \e[0m\n" <> - "\e[90m> \e[0m\e[36mcode\e[0m\n" <> - "\e[90m> \e[0msome-extremly-long-word-which-can-not-possibly-fit-into-the-previous-line\n" <> - "\e[0m\n" <> - "line2\n" <> - "\e[0m" + """ + line + \e[0m + \e[90m> \e[0mnormal \e[1mitalics\e[0m + \e[90m> \e[0m + \e[90m> \e[0m\e[36mcode\e[0m + \e[90m> \e[0msome-extremly-long-word-which-can-not-possibly-fit-into-the-previous-line + \e[0m + line2 + \e[0m\ + """ end test "code block is converted" do From 9fdea6921137dd79d2aa35ca299d05b6f0fe97bc Mon Sep 17 00:00:00 2001 From: Sascha Wolf Date: Fri, 3 Jan 2020 16:06:32 +0100 Subject: [PATCH 14/14] IO.ANSI.Docs: Cleanup and do not carry `prefix` through all `process_quote` calls --- lib/elixir/lib/io/ansi/docs.ex | 38 ++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/lib/elixir/lib/io/ansi/docs.ex b/lib/elixir/lib/io/ansi/docs.ex index 9b8a0049262..933dbe14f8a 100644 --- a/lib/elixir/lib/io/ansi/docs.ex +++ b/lib/elixir/lib/io/ansi/docs.ex @@ -143,7 +143,7 @@ defmodule IO.ANSI.Docs do defp process([">" <> line | rest], text, indent, options) do write_text(text, indent, options) - process_quote(rest, [line], indent, options, quote_prefix(options)) + process_quote(rest, [line], indent, options) end defp process(["" | rest], text, indent, options) do @@ -192,30 +192,26 @@ defmodule IO.ANSI.Docs do ## Quotes - defp quote_prefix(options) do - IO.iodata_to_binary([color(:doc_quote, options), "> ", IO.ANSI.reset()]) + defp process_quote([], lines, indent, options) do + write_quote(lines, indent, options, false) end - defp process_quote([], lines, indent, options, prefix) do - write_quote(lines, indent, options, false, prefix) + defp process_quote([">", ">" <> line | rest], lines, indent, options) do + write_quote(lines, indent, options, true) + write_empty_quote_line(options) + process_quote(rest, [line], indent, options) end - defp process_quote([">", ">" <> line | rest], lines, indent, options, prefix) do - write_quote(lines, indent, options, true, prefix) - write_empty_quote_line(prefix) - process_quote(rest, [line], indent, options, prefix) + defp process_quote([">" <> line | rest], lines, indent, options) do + process_quote(rest, [line | lines], indent, options) end - defp process_quote([">" <> line | rest], lines, indent, options, prefix) do - process_quote(rest, [line | lines], indent, options, prefix) - end - - defp process_quote(rest, lines, indent, options, prefix) do - write_quote(lines, indent, options, false, prefix) + defp process_quote(rest, lines, indent, options) do + write_quote(lines, indent, options, false) process(rest, [], indent, options) end - defp write_quote(lines, indent, options, no_wrap, prefix) do + defp write_quote(lines, indent, options, no_wrap) do lines |> Enum.map(&String.trim/1) |> Enum.reverse() @@ -223,11 +219,17 @@ defmodule IO.ANSI.Docs do indent, options, no_wrap, - prefix + quote_prefix(options) ) end - defp write_empty_quote_line(prefix), do: IO.puts(prefix) + defp quote_prefix(options), do: "#{color(:doc_quote, options)}> #{IO.ANSI.reset()}" + + defp write_empty_quote_line(options) do + options + |> quote_prefix() + |> IO.puts() + end ## Lists