From 65826db5f0a8c3f5817ef62dd66dcac6f66782ec Mon Sep 17 00:00:00 2001
From: aviatesk
Date: Sat, 12 Oct 2019 00:44:55 +0900
Subject: [PATCH 01/19] local refactoring
---
src/Atom.jl | 1 +
src/refactor.jl | 84 +++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 85 insertions(+)
create mode 100644 src/refactor.jl
diff --git a/src/Atom.jl b/src/Atom.jl
index 6cf4c10b..02d69595 100644
--- a/src/Atom.jl
+++ b/src/Atom.jl
@@ -51,6 +51,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")
diff --git a/src/refactor.jl b/src/refactor.jl
new file mode 100644
index 00000000..a9521b44
--- /dev/null
+++ b/src/refactor.jl
@@ -0,0 +1,84 @@
+handle("refactor") do data
+ @destruct [
+ old,
+ new,
+ path,
+ # local context
+ column || 1,
+ row || 1,
+ startRow || 0,
+ context || "",
+ # module context
+ mod || "Main",
+ ] = data
+ refactor(old, new, path, column, row, startRow, context, mod)
+end
+
+function refactor(
+ old, new, path,
+ column = 1, row = 1, startrow = 0, context = "",
+ mod = "Main",
+)
+ expr = CSTParser.parse(context, true)
+ bind = CSTParser.bindingof(expr)
+
+ # local refactor only if `old` is really a local binding
+ if bind === nothing || old != bind.name
+ try
+ refactored = localrefactor(old, new, path, column, row, startrow, context)
+ isempty(refactored) || return Dict(:text => refactored)
+ catch err
+ @error err
+ end
+ end
+
+ try
+ refactored = localrefactor(old, new, path, column, row, startrow, context)
+ isempty(refactored) || return Dict(:text => refactored)
+ catch err
+ @error err
+ end
+
+ # try
+ # globalrefactor(old, new, path, mod) && return nothing
+ # catch err
+ # @error err
+ # end
+
+ return Dict(:error => true, :msg => "no refactor")
+end
+
+function localrefactor(old, new, path, column, row, startrow, context)
+ old = first(split(old, '.')) # ignore dot accessors
+ position = row - startrow
+
+ return if old ∈ map(l -> l[:name], locals(context, position, column))
+ oldsym = Symbol(old)
+ quote
+ MacroTools.textwalk($context) do ex
+ @capture(ex, $oldsym) ? Symbol($new) : ex
+ end
+ end |> eval
+ else
+ ""
+ end
+end
+
+# mod = getmodule(m)
+# parentfile, modulefiles = modulefiles(mod)
+# sourcewalk("../packages/Atom/src/goto.jl") do x
+# isshort = MacroTools.isshortdef(x)
+# ex = MacroTools.shortdef(x)
+# if @capture(ex, locals(args__) = body_)
+# return if isshort
+# :(newlocals(args...) = body)
+# else
+# :(function newlocals(args...)
+# body
+# end)
+# end
+# end
+# return x
+# isstruct = MacroTools.isstructdef(x)
+# if @capture(x, struct )
+# end
From e2da219733274585487daf95d530d92e398b752e Mon Sep 17 00:00:00 2001
From: aviatesk
Date: Tue, 22 Oct 2019 01:53:24 +0900
Subject: [PATCH 02/19] global refactoring
---
src/refactor.jl | 99 ++++++++++++++++++++++++++++++-------------------
src/utils.jl | 28 ++++++++++++++
2 files changed, 88 insertions(+), 39 deletions(-)
diff --git a/src/refactor.jl b/src/refactor.jl
index a9521b44..5fde8f54 100644
--- a/src/refactor.jl
+++ b/src/refactor.jl
@@ -19,10 +19,8 @@ function refactor(
column = 1, row = 1, startrow = 0, context = "",
mod = "Main",
)
- expr = CSTParser.parse(context, true)
- bind = CSTParser.bindingof(expr)
-
# local refactor only if `old` is really a local binding
+ bind = CSTParser.bindingof(CSTParser.parse(context))
if bind === nothing || old != bind.name
try
refactored = localrefactor(old, new, path, column, row, startrow, context)
@@ -33,52 +31,75 @@ function refactor(
end
try
- refactored = localrefactor(old, new, path, column, row, startrow, context)
- isempty(refactored) || return Dict(:text => refactored)
+ mod = getmodule(mod)
+ val = getfield′(mod, old)
+ result = globalrefactor(old, new, mod, val)
+ return result isa String ? Dict(:error => result) : Dict(:error => false)
catch err
@error err
end
- # try
- # globalrefactor(old, new, path, mod) && return nothing
- # catch err
- # @error err
- # end
-
- return Dict(:error => true, :msg => "no refactor")
+ return Dict(:error => "Rename refactoring failed: `$old` -> `$new`")
end
-function localrefactor(old, new, path, column, row, startrow, context)
- old = first(split(old, '.')) # ignore dot accessors
- position = row - startrow
+# local refactor
+# --------------
- return if old ∈ map(l -> l[:name], locals(context, position, column))
+function localrefactor(old, new, path, column, row, startrow, context)
+ return if old ∈ map(l -> l[:name], locals(context, row - startrow, column))
oldsym = Symbol(old)
- quote
- MacroTools.textwalk($context) do ex
- @capture(ex, $oldsym) ? Symbol($new) : ex
- end
- end |> eval
+ newsym = Symbol(new)
+ MacroTools.textwalk(context) do sym
+ sym === oldsym ? newsym : sym
+ end
else
""
end
end
-# mod = getmodule(m)
-# parentfile, modulefiles = modulefiles(mod)
-# sourcewalk("../packages/Atom/src/goto.jl") do x
-# isshort = MacroTools.isshortdef(x)
-# ex = MacroTools.shortdef(x)
-# if @capture(ex, locals(args__) = body_)
-# return if isshort
-# :(newlocals(args...) = body)
-# else
-# :(function newlocals(args...)
-# body
-# end)
-# end
-# end
-# return x
-# isstruct = MacroTools.isstructdef(x)
-# if @capture(x, struct )
-# end
+# global refactor
+# ---------------
+
+globalrefactor(old, new, mod, @nospecialize(val)) = _globalrefactor(old, new, mod) # general case
+function globalrefactor(old, new, mod, val::Undefined)
+ Symbol(old) in keys(Docs.keywords) ?
+ "Keywords can't be renamed: `$old`" :
+ _globalrefactor(old, new, mod)
+end
+
+function _globalrefactor(old, new, mod)
+ entrypath, line = moduledefinition(mod)
+ files = modulefiles(entrypath)
+
+ with_logger(JunoProgressLogger()) do
+ refactorfiles(old, new, mod, files)
+ end
+end
+
+function refactorfiles(old, new, obj, files)
+ id = "global_rename_refactor_progress"
+ @info "Start global rename refactoring" progress=0 _id=id
+
+ oldsym = Symbol(old)
+ newsym = Symbol(new)
+ modulesyms = Set(Symbol.(Base.loaded_modules_array()))
+ total = length(files)
+
+ for (i, file) ∈ enumerate(files)
+ @info "Refactoring: $file ($i / $total)" progress=i/total _id=id
+ MacroTools.sourcewalk(file) do ex
+ return if ex === oldsym
+ newsym
+ elseif @capture(ex, obj_.$oldsym)
+ if obj in modulesyms
+ @warn "Came across a global rename refactoring across different modules: `$obj.$old` -> `$obj.$new`"
+ end
+ Expr(:., obj, newsym)
+ else
+ ex
+ end
+ end
+ end
+
+ @info "Finish global rename refactoring" progress=1 _id=id
+end
diff --git a/src/utils.jl b/src/utils.jl
index f4e6613e..a94accc3 100644
--- a/src/utils.jl
+++ b/src/utils.jl
@@ -204,6 +204,34 @@ function modulefiles(mod::Module)
return fixpath(parentfile), [fixpath(mf[2]) for mf in included_files]
end
+"""
+ included_files = modulefiles(entrypath::String)::Vector{String}
+
+Return all the file paths that can be reached via [`include`](@ref) calls.
+Note this function currently only looks for static _toplevel_ calls.
+"""
+function modulefiles(entrypath::String, files = [])
+ push!(files, entrypath)
+
+ text = read(entrypath, String)
+ parsed = CSTParser.parse(text, true)
+ items = toplevelitems(parsed, text)
+
+ for item in items
+ if item isa ToplevelCall
+ expr = item.expr
+ if isinclude(expr)
+ nextfile = expr.args[3].val
+ nextentrypath = joinpath(dirname(entrypath), nextfile)
+ isfile(nextentrypath) || continue
+ modulefiles(nextentrypath, files)
+ end
+ end
+ end
+
+ return files
+end
+
function moduledefinition(mod::Module) # NOTE: added when adapted
evalmethod = first(methods(getfield(mod, :eval)))
parentfile = String(evalmethod.file)
From abbb84c8f9cd1df80894d26e63336aea1b6fbbc3 Mon Sep 17 00:00:00 2001
From: aviatesk
Date: Tue, 22 Oct 2019 05:40:22 +0900
Subject: [PATCH 03/19] better messaging & suppress field renaming
---
src/refactor.jl | 55 +++++++++++++++++++++++++++++++------------------
1 file changed, 35 insertions(+), 20 deletions(-)
diff --git a/src/refactor.jl b/src/refactor.jl
index 5fde8f54..bcdf86bb 100644
--- a/src/refactor.jl
+++ b/src/refactor.jl
@@ -1,6 +1,7 @@
-handle("refactor") do data
+handle("renamerefactor") do data
@destruct [
old,
+ full,
new,
path,
# local context
@@ -11,35 +12,48 @@ handle("refactor") do data
# module context
mod || "Main",
] = data
- refactor(old, new, path, column, row, startRow, context, mod)
+ renamerefactor(old, full, new, path, column, row, startRow, context, mod)
end
-function refactor(
- old, new, path,
+function renamerefactor(
+ old, full, new, path,
column = 1, row = 1, startrow = 0, context = "",
mod = "Main",
)
+ mod = getmodule(mod)
+
+ # catch field renaming
+ if (obj = first(split(full, '.'))) != old && !isa(getfield′(mod, obj), Module)
+ return Dict(:warning => "Rename refactoring on a field isn't available: `$obj.$old`")
+ end
+
# local refactor only if `old` is really a local binding
bind = CSTParser.bindingof(CSTParser.parse(context))
if bind === nothing || old != bind.name
try
refactored = localrefactor(old, new, path, column, row, startrow, context)
- isempty(refactored) || return Dict(:text => refactored)
+ isempty(refactored) || return Dict(
+ :text => refactored,
+ :success => "Local rename refactoring `$old` ⟹ `$new` succeeded"
+ )
catch err
@error err
end
end
try
- mod = getmodule(mod)
val = getfield′(mod, old)
- result = globalrefactor(old, new, mod, val)
- return result isa String ? Dict(:error => result) : Dict(:error => false)
+ kind, description = globalrefactor(old, new, mod, val)
+ return Dict(
+ kind => description,
+ :success => kind !== :info ? false :
+ "Global rename refactoring `$old` ⟹ `$new` succeeded"
+ )
catch err
@error err
end
- return Dict(:error => "Rename refactoring failed: `$old` -> `$new`")
+ return Dict(:error => "Rename refactoring `$old` ⟹ `$new` failed")
end
# local refactor
@@ -63,7 +77,7 @@ end
globalrefactor(old, new, mod, @nospecialize(val)) = _globalrefactor(old, new, mod) # general case
function globalrefactor(old, new, mod, val::Undefined)
Symbol(old) in keys(Docs.keywords) ?
- "Keywords can't be renamed: `$old`" :
+ (:warning, "Keywords can't be renamed: `$old`") :
_globalrefactor(old, new, mod)
end
@@ -76,25 +90,24 @@ function _globalrefactor(old, new, mod)
end
end
-function refactorfiles(old, new, obj, files)
+function refactorfiles(old, new, mod, files)
id = "global_rename_refactor_progress"
@info "Start global rename refactoring" progress=0 _id=id
- oldsym = Symbol(old)
- newsym = Symbol(new)
- modulesyms = Set(Symbol.(Base.loaded_modules_array()))
- total = length(files)
+ oldsym = Symbol(old)
+ newsym = Symbol(new)
+ total = length(files)
+
+ desc = ""
for (i, file) ∈ enumerate(files)
@info "Refactoring: $file ($i / $total)" progress=i/total _id=id
MacroTools.sourcewalk(file) do ex
return if ex === oldsym
newsym
- elseif @capture(ex, obj_.$oldsym)
- if obj in modulesyms
- @warn "Came across a global rename refactoring across different modules: `$obj.$old` -> `$obj.$new`"
- end
- Expr(:., obj, newsym)
+ elseif @capture(ex, m_.$oldsym) && getfield′(mod, m) isa Module
+ desc *= "`$m.$old` ⟹ `$m.$new` in $(fullpath(file))\n"
+ Expr(:., m, newsym)
else
ex
end
@@ -102,4 +115,6 @@ function refactorfiles(old, new, obj, files)
end
@info "Finish global rename refactoring" progress=1 _id=id
+
+ (:info, isempty(desc) ? "" : "Refactorings across modules\n\n" * desc)
end
From 79bd61778263281a05e9b2bc02609b1ecf5564ff Mon Sep 17 00:00:00 2001
From: aviatesk
Date: Tue, 22 Oct 2019 05:49:25 +0900
Subject: [PATCH 04/19] add more methods for get utilities
---
src/utils.jl | 31 ++++++++++++++++++++++---------
1 file changed, 22 insertions(+), 9 deletions(-)
diff --git a/src/utils.jl b/src/utils.jl
index a94accc3..e035ceb1 100644
--- a/src/utils.jl
+++ b/src/utils.jl
@@ -1,3 +1,6 @@
+# path utilities
+# --------------
+
include("path_matching.jl")
isuntitled(p) = occursin(r"^(\.\\|\./)?untitled-[\d\w]+(:\d+)?$", p)
@@ -99,6 +102,9 @@ function md_hlines(md)
return MD(v)
end
+# string utilties
+# ---------------
+
function strlimit(str::AbstractString, limit::Int = 30, ellipsis::AbstractString = "…")
will_append = length(str) > limit
@@ -122,33 +128,39 @@ shortstr(val) = strlimit(string(val), 20)
struct Undefined end
# get utilities
+# -------------
+
using CodeTools
"""
- getfield′(mod::Module, name::String, default = Undefined())
+ getfield′(mod::Module, name::AbstractString, default = Undefined())
getfield′(mod::Module, name::Symbol, default = Undefined())
+ getfield′(mod::AbstractString, name::Symbol, default = Undefined())
getfield′(object, name::Symbol, default = Undefined())
+ getfield′(object, name::AbstractString, default = Undefined())
Returns the specified field of a given `Module` or some arbitrary `object`,
or `default` if no such a field is found.
"""
-getfield′(mod::Module, name::String, default = Undefined()) = CodeTools.getthing(mod, name, default)
+getfield′(mod::Module, name::AbstractString, default = Undefined()) = CodeTools.getthing(mod, name, default)
getfield′(mod::Module, name::Symbol, default = Undefined()) = getfield′(mod, string(name), default)
+getfield′(mod::AbstractString, name::Symbol, default = Undefined()) = getfield′(getmodule(mod), string(name), default)
getfield′(@nospecialize(object), name::Symbol, default = Undefined()) = isdefined(object, name) ? getfield(object, name) : default
+getfield′(@nospecialize(object), name::AbstractString, default = Undefined()) = isdefined(object, name) ? getfield(object, Symbol(name)) : default
"""
- getmodule(mod::String)
- getmodule(parent::Union{Nothing, Module}, mod::String)
+ getmodule(mod::AbstractString)
+ getmodule(parent::Union{Nothing, Module}, mod::AbstractString)
getmodule(code::AbstractString, pos; filemod)
Calls `CodeTools.getmodule(args...)`, but returns `Main` instead of `nothing` in a fallback case.
"""
getmodule(args...) = (m = CodeTools.getmodule(args...)) === nothing ? Main : m
-getmethods(mod::Module, word::String) = methods(CodeTools.getthing(mod, word))
-getmethods(mod::String, word::String) = getmethods(getmodule(mod), word)
+getmethods(mod::Module, word::AbstractString) = methods(CodeTools.getthing(mod, word))
+getmethods(mod::AbstractString, word::AbstractString) = getmethods(getmodule(mod), word)
-getdocs(mod::Module, word::String, fallbackmod::Module = Main) = begin
+getdocs(mod::Module, word::AbstractString, fallbackmod::Module = Main) = begin
md = if Symbol(word) in keys(Docs.keywords)
Core.eval(Main, :(@doc($(Symbol(word)))))
else
@@ -164,13 +176,14 @@ getdocs(mod::Module, word::String, fallbackmod::Module = Main) = begin
end
md_hlines(md)
end
-getdocs(mod::String, word::String, fallbackmod::Module = Main) =
+getdocs(mod::AbstractString, word::AbstractString, fallbackmod::Module = Main) =
getdocs(getmodule(mod), word, fallbackmod)
cangetdocs(mod::Module, word::Symbol) =
Base.isbindingresolved(mod, word) &&
!Base.isdeprecated(mod, word)
-cangetdocs(mod::Module, word::String) = cangetdocs(mod, Symbol(word))
+cangetdocs(mod::Module, word::AbstractString) = cangetdocs(mod, Symbol(word))
+cangetdocs(mod::AbstractString, word::Union{Symbol, AbstractString}) = cangetdocs(getmodule(mod), word)
#=
module file detections
From e618a16dfe1cb6660da580506eabbaba70108f5e Mon Sep 17 00:00:00 2001
From: aviatesk
Date: Tue, 22 Oct 2019 05:49:57 +0900
Subject: [PATCH 05/19] jumpable global rename refactor information
---
src/refactor.jl | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/src/refactor.jl b/src/refactor.jl
index bcdf86bb..6e4b9c87 100644
--- a/src/refactor.jl
+++ b/src/refactor.jl
@@ -97,7 +97,6 @@ function refactorfiles(old, new, mod, files)
oldsym = Symbol(old)
newsym = Symbol(new)
total = length(files)
-
desc = ""
for (i, file) ∈ enumerate(files)
@@ -105,8 +104,11 @@ function refactorfiles(old, new, mod, files)
MacroTools.sourcewalk(file) do ex
return if ex === oldsym
newsym
- elseif @capture(ex, m_.$oldsym) && getfield′(mod, m) isa Module
- desc *= "`$m.$old` ⟹ `$m.$new` in $(fullpath(file))\n"
+ elseif @capture(ex, m_.$oldsym) && getfield′(mod, Symbol(m)) isa Module
+ # TODO: enable line location information (the upstream needs to be enhanced)
+ file = fullpath(file)
+ link = "atom://julia-client/?open=true&file=$(file)&line=0"
+ desc *= "- `$m.$old` ⟹ `$m.$new` in [$file]($link)\n"
Expr(:., m, newsym)
else
ex
@@ -116,5 +118,5 @@ function refactorfiles(old, new, mod, files)
@info "Finish global rename refactoring" progress=1 _id=id
- (:info, isempty(desc) ? "" : "Refactorings across modules\n\n" * desc)
+ (:info, isempty(desc) ? "" : "Refactorings across modules\n" * desc)
end
From 4682c11ea015ee1e48074ba9e58b052bb7541430 Mon Sep 17 00:00:00 2001
From: aviatesk
Date: Tue, 22 Oct 2019 07:08:02 +0900
Subject: [PATCH 06/19] even better messaging
---
src/refactor.jl | 57 ++++++++++++++++++++++++++++++-------------------
1 file changed, 35 insertions(+), 22 deletions(-)
diff --git a/src/refactor.jl b/src/refactor.jl
index 6e4b9c87..719aeab6 100644
--- a/src/refactor.jl
+++ b/src/refactor.jl
@@ -23,8 +23,14 @@ function renamerefactor(
mod = getmodule(mod)
# catch field renaming
- if (obj = first(split(full, '.'))) != old && !isa(getfield′(mod, obj), Module)
- return Dict(:warning => "Rename refactoring on a field isn't available: `$obj.$old`")
+ modulenote = if (obj = first(split(full, '.'))) != old
+ if (parentmod = getfield′(mod, obj)) isa Module && parentmod != mod
+ "**NOTE**: `$old` is defined in `$parentmod` -- you may need the same rename refactorings in that module as well."
+ else
+ return Dict(:warning => "Rename refactoring on a field isn't available: `$obj.$old`")
+ end
+ else
+ ""
end
# local refactor only if `old` is really a local binding
@@ -34,7 +40,7 @@ function renamerefactor(
refactored = localrefactor(old, new, path, column, row, startrow, context)
isempty(refactored) || return Dict(
:text => refactored,
- :success => "Local rename refactoring `$old` ⟹ `$new` succeeded"
+ :success => "_Local_ rename refactoring `$old` ⟹ `$new` succeeded"
)
catch err
@error err
@@ -42,12 +48,21 @@ function renamerefactor(
end
try
- val = getfield′(mod, old)
- kind, description = globalrefactor(old, new, mod, val)
+ val = getfield′(mod, full)
+ # catch keyword renaming
+ if val isa Undefined && Symbol(old) in keys(Docs.keywords)
+ return Dict(:warning => "Keywords can't be renamed: `$old`")
+ end
+ # update modulenote
+ if isempty(modulenote) && applicable(parentmodule, val) && (parentmod = parentmodule(val)) != mod
+ modulenote =
+ "**NOTE**: `$old` is defined in `$parentmod` -- you may need the same rename refactorings in that module as well."
+ end
+ kind, desc = globalrefactor(old, new, mod)
return Dict(
- kind => description,
- :success => kind !== :info ? false :
- "Global rename refactoring `$old` ⟹ `$new` succeeded"
+ kind => kind === :success ?
+ join(("_Global_ rename refactoring `$old` ⟹ `$new` succeeded.", modulenote, desc), "\n\n") :
+ desc
)
catch err
@error err
@@ -74,14 +89,7 @@ end
# global refactor
# ---------------
-globalrefactor(old, new, mod, @nospecialize(val)) = _globalrefactor(old, new, mod) # general case
-function globalrefactor(old, new, mod, val::Undefined)
- Symbol(old) in keys(Docs.keywords) ?
- (:warning, "Keywords can't be renamed: `$old`") :
- _globalrefactor(old, new, mod)
-end
-
-function _globalrefactor(old, new, mod)
+function globalrefactor(old, new, mod)
entrypath, line = moduledefinition(mod)
files = modulefiles(entrypath)
@@ -97,18 +105,18 @@ function refactorfiles(old, new, mod, files)
oldsym = Symbol(old)
newsym = Symbol(new)
total = length(files)
- desc = ""
+
+ # TODO: enable line location information (the upstream needs to be enhanced)
+ refactoredfiles = Set{String}()
for (i, file) ∈ enumerate(files)
@info "Refactoring: $file ($i / $total)" progress=i/total _id=id
MacroTools.sourcewalk(file) do ex
return if ex === oldsym
+ push!(refactoredfiles, fullpath(file))
newsym
elseif @capture(ex, m_.$oldsym) && getfield′(mod, Symbol(m)) isa Module
- # TODO: enable line location information (the upstream needs to be enhanced)
- file = fullpath(file)
- link = "atom://julia-client/?open=true&file=$(file)&line=0"
- desc *= "- `$m.$old` ⟹ `$m.$new` in [$file]($link)\n"
+ push!(refactoredfiles, fullpath(file))
Expr(:., m, newsym)
else
ex
@@ -118,5 +126,10 @@ function refactorfiles(old, new, mod, files)
@info "Finish global rename refactoring" progress=1 _id=id
- (:info, isempty(desc) ? "" : "Refactorings across modules\n" * desc)
+ return if !isempty(refactoredfiles)
+ filelist = ("- [$file](atom://julia-client/?open=true&file=$(file)&line=0)" for file in refactoredfiles)
+ (:success, string("Refactored files (all in `$mod` module):\n\n", join(filelist, '\n')))
+ else
+ (:warning, "No rename refactoring occured on `$old`")
+ end
end
From 713312acae42b2b8d2cd448677926f6d24a12b26 Mon Sep 17 00:00:00 2001
From: aviatesk
Date: Tue, 22 Oct 2019 07:58:44 +0900
Subject: [PATCH 07/19] create uri utilities
---
src/completions.jl | 15 +++++----------
src/refactor.jl | 20 +++++++++++---------
src/utils.jl | 7 +++++++
3 files changed, 23 insertions(+), 19 deletions(-)
diff --git a/src/completions.jl b/src/completions.jl
index 80dbb01d..6682ec21 100644
--- a/src/completions.jl
+++ b/src/completions.jl
@@ -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)
diff --git a/src/refactor.jl b/src/refactor.jl
index 719aeab6..d3b4cb1d 100644
--- a/src/refactor.jl
+++ b/src/refactor.jl
@@ -23,9 +23,9 @@ function renamerefactor(
mod = getmodule(mod)
# catch field renaming
- modulenote = if (obj = first(split(full, '.'))) != old
+ modnote = if (obj = first(split(full, '.'))) != old
if (parentmod = getfield′(mod, obj)) isa Module && parentmod != mod
- "**NOTE**: `$old` is defined in `$parentmod` -- you may need the same rename refactorings in that module as well."
+ modulenote(old, parentmod)
else
return Dict(:warning => "Rename refactoring on a field isn't available: `$obj.$old`")
end
@@ -53,15 +53,14 @@ function renamerefactor(
if val isa Undefined && Symbol(old) in keys(Docs.keywords)
return Dict(:warning => "Keywords can't be renamed: `$old`")
end
- # update modulenote
- if isempty(modulenote) && applicable(parentmodule, val) && (parentmod = parentmodule(val)) != mod
- modulenote =
- "**NOTE**: `$old` is defined in `$parentmod` -- you may need the same rename refactorings in that module as well."
+ # update modnote
+ if isempty(modnote) && applicable(parentmodule, val) && (parentmod = parentmodule(val)) != mod
+ modnote = modulenote(old, parentmod)
end
kind, desc = globalrefactor(old, new, mod)
return Dict(
kind => kind === :success ?
- join(("_Global_ rename refactoring `$old` ⟹ `$new` succeeded.", modulenote, desc), "\n\n") :
+ join(("_Global_ rename refactoring `$old` ⟹ `$new` succeeded.", modnote, desc), "\n\n") :
desc
)
catch err
@@ -71,6 +70,9 @@ function renamerefactor(
return Dict(:error => "Rename refactoring `$old` ⟹ `$new` failed")
end
+modulenote(old, parentmod) =
+ "**NOTE**: `$old` is defined in `$parentmod` -- you may need the same rename refactorings in that module as well."
+
# local refactor
# --------------
@@ -127,9 +129,9 @@ function refactorfiles(old, new, mod, files)
@info "Finish global rename refactoring" progress=1 _id=id
return if !isempty(refactoredfiles)
- filelist = ("- [$file](atom://julia-client/?open=true&file=$(file)&line=0)" for file in refactoredfiles)
+ filelist = ("- [$file]($(uriopen(file)))" for file in refactoredfiles)
(:success, string("Refactored files (all in `$mod` module):\n\n", join(filelist, '\n')))
else
- (:warning, "No rename refactoring occured on `$old`")
+ (:warning, "No rename refactoring occured on `$old` in `$mod` module.")
end
end
diff --git a/src/utils.jl b/src/utils.jl
index e035ceb1..ee97a660 100644
--- a/src/utils.jl
+++ b/src/utils.jl
@@ -185,6 +185,13 @@ cangetdocs(mod::Module, word::Symbol) =
cangetdocs(mod::Module, word::AbstractString) = cangetdocs(mod, Symbol(word))
cangetdocs(mod::AbstractString, word::Union{Symbol, AbstractString}) = cangetdocs(getmodule(mod), word)
+# uri utilties
+# ------------
+
+uriopen(file, line = 0) = "atom://julia-client/?open=true&file=$(file)&line=$(line)"
+uridocs(mod, word) = "atom://julia-client/?docs=true&mod=$(mod)&word=$(word)"
+urimoduleinfo(mod) = "atom://julia-client/?moduleinfo=true&mod=$(mod)"
+
#=
module file detections
From bf977e5dd8c496adf6d57d099fc2d3e068bd4bdd Mon Sep 17 00:00:00 2001
From: aviatesk
Date: Tue, 22 Oct 2019 18:31:31 +0900
Subject: [PATCH 08/19] moar docs
---
src/Atom.jl | 1 +
src/utils.jl | 24 ++++++++++++++++++++++++
test/goto.jl | 2 +-
3 files changed, 26 insertions(+), 1 deletion(-)
diff --git a/src/Atom.jl b/src/Atom.jl
index 02d69595..cca3c671 100644
--- a/src/Atom.jl
+++ b/src/Atom.jl
@@ -1,5 +1,6 @@
__precompile__()
+@doc read(joinpath(dirname(@__DIR__), "README.md"), String)
module Atom
using Juno, Lazy, JSON, MacroTools, Media, Base.StackTraces
diff --git a/src/utils.jl b/src/utils.jl
index ee97a660..cbabe045 100644
--- a/src/utils.jl
+++ b/src/utils.jl
@@ -157,9 +157,25 @@ Calls `CodeTools.getmodule(args...)`, but returns `Main` instead of `nothing` in
"""
getmodule(args...) = (m = CodeTools.getmodule(args...)) === nothing ? Main : m
+"""
+ getmethods(mod::Module, word::AbstractString)
+ getmethods(mod::AbstractString, word::AbstractString)
+
+Returns the [`MethodList`](@ref) for `word`, which is bound within `mod` module.
+"""
getmethods(mod::Module, word::AbstractString) = methods(CodeTools.getthing(mod, word))
getmethods(mod::AbstractString, word::AbstractString) = getmethods(getmodule(mod), word)
+"""
+ getdocs(mod::Module, word::AbstractString, fallbackmod::Module = Main)
+ getdocs(mod::AbstractString, word::AbstractString, fallbackmod::Module = Main)
+
+Retrieves docs for `mod.word` with [`@doc`](@ref) macro. If `@doc` is not available
+ within `mod` module, `@doc` will be evaluated in `fallbackmod` module if possible.
+
+!!! note
+ You may want to run [`cangetdocs`](@ref) in advance.
+"""
getdocs(mod::Module, word::AbstractString, fallbackmod::Module = Main) = begin
md = if Symbol(word) in keys(Docs.keywords)
Core.eval(Main, :(@doc($(Symbol(word)))))
@@ -179,6 +195,14 @@ end
getdocs(mod::AbstractString, word::AbstractString, fallbackmod::Module = Main) =
getdocs(getmodule(mod), word, fallbackmod)
+"""
+ cangetdocs(mod::Module, word::Symbol)
+ cangetdocs(mod::Module, word::AbstractString)
+ cangetdocs(mod::AbstractString, word::Union{Symbol, AbstractString})
+
+Checks if the documentation bindings for `mod.word` is resolved and `mod.word`
+ is not deprecated.
+"""
cangetdocs(mod::Module, word::Symbol) =
Base.isbindingresolved(mod, word) &&
!Base.isdeprecated(mod, word)
diff --git a/test/goto.jl b/test/goto.jl
index 246a21e0..3ebf158d 100644
--- a/test/goto.jl
+++ b/test/goto.jl
@@ -52,7 +52,7 @@
@testset "module goto" begin
let item = modulegotoitems("Atom", Main)[1]
@test item.file == realpath′(joinpath(@__DIR__, "..", "src", "Atom.jl"))
- @test item.line == 2
+ @test item.line == 3
end
let item = modulegotoitems("Junk2", Main.Junk)[1]
@test item.file == joinpath(@__DIR__, "fixtures", "Junk.jl")
From e4d7ee49dc54e40b00f8216378f65220d69fe8ae Mon Sep 17 00:00:00 2001
From: aviatesk
Date: Wed, 23 Oct 2019 05:08:24 +0900
Subject: [PATCH 09/19] global refactor only from its definition
---
src/refactor.jl | 87 ++++++++++++++++++++++++++++++++++---------------
1 file changed, 61 insertions(+), 26 deletions(-)
diff --git a/src/refactor.jl b/src/refactor.jl
index d3b4cb1d..86b5fbfb 100644
--- a/src/refactor.jl
+++ b/src/refactor.jl
@@ -22,20 +22,27 @@ function renamerefactor(
)
mod = getmodule(mod)
- # catch field renaming
+ # check on dot accessor
modnote = if (obj = first(split(full, '.'))) != old
- if (parentmod = getfield′(mod, obj)) isa Module && parentmod != mod
- modulenote(old, parentmod)
+ if (parent = getfield′(mod, obj)) isa Module
+ if parent != mod
+ modulenote(old, parent)
+ else
+ ""
+ end
else
+ # catch field renaming
return Dict(:warning => "Rename refactoring on a field isn't available: `$obj.$old`")
end
else
""
end
- # local refactor only if `old` is really a local binding
- bind = CSTParser.bindingof(CSTParser.parse(context))
- if bind === nothing || old != bind.name
+ expr = CSTParser.parse(context)
+ bind = CSTParser.bindingof(expr)
+
+ # local rename refactor if `old` isn't a toplevel binding
+ if bind === nothing || old ≠ bind.name
try
refactored = localrefactor(old, new, path, column, row, startrow, context)
isempty(refactored) || return Dict(
@@ -47,22 +54,34 @@ function renamerefactor(
end
end
+ # global rename refactor if the local rename refactor didn't happen
try
val = getfield′(mod, full)
+
# catch keyword renaming
if val isa Undefined && Symbol(old) in keys(Docs.keywords)
return Dict(:warning => "Keywords can't be renamed: `$old`")
end
- # update modnote
- if isempty(modnote) && applicable(parentmodule, val) && (parentmod = parentmodule(val)) != mod
- modnote = modulenote(old, parentmod)
+
+ # catch global refactoring not on definition, e.g.: on a call site
+ if bind === nothing || old ≠ bind.name
+ # TODO: `goto` uri
+ return Dict(:info => contextnote(old, mod, context))
end
- kind, desc = globalrefactor(old, new, mod)
- return Dict(
- kind => kind === :success ?
- join(("_Global_ rename refactoring `$old` ⟹ `$new` succeeded.", modnote, desc), "\n\n") :
- desc
- )
+
+ kind, desc = globalrefactor(old, new, mod, expr)
+
+ # make description
+ if kind === :success
+ # update modnote
+ if isempty(modnote) && applicable(parentmodule, val) && (parent = parentmodule(val)) ≠ mod
+ modnote = modulenote(old, parent)
+ end
+
+ desc = join(("_Global_ rename refactoring `$old` ⟹ `$new` succeeded.", modnote, desc), "\n\n")
+ end
+
+ return Dict(kind => desc)
catch err
@error err
end
@@ -70,8 +89,17 @@ function renamerefactor(
return Dict(:error => "Rename refactoring `$old` ⟹ `$new` failed")
end
-modulenote(old, parentmod) =
- "**NOTE**: `$old` is defined in `$parentmod` -- you may need the same rename refactorings in that module as well."
+modulenote(old, parentmod) = """
+ **NOTE**: `$old` is defined in `$parentmod`
+ -- you may need the same rename refactorings in that module as well.
+ """
+
+contextnote(old, mod, context) = """
+ `$old` isn't found in local bindings in the current context:
+ Context
$(strip(context))
+
+ If you want a global rename refactoring on `$mod.$old`, you need to call from its definition.
+ """
# local refactor
# --------------
@@ -91,35 +119,42 @@ end
# global refactor
# ---------------
-function globalrefactor(old, new, mod)
+function globalrefactor(old, new, mod, expr)
entrypath, line = moduledefinition(mod)
files = modulefiles(entrypath)
with_logger(JunoProgressLogger()) do
- refactorfiles(old, new, mod, files)
+ refactorfiles(old, new, mod, files, expr)
end
end
-function refactorfiles(old, new, mod, files)
- id = "global_rename_refactor_progress"
- @info "Start global rename refactoring" progress=0 _id=id
+function refactorfiles(old, new, mod, files, expr)
+ ismacro = CSTParser.defines_macro(expr)
+ oldsym = ismacro ? Symbol("@" * old) : Symbol(old)
+ newsym = ismacro ? Symbol("@" * new) : Symbol(new)
- oldsym = Symbol(old)
- newsym = 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
- return if ex === oldsym
+ 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
From d746383748816e5f027ac4b12019dce5d630df04 Mon Sep 17 00:00:00 2001
From: aviatesk
Date: Wed, 23 Oct 2019 05:39:36 +0900
Subject: [PATCH 10/19] organize description
---
src/refactor.jl | 57 +++++++++++++++++++++++++++++--------------------
1 file changed, 34 insertions(+), 23 deletions(-)
diff --git a/src/refactor.jl b/src/refactor.jl
index 86b5fbfb..2fb78e27 100644
--- a/src/refactor.jl
+++ b/src/refactor.jl
@@ -23,10 +23,10 @@ function renamerefactor(
mod = getmodule(mod)
# check on dot accessor
- modnote = if (obj = first(split(full, '.'))) != old
- if (parent = getfield′(mod, obj)) isa Module
- if parent != mod
- modulenote(old, parent)
+ moddesc = if (obj = first(split(full, '.'))) != old
+ if (parentmod = getfield′(mod, obj)) isa Module
+ if parentmod != mod
+ moduledescription(old, parentmod)
else
""
end
@@ -66,19 +66,19 @@ function renamerefactor(
# catch global refactoring not on definition, e.g.: on a call site
if bind === nothing || old ≠ bind.name
# TODO: `goto` uri
- return Dict(:info => contextnote(old, mod, context))
+ return Dict(:info => contextdescription(old, mod, context))
end
kind, desc = globalrefactor(old, new, mod, expr)
# make description
if kind === :success
- # update modnote
- if isempty(modnote) && applicable(parentmodule, val) && (parent = parentmodule(val)) ≠ mod
- modnote = modulenote(old, parent)
+ # update modesc
+ if isempty(moddesc) && applicable(parentmodule, val) && (parentmod = parentmodule(val)) ≠ mod
+ moddesc = moduledescription(old, parentmod)
end
- desc = join(("_Global_ rename refactoring `$old` ⟹ `$new` succeeded.", modnote, desc), "\n\n")
+ desc = join(("_Global_ rename refactoring `$old` ⟹ `$new` succeeded.", moddesc, desc), "\n\n")
end
return Dict(kind => desc)
@@ -89,18 +89,6 @@ function renamerefactor(
return Dict(:error => "Rename refactoring `$old` ⟹ `$new` failed")
end
-modulenote(old, parentmod) = """
- **NOTE**: `$old` is defined in `$parentmod`
- -- you may need the same rename refactorings in that module as well.
- """
-
-contextnote(old, mod, context) = """
- `$old` isn't found in local bindings in the current context:
- Context
$(strip(context))
-
- If you want a global rename refactoring on `$mod.$old`, you need to call from its definition.
- """
-
# local refactor
# --------------
@@ -164,9 +152,32 @@ function refactorfiles(old, new, mod, files, expr)
@info "Finish global rename refactoring" progress=1 _id=id
return if !isempty(refactoredfiles)
- filelist = ("- [$file]($(uriopen(file)))" for file in refactoredfiles)
- (:success, string("Refactored files (all in `$mod` module):\n\n", join(filelist, '\n')))
+ (:success, filedescription(mod, refactoredfiles))
else
(:warning, "No rename refactoring occured on `$old` in `$mod` module.")
end
end
+
+# descriptions
+# ------------
+
+moduledescription(old, parentmod) = """
+ **NOTE**: `$old` is defined in `$parentmod`
+ -- you may need the same rename refactorings in that module as well.
+ """
+
+contextdescription(old, mod, context) = """
+ `$old` isn't found in local bindings in the current context:
+ Context
$(strip(context))
+
+ If you want a global rename refactoring on `$mod.$old`, you need to call from its definition.
+ """
+
+function filedescription(mod, files)
+ filelist = join(("
[$file]($(uriopen(file)))
" for file in files), '\n')
+ """
+
+ Refactored files (all in `$mod` module):
+
$(filelist)
+ """
+end
From 6f84942652a822e82843b04fd9a5cf5ba3e6333c Mon Sep 17 00:00:00 2001
From: aviatesk
Date: Wed, 23 Oct 2019 06:34:16 +0900
Subject: [PATCH 11/19] add goto uri :tada:
---
src/refactor.jl | 20 +++++++++++++-------
src/utils.jl | 1 +
2 files changed, 14 insertions(+), 7 deletions(-)
diff --git a/src/refactor.jl b/src/refactor.jl
index 2fb78e27..a33bceb8 100644
--- a/src/refactor.jl
+++ b/src/refactor.jl
@@ -65,7 +65,6 @@ function renamerefactor(
# catch global refactoring not on definition, e.g.: on a call site
if bind === nothing || old ≠ bind.name
- # TODO: `goto` uri
return Dict(:info => contextdescription(old, mod, context))
end
@@ -78,7 +77,7 @@ function renamerefactor(
moddesc = moduledescription(old, parentmod)
end
- desc = join(("_Global_ rename refactoring `$old` ⟹ `$new` succeeded.", moddesc, desc), "\n\n")
+ desc = join(("_Global_ rename refactoring `$mod.$old` ⟹ `$mod.$new` succeeded.", moddesc, desc), "\n\n")
end
return Dict(kind => desc)
@@ -161,17 +160,24 @@ end
# descriptions
# ------------
-moduledescription(old, parentmod) = """
- **NOTE**: `$old` is defined in `$parentmod`
- -- you may need the same rename refactorings in that module as well.
+function moduledescription(old, parentmod)
+ gotouri = urigoto(parentmod, old)
"""
+ **NOTE**: `$old` was defined in `$parentmod` -- you may need the same rename refactorings
+ in that module as well.
+ """
+end
-contextdescription(old, mod, context) = """
+function contextdescription(old, mod, context)
+ gotouri = urigoto(mod, old)
+ """
`$old` isn't found in local bindings in the current context:
Context
$(strip(context))
- If you want a global rename refactoring on `$mod.$old`, you need to call from its definition.
+ If you want a global rename refactoring on `$mod.$old`, you need to run this command
+ from its definition.
"""
+end
function filedescription(mod, files)
filelist = join(("
[$file]($(uriopen(file)))
" for file in files), '\n')
diff --git a/src/utils.jl b/src/utils.jl
index cbabe045..f2577838 100644
--- a/src/utils.jl
+++ b/src/utils.jl
@@ -214,6 +214,7 @@ cangetdocs(mod::AbstractString, word::Union{Symbol, AbstractString}) = cangetdoc
uriopen(file, line = 0) = "atom://julia-client/?open=true&file=$(file)&line=$(line)"
uridocs(mod, word) = "atom://julia-client/?docs=true&mod=$(mod)&word=$(word)"
+urigoto(mod, word) = "atom://julia-client/?goto=true&mod=$(mod)&word=$(word)"
urimoduleinfo(mod) = "atom://julia-client/?moduleinfo=true&mod=$(mod)"
#=
From 39392af3c9c9d2454de335b97cb093bdd31da752 Mon Sep 17 00:00:00 2001
From: aviatesk
Date: Wed, 23 Oct 2019 06:52:13 +0900
Subject: [PATCH 12/19] handle entrypath for Main module gracefully
---
src/refactor.jl | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/refactor.jl b/src/refactor.jl
index a33bceb8..32b672ce 100644
--- a/src/refactor.jl
+++ b/src/refactor.jl
@@ -107,7 +107,11 @@ end
# ---------------
function globalrefactor(old, new, mod, expr)
- entrypath, line = moduledefinition(mod)
+ entrypath, line = if mod == Main
+ MAIN_MODULE_LOCATION[]
+ else
+ moduledefinition(mod)
+ end
files = modulefiles(entrypath)
with_logger(JunoProgressLogger()) do
From be7c0d3f32b39dde224bdb8467334a189b76382e Mon Sep 17 00:00:00 2001
From: aviatesk
Date: Wed, 23 Oct 2019 07:38:32 +0900
Subject: [PATCH 13/19] improve parent module detection logic
---
src/refactor.jl | 29 +++++++++++------------------
1 file changed, 11 insertions(+), 18 deletions(-)
diff --git a/src/refactor.jl b/src/refactor.jl
index 32b672ce..24c6d854 100644
--- a/src/refactor.jl
+++ b/src/refactor.jl
@@ -21,21 +21,12 @@ function renamerefactor(
mod = "Main",
)
mod = getmodule(mod)
+ head = first(split(full, '.'))
+ headval = getfield′(mod, head)
- # check on dot accessor
- moddesc = if (obj = first(split(full, '.'))) != old
- if (parentmod = getfield′(mod, obj)) isa Module
- if parentmod != mod
- moduledescription(old, parentmod)
- else
- ""
- end
- else
- # catch field renaming
- return Dict(:warning => "Rename refactoring on a field isn't available: `$obj.$old`")
- end
- else
- ""
+ # catch field renaming
+ if head ≠ old && !isa(headval, Module)
+ return Dict(:warning => "Rename refactoring on a field isn't available: `$obj.$old`")
end
expr = CSTParser.parse(context)
@@ -72,9 +63,11 @@ function renamerefactor(
# make description
if kind === :success
- # update modesc
- if isempty(moddesc) && applicable(parentmodule, val) && (parentmod = parentmodule(val)) ≠ mod
- moddesc = moduledescription(old, parentmod)
+ 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")
@@ -167,7 +160,7 @@ end
function moduledescription(old, parentmod)
gotouri = urigoto(parentmod, old)
"""
- **NOTE**: `$old` was defined in `$parentmod` -- you may need the same rename refactorings
+ **NOTE**: `$old` is defined in `$parentmod` -- you may need the same rename refactorings
in that module as well.
"""
end
From df93c729ae0abfcac32c4314b7c02e12934bcab2 Mon Sep 17 00:00:00 2001
From: aviatesk
Date: Wed, 23 Oct 2019 07:51:36 +0900
Subject: [PATCH 14/19] handle non-writable case
---
src/refactor.jl | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/src/refactor.jl b/src/refactor.jl
index 24c6d854..a374f402 100644
--- a/src/refactor.jl
+++ b/src/refactor.jl
@@ -107,6 +107,11 @@ function globalrefactor(old, new, mod, expr)
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
@@ -176,6 +181,18 @@ function contextdescription(old, mod, context)
"""
end
+function nonwritabledescription(mod, files)
+ filelist = join(("
[$file]($(uriopen(file)))
" for file in files), '\n')
+ """
+ Global rename refactor failed, since there are non-writable files detected in
+ `$mod` module.
+
+
+ Non writable files (all in `$mod` module):
+
$(filelist)
+ """
+end
+
function filedescription(mod, files)
filelist = join(("
[$file]($(uriopen(file)))
" for file in files), '\n')
"""
From 517d64581e7646cb19152c3a67714763419f1c04 Mon Sep 17 00:00:00 2001
From: aviatesk
Date: Wed, 23 Oct 2019 08:28:42 +0900
Subject: [PATCH 15/19] improve toplevel binding detection logic
---
src/refactor.jl | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/refactor.jl b/src/refactor.jl
index a374f402..ab7f94cc 100644
--- a/src/refactor.jl
+++ b/src/refactor.jl
@@ -30,7 +30,9 @@ function renamerefactor(
end
expr = CSTParser.parse(context)
- bind = CSTParser.bindingof(expr)
+ 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 bind === nothing || old ≠ bind.name
From 486b619317eae2d5a765a7c14d508e79cce5880f Mon Sep 17 00:00:00 2001
From: aviatesk
Date: Wed, 23 Oct 2019 17:31:11 +0900
Subject: [PATCH 16/19] local refactor only on the current scope
---
src/refactor.jl | 40 +++++++++++++++++++++++++++++++---------
1 file changed, 31 insertions(+), 9 deletions(-)
diff --git a/src/refactor.jl b/src/refactor.jl
index ab7f94cc..df288801 100644
--- a/src/refactor.jl
+++ b/src/refactor.jl
@@ -37,7 +37,7 @@ function renamerefactor(
# local rename refactor if `old` isn't a toplevel binding
if bind === nothing || old ≠ bind.name
try
- refactored = localrefactor(old, new, path, column, row, startrow, context)
+ refactored = localrefactor(old, new, path, column, row, startrow, context, expr)
isempty(refactored) || return Dict(
:text => refactored,
:success => "_Local_ rename refactoring `$old` ⟹ `$new` succeeded"
@@ -86,16 +86,38 @@ end
# local refactor
# --------------
-function localrefactor(old, new, path, column, row, startrow, context)
- return if old ∈ map(l -> l[:name], locals(context, row - startrow, column))
- oldsym = Symbol(old)
- newsym = Symbol(new)
- MacroTools.textwalk(context) do sym
- sym === oldsym ? newsym : sym
+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
+
+ scope = binding
+ if byteoffset in scope.span &&
+ any(bind -> bind isa LocalBinding && name == bind.name, scope.children)
+ return scope
+ else
+ let scope = current_scope(name, scope.children, byteoffset)
+ scope !== nothing && return scope
+ end
end
- else
- ""
end
+
+ return nothing
end
# global refactor
From 75e2592b836b9daf75d10bf5fd4e1fd6bf6f7ffa Mon Sep 17 00:00:00 2001
From: aviatesk
Date: Wed, 23 Oct 2019 17:32:20 +0900
Subject: [PATCH 17/19] simpler returns
---
src/docs.jl | 2 +-
src/refactor.jl | 22 ++++++++++------------
src/utils.jl | 8 +++++++-
3 files changed, 18 insertions(+), 14 deletions(-)
diff --git a/src/docs.jl b/src/docs.jl
index d4909835..473a42f3 100644
--- a/src/docs.jl
+++ b/src/docs.jl
@@ -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)
diff --git a/src/refactor.jl b/src/refactor.jl
index df288801..a4f9848e 100644
--- a/src/refactor.jl
+++ b/src/refactor.jl
@@ -20,14 +20,17 @@ function renamerefactor(
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
- if head ≠ old && !isa(headval, Module)
- return Dict(:warning => "Rename refactoring on a field isn't available: `$obj.$old`")
- end
+ 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)
@@ -35,7 +38,7 @@ function renamerefactor(
bind = ind === nothing ? nothing : items[ind].bind
# local rename refactor if `old` isn't a toplevel binding
- if bind === nothing || old ≠ bind.name
+ if islocalrefactor(bind, old)
try
refactored = localrefactor(old, new, path, column, row, startrow, context, expr)
isempty(refactored) || return Dict(
@@ -51,15 +54,8 @@ function renamerefactor(
try
val = getfield′(mod, full)
- # catch keyword renaming
- if val isa Undefined && Symbol(old) in keys(Docs.keywords)
- return Dict(:warning => "Keywords can't be renamed: `$old`")
- end
-
# catch global refactoring not on definition, e.g.: on a call site
- if bind === nothing || old ≠ bind.name
- return Dict(:info => contextdescription(old, mod, context))
- end
+ islocalrefactor(bind, old) && return Dict(:info => contextdescription(old, mod, context))
kind, desc = globalrefactor(old, new, mod, expr)
@@ -83,6 +79,8 @@ function renamerefactor(
return Dict(:error => "Rename refactoring `$old` ⟹ `$new` failed")
end
+islocalrefactor(bind, name) = bind === nothing || name ≠ bind.name
+
# local refactor
# --------------
diff --git a/src/utils.jl b/src/utils.jl
index f2577838..d4d5838c 100644
--- a/src/utils.jl
+++ b/src/utils.jl
@@ -177,7 +177,7 @@ Retrieves docs for `mod.word` with [`@doc`](@ref) macro. If `@doc` is not availa
You may want to run [`cangetdocs`](@ref) in advance.
"""
getdocs(mod::Module, word::AbstractString, fallbackmod::Module = Main) = begin
- md = if Symbol(word) in keys(Docs.keywords)
+ md = if iskeyword(word)
Core.eval(Main, :(@doc($(Symbol(word)))))
else
docsym = Symbol("@doc")
@@ -209,6 +209,12 @@ cangetdocs(mod::Module, word::Symbol) =
cangetdocs(mod::Module, word::AbstractString) = cangetdocs(mod, Symbol(word))
cangetdocs(mod::AbstractString, word::Union{Symbol, AbstractString}) = cangetdocs(getmodule(mod), word)
+# is utilities
+# ------------
+
+iskeyword(word::Symbol) = word in keys(Docs.keywords)
+iskeyword(word::AbstractString) = iskeyword(Symbol(word))
+
# uri utilties
# ------------
From df134489be20f08afc5d9c6648495f73da054e01 Mon Sep 17 00:00:00 2001
From: aviatesk
Date: Wed, 23 Oct 2019 17:42:27 +0900
Subject: [PATCH 18/19] simplify the refactoring logic
---
src/refactor.jl | 39 +++++++++++++++++++--------------------
1 file changed, 19 insertions(+), 20 deletions(-)
diff --git a/src/refactor.jl b/src/refactor.jl
index a4f9848e..a54c469f 100644
--- a/src/refactor.jl
+++ b/src/refactor.jl
@@ -41,10 +41,13 @@ function renamerefactor(
if islocalrefactor(bind, old)
try
refactored = localrefactor(old, new, path, column, row, startrow, context, expr)
- isempty(refactored) || return Dict(
- :text => refactored,
- :success => "_Local_ rename refactoring `$old` ⟹ `$new` succeeded"
- )
+ 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
@error err
end
@@ -52,17 +55,13 @@ function renamerefactor(
# global rename refactor if the local rename refactor didn't happen
try
- val = getfield′(mod, full)
-
- # catch global refactoring not on definition, e.g.: on a call site
- islocalrefactor(bind, old) && return Dict(:info => contextdescription(old, mod, context))
-
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)
+ (applicable(parentmodule, val) && (headval = parentmodule(val)) ≠ mod)
moduledescription(old, headval)
else
""
@@ -175,23 +174,15 @@ function refactorfiles(old, new, mod, files, expr)
@info "Finish global rename refactoring" progress=1 _id=id
return if !isempty(refactoredfiles)
- (:success, filedescription(mod, refactoredfiles))
+ :success, filedescription(mod, refactoredfiles)
else
- (:warning, "No rename refactoring occured on `$old` in `$mod` module.")
+ :warning, "No rename refactoring occured on `$old` in `$mod` module."
end
end
# descriptions
# ------------
-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.
- """
-end
-
function contextdescription(old, mod, context)
gotouri = urigoto(mod, old)
"""
@@ -203,6 +194,14 @@ function contextdescription(old, mod, context)
"""
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.
+ """
+end
+
function nonwritabledescription(mod, files)
filelist = join(("
[$file]($(uriopen(file)))
" for file in files), '\n')
"""
From b8802f34ded21f937f45be7ee384b70a6abc36a8 Mon Sep 17 00:00:00 2001
From: aviatesk
Date: Wed, 23 Oct 2019 18:09:49 +0900
Subject: [PATCH 19/19] return on error and report back the error message
---
src/refactor.jl | 16 +++++++++++-----
1 file changed, 11 insertions(+), 5 deletions(-)
diff --git a/src/refactor.jl b/src/refactor.jl
index a54c469f..07dccdd0 100644
--- a/src/refactor.jl
+++ b/src/refactor.jl
@@ -49,7 +49,7 @@ function renamerefactor(
:success => "_Local_ rename refactoring `$old` ⟹ `$new` succeeded"
)
catch err
- @error err
+ return Dict(:error => errdescription(old, new, err))
end
end
@@ -72,10 +72,8 @@ function renamerefactor(
return Dict(kind => desc)
catch err
- @error err
+ return Dict(:error => errdescription(old, new, err))
end
-
- return Dict(:error => "Rename refactoring `$old` ⟹ `$new` failed")
end
islocalrefactor(bind, name) = bind === nothing || name ≠ bind.name
@@ -187,7 +185,7 @@ function contextdescription(old, mod, context)
gotouri = urigoto(mod, old)
"""
`$old` isn't found in local bindings in the current context:
- Context
$(strip(context))
+ Context:
$(strip(context))
If you want a global rename refactoring on `$mod.$old`, you need to run this command
from its definition.
@@ -222,3 +220,11 @@ function filedescription(mod, files)