Skip to content

Commit 0bfb3b2

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 59d2f93 commit 0bfb3b2

File tree

7 files changed

+204
-139
lines changed

7 files changed

+204
-139
lines changed

doc/Manifest.toml

Lines changed: 6 additions & 2 deletions
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]]
@@ -94,7 +98,7 @@ deps = ["Unicode"]
9498
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
9599

96100
[[deps.REPL]]
97-
deps = ["InteractiveUtils", "Markdown", "Sockets", "StyledStrings", "Unicode"]
101+
deps = ["InteractiveUtils", "JuliaSyntaxHighlighting", "Markdown", "Sockets", "StyledStrings", "Unicode"]
98102
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
99103

100104
[[deps.Random]]

pkgimage.mk

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,11 @@ $(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))
9897
$(eval $(call stdlib_builder,DelimitedFiles,Mmap))
99-
$(eval $(call stdlib_builder,JuliaSyntaxHighlighting,))
98+
$(eval $(call stdlib_builder,JuliaSyntaxHighlighting,StyledStrings))
10099

101100
# 2-depth packages
102101
$(eval $(call stdlib_builder,LLD_jll,Zlib_jll libLLVM_jll Artifacts Libdl))
@@ -107,19 +106,20 @@ $(eval $(call stdlib_builder,Dates,Printf))
107106
$(eval $(call stdlib_builder,Distributed,Random Serialization Sockets))
108107
$(eval $(call stdlib_builder,Future,Random))
109108
$(eval $(call stdlib_builder,UUIDs,Random SHA))
110-
$(eval $(call stdlib_builder,InteractiveUtils,Markdown))
109+
$(eval $(call stdlib_builder,Markdown,Base64,JuliaSyntaxHighlighting,StyledStrings))
111110

112111
# 3-depth packages
113112
$(eval $(call stdlib_builder,LibGit2_jll,MbedTLS_jll LibSSH2_jll Artifacts Libdl))
114113
$(eval $(call stdlib_builder,LibCURL_jll,LibSSH2_jll nghttp2_jll MbedTLS_jll Zlib_jll Artifacts Libdl))
115-
$(eval $(call stdlib_builder,REPL,InteractiveUtils Markdown Sockets StyledStrings Unicode))
114+
$(eval $(call stdlib_builder,InteractiveUtils,Markdown))
116115
$(eval $(call stdlib_builder,SharedArrays,Distributed Mmap Random Serialization))
117116
$(eval $(call stdlib_builder,TOML,Dates))
118117
$(eval $(call stdlib_builder,Test,Logging Random Serialization InteractiveUtils))
119118

120119
# 4-depth packages
121120
$(eval $(call stdlib_builder,LibGit2,LibGit2_jll NetworkOptions Printf SHA Base64))
122121
$(eval $(call stdlib_builder,LibCURL,LibCURL_jll MozillaCACerts_jll))
122+
$(eval $(call stdlib_builder,REPL,InteractiveUtils Markdown Sockets StyledStrings Unicode))
123123

124124
# 5-depth packages
125125
$(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)