Skip to content

Commit

Permalink
re-add commits from 65826db to 9cea4e7
Browse files Browse the repository at this point in the history
  • Loading branch information
aviatesk committed Oct 23, 2019
1 parent 283d466 commit 86b3b2c
Show file tree
Hide file tree
Showing 6 changed files with 327 additions and 22 deletions.
2 changes: 2 additions & 0 deletions src/Atom.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
__precompile__()

@doc read(joinpath(dirname(@__DIR__), "README.md"), String)
module Atom

using Juno, Lazy, JSON, MacroTools, Media, Base.StackTraces
Expand Down Expand Up @@ -51,6 +52,7 @@ include("outline.jl")
include("completions.jl")
include("goto.jl")
include("datatip.jl")
include("refactor.jl")
include("misc.jl")
include("formatter.jl")
include("frontend.jl")
Expand Down
15 changes: 5 additions & 10 deletions src/completions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -145,19 +145,14 @@ completionurl(c::REPLCompletions.ModuleCompletion) = begin
mod, name = c.parent, c.mod
val = getfield′(mod, name)
if val isa Module # module info
parentmodule(val) == val || val (Main, Base, Core) ?
"atom://julia-client/?moduleinfo=true&mod=$(name)" :
"atom://julia-client/?moduleinfo=true&mod=$(mod).$(name)"
urimoduleinfo(parentmodule(val) == val || val (Base, Core) ? name : "$mod.$name")
else
"atom://julia-client/?docs=true&mod=$(mod)&word=$(name)"
uridocs(mod, name)
end
end
completionurl(c::REPLCompletions.MethodCompletion) =
"atom://julia-client/?docs=true&mod=$(c.method.module)&word=$(c.method.name)"
completionurl(c::REPLCompletions.PackageCompletion) =
"atom://julia-client/?moduleinfo=true&mod=$(c.package)"
completionurl(c::REPLCompletions.KeywordCompletion) =
"atom://julia-client/?docs=true&mod=Main&word=$(c.keyword)"
completionurl(c::REPLCompletions.MethodCompletion) = uridocs(c.method.module, c.method.name)
completionurl(c::REPLCompletions.PackageCompletion) = urimoduleinfo(c.package)
completionurl(c::REPLCompletions.KeywordCompletion) = uridocs("Main", c.keyword)

completionmodule(mod, c) = shortstr(mod)
completionmodule(mod, c::REPLCompletions.ModuleCompletion) = shortstr(c.parent)
Expand Down
2 changes: 1 addition & 1 deletion src/docs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function renderitem(x)

mod = getmodule(x.mod)
name = Symbol(x.name)
r[:typ], r[:icon], r[:nativetype] = if (name !== :ans || mod === Base) && name keys(Docs.keywords)
r[:typ], r[:icon], r[:nativetype] = if (name !== :ans || mod === Base) && iskeyword(name)
"keyword", "k", x.typ
else
val = getfield′(mod, name)
Expand Down
229 changes: 229 additions & 0 deletions src/refactor.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
handle("renamerefactor") do data
@destruct [
old,
full,
new,
path,
# local context
column || 1,
row || 1,
startRow || 0,
context || "",
# module context
mod || "Main",
] = data
renamerefactor(old, full, new, path, column, row, startRow, context, mod)
end

function renamerefactor(
old, full, new, path,
column = 1, row = 1, startrow = 0, context = "",
mod = "Main",
)
# catch keyword renaming
iskeyword(old) && return Dict(:warning => "Keywords can't be renamed: `$old`")

mod = getmodule(mod)
head = first(split(full, '.'))
headval = getfield′(mod, head)

# catch field renaming
head old && !isa(headval, Module) && return Dict(
:warning => "Rename refactoring on a field isn't available: `$obj.$old`"
)

expr = CSTParser.parse(context)
items = toplevelitems(expr, context)
ind = findfirst(item -> item isa ToplevelBinding, items)
bind = ind === nothing ? nothing : items[ind].bind

# local rename refactor if `old` isn't a toplevel binding
if islocalrefactor(bind, old)
try
refactored = localrefactor(old, new, path, column, row, startrow, context, expr)
return isempty(refactored) ?
# NOTE: global refactoring not on definition, e.g.: on a call site, will be caught here
Dict(:info => contextdescription(old, mod, context)) :
Dict(
:text => refactored,
:success => "_Local_ rename refactoring `$old` ⟹ `$new` succeeded"
)
catch err
return Dict(:error => errdescription(old, new, err))
end
end

# global rename refactor if the local rename refactor didn't happen
try
kind, desc = globalrefactor(old, new, mod, expr)

# make description
if kind === :success
val = getfield′(mod, full)
moddesc = if (headval isa Module && headval mod) ||
(applicable(parentmodule, val) && (headval = parentmodule(val)) mod)
moduledescription(old, headval)
else
""
end

desc = join(("_Global_ rename refactoring `$mod.$old` ⟹ `$mod.$new` succeeded.", moddesc, desc), "\n\n")
end

return Dict(kind => desc)
catch err
return Dict(:error => errdescription(old, new, err))
end
end

islocalrefactor(bind, name) = bind === nothing || name bind.name

# local refactor
# --------------

function localrefactor(old, new, path, column, row, startrow, context, expr)
bindings = local_bindings(expr, context)
line = row - startrow
scope = current_scope(old, bindings, byteoffset(context, line, column))
scope === nothing && return ""

current_context = scope.bindstr
oldsym = Symbol(old)
newsym = Symbol(new)
new_context = MacroTools.textwalk(current_context) do sym
sym === oldsym ? newsym : sym
end

replace(context, current_context => new_context)
end

function current_scope(name, bindings, byteoffset)
for binding in bindings
isa(binding, LocalScope) || continue

# first looks for innermost scope
childscope = current_scope(name, binding.children, byteoffset)
childscope !== nothing && return childscope

if byteoffset in binding.span &&
any(bind -> bind isa LocalBinding && name == bind.name, binding.children)
return binding
end
end

return nothing
end

# global refactor
# ---------------

function globalrefactor(old, new, mod, expr)
entrypath, line = if mod == Main
MAIN_MODULE_LOCATION[]
else
moduledefinition(mod)
end
files = modulefiles(entrypath)

nonwritablefiles = filter(f -> Int(Base.uperm(f)) 6, files)
if !isempty(nonwritablefiles)
return :warning, nonwritabledescription(mod, nonwritablefiles)
end

with_logger(JunoProgressLogger()) do
refactorfiles(old, new, mod, files, expr)
end
end

function refactorfiles(old, new, mod, files, expr)
ismacro = CSTParser.defines_macro(expr)
oldsym = ismacro ? Symbol("@" * old) : Symbol(old)
newsym = ismacro ? Symbol("@" * new) : Symbol(new)

total = length(files)
# TODO: enable line location information (the upstream needs to be enhanced)
refactoredfiles = Set{String}()

id = "global_rename_refactor_progress"
@info "Start global rename refactoring" progress=0 _id=id

for (i, file) enumerate(files)
@info "Refactoring: $file ($i / $total)" progress=i/total _id=id

MacroTools.sourcewalk(file) do ex
if ex === oldsym
push!(refactoredfiles, fullpath(file))
newsym
# handle dot (module) accessor
elseif @capture(ex, m_.$oldsym) && getfield′(mod, Symbol(m)) isa Module
push!(refactoredfiles, fullpath(file))
Expr(:., m, newsym)
# macro case
elseif ismacro && @capture(ex, macro $(Symbol(old))(args__) body_ end)
push!(refactoredfiles, fullpath(file))
Expr(:macro, :($(Symbol(new))($(args...))), :($body))
else
ex
end
end
end

@info "Finish global rename refactoring" progress=1 _id=id

return if !isempty(refactoredfiles)
:success, filedescription(mod, refactoredfiles)
else
:warning, "No rename refactoring occured on `$old` in `$mod` module."
end
end

# descriptions
# ------------

function contextdescription(old, mod, context)
gotouri = urigoto(mod, old)
"""
`$old` isn't found in local bindings in the current context:
<details><summary>Context:</summary><pre><code>$(strip(context))</code></p></details>
If you want a global rename refactoring on `$mod.$old`, you need to run this command
from its definition. <button>[Go to `$mod.$old`]($gotouri)</button>
"""
end

function moduledescription(old, parentmod)
gotouri = urigoto(parentmod, old)
"""
**NOTE**: `$old` is defined in `$parentmod` -- you may need the same rename refactorings
in that module as well. <button>[Go to `$parentmod.$old`]($gotouri)</button>
"""
end

function nonwritabledescription(mod, files)
filelist = join(("<li>[$file]($(uriopen(file)))</li>" for file in files), '\n')
"""
Global rename refactor failed, since there are non-writable files detected in
`$mod` module.
<details><summary>
Non writable files (all in `$mod` module):
</summary><ul>$(filelist)</ul></details>
"""
end

function filedescription(mod, files)
filelist = join(("<li>[$file]($(uriopen(file)))</li>" for file in files), '\n')
"""
<details><summary>
Refactored files (all in `$mod` module):
</summary><ul>$(filelist)</ul></details>
"""
end

function errdescription(old, new, err)
"""
Rename refactoring `$old` ⟹ `$new` failed.
<details><summary>Error:</summary><pre><code>$(errmsg(err))</code></p></details>
"""
end
Loading

0 comments on commit 86b3b2c

Please sign in to comment.