Skip to content

Commit 453a91b

Browse files
committed
Reimplement Markdown printing using StyledStrings
Using StyledStrings for styled printing has a number of benefits, including but not limited to: - Italics "just working" on terminals that announce support - Functioning links, for the first time - Greater compossibility of rendered markdown content - Customisability of the printing style Then with JuliaSyntaxHighlighting, we get support for syntax-highlighted Julia code too.
1 parent 1b47feb commit 453a91b

File tree

7 files changed

+201
-137
lines changed

7 files changed

+201
-137
lines changed

doc/Manifest.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ git-tree-sha1 = "3c837543ddb02250ef42f4738347454f95079d4e"
4747
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
4848
version = "0.21.3"
4949

50+
[[deps.JuliaSyntaxHighlighting]]
51+
deps = ["StyledStrings"]
52+
uuid = "dc6e5ff7-fb65-4e79-a425-ec3bc9c03011"
53+
5054
[[deps.LibGit2]]
5155
deps = ["Base64", "LibGit2_jll", "NetworkOptions", "Printf", "SHA"]
5256
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"
@@ -68,7 +72,7 @@ uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
6872
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
6973

7074
[[deps.Markdown]]
71-
deps = ["Base64"]
75+
deps = ["Base64", "JuliaSyntaxHighlighting", "StyledStrings"]
7276
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
7377

7478
[[deps.MbedTLS_jll]]

pkgimage.mk

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ $(eval $(call stdlib_builder,libLLVM_jll,Artifacts Libdl))
9191
$(eval $(call stdlib_builder,libblastrampoline_jll,Artifacts Libdl))
9292
$(eval $(call stdlib_builder,p7zip_jll,Artifacts Libdl))
9393
$(eval $(call stdlib_builder,OpenBLAS_jll,Artifacts Libdl))
94-
$(eval $(call stdlib_builder,Markdown,Base64))
9594
$(eval $(call stdlib_builder,Printf,Unicode))
9695
$(eval $(call stdlib_builder,Random,SHA))
9796
$(eval $(call stdlib_builder,Tar,ArgTools,SHA))
@@ -106,7 +105,7 @@ $(eval $(call stdlib_builder,Dates,Printf))
106105
$(eval $(call stdlib_builder,Distributed,Random Serialization Sockets))
107106
$(eval $(call stdlib_builder,Future,Random))
108107
$(eval $(call stdlib_builder,UUIDs,Random SHA))
109-
$(eval $(call stdlib_builder,InteractiveUtils,Markdown))
108+
$(eval $(call stdlib_builder,Markdown,Base64,JuliaSyntaxHighlighting,StyledStrings))
110109

111110
# 3-depth packages
112111
$(eval $(call stdlib_builder,LibGit2_jll,MbedTLS_jll LibSSH2_jll Artifacts Libdl))
@@ -119,6 +118,7 @@ $(eval $(call stdlib_builder,Test,Logging Random Serialization InteractiveUtils)
119118
# 4-depth packages
120119
$(eval $(call stdlib_builder,LibGit2,LibGit2_jll NetworkOptions Printf SHA Base64))
121120
$(eval $(call stdlib_builder,LibCURL,LibCURL_jll MozillaCACerts_jll))
121+
$(eval $(call stdlib_builder,REPL,InteractiveUtils Markdown Sockets StyledStrings Unicode))
122122

123123
# 5-depth packages
124124
$(eval $(call stdlib_builder,Downloads,ArgTools FileWatching LibCURL NetworkOptions))

stdlib/Markdown/Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ version = "1.11.0"
44

55
[deps]
66
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
7+
StyledStrings = "f489334b-da3d-4c2e-b8f0-e476e12c162b"
8+
JuliaSyntaxHighlighting = "dc6e5ff7-fb65-4e79-a425-ec3bc9c03011"
79

810
[extras]
911
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

stdlib/Markdown/src/GitHub/table.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,15 +140,15 @@ end
140140

141141
function term(io::IO, md::Table, columns)
142142
margin_str = " "^margin
143-
cells = mapmap(x -> terminline_string(io, x), md.rows)
144-
padcells!(cells, md.align, len = ansi_length)
143+
cells = mapmap(x -> annotprint(terminline, x), md.rows)
144+
padcells!(cells, md.align, len = textwidth)
145145
for i = 1:length(cells)
146146
print(io, margin_str)
147147
join(io, cells[i], " ")
148148
if i == 1
149149
println(io)
150150
print(io, margin_str)
151-
join(io, [""^ansi_length(cells[i][j]) for j = 1:length(cells[1])], " ")
151+
join(io, [""^textwidth(cells[i][j]) for j = 1:length(cells[1])], " ")
152152
end
153153
i < length(cells) && println(io)
154154
end

stdlib/Markdown/src/Markdown.jl

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ Tools for working with the Markdown file format. Mainly for documentation.
55
"""
66
module Markdown
77

8-
import Base: show, ==, with_output_color, mapany
8+
import Base: AnnotatedString, AnnotatedIOBuffer, show, ==, with_output_color, mapany
99
using Base64: stringmime
1010

11+
using StyledStrings: StyledStrings, Face, addface!, @styled_str
12+
using JuliaSyntaxHighlighting: highlight, highlight!
13+
1114
# Margin for printing in terminal.
1215
const margin = 2
1316

@@ -28,6 +31,26 @@ include("render/terminal/render.jl")
2831

2932
export @md_str, @doc_str
3033

34+
const MARKDOWN_FACES = [
35+
:markdown_header => Face(weight=:bold),
36+
:markdown_h1 => Face(height=1.25, inherit=:markdown_header),
37+
:markdown_h2 => Face(height=1.20, inherit=:markdown_header),
38+
:markdown_h3 => Face(height=1.15, inherit=:markdown_header),
39+
:markdown_h4 => Face(height=1.12, inherit=:markdown_header),
40+
:markdown_h5 => Face(height=1.08, inherit=:markdown_header),
41+
:markdown_h6 => Face(height=1.05, inherit=:markdown_header),
42+
:markdown_admonition => Face(weight=:bold),
43+
:markdown_code => Face(inherit=:code),
44+
:markdown_footnote => Face(inherit=:bright_yellow),
45+
:markdown_hrule => Face(inherit=:shadow),
46+
:markdown_inlinecode => Face(inherit=:markdown_code),
47+
:markdown_latex => Face(inherit=:magenta),
48+
:markdown_link => Face(underline=:bright_blue),
49+
:markdown_list => Face(foreground=:blue),
50+
]
51+
52+
__init__() = foreach(addface!, MARKDOWN_FACES)
53+
3154
parse(markdown::AbstractString; flavor = julia) = parse(IOBuffer(markdown), flavor = flavor)
3255
parse_file(file::AbstractString; flavor = julia) = parse(read(file, String), flavor = flavor)
3356

Lines changed: 51 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,65 @@
11
# This file is a part of Julia. License is MIT: https://julialang.org/license
22

3-
# Wrapping
3+
const AnnotIO = Union{AnnotatedIOBuffer, IOContext{AnnotatedIOBuffer}}
44

5-
function ansi_length(s)
6-
replace(s, r"\e\[[0-9]+m" => "") |> textwidth
5+
function annotprint(f::Function, args...)
6+
buf = AnnotatedIOBuffer()
7+
f(buf, args...)
8+
read(buf, AnnotatedString)
79
end
810

9-
words(s) = split(s, " ")
10-
lines(s) = split(s, "\n")
11+
"""
12+
with_output_annotations(f::Function, io::AnnotIO, annots::Pair{Symbol, <:Any}...)
1113
12-
function wrapped_line(io::IO, s::AbstractString, width, i)
13-
ws = words(s)
14-
lines = String[]
15-
for word in ws
16-
word_length = ansi_length(word)
17-
word_length == 0 && continue
18-
if isempty(lines) || i + word_length + 1 > width
19-
i = word_length
20-
if length(lines) > 0
21-
last_line = lines[end]
22-
maybe_underline = findlast(Base.text_colors[:underline], last_line)
23-
if !isnothing(maybe_underline)
24-
# disable underline style at end of line if not already disabled.
25-
maybe_disable_underline = max(
26-
last(something(findlast(Base.disable_text_style[:underline], last_line), -1)),
27-
last(something(findlast(Base.text_colors[:normal], last_line), -1)),
28-
)
14+
Call `f(io)`, and apply `annots` to the output created by doing so.
15+
"""
16+
function with_output_annotations(f::Function, io::AnnotIO, annots::Pair{Symbol, <:Any}...)
17+
@nospecialize f annots
18+
aio = if io isa AnnotatedIOBuffer io else io.io end
19+
start = position(aio) + 1
20+
f(io)
21+
stop = position(aio)
22+
for annot in annots
23+
push!(aio.annotations, (start:stop, annot))
24+
end
25+
end
2926

30-
if maybe_disable_underline < 0 || maybe_disable_underline < last(maybe_underline)
27+
"""
28+
wraplines(content::AnnotatedString, width::Integer = 80, column::Integer = 0)
3129
32-
lines[end] = last_line * Base.disable_text_style[:underline]
33-
word = Base.text_colors[:underline] * word
34-
end
30+
Wrap `content` into a vector of lines of at most `width` (according to
31+
`textwidth`), with the first line starting at `column`.
32+
"""
33+
function wraplines(content::Annot, width::Integer = 80, column::Integer = 0) where { Annot <: AnnotatedString}
34+
s, lines = content.string, SubString{Annot}[]
35+
i, lastwrap, slen = firstindex(s), 0, ncodeunits(s)
36+
most_recent_break_oppotunity = 1
37+
while i < slen
38+
if s[i] == ' '
39+
most_recent_break_oppotunity = i
40+
elseif s[i] == '\n'
41+
push!(lines, content[nextind(s, lastwrap):prevind(s, i)])
42+
lastwrap = i
43+
column = 0
44+
elseif column >= width && most_recent_break_oppotunity > 1
45+
if lastwrap == most_recent_break_oppotunity
46+
nextbreak = findfirst(' ', @view s[nextind(s, lastwrap):end])
47+
if isnothing(nextbreak)
48+
break
49+
else
50+
most_recent_break_oppotunity = lastwrap + nextbreak
3551
end
52+
i = most_recent_break_oppotunity
3653
end
37-
push!(lines, word)
38-
else
39-
i += word_length + 1
40-
lines[end] *= " " * word # this could be more efficient
54+
push!(lines, content[nextind(s, lastwrap):prevind(s, most_recent_break_oppotunity)])
55+
lastwrap = most_recent_break_oppotunity
56+
column = 0
4157
end
58+
column += textwidth(s[i])
59+
i = nextind(s, i)
4260
end
43-
return i, lines
44-
end
45-
46-
function wrapped_lines(io::IO, s::AbstractString; width = 80, i = 0)
47-
ls = String[]
48-
for ss in lines(s)
49-
i, line = wrapped_line(io, ss, width, i)
50-
append!(ls, line)
61+
if lastwrap < slen
62+
push!(lines, content[nextind(s, lastwrap):end])
5163
end
52-
return ls
64+
lines
5365
end
54-
55-
wrapped_lines(io::IO, f::Function, args...; width = 80, i = 0) =
56-
wrapped_lines(io, sprint(f, args...; context=io), width = width, i = 0)
57-
58-
function print_wrapped(io::IO, s...; width = 80, pre = "", i = 0)
59-
lines = wrapped_lines(io, s..., width = width, i = i)
60-
isempty(lines) && return 0, 0
61-
print(io, lines[1])
62-
for line in lines[2:end]
63-
print(io, '\n', pre, line)
64-
end
65-
length(lines), length(pre) + ansi_length(lines[end])
66-
end
67-
68-
print_wrapped(f::Function, io::IO, args...; kws...) = print_wrapped(io, f, args...; kws...)

0 commit comments

Comments
 (0)