Skip to content

Commit

Permalink
clean up help mode, add tests, and fix \xor=
Browse files Browse the repository at this point in the history
  • Loading branch information
epithet committed Jun 9, 2021
1 parent e660918 commit a0a9fc9
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 42 deletions.
2 changes: 1 addition & 1 deletion base/docs/Docs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ astname(@nospecialize(other), ismacro::Bool) = other
macroname(s::Symbol) = Symbol('@', s)
macroname(x::Expr) = Expr(x.head, x.args[1], macroname(x.args[end].value))

isfield(@nospecialize x) = isexpr(x, :.) &&
isfield(@nospecialize x) = isexpr(x, :.) && length(x.args) == 2 &&
(isa(x.args[1], Symbol) || isfield(x.args[1])) &&
(isa(x.args[2], QuoteNode) || isexpr(x.args[2], :quote))

Expand Down
86 changes: 47 additions & 39 deletions stdlib/REPL/src/docview.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,8 @@ helpmode(line::AbstractString) = helpmode(stdout, line)

const extended_help_on = Ref{Any}(nothing)

function _helpmode(io::IO, line::AbstractString)
function _helpmode_parse(line::AbstractString)
line = strip(line)
ternary_operator_help = (line == "?" || line == "?:")
if startswith(line, '?') && !ternary_operator_help
line = line[2:end]
extended_help_on[] = line
brief = false
else
extended_help_on[] = nothing
brief = true
end
# interpret anything starting with # or #= as asking for help on comments
if startswith(line, "#")
if startswith(line, "#=")
Expand All @@ -45,26 +36,52 @@ function _helpmode(io::IO, line::AbstractString)
end
end
x = Meta.parse(line, raise = false, depwarn = false)
assym = Symbol(line)
expr =
if haskey(keywords, Symbol(line)) || Base.isoperator(assym) || isexpr(x, :error) ||
isexpr(x, :invalid) || isexpr(x, :incomplete)
# Docs for keywords must be treated separately since trying to parse a single
# keyword such as `function` would throw a parse error due to the missing `end`.
assym
elseif isexpr(x, (:using, :import))
(x::Expr).head
else
# Retrieving docs for macros requires us to make a distinction between the text
# `@macroname` and `@macroname()`. These both parse the same, but are used by
# the docsystem to return different results. The first returns all documentation
# for `@macroname`, while the second returns *only* the docs for the 0-arg
# definition if it exists.
(isexpr(x, :macrocall, 1) && !endswith(line, "()")) ? quot(x) : x
if Meta.isexpr(x, :error)
# handle operators like +=
asinfix = Meta.parse("x $line x", raise = false, depwarn = false)
if asinfix isa Expr && length(asinfix.args) == 2 && asinfix.args[1] == asinfix.args[2] == :x
x = asinfix.head
end
end
if Meta.isexpr(x, :.) && length(x.args) == 1 && x.args[1] isa Symbol
# handle broadcast operators
x = Symbol(string(x.head) * string(x.args[1]))
end
assym = Symbol(line)
if haskey(keywords, assym) || isexpr(x, :error) || isexpr(x, :invalid) || isexpr(x, :incomplete)
# Docs for keywords must be treated separately since trying to parse a single
# keyword such as `function` would throw a parse error due to the missing `end`.
assym
elseif isexpr(x, (:using, :import))
(x::Expr).head
else
# Retrieving docs for macros requires us to make a distinction between the text
# `@macroname` and `@macroname()`. These both parse the same, but are used by
# the docsystem to return different results. The first returns all documentation
# for `@macroname`, while the second returns *only* the docs for the 0-arg
# definition if it exists.
(isexpr(x, :macrocall, 1) && !endswith(line, "()")) ? quot(x) : x
end
end

function _helpmode(io::IO, line::AbstractString)
line = strip(line)
ternary_operator_help = (line == "?" || line == "?:")
if startswith(line, '?') && !ternary_operator_help
line = line[2:end]
extended_help_on[] = line
brief = false
else
extended_help_on[] = nothing
brief = true
end
expr = _helpmode_parse(line)
# 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 $brief)
quote
$REPL.repl_latex($io, $line)
$REPL.@repl $io $expr $brief
end
end
_helpmode(line::AbstractString) = _helpmode(stdout, line)

Expand Down Expand Up @@ -221,7 +238,7 @@ function lookup_doc(ex)
str = string(ex)
isdotted = startswith(str, ".")
if endswith(str, "=") && Base.operator_precedence(ex) == Base.prec_assignment && ex !== :(:=)
op = str[1:end-1]
op = first(rsplit(str, c->true, limit=2))
eq = isdotted ? ".=" : "="
return Markdown.parse("`x $op= y` is a synonym for `x $eq x $op y`")
elseif isdotted && ex !== :(..)
Expand Down Expand Up @@ -402,7 +419,7 @@ function symbol_latex(s::String)

return get(symbols_latex, s, "")
end
function repl_latex(io::IO, s::String)
function repl_latex(io::IO, s::AbstractString)
# decompose NFC-normalized identifier to match tab-completion input
s = normalize(s, :NFD)
latex = symbol_latex(s)
Expand Down Expand Up @@ -450,23 +467,14 @@ function repl_latex(io::IO, s::String)
println(io, '\n')
end
end
repl_latex(s::String) = repl_latex(stdout, s)

function normalize_symbol(s::Symbol)
# parse to apply string->normsymbol
normalized = Meta.parse(string(s), raise = false, depwarn = false)
normalized isa Symbol ? normalized : s
end
repl_latex(s::AbstractString) = repl_latex(stdout, s)

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

function repl(io::IO, s::Symbol; brief::Bool=true)
str_orig = string(s)
s = normalize_symbol(s)
str = string(s)
quote
repl_latex($io, $str_orig)
repl_search($io, $str)
$(if !isdefined(Main, s) && !haskey(keywords, s) && !Base.isoperator(s)
:(repl_corrections($io, $str))
Expand Down
4 changes: 3 additions & 1 deletion stdlib/REPL/test/docview.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ import Markdown
@test all(duplicates .∈ Ref(keys(REPLCompletions.symbols_latex_canonical)))
end

@testset "unicode normalization" begin
@testset "unicode operators" begin
@test contains(let buf = IOBuffer()
Core.eval(Main, REPL.helpmode(buf, "\u2212"))
String(take!(buf))
end, "\nsearch: - ")

@test REPL.lookup_doc(:⊻=) == Markdown.parse("`x ⊻= y` is a synonym for `x = x ⊻ y`")
end

@testset "quoting in doc search" begin
Expand Down
23 changes: 22 additions & 1 deletion stdlib/REPL/test/repl.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1100,8 +1100,29 @@ for (line, expr) in Pair[
"r\"...\"" => Expr(:macrocall, Symbol("@r_str"), LineNumberNode(1, :none), "..."),
"using Foo" => :using,
"import Foo" => :import,
"# comment" => Symbol("#"),
"#= foo\n=#" => Symbol("#="),
"=#" => Symbol("=#"),
"+" => :+,
"+=" => :+=,
".+" => :.+,
".+=" => :.+=,
"" => :,
"⊻=" => :⊻=,
".⊻" => :.⊻,
".⊻=" => :.⊻=,
"\u2212" => :-,
"\u2212=" => :-=,
".\u2212" => :.-,
".\u2212=" => :.-=,
"1+2" => :(1 + 2),
"1\u22122" => :(1 - 2),
"2e-2" => 2e-2,
"2e\u22122" => 2e-2,
"\ub5" => Symbol("\u03bc"), # µ (U+00B5 micro) -> μ (U+03BC greek small letter mu)
"\ub7" => Symbol("\u22c5"), # \cdotp -> \cdot
]
@test REPL._helpmode(line).args[4] == expr
@test REPL._helpmode_parse(line) == expr
@test help_result(line) isa Union{Markdown.MD,Nothing}
end

Expand Down

0 comments on commit a0a9fc9

Please sign in to comment.