Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions src/datatip.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#=
@TODO:
Use our own UI components for this: atom-ide-ui is already deprecated, ugly, not fully functional, and and...
Once we can come to handle links within datatips, we may want to append method tables as well
@TODO use our own UI components for this:
atom-ide-ui is already deprecated, ugly, not fully functional, and and...
=#

handle("datatip") do data
Expand All @@ -25,7 +24,7 @@ function datatip(word, mod, path, column = 1, row = 1, startrow = 0, context = "
ldt = localdatatip(word, column, row, startrow, context)
isempty(ldt) || return datatip(ldt)

tdt = topleveldatatip(mod, word)
tdt = globaldatatip(mod, word)
tdt !== nothing && return Dict(:error => false, :strings => tdt)

return Dict(:error => true) # nothing hits
Expand All @@ -36,7 +35,7 @@ datatip(dt::Int) = Dict(:error => false, :line => dt)
datatip(dt::Vector{Int}) = datatip(dt[1])

function localdatatip(word, column, row, startrow, context)
word = first(split(word, '.')) # ignore dot accessors
word = first(split(word, '.')) # always ignore dot accessors
position = row - startrow
ls = locals(context, position, column)
filter!(ls) do l
Expand All @@ -56,7 +55,7 @@ function localdatatip(l, word, startrow)
end
end

function topleveldatatip(mod, word)
function globaldatatip(mod, word)
docs = @errs getdocs(mod, word)
docs isa EvalError && return nothing

Expand All @@ -71,6 +70,9 @@ function topleveldatatip(mod, word)

processdoc!(docs, docstr, datatip)

ml = methods(val)
processmethods!(ml, datatip)

return datatip
end

Expand Down Expand Up @@ -122,6 +124,20 @@ processval!(val::Function, docstr, datatip) = begin
end
processval!(::Undefined, docstr, datatip) = nothing

function processmethods!(ml, datatip)
ms = collect(ml)
isempty(ms) && return

substr = s"<code>\g<sig></code> in <code>\g<mod></code> at \g<loc>"
msstr = map(ms) do m
s = replace(string(m), methodloc_regex => substr)
"<li>$s</li>"
end |> join

name = ms[1].name
pushmarkdown!(datatip, "<details><summary><code>$name</code> has **$(length(ms))** methods:</summary><ul>$(msstr)</ul></details>")
end

function pushmarkdown!(datatip, markdown)
(markdown == "" || markdown == "\n") && return
push!(datatip, Dict(:type => :markdown, :value => markdown))
Expand Down
4 changes: 3 additions & 1 deletion src/display/methods.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ stripparams(t) = replace(t, r"\{([A-Za-z, ]*?)\}" => "")

interpose(xs, y) = map(i -> iseven(i) ? xs[i÷2] : y, 2:2length(xs))

const methodloc_regex = r"(?<sig>.+) in (?<mod>.+) at (?<loc>.+)$"

function view(m::Method)
str = sprint(show, "text/html", m)
str = replace(str, r" in .* at .*$" => "")
str = replace(str, methodloc_regex => s"\g<sig>")
str = string("<span>", str, "</span>")
tv, decls, file, line = Base.arg_decl_parts(m)
HTML(str), file == :null ? "not found" : Atom.baselink(string(file), line)
Expand Down
79 changes: 30 additions & 49 deletions src/goto.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function gotosymbol(
localitems = localgotoitem(word, path, column, row, startrow, context)
isempty(localitems) || return Dict(
:error => false,
:items => map(Dict, localitems),
:items => map(Dict, localitems)
)
end

Expand Down Expand Up @@ -66,7 +66,7 @@ Dict(gotoitem::GotoItem) = Dict(
### local goto

function localgotoitem(word, path, column, row, startrow, context)
word = first(split(word, '.')) # ignore dot accessors
word = first(split(word, '.')) # always ignore dot accessors
position = row - startrow
ls = locals(context, position, column)
filter!(ls) do l
Expand All @@ -86,31 +86,31 @@ localgotoitem(word, ::Nothing, column, row, startrow, context) = [] # when `path
function globalgotoitems(word, mod, text, path)
mod = getmodule(mod)

moduleitems = modulegotoitems(word, mod)
isempty(moduleitems) || return moduleitems
# strip a dot-accessed module if exists
identifiers = split(word, '.')
head = string(identifiers[1])
if head ≠ word && getfield′(mod, head) isa Module
# if `head` is a module, update `word` and `mod`
nextword = join(identifiers[2:end], '.')
return globalgotoitems(nextword, head, text, path)
end

val = getfield′(mod, word)
val isa Module && return [GotoItem(val)] # module goto

toplevelitems = toplevelgotoitems(word, mod, text, path)

# only append methods that are not caught by `toplevelgotoitems`
# append method gotos that are not caught by `toplevelgotoitems`
ml = methods(val)
files = map(item -> item.file, toplevelitems)
methoditems = filter!(item -> item.file ∉ files, methodgotoitems(mod, word))

methoditems = filter!(item -> item.file ∉ files, methodgotoitems(ml))
append!(toplevelitems, methoditems)
end

## module goto

function modulegotoitems(word, mod)::Vector{GotoItem}
mod = getfield′(mod, Symbol(word))
return mod isa Module ? [GotoItem(mod)] : []
end

function GotoItem(mod::Module)
file, line = if mod == Main
MAIN_MODULE_LOCATION[]
else
moduledefinition(mod)
end
file, line = mod == Main ? MAIN_MODULE_LOCATION[] : moduledefinition(mod)
GotoItem(string(mod), file, line - 1)
end

Expand All @@ -120,16 +120,6 @@ const PathItemsMaps = Dict{String, Vector{ToplevelItem}}
const SYMBOLSCACHE = Dict{String, PathItemsMaps}()

function toplevelgotoitems(word, mod, text, path)
# strip a dot-accessed module if exists
identifiers = split(word, '.')
head = identifiers[1]
if head ≠ word && (val = getfield′(mod, string(head))) isa Module
# if `head` is a module, update `word` and `mod`
nextword = join(identifiers[2:end], '.')
nextmod = val
return toplevelgotoitems(nextword, nextmod, text, path)
end

key = string(mod)
pathitemsmaps = if haskey(SYMBOLSCACHE, key)
SYMBOLSCACHE[key]
Expand All @@ -139,8 +129,8 @@ function toplevelgotoitems(word, mod, text, path)

ismacro(word) && (word = lstrip(word, '@'))
ret = Vector{GotoItem}()
for (path, items) pathitemsmaps
for item filter(item -> filtertoplevelitem(word, item), items)
for (path, items) in pathitemsmaps
for item in filter(item -> filtertoplevelitem(word, item), items)
push!(ret, GotoItem(path, item))
end
end
Expand Down Expand Up @@ -169,7 +159,7 @@ end
function _searchtoplevelitems(mod::Module, pathitemsmaps::PathItemsMaps)
entrypath, paths = modulefiles(mod) # Revise-like approach
if entrypath !== nothing
for p [entrypath; paths]
for p in [entrypath; paths]
_searchtoplevelitems(p, pathitemsmaps)
end
else # if Revise-like approach fails, fallback to CSTParser-based approach
Expand All @@ -188,14 +178,13 @@ function _searchtoplevelitems(path::String, pathitemsmaps::PathItemsMaps)
push!(pathitemsmaps, pathitemsmap)
end

# module-walk by CSTParser-based, looking for toplevel `installed` calls
# module-walk based on CSTParser, looking for toplevel `installed` calls
function _searchtoplevelitems(text::String, path::String, pathitemsmaps::PathItemsMaps)
parsed = CSTParser.parse(text, true)
items = toplevelitems(parsed, text)
pathitemsmap = path => items
push!(pathitemsmaps, pathitemsmap)
push!(pathitemsmaps, path => items)

# looking for toplevel `installed` calls
# looking for toplevel `include` calls
for item in items
if item isa ToplevelCall
expr = item.expr
Expand Down Expand Up @@ -278,7 +267,7 @@ function regeneratesymbols()
unloadedlen = length(unloaded)
total = loadedlen + unloadedlen

for (i, mod) enumerate(Base.loaded_modules_array())
for (i, mod) in enumerate(Base.loaded_modules_array())
try
modstr = string(mod)
modstr == "__PackagePrecompilationStatementModule" && continue # will cause error
Expand All @@ -292,7 +281,7 @@ function regeneratesymbols()
end
end

for (i, pkg) enumerate(unloaded)
for (i, pkg) in enumerate(unloaded)
try
path = Base.find_package(pkg)
text = read(path, String)
Expand All @@ -311,27 +300,19 @@ end

## method goto

function methodgotoitems(mod, word)::Vector{GotoItem}
ms = @errs getmethods(mod, word)
if ms isa EvalError
[]
else
map(GotoItem, aggregatemethods(ms))
end
end
methodgotoitems(ml) = map(GotoItem, aggregatemethods(ml))

# aggregate methods with default arguments to the ones with full arguments
aggregatemethods(f) = aggregatemethods(methods(f))
aggregatemethods(ms::MethodList) = aggregatemethods(collect(ms))
function aggregatemethods(ms::Vector{Method})
ms = sort(ms, by = m -> m.nargs, rev = true)
function aggregatemethods(ml)
ms = collect(ml)
sort!(ms, by = m -> m.nargs, rev = true)
unique(m -> (m.file, m.line), ms)
end

function GotoItem(m::Method)
_, link = view(m)
sig = sprint(show, m)
text = replace(sig, r" in .* at .*$" => "")
text = replace(sig, methodloc_regex => s"\g<sig>")
file = link.file
line = link.line - 1
secondary = join(link.contents)
Expand Down
15 changes: 14 additions & 1 deletion src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ end
# string utilties
# ---------------

"""
strlimit(str::AbstractString, limit::Int = 30, ellipsis::AbstractString = "…")

Chops off `str` so that its _length_ doesn't exceed `limit`. The excessive part
will be replaced by `ellipsis`.

!!! note
The length of returned string will _never_ exceed `limit`.
"""
function strlimit(str::AbstractString, limit::Int = 30, ellipsis::AbstractString = "…")
will_append = length(str) > limit

Expand All @@ -130,7 +139,11 @@ end

shortstr(val) = strlimit(string(val), 20)

# singleton type for undefined values
"""
Undefined

singleton type representing undefined values
"""
struct Undefined end

# get utilities
Expand Down
10 changes: 5 additions & 5 deletions test/datatip.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
@test localdatatip("l", 4) == 3 # line
end

# remove dot accessors
# ignore dot accessors
let str = """
function withdots(expr::CSTParser.EXPR)
bind = CSTParser.bindingof(expr.args[1])
Expand Down Expand Up @@ -68,8 +68,8 @@
end
end

@testset "toplevel datatips" begin
using Atom: topleveldatatip
@testset "global datatips" begin
using Atom: globaldatatip

## method datatip
@eval Main begin
Expand All @@ -81,7 +81,7 @@
datatipmethodtest() = nothing
end

let datatip = topleveldatatip("Main", "datatipmethodtest")
let datatip = globaldatatip("Main", "datatipmethodtest")
@test datatip isa Vector
firsttip = datatip[1]
secondtip = datatip[2]
Expand All @@ -94,7 +94,7 @@
## variable datatip
@eval Main datatipvariabletest = "this string should be shown in datatip"

let datatip = topleveldatatip("Main", "datatipvariabletest")
let datatip = globaldatatip("Main", "datatipvariabletest")
@test datatip isa Vector
firsttip = datatip[1]
@test firsttip[:type] == :snippet
Expand Down
Loading