Skip to content

Commit

Permalink
canonical module keys in SYMBOLSCACHE
Browse files Browse the repository at this point in the history
  • Loading branch information
aviatesk committed Nov 4, 2019
1 parent 9486773 commit ca2f8fd
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 48 deletions.
54 changes: 31 additions & 23 deletions src/goto.jl
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function gotosymbol(
end

# global goto
globalitems = globalgotoitems(word, mod, path, text)
globalitems = globalgotoitems(word, getmodule(mod), path, text)
isempty(globalitems) || return Dict(
:error => false,
:items => todict.(globalitems),
Expand Down Expand Up @@ -85,18 +85,16 @@ localgotoitem(word, ::Nothing, column, row, startrow, context) = [] # when calle
### global goto - bundles toplevel gotos & method gotos

function globalgotoitems(word, mod, path, text)
m = getmodule(mod)

# strip a dot-accessed module if exists
identifiers = split(word, '.')
head = string(identifiers[1])
if head word && getfield′(m, head) isa Module
if head word && (nextmod = 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)
return globalgotoitems(nextword, nextmod, text, path)
end

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

toplevelitems = toplevelgotoitems(word, mod, path, text)
Expand All @@ -118,13 +116,24 @@ end
## toplevel goto

const PathItemsMaps = Dict{String, Vector{ToplevelItem}}

"""
Atom.SYMBOLSCACHE
"module" (`String`) ⟶ "path" (`String`) ⟶ "symbols" (`Vector{ToplevelItem}`) map
!!! note
"module" should be canonical, i.e.: should be identical to names that are
constructed from `string(mod::Module)`.
"""
const SYMBOLSCACHE = Dict{String, PathItemsMaps}()

function toplevelgotoitems(word, mod, path, text)
pathitemsmaps = if haskey(SYMBOLSCACHE, mod)
SYMBOLSCACHE[mod]
key = string(mod)
pathitemsmaps = if haskey(SYMBOLSCACHE, key)
SYMBOLSCACHE[key]
else
SYMBOLSCACHE[mod] = collecttoplevelitems(mod, path, text) # caching
SYMBOLSCACHE[key] = collecttoplevelitems(mod, path, text) # caching
end

ismacro(word) && (word = lstrip(word, '@'))
Expand All @@ -138,9 +147,9 @@ function toplevelgotoitems(word, mod, path, text)
end

# entry method
function collecttoplevelitems(mod::String, path::String, text::String)
function collecttoplevelitems(mod::Module, path::String, text::String)
pathitemsmaps = PathItemsMaps()
return if mod == "Main" || isuntitled(path)
return if mod == Main || isuntitled(path)
# for `Main` module and unsaved editors, always use CSTPraser-based approach
# with a given buffer text, and don't check module validity
_collecttoplevelitems!(nothing, path, text, pathitemsmaps)
Expand All @@ -150,20 +159,19 @@ function collecttoplevelitems(mod::String, path::String, text::String)
end

# entry method when called from docpane/workspace
function collecttoplevelitems(mod::String, path::Nothing, text::String)
function collecttoplevelitems(mod::Module, path::Nothing, text::String)
pathitemsmaps = PathItemsMaps()
_collecttoplevelitems!(mod, pathitemsmaps)
end

# sub entry method
function _collecttoplevelitems!(mod::String, pathitemsmaps::PathItemsMaps)
m = getmodule(mod)
entrypath, paths = modulefiles(m)
function _collecttoplevelitems!(mod::Module, pathitemsmaps::PathItemsMaps)
entrypath, paths = modulefiles(mod)
return if entrypath !== nothing # Revise-like approach
_collecttoplevelitems!([entrypath; paths], pathitemsmaps)
else # if Revise-like approach fails, fallback to CSTParser-based approach
entrypath, line = moduledefinition(m)
mod = string(last(split(mod, '.'))) # strip parent module prefixes e.g.: `"Main.Junk"`
entrypath, line = moduledefinition(mod)
mod = string(last(split(string(mod), '.'))) # strip parent module prefixes e.g.: `"Main.Junk"`
_collecttoplevelitems!(mod, entrypath, pathitemsmaps)
end
end
Expand Down Expand Up @@ -247,7 +255,7 @@ function updatesymbols(items, mod, path::Nothing, text) end # fallback case
function updatesymbols(items, mod, path::String, text)
# initialize the cache if there is no previous one
if !haskey(SYMBOLSCACHE, mod)
SYMBOLSCACHE[mod] = collecttoplevelitems(mod, path, text)
SYMBOLSCACHE[mod] = collecttoplevelitems(getmodule(mod), path, text)
end
push!(SYMBOLSCACHE[mod], path => items)
end
Expand Down Expand Up @@ -276,13 +284,13 @@ function regeneratesymbols()
unloadedlen = length(unloaded)
total = loadedlen + unloadedlen

for (i, m) in enumerate(Base.loaded_modules_array())
for (i, mod) in enumerate(Base.loaded_modules_array())
try
mod = string(m)
mod == "__PackagePrecompilationStatementModule" && continue # will cause error
key = string(mod)
key == "__PackagePrecompilationStatementModule" && continue # will cause error

@logmsg -1 "Symbols: $mod ($i / $total)" progress=i/total _id=id
SYMBOLSCACHE[mod] = collecttoplevelitems(mod, nothing, "")
@logmsg -1 "Symbols: $key ($i / $total)" progress=i/total _id=id
SYMBOLSCACHE[key] = collecttoplevelitems(mod, nothing, "")
catch err
@error err
end
Expand Down
51 changes: 27 additions & 24 deletions test/goto.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,30 +55,30 @@
let
path = joinpath′(@__DIR__, "..", "src", "comm.jl")
text = read(path, String)
items = todict.(globalgotoitems("Atom.handlers", "Atom", path, text))
items = todict.(globalgotoitems("Atom.handlers", Atom, path, text))
@test !isempty(items)
@test items[1][:file] == path
@test items[1][:text] == "handlers"
items = todict.(globalgotoitems("Main.Atom.handlers", "Atom", path, text))
items = todict.(globalgotoitems("Main.Atom.handlers", Atom, path, text))
@test !isempty(items)
@test items[1][:file] == path
@test items[1][:text] == "handlers"

# can access the non-exported (non-method) bindings in the other module
path = joinpath′(@__DIR__, "..", "src", "goto.jl")
text = read(@__FILE__, String)
items = todict.(globalgotoitems("Atom.SYMBOLSCACHE", "Main", @__FILE__, text))
items = todict.(globalgotoitems("Atom.SYMBOLSCACHE", Main, @__FILE__, text))
@test !isempty(items)
@test items[1][:file] == path
@test items[1][:text] == "SYMBOLSCACHE"
end

@testset "goto modules" begin
let item = globalgotoitems("Atom", "Main", nothing, "")[1] |> todict
let item = globalgotoitems("Atom", Main, nothing, "")[1] |> todict
@test item[:file] == joinpath′(atomjldir, "Atom.jl")
@test item[:line] == 3
end
let item = globalgotoitems("SubJunk", "Main.Junk", nothing, "")[1] |> todict
let item = globalgotoitems("SubJunk", Junk, nothing, "")[1] |> todict
@test item[:file] == subjunkspath
@test item[:line] == 3
end
Expand All @@ -87,7 +87,8 @@
@testset "goto toplevel symbols" begin
## where Revise approach works, i.e.: precompiled modules
let path = joinpath′(atomjldir, "comm.jl")
mod = "Atom"
mod = Atom
key = "Atom"
word = "handlers"

# basic
Expand All @@ -98,10 +99,10 @@
end

# check caching works
@test haskey(SYMBOLSCACHE, mod)
@test haskey(SYMBOLSCACHE, key)

# check the Revise-like approach finds all files in Atom module
@test length(SYMBOLSCACHE[mod]) == length(atommodfiles)
@test length(SYMBOLSCACHE[key]) == length(atommodfiles)

# when `path` isn't given, i.e. via docpane / workspace
let items = todict.(toplevelgotoitems(word, mod, nothing, ""))
Expand All @@ -111,7 +112,7 @@
end

# same as above, but without any previous cache -- falls back to CSTPraser-based module-walk
delete!(SYMBOLSCACHE, mod)
delete!(SYMBOLSCACHE, key)

let items = toplevelgotoitems(word, mod, nothing, "") .|> todict
@test !isempty(items)
Expand All @@ -121,12 +122,13 @@

# check CSTPraser-based module-walk finds all the included files
# NOTE: webio.jl is excluded since `include("webio.jl")` is a toplevel call
@test length(SYMBOLSCACHE[mod]) == length(atommodfiles)
@test length(SYMBOLSCACHE[key]) == length(atommodfiles)
end

## where the Revise-like approach doesn't work, e.g. non-precompiled modules
let path = junkpath
mod = "Main.Junk"
mod = Main.Junk
key = "Main.Junk"
word = "toplevelval"

# basic -- no need to pass a buffer text
Expand All @@ -138,7 +140,7 @@
end

# check caching works
@test haskey(Atom.SYMBOLSCACHE, mod)
@test haskey(Atom.SYMBOLSCACHE, key)

# when `path` isn't given, i.e.: via docpane / workspace
let items = toplevelgotoitems(word, mod, nothing, "") .|> todict
Expand All @@ -152,7 +154,7 @@
## don't include bindings outside of a module
let path = subjunkspath
text = read(subjunkspath, String)
mod = "Main.Junk.SubJunk"
mod = Main.Junk.SubJunk
word = "imwithdoc"

items = toplevelgotoitems(word, mod, path, text) .|> todict
Expand All @@ -167,7 +169,7 @@
## `Main` module -- use a passed buffer text
let path = joinpath′(@__DIR__, "runtests.jl")
text = read(path, String)
mod = "Main"
mod = Main
word = "atomjldir"

items = toplevelgotoitems(word, mod, path, text) .|> todict
Expand All @@ -186,10 +188,11 @@
end

# check there is no cache before updating
mod = "Main.Junk"
mod = Main.Junk
key = "Main.Junk"
path = junkpath
text = read(path, String)
@test filter(SYMBOLSCACHE[mod][path]) do item
@test filter(SYMBOLSCACHE[key][path]) do item
Atom.str_value(item.expr) == "toplevelval2"
end |> isempty

Expand All @@ -198,10 +201,10 @@
newtext = join(originallines[1:end - 1], '\n')
word = "toplevelval2"
newtext *= "\n$word = :youshoulderaseme\nend"
updatesymbols(mod, path, newtext)
updatesymbols(key, path, newtext)

# check the cache is updated
@test filter(SYMBOLSCACHE[mod][path]) do item
@test filter(SYMBOLSCACHE[key][path]) do item
Atom.str_value(item.expr) == word
end |> !isempty

Expand All @@ -212,13 +215,13 @@
end

# re-update the cache
updatesymbols(mod, path, text)
@test filter(SYMBOLSCACHE[mod][path]) do item
updatesymbols(key, path, text)
@test filter(SYMBOLSCACHE[key][path]) do item
Atom.str_value(item.expr) == word
end |> isempty

# don't error on fallback case
@test_nowarn @test updatesymbols(mod, nothing, text) === nothing
@test_nowarn @test updatesymbols(key, nothing, text) === nothing
end

@testset "regenerating toplevel symbols" begin
Expand All @@ -227,7 +230,7 @@
@test haskey(SYMBOLSCACHE, "Base")
@test length(keys(SYMBOLSCACHE["Base"])) > 100
@test haskey(SYMBOLSCACHE, "Example") # cache symbols even if not loaded
@test toplevelgotoitems("hello", "Example", "", nothing) |> !isempty
@test toplevelgotoitems("hello", Example, "", nothing) |> !isempty
end

@testset "clear toplevel symbols" begin
Expand Down Expand Up @@ -264,13 +267,13 @@
end

## both the original methods and the toplevel bindings that are overloaded in a context module should be shown
let items = globalgotoitems("isconst", "Main.Junk", nothing, "")
let items = globalgotoitems("isconst", Main.Junk, nothing, "")
@test length(items) === 2
@test "isconst(m::Module, s::Symbol)" in map(item -> item.text, items) # from Base
@test "Base.isconst(::JunkType)" in map(item -> item.text, items) # from Junk
end

## don't error on the fallback case
@test_nowarn @test globalgotoitems("word", "Main", nothing, "") == []
@test_nowarn @test globalgotoitems("word", Main, nothing, "") == []
end
end
2 changes: 1 addition & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Atom, Test, JSON, Logging, CSTParser
using Atom, Test, JSON, Logging, CSTParser#, Example


joinpath′(files...) = Atom.fullpath(joinpath(files...))
Expand Down

0 comments on commit ca2f8fd

Please sign in to comment.