diff --git a/deps/checksums/StyledStrings-ac472083359dde956aed8c61d43b8158ac84d9ce.tar.gz/md5 b/deps/checksums/StyledStrings-ac472083359dde956aed8c61d43b8158ac84d9ce.tar.gz/md5 deleted file mode 100644 index 758a74bce9dae..0000000000000 --- a/deps/checksums/StyledStrings-ac472083359dde956aed8c61d43b8158ac84d9ce.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -6969fb6d2e8585d26beef865910ec8ef diff --git a/deps/checksums/StyledStrings-ac472083359dde956aed8c61d43b8158ac84d9ce.tar.gz/sha512 b/deps/checksums/StyledStrings-ac472083359dde956aed8c61d43b8158ac84d9ce.tar.gz/sha512 deleted file mode 100644 index 3d1ac8791e14d..0000000000000 --- a/deps/checksums/StyledStrings-ac472083359dde956aed8c61d43b8158ac84d9ce.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -281292e8478d72ab66b84cbd4f42e5dc2dd5054e8c54a79de8f0c0537d28962b460e67fe71230ead6b02386b87d0423879d51ce53a2b2427ce55866d62d6ebde diff --git a/deps/checksums/StyledStrings-d7496d24d3f05536bce6a7eb4cd8ca05a75c02aa.tar.gz/md5 b/deps/checksums/StyledStrings-d7496d24d3f05536bce6a7eb4cd8ca05a75c02aa.tar.gz/md5 new file mode 100644 index 0000000000000..3a5fccdec0fba --- /dev/null +++ b/deps/checksums/StyledStrings-d7496d24d3f05536bce6a7eb4cd8ca05a75c02aa.tar.gz/md5 @@ -0,0 +1 @@ +a02cd2c8bedd83b74917cf3821c89f46 diff --git a/deps/checksums/StyledStrings-d7496d24d3f05536bce6a7eb4cd8ca05a75c02aa.tar.gz/sha512 b/deps/checksums/StyledStrings-d7496d24d3f05536bce6a7eb4cd8ca05a75c02aa.tar.gz/sha512 new file mode 100644 index 0000000000000..a042e4f306275 --- /dev/null +++ b/deps/checksums/StyledStrings-d7496d24d3f05536bce6a7eb4cd8ca05a75c02aa.tar.gz/sha512 @@ -0,0 +1 @@ +2e86daa832533f0369e66e359d7d8f47002f93525f83233c809007a13dfd05a201bcd273b3cb4f3eba2586e98cc9afa43c242f67dc18b91fc898d98a0bd8fde9 diff --git a/doc/Manifest.toml b/doc/Manifest.toml index 254becb59a0c7..cb9d42561be5f 100644 --- a/doc/Manifest.toml +++ b/doc/Manifest.toml @@ -114,6 +114,10 @@ deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2 uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" version = "8.6.0+0" +[[deps.JuliaSyntaxHighlighting]] +deps = ["StyledStrings"] +uuid = "dc6e5ff7-fb65-4e79-a425-ec3bc9c03011" + [[deps.LibGit2]] deps = ["LibGit2_jll", "NetworkOptions", "Printf", "SHA"] uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" @@ -145,7 +149,7 @@ uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" version = "1.11.0" [[deps.Markdown]] -deps = ["Base64"] +deps = ["Base64", "JuliaSyntaxHighlighting", "StyledStrings"] uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" version = "1.11.0" diff --git a/stdlib/Manifest.toml b/stdlib/Manifest.toml index 4e4f48b4e6af4..8d59f63c4733c 100644 --- a/stdlib/Manifest.toml +++ b/stdlib/Manifest.toml @@ -140,7 +140,7 @@ uuid = "3a97d323-0669-5f0c-9066-3539efd106a3" version = "4.2.1+0" [[deps.Markdown]] -deps = ["Base64"] +deps = ["Base64", "JuliaSyntaxHighlighting", "StyledStrings"] uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" version = "1.11.0" diff --git a/stdlib/Markdown/Project.toml b/stdlib/Markdown/Project.toml index b40de17b9422d..e2edcdefea537 100644 --- a/stdlib/Markdown/Project.toml +++ b/stdlib/Markdown/Project.toml @@ -4,6 +4,8 @@ version = "1.11.0" [deps] Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" +StyledStrings = "f489334b-da3d-4c2e-b8f0-e476e12c162b" +JuliaSyntaxHighlighting = "dc6e5ff7-fb65-4e79-a425-ec3bc9c03011" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/stdlib/Markdown/src/Common/Common.jl b/stdlib/Markdown/src/Common/Common.jl index 3036f2b4b730b..4bd3e5b4af8d6 100644 --- a/stdlib/Markdown/src/Common/Common.jl +++ b/stdlib/Markdown/src/Common/Common.jl @@ -1,5 +1,7 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +abstract type MarkdownElement end + include("block.jl") include("inline.jl") diff --git a/stdlib/Markdown/src/Common/block.jl b/stdlib/Markdown/src/Common/block.jl index bd184b60c40fa..1b5cc1a752bcb 100644 --- a/stdlib/Markdown/src/Common/block.jl +++ b/stdlib/Markdown/src/Common/block.jl @@ -4,7 +4,7 @@ # Paragraphs # –––––––––– -mutable struct Paragraph +mutable struct Paragraph <: MarkdownElement content end @@ -39,7 +39,7 @@ end # Headers # ––––––– -mutable struct Header{level} +mutable struct Header{level} <: MarkdownElement text end @@ -95,7 +95,7 @@ end # Code # –––– -mutable struct Code +mutable struct Code <: MarkdownElement language::String code::String end @@ -124,7 +124,7 @@ end # Footnote # -------- -mutable struct Footnote +mutable struct Footnote <: MarkdownElement id::String text end @@ -159,7 +159,7 @@ end # Quotes # –––––– -mutable struct BlockQuote +mutable struct BlockQuote <: MarkdownElement content end @@ -188,7 +188,7 @@ end # Admonitions # ----------- -mutable struct Admonition +mutable struct Admonition <: MarkdownElement category::String title::String content::Vector @@ -246,7 +246,7 @@ end # Lists # ––––– -mutable struct List +mutable struct List <: MarkdownElement items::Vector{Any} ordered::Int # `-1` is unordered, `>= 0` is ordered. loose::Bool # TODO: Renderers should use this field @@ -332,7 +332,7 @@ pushitem!(list, buffer) = push!(list.items, parse(String(take!(buffer))).content # HorizontalRule # –––––––––––––– -mutable struct HorizontalRule +mutable struct HorizontalRule <: MarkdownElement end function horizontalrule(stream::IO, block::MD) diff --git a/stdlib/Markdown/src/Common/inline.jl b/stdlib/Markdown/src/Common/inline.jl index fda716a10fae7..a2a4140f80050 100644 --- a/stdlib/Markdown/src/Common/inline.jl +++ b/stdlib/Markdown/src/Common/inline.jl @@ -4,7 +4,7 @@ # Emphasis # –––––––– -mutable struct Italic +mutable struct Italic <: MarkdownElement text end @@ -20,7 +20,7 @@ function underscore_italic(stream::IO, md::MD) return result === nothing ? nothing : Italic(parseinline(result, md)) end -mutable struct Bold +mutable struct Bold <: MarkdownElement text end @@ -66,7 +66,7 @@ end # Images & Links # –––––––––––––– -mutable struct Image +mutable struct Image <: MarkdownElement url::String alt::String end @@ -85,7 +85,7 @@ function image(stream::IO, md::MD) end end -mutable struct Link +mutable struct Link <: MarkdownElement text url::String end @@ -156,7 +156,7 @@ end # Punctuation # ––––––––––– -mutable struct LineBreak end +mutable struct LineBreak <: MarkdownElement end @trigger '\\' -> function linebreak(stream::IO, md::MD) diff --git a/stdlib/Markdown/src/GitHub/table.jl b/stdlib/Markdown/src/GitHub/table.jl index 29f956e9a0710..7c174007a75ba 100644 --- a/stdlib/Markdown/src/GitHub/table.jl +++ b/stdlib/Markdown/src/GitHub/table.jl @@ -140,15 +140,15 @@ end function term(io::IO, md::Table, columns) margin_str = " "^margin - cells = mapmap(x -> terminline_string(io, x), md.rows) - padcells!(cells, md.align, len = ansi_length) + cells = mapmap(x -> annotprint(terminline, x), md.rows) + padcells!(cells, md.align, len = textwidth) for i = 1:length(cells) print(io, margin_str) join(io, cells[i], " ") if i == 1 println(io) print(io, margin_str) - join(io, ["–"^ansi_length(cells[i][j]) for j = 1:length(cells[1])], " ") + join(io, ["–"^textwidth(cells[i][j]) for j = 1:length(cells[1])], " ") end i < length(cells) && println(io) end diff --git a/stdlib/Markdown/src/Markdown.jl b/stdlib/Markdown/src/Markdown.jl index 93d8dbc39fc59..935b5d981d6f9 100644 --- a/stdlib/Markdown/src/Markdown.jl +++ b/stdlib/Markdown/src/Markdown.jl @@ -9,9 +9,12 @@ literals `md"..."` and `doc"..."`. """ module Markdown -import Base: show, ==, with_output_color, mapany +import Base: AnnotatedString, AnnotatedIOBuffer, show, ==, with_output_color, mapany using Base64: stringmime +using StyledStrings: StyledStrings, Face, addface!, @styled_str, styled +using JuliaSyntaxHighlighting: highlight, highlight! + # Margin for printing in terminal. const margin = 2 @@ -32,6 +35,26 @@ include("render/terminal/render.jl") export @md_str, @doc_str +const MARKDOWN_FACES = [ + :markdown_header => Face(weight=:bold), + :markdown_h1 => Face(height=1.25, inherit=:markdown_header), + :markdown_h2 => Face(height=1.20, inherit=:markdown_header), + :markdown_h3 => Face(height=1.15, inherit=:markdown_header), + :markdown_h4 => Face(height=1.12, inherit=:markdown_header), + :markdown_h5 => Face(height=1.08, inherit=:markdown_header), + :markdown_h6 => Face(height=1.05, inherit=:markdown_header), + :markdown_admonition => Face(weight=:bold), + :markdown_code => Face(inherit=:code), + :markdown_footnote => Face(inherit=:bright_yellow), + :markdown_hrule => Face(inherit=:shadow), + :markdown_inlinecode => Face(inherit=:markdown_code), + :markdown_latex => Face(inherit=:magenta), + :markdown_link => Face(underline=:bright_blue), + :markdown_list => Face(foreground=:blue), +] + +__init__() = foreach(addface!, MARKDOWN_FACES) + parse(markdown::AbstractString; flavor = julia) = parse(IOBuffer(markdown), flavor = flavor) parse_file(file::AbstractString; flavor = julia) = parse(read(file, String), flavor = flavor) diff --git a/stdlib/Markdown/src/render/html.jl b/stdlib/Markdown/src/render/html.jl index e7d436f2ccbda..829fa6c7bf986 100644 --- a/stdlib/Markdown/src/render/html.jl +++ b/stdlib/Markdown/src/render/html.jl @@ -67,6 +67,9 @@ end function html(io::IO, code::Code) withtag(io, :pre) do + if code.language == "styled" + code = Code("", String(styled(code.code))) + end maybe_lang = !isempty(code.language) ? Any[:class=>"language-$(code.language)"] : [] withtag(io, :code, maybe_lang...) do htmlesc(io, code.code) @@ -134,6 +137,9 @@ function htmlinline(io::IO, content::Vector) end function htmlinline(io::IO, code::Code) + if code.language == "styled" + code = Code("", String(styled(code.code))) + end withtag(io, :code) do htmlesc(io, code.code) end diff --git a/stdlib/Markdown/src/render/latex.jl b/stdlib/Markdown/src/render/latex.jl index df52b2849f2b0..fad0508ce0e59 100644 --- a/stdlib/Markdown/src/render/latex.jl +++ b/stdlib/Markdown/src/render/latex.jl @@ -33,6 +33,9 @@ function latex(io::IO, header::Header{l}) where l end function latex(io::IO, code::Code) + if code.language == "styled" + code = Code("", String(styled(code.code))) + end occursin("\\end{verbatim}", code.code) && error("Cannot include \"\\end{verbatim}\" in a latex code block") wrapblock(io, "verbatim") do println(io, code.code) diff --git a/stdlib/Markdown/src/render/rst.jl b/stdlib/Markdown/src/render/rst.jl index 752916c581a07..e441ee0495da0 100644 --- a/stdlib/Markdown/src/render/rst.jl +++ b/stdlib/Markdown/src/render/rst.jl @@ -23,10 +23,16 @@ end function rst(io::IO, code::Code) if code.language == "jldoctest" println(io, ".. doctest::\n") - elseif code.language != "rst" + elseif code.language in ("", "julia", "julia-repl") println(io, ".. code-block:: julia\n") + elseif code.language == "rst" + elseif code.language == "styled" + code = Code("", String(styled(code.code))) + println(io, "::\n") + else + println(io, "::\n") end - for l in lines(code.code) + for l in eachsplit(code.code, '\n') println(io, " ", l) end end @@ -90,7 +96,7 @@ end function rst(io::IO, l::LaTeX) println(io, ".. math::\n") - for line in lines(l.formula) + for line in eachsplit(l.formula, '\n') println(io, " ", line) end end diff --git a/stdlib/Markdown/src/render/terminal/formatting.jl b/stdlib/Markdown/src/render/terminal/formatting.jl index a031de4d9ad82..009fd2eb3af18 100644 --- a/stdlib/Markdown/src/render/terminal/formatting.jl +++ b/stdlib/Markdown/src/render/terminal/formatting.jl @@ -1,68 +1,68 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -# Wrapping +const AnnotIO = Union{AnnotatedIOBuffer, IOContext{AnnotatedIOBuffer}} -function ansi_length(s) - replace(s, r"\e\[[0-9]+m" => "") |> textwidth +function annotprint(f::Function, args...) + buf = AnnotatedIOBuffer() + f(buf, args...) + read(seekstart(buf), AnnotatedString) end -words(s) = split(s, " ") -lines(s) = split(s, "\n") +""" + with_output_annotations(f::Function, io::AnnotIO, annots::Pair{Symbol, <:Any}...) -function wrapped_line(io::IO, s::AbstractString, width, i) - ws = words(s) - lines = String[] - for word in ws - word_length = ansi_length(word) - word_length == 0 && continue - if isempty(lines) || i + word_length + 1 > width - i = word_length - if length(lines) > 0 - last_line = lines[end] - maybe_underline = findlast(Base.text_colors[:underline], last_line) - if !isnothing(maybe_underline) - # disable underline style at end of line if not already disabled. - maybe_disable_underline = max( - last(something(findlast(Base.disable_text_style[:underline], last_line), -1)), - last(something(findlast(Base.text_colors[:normal], last_line), -1)), - ) +Call `f(io)`, and apply `annots` to the output created by doing so. +""" +function with_output_annotations(f::Function, io::AnnotIO, annots::Pair{Symbol, <:Any}...) + @nospecialize annots + aio = if io isa AnnotatedIOBuffer io else io.io end + start = position(aio) + 1 + f(io) + stop = position(aio) + sortedindex = searchsortedlast(aio.annotations, (start:stop,), by=first) + for (i, annot) in enumerate(annots) + insert!(aio.annotations, sortedindex + i, (start:stop, annot)) + end +end - if maybe_disable_underline < 0 || maybe_disable_underline < last(maybe_underline) +""" + wraplines(content::AnnotatedString, width::Integer = 80, column::Integer = 0) - lines[end] = last_line * Base.disable_text_style[:underline] - word = Base.text_colors[:underline] * word - end +Wrap `content` into a vector of lines of at most `width` (according to +`textwidth`), with the first line starting at `column`. +""" +function wraplines(content::Union{Annot, SubString{<:Annot}}, width::Integer = 80, column::Integer = 0) where { Annot <: AnnotatedString} + s, lines = String(content), SubString{Annot}[] + i, lastwrap, slen = firstindex(s), 0, ncodeunits(s) + most_recent_break_opportunity = 1 + while i < slen + if isspace(s[i]) && s[i] != '\n' + most_recent_break_opportunity = i + elseif s[i] == '\n' + push!(lines, content[nextind(s, lastwrap):prevind(s, i)]) + lastwrap = i + column = 0 + elseif column >= width && most_recent_break_opportunity > 1 + if lastwrap == most_recent_break_opportunity + nextbreak = findfirst(isspace, @view s[nextind(s, lastwrap):end]) + if isnothing(nextbreak) + break + else + most_recent_break_opportunity = lastwrap + nextbreak end + i = most_recent_break_opportunity + else + i = nextind(s, most_recent_break_opportunity) end - push!(lines, word) - else - i += word_length + 1 - lines[end] *= " " * word # this could be more efficient + push!(lines, content[nextind(s, lastwrap):prevind(s, most_recent_break_opportunity)]) + lastwrap = most_recent_break_opportunity + column = 0 end + column += textwidth(s[i]) + i = nextind(s, i) end - return i, lines -end - -function wrapped_lines(io::IO, s::AbstractString; width = 80, i = 0) - ls = String[] - for ss in lines(s) - i, line = wrapped_line(io, ss, width, i) - append!(ls, line) + if lastwrap < slen + push!(lines, content[nextind(s, lastwrap):end]) end - return ls + lines end - -wrapped_lines(io::IO, f::Function, args...; width = 80, i = 0) = - wrapped_lines(io, sprint(f, args...; context=io), width = width, i = 0) - -function print_wrapped(io::IO, s...; width = 80, pre = "", i = 0) - lines = wrapped_lines(io, s..., width = width, i = i) - isempty(lines) && return 0, 0 - print(io, lines[1]) - for line in lines[2:end] - print(io, '\n', pre, line) - end - length(lines), length(pre) + ansi_length(lines[end]) -end - -print_wrapped(f::Function, io::IO, args...; kws...) = print_wrapped(io, f, args...; kws...) diff --git a/stdlib/Markdown/src/render/terminal/render.jl b/stdlib/Markdown/src/render/terminal/render.jl index 20b1ef6d041fc..2afd0bd99ff9f 100644 --- a/stdlib/Markdown/src/render/terminal/render.jl +++ b/stdlib/Markdown/src/render/terminal/render.jl @@ -16,118 +16,135 @@ end term(io::IO, md::MD, columns = cols(io)) = term(io, md.content, columns) function term(io::IO, md::Paragraph, columns) - print(io, ' '^margin) - print_wrapped(io, width = columns-2margin, pre = ' '^margin) do io - terminline(io, md.content) + lines = wraplines(annotprint(terminline, md.content), columns-2margin) + for (i, line) in enumerate(lines) + print(io, ' '^margin, line) + i < length(lines) && println(io) end end function term(io::IO, md::BlockQuote, columns) - s = sprint(term, md.content, columns - 10; context=io) - lines = split(rstrip(s), '\n') - print(io, ' '^margin, '│', lines[1]) - for i = 2:length(lines) - print(io, '\n', ' '^margin, '│', lines[i]) + content = annotprint(term, md.content, columns - 10) + lines = wraplines(rstrip(content), columns - 10) + for (i, line) in enumerate(lines) + print(io, ' '^margin, '│', line) + i < length(lines) && println(io) end end function term(io::IO, md::Admonition, columns) - col = :default - # If the types below are modified, the page manual/documentation.md must be updated accordingly. - if md.category == "danger" - col = Base.error_color() - elseif md.category == "warning" - col = Base.warn_color() - elseif md.category in ("info", "note") - col = Base.info_color() - elseif md.category == "tip" - col = :green + accent = if md.category == "danger" + :error + elseif md.category in ("warning", "info", "note", "tip") + Symbol(md.category) + elseif md.category == "compat" + :bright_cyan + else + :default end - printstyled(io, ' '^margin, "│ "; color=col, bold=true) - printstyled(io, isempty(md.title) ? md.category : md.title; color=col, bold=true) - printstyled(io, '\n', ' '^margin, '│', '\n'; color=col, bold=true) - s = sprint(term, md.content, columns - 10; context=io) - lines = split(rstrip(s), '\n') - for i in eachindex(lines) - printstyled(io, ' '^margin, '│'; color=col, bold=true) - print(io, lines[i]) - i < lastindex(lines) && println(io) + title = if isempty(md.title) md.category else md.title end + print(io, ' '^margin, styled"{$accent,markdown_admonition:│ $title}", + '\n', ' '^margin, styled"{$accent,markdown_admonition:│}", '\n') + content = annotprint(term, md.content, columns - 10) + lines = split(rstrip(content), '\n') + for (i, line) in enumerate(lines) + print(io, ' '^margin, styled"{$accent,markdown_admonition:│}", line) + i < length(lines) && println(io) end end function term(io::IO, f::Footnote, columns) print(io, ' '^margin, "│ ") - printstyled(io, "[^$(f.id)]", bold=true) + print(io, styled"{markdown_footnote:[^$(f.id)]}") println(io, '\n', ' '^margin, '│') - s = sprint(term, f.text, columns - 10; context=io) - lines = split(rstrip(s), '\n') - for i in eachindex(lines) - print(io, ' '^margin, '│', lines[i]) - i < lastindex(lines) && println(io) + content = annotprint(term, f.text, columns - 10) + lines = split(rstrip(content), '\n') + for (i, line) in enumerate(lines) + print(io, ' '^margin, '│', line) + i < length(lines) && println(io) end end function term(io::IO, md::List, columns) for (i, point) in enumerate(md.items) - print(io, ' '^2margin, isordered(md) ? "$(i + md.ordered - 1). " : "• ") - print_wrapped(io, width = columns-(4margin+2), pre = ' '^(2margin+3), - i = 2margin+2) do io - term(io, point, columns - 10) + bullet = isordered(md) ? "$(i + md.ordered - 1)." : "• " + print(io, ' '^2margin, styled"{markdown_list:$bullet} ") + content = annotprint(term, point, columns - 10) + lines = split(rstrip(content), '\n') + for (l, line) in enumerate(lines) + l > 1 && print(io, ' '^(2margin+3)) + print(io, lstrip(line)) + l < length(lines) && println(io) end - i < lastindex(md.items) && print(io, '\n', '\n') - end -end - -function _term_header(io::IO, md, char, columns) - text = terminline_string(io, md.text) - with_output_color(:bold, io) do io - pre = ' '^margin - print(io, pre) - line_no, lastline_width = print_wrapped(io, text, - width=columns - 4margin; pre) - line_width = min(lastline_width, columns) - if line_no > 1 - line_width = max(line_width, div(columns, 3)+length(pre)) - end - header_width = max(0, line_width-length(pre)) - char != ' ' && header_width > 0 && print(io, '\n', ' '^(margin), char^header_width) + i < length(md.items) && print(io, '\n'^(1 + md.loose)) end end const _header_underlines = collect("≡=–-⋅ ") # TODO settle on another option with unicode e.g. "≡=≃–∼⋅" ? -function term(io::IO, md::Header{l}, columns) where l +function term(io::AnnotIO, md::Header{l}, columns) where l + face = Symbol("markdown_h$l") underline = _header_underlines[l] - _term_header(io, md, underline, columns) + pre = ' '^margin + local line_width + with_output_annotations(io, :face => face) do io + headline = annotprint(terminline, md.text) + lines = wraplines(headline, columns - 4margin) + for (i, line) in enumerate(lines) + print(io, pre, line) + i < length(lines) && println(io) + end + line_width = if length(lines) == 1 + min(textwidth(lines[end]), columns) + elseif length(lines) > 1 + max(textwidth(lines[end]), div(columns, 3)+length(pre)) + else + 0 + end + end + header_width = max(0, line_width) + if underline != ' ' && header_width > 0 + print(io, '\n', ' '^(margin)) + with_output_annotations(io -> print(io, underline^header_width), io, :face => face) + end end function term(io::IO, md::Code, columns) - with_output_color(:cyan, io) do io - L = lines(md.code) - for i in eachindex(L) - print(io, ' '^margin, L[i]) - i < lastindex(L) && println(io) - end + code = if md.language ∈ ("", "julia", "julia-repl", "jldoctest") + highlight(md.code) + elseif md.language == "styled" + styled(md.code) + else + styled"{markdown_code:$(md.code)}" + end + lines = split(code, '\n') + for (i, line) in enumerate(lines) + print(io, ' '^margin, line) + i < length(lines) && println(io) end end function term(io::IO, tex::LaTeX, columns) - printstyled(io, ' '^margin, tex.formula, color=:magenta) + print(io, ' '^margin, styled"{markdown_latex:$(tex.formula)}") end term(io::IO, br::LineBreak, columns) = nothing # line breaks already printed between subsequent elements function term(io::IO, br::HorizontalRule, columns) - print(io, ' '^margin, '─'^(columns - 2margin)) + print(io, ' '^margin, styled"{markdown_hrule:$('─'^(columns - 2margin))}") +end + +function term(io::IO, md::MarkdownElement, columns) + a = IOContext(AnnotatedIOBuffer(), io) + term(a, md, columns) + print(io, read(seekstart(a.io), AnnotatedString)) end term(io::IO, x, _) = show(io, MIME"text/plain"(), x) # Inline Content -terminline_string(io::IO, md) = sprint(terminline, md; context=io) - terminline(io::IO, content...) = terminline(io, collect(content)) function terminline(io::IO, content::Vector) @@ -140,12 +157,12 @@ function terminline(io::IO, md::AbstractString) print(io, replace(md, r"[\s\t\n]+" => ' ')) end -function terminline(io::IO, md::Bold) - with_output_color(terminline, :bold, io, md.text) +function terminline(io::AnnotIO, md::Bold) + with_output_annotations(io -> terminline(io, md.text), io, :face => :bold) end -function terminline(io::IO, md::Italic) - with_output_color(terminline, :underline, io, md.text) +function terminline(io::AnnotIO, md::Italic) + with_output_annotations(io -> terminline(io, md.text), io, :face => :italic) end function terminline(io::IO, md::LineBreak) @@ -156,20 +173,36 @@ function terminline(io::IO, md::Image) terminline(io, "(Image: $(md.alt))") end -terminline(io::IO, f::Footnote) = with_output_color(terminline, :bold, io, "[^$(f.id)]") +function terminline(io::IO, f::Footnote) + print(io, styled"{markdown_footnote:[^$(f.id)]}") +end -function terminline(io::IO, md::Link) - url = !Base.startswith(md.url, "@ref") ? " ($(md.url))" : "" - text = terminline_string(io, md.text) - terminline(io, text, url) +function terminline(io::AnnotIO, md::Link) + annots = if occursin(r"^(https?|file)://", md.url) + (:face => :markdown_link, :link => md.url) + else + (:face => :markdown_link,) + end + with_output_annotations(io -> terminline(io, md.text), io, annots...) end function terminline(io::IO, code::Code) - printstyled(io, code.code, color=:cyan) + body = if code.language == "styled" + styled(code.code) + else + code.code + end + print(io, styled"{markdown_inlinecode:$body}") end function terminline(io::IO, tex::LaTeX) - printstyled(io, tex.formula, color=:magenta) + print(io, styled"{markdown_latex:$(tex.formula)}") +end + +function terminline(io::IO, md::MarkdownElement) + a = IOContext(AnnotatedIOBuffer(), io) + terminline(a, md) + print(io, read(seekstart(a.io), AnnotatedString)) end terminline(io::IO, x) = show(io, MIME"text/plain"(), x) diff --git a/stdlib/Markdown/test/runtests.jl b/stdlib/Markdown/test/runtests.jl index 116282a0bea3b..a3026683ad1e7 100644 --- a/stdlib/Markdown/test/runtests.jl +++ b/stdlib/Markdown/test/runtests.jl @@ -1,6 +1,6 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -using Test, Markdown +using Test, Markdown, StyledStrings import Markdown: MD, Paragraph, Header, Italic, Bold, LineBreak, plain, term, html, rst, Table, Code, LaTeX, Footnote import Base: show @@ -233,7 +233,7 @@ World""" |> plain == "Hello\n\n---\n\nWorld\n" # multiple whitespace is ignored @test sprint(term, md"a b") == " a b" -@test sprint(term, md"[x](https://julialang.org)") == " x (https://julialang.org)" +@test sprint(term, md"[x](https://julialang.org)") == " x" @test sprint(term, md"[x](@ref)") == " x" @test sprint(term, md"[x](@ref something)") == " x" @test sprint(term, md"![x](https://julialang.org)") == " (Image: x)" @@ -298,6 +298,7 @@ end let doc = md""" 1. a bc def ghij a bc def ghij a bc def ghij a bc def ghij a bc def ghij a bc def ghij a bc def ghij a bc def ghij a bc def ghij + 2. a bc def ghij a bc def ghij a bc def ghij a bc def ghij a bc def ghij a bc def ghij a bc def ghij a bc def ghij a bc def ghij """ str = sprint(term, doc, 50) @@ -376,8 +377,8 @@ table = md""" # mime output let out = @test sprint(show, "text/plain", book) == - " Title\n ≡≡≡≡≡\n\n Some discussion\n\n │ A quote\n\n Section important\n =================\n\n Some bolded\n\n • list1\n\n • list2" - @test sprint(show, "text/plain", md"#") == " " # edge case of empty header + " Title\n ≡≡≡≡≡\n\n Some discussion\n\n │ A quote\n\n Section important\n =================\n\n Some bolded\n\n • list1\n • list2" + @test sprint(show, "text/plain", md"#") == "" # edge case of empty header @test sprint(show, "text/markdown", book) == """ # Title @@ -1157,7 +1158,7 @@ let buf = IOBuffer() show(buf, "text/markdown", md"*emph*") @test String(take!(buf)) == "*emph*\n" show(IOContext(buf, :color=>true), "text/plain", md"*emph*") - @test String(take!(buf)) == " \e[4memph\e[24m" + @test String(take!(buf)) in (" \e[3memph\e[23m", " \e[4memph\e[24m") end let word = "Markdown" # disable underline when wrapping lines @@ -1166,8 +1167,8 @@ let word = "Markdown" # disable underline when wrapping lines long_italic_text = Markdown.parse('_' * join(fill(word, 10), ' ') * '_') show(ctx, MIME("text/plain"), long_italic_text) lines = split(String(take!(buf)), '\n') - @test endswith(lines[begin], Base.disable_text_style[:underline]) - @test startswith(lines[begin+1], ' '^Markdown.margin * Base.text_colors[:underline]) + @test endswith(lines[begin], r"\e\[2[34]m") + @test startswith(lines[begin+1], Regex(' '^Markdown.margin * "\e\\[[34]m")) end let word = "Markdown" # pre is of size Markdown.margin when wrapping title @@ -1176,7 +1177,9 @@ let word = "Markdown" # pre is of size Markdown.margin when wrapping title long_title = Markdown.parse("# " * join(fill(word, 3))) show(ctx, MIME("text/plain"), long_title) lines = split(String(take!(buf)), '\n') - @test all(startswith(Base.text_colors[:bold] * ' '^Markdown.margin), lines) + @test all(l -> startswith(l, ' '^Markdown.margin * StyledStrings.ANSI_STYLE_CODES.bold_weight) || + startswith(l, StyledStrings.ANSI_STYLE_CODES.bold_weight * ' '^Markdown.margin), + lines) end struct Struct49454 end @@ -1259,8 +1262,9 @@ end s = @md_str """ Misc:\\ - line\\ + break """ - @test sprint(show, MIME("text/plain"), s) == " Misc:\n - line" + @test sprint(show, MIME("text/plain"), s) == " Misc:\n - line\n break" end @testset "pullrequest #41552: a code block has \\end{verbatim}" begin diff --git a/stdlib/StyledStrings.version b/stdlib/StyledStrings.version index 81a599f125406..2067083aec74b 100644 --- a/stdlib/StyledStrings.version +++ b/stdlib/StyledStrings.version @@ -1,4 +1,4 @@ STYLEDSTRINGS_BRANCH = main -STYLEDSTRINGS_SHA1 = ac472083359dde956aed8c61d43b8158ac84d9ce +STYLEDSTRINGS_SHA1 = d7496d24d3f05536bce6a7eb4cd8ca05a75c02aa STYLEDSTRINGS_GIT_URL := https://github.com/JuliaLang/StyledStrings.jl.git STYLEDSTRINGS_TAR_URL = https://api.github.com/repos/JuliaLang/StyledStrings.jl/tarball/$1 diff --git a/test/precompile.jl b/test/precompile.jl index ffd6e24789aba..ea99653fe89be 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -445,6 +445,7 @@ precompile_test_harness(false) do dir # and their dependencies Dict(Base.PkgId(Base.root_module(Base, :SHA)) => Base.module_build_id(Base.root_module(Base, :SHA))), Dict(Base.PkgId(Base.root_module(Base, :Markdown)) => Base.module_build_id(Base.root_module(Base, :Markdown))), + Dict(Base.PkgId(Base.root_module(Base, :JuliaSyntaxHighlighting)) => Base.module_build_id(Base.root_module(Base, :JuliaSyntaxHighlighting))), Dict(Base.PkgId(Base.root_module(Base, :StyledStrings)) => Base.module_build_id(Base.root_module(Base, :StyledStrings))), # and their dependencies Dict(Base.PkgId(Base.root_module(Base, :Base64)) => Base.module_build_id(Base.root_module(Base, :Base64))),