Navigation Menu

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support brief and extended docs (closes #25930) #34226

Merged
merged 3 commits into from Jan 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions NEWS.md
Expand Up @@ -12,6 +12,14 @@ Language changes
where it used to be incorrectly allowed. This is because `NTuple` refers only to homogeneous
tuples (this meaning has not changed) ([#34272]).

* In docstrings, a level-1 markdown header "Extended help" is now
interpreted as a marker dividing "brief help" from "extended help."
The REPL help mode only shows the brief help (the content before the
"Extended help" header) by default; prepend the expression with '?'
(in addition to the one that enters the help mode) to see the full
docstring. ([#25903])


Multi-threading changes
-----------------------

Expand Down
4 changes: 4 additions & 0 deletions doc/src/manual/documentation.md
Expand Up @@ -197,6 +197,10 @@ As in the example above, we recommend following some simple conventions when wri
rather than users, explaining e.g. which functions should be overridden and which
functions automatically use appropriate fallbacks. Such details are best kept separate
from the main description of the function's behavior.
5. For long docstrings, consider splitting the documentation with an
`# Extended help` header. The typical help-mode will show only the
material above the header; you can access the full help by adding a '?'
at the beginning of the expression (i.e., "??foo" rather than "?foo").

## Accessing Documentation

Expand Down
75 changes: 64 additions & 11 deletions stdlib/REPL/src/docview.jl
Expand Up @@ -19,8 +19,18 @@ using InteractiveUtils: subtypes
helpmode(io::IO, line::AbstractString) = :($REPL.insert_hlines($io, $(REPL._helpmode(io, line))))
helpmode(line::AbstractString) = helpmode(stdout, line)

const extended_help_on = Ref{Any}(nothing)

function _helpmode(io::IO, line::AbstractString)
line = strip(line)
if startswith(line, '?')
line = line[2:end]
extended_help_on[] = line
brief = false
else
extended_help_on[] = nothing
brief = true
end
x = Meta.parse(line, raise = false, depwarn = false)
expr =
if haskey(keywords, Symbol(line)) || isexpr(x, :error) || isexpr(x, :invalid)
Expand All @@ -37,7 +47,7 @@ function _helpmode(io::IO, line::AbstractString)
end
# the following must call repl(io, expr) via the @repl macro
# so that the resulting expressions are evaluated in the Base.Docs namespace
:($REPL.@repl $io $expr)
:($REPL.@repl $io $expr $brief)
end
_helpmode(line::AbstractString) = _helpmode(stdout, line)

Expand Down Expand Up @@ -73,6 +83,48 @@ function parsedoc(d::DocStr)
d.object
end

## Trimming long help ("# Extended help")

struct Message # For direct messages to the terminal
msg # AbstractString
fmt # keywords to `printstyled`
end
Message(msg) = Message(msg, ())

function Markdown.term(io::IO, msg::Message, columns)
printstyled(io, msg.msg; msg.fmt...)
end

function trimdocs(md::Markdown.MD, brief::Bool)
brief || return md
md, trimmed = _trimdocs(md, brief)
if trimmed
line = extended_help_on[]
line = isa(line, AbstractString) ? line : ""
push!(md.content, Message("Extended help is available with `??$line`", (color=Base.info_color(), bold=true)))
end
return md
end

function _trimdocs(md::Markdown.MD, brief::Bool)
content, trimmed = [], false
for c in md.content
if isa(c, Markdown.Header{1}) && isa(c.text, AbstractArray) &&
lowercase(c.text[1]) ∈ ("extended help",
"extended documentation",
"extended docs")
trimmed = true
break
end
c, trm = _trimdocs(c, brief)
trimmed |= trm
push!(content, c)
end
return Markdown.MD(content, md.meta), trimmed
end

_trimdocs(md, brief::Bool) = md, false

"""
Docs.doc(binding, sig)

Expand Down Expand Up @@ -273,29 +325,29 @@ function repl_latex(io::IO, s::String)
end
repl_latex(s::String) = repl_latex(stdout, s)

macro repl(ex) repl(ex) end
macro repl(io, ex) repl(io, ex) end
macro repl(ex, brief=false) repl(ex; brief=brief) end
macro repl(io, ex, brief) repl(io, ex; brief=brief) end

function repl(io::IO, s::Symbol)
function repl(io::IO, s::Symbol; brief::Bool=true)
str = string(s)
quote
repl_latex($io, $str)
repl_search($io, $str)
$(if !isdefined(Main, s) && !haskey(keywords, s)
:(repl_corrections($io, $str))
end)
$(_repl(s))
$(_repl(s, brief))
end
end
isregex(x) = isexpr(x, :macrocall, 3) && x.args[1] === Symbol("@r_str") && !isempty(x.args[3])
repl(io::IO, ex::Expr) = isregex(ex) ? :(apropos($io, $ex)) : _repl(ex)
repl(io::IO, str::AbstractString) = :(apropos($io, $str))
repl(io::IO, other) = esc(:(@doc $other))
repl(io::IO, ex::Expr; brief::Bool=true) = isregex(ex) ? :(apropos($io, $ex)) : _repl(ex, brief)
repl(io::IO, str::AbstractString; brief::Bool=true) = :(apropos($io, $str))
repl(io::IO, other; brief::Bool=true) = esc(:(@doc $other))
#repl(io::IO, other) = lookup_doc(other) # TODO

repl(x) = repl(stdout, x)
repl(x; brief=true) = repl(stdout, x; brief=brief)

function _repl(x)
function _repl(x, brief=true)
if isexpr(x, :call)
# determine the types of the values
kwargs = nothing
Expand Down Expand Up @@ -349,7 +401,7 @@ function _repl(x)
end
#docs = lookup_doc(x) # TODO
docs = esc(:(@doc $x))
if isfield(x)
docs = if isfield(x)
quote
if isa($(esc(x.args[1])), DataType)
fielddoc($(esc(x.args[1])), $(esc(x.args[2])))
Expand All @@ -360,6 +412,7 @@ function _repl(x)
else
docs
end
:(REPL.trimdocs($docs, $brief))
end

"""
Expand Down
43 changes: 43 additions & 0 deletions stdlib/REPL/test/repl.jl
Expand Up @@ -1040,6 +1040,49 @@ for line in ["′", "abstract", "type", "|=", ".="]
sprint(show, Base.eval(REPL._helpmode(IOBuffer(), line))::Union{Markdown.MD,Nothing}))
end

# Issue #25930

# Brief and extended docs (issue #25930)
let text =
"""
brief_extended()

Short docs

# Extended help

Long docs
""",
md = Markdown.parse(text)
@test md == REPL.trimdocs(md, false)
@test !isa(md.content[end], REPL.Message)
mdbrief = REPL.trimdocs(md, true)
@test length(mdbrief.content) == 3
@test isa(mdbrief.content[1], Markdown.Code)
@test isa(mdbrief.content[2], Markdown.Paragraph)
@test isa(mdbrief.content[3], REPL.Message)
@test occursin("??", mdbrief.content[3].msg)
end

module BriefExtended
"""
f()

Short docs

# Extended help

Long docs
"""
f() = nothing
end # module BriefExtended
buf = IOBuffer()
md = Base.eval(REPL._helpmode(buf, "$(@__MODULE__).BriefExtended.f"))
@test length(md.content) == 2 && isa(md.content[2], REPL.Message)
buf = IOBuffer()
md = Base.eval(REPL._helpmode(buf, "?$(@__MODULE__).BriefExtended.f"))
@test length(md.content) == 1 && length(md.content[1].content[1].content) == 4

# PR #27562
fake_repl() do stdin_write, stdout_read, repl
repltask = @async begin
Expand Down
22 changes: 18 additions & 4 deletions test/docs.jl
Expand Up @@ -1011,28 +1011,42 @@ dynamic_test.x = "test 2"
@test @doc(dynamic_test) == "test 2 Union{}"
@test @doc(dynamic_test(::String)) == "test 2 Tuple{String}"

let dt1 = _repl(:(dynamic_test(1.0)))
# For testing purposes, strip off the `trimdocs(expr)` wrapper
function striptrimdocs(expr)
if Meta.isexpr(expr, :call)
fex = expr.args[1]
if Meta.isexpr(fex, :.) && fex.args[1] == :REPL
fmex = fex.args[2]
if isa(fmex, QuoteNode) && fmex.value == :trimdocs
expr = expr.args[2]
end
end
end
return expr
end

let dt1 = striptrimdocs(_repl(:(dynamic_test(1.0))))
@test dt1 isa Expr
@test dt1.args[1] isa Expr
@test dt1.args[1].head === :macrocall
@test dt1.args[1].args[1] == Symbol("@doc")
@test dt1.args[1].args[3] == :(dynamic_test(::typeof(1.0)))
end
let dt2 = _repl(:(dynamic_test(::String)))
let dt2 = striptrimdocs(_repl(:(dynamic_test(::String))))
@test dt2 isa Expr
@test dt2.args[1] isa Expr
@test dt2.args[1].head === :macrocall
@test dt2.args[1].args[1] == Symbol("@doc")
@test dt2.args[1].args[3] == :(dynamic_test(::String))
end
let dt3 = _repl(:(dynamic_test(a)))
let dt3 = striptrimdocs(_repl(:(dynamic_test(a))))
@test dt3 isa Expr
@test dt3.args[1] isa Expr
@test dt3.args[1].head === :macrocall
@test dt3.args[1].args[1] == Symbol("@doc")
@test dt3.args[1].args[3].args[2].head == :(::) # can't test equality due to line numbers
end
let dt4 = _repl(:(dynamic_test(1.0,u=2.0)))
let dt4 = striptrimdocs(_repl(:(dynamic_test(1.0,u=2.0))))
@test dt4 isa Expr
@test dt4.args[1] isa Expr
@test dt4.args[1].head === :macrocall
Expand Down