Skip to content

Commit 86b3b2c

Browse files
committed
re-add commits from 65826db to 9cea4e7
1 parent 283d466 commit 86b3b2c

File tree

6 files changed

+327
-22
lines changed

6 files changed

+327
-22
lines changed

src/Atom.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
__precompile__()
22

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

56
using Juno, Lazy, JSON, MacroTools, Media, Base.StackTraces
@@ -51,6 +52,7 @@ include("outline.jl")
5152
include("completions.jl")
5253
include("goto.jl")
5354
include("datatip.jl")
55+
include("refactor.jl")
5456
include("misc.jl")
5557
include("formatter.jl")
5658
include("frontend.jl")

src/completions.jl

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -145,19 +145,14 @@ completionurl(c::REPLCompletions.ModuleCompletion) = begin
145145
mod, name = c.parent, c.mod
146146
val = getfield′(mod, name)
147147
if val isa Module # module info
148-
parentmodule(val) == val || val (Main, Base, Core) ?
149-
"atom://julia-client/?moduleinfo=true&mod=$(name)" :
150-
"atom://julia-client/?moduleinfo=true&mod=$(mod).$(name)"
148+
urimoduleinfo(parentmodule(val) == val || val (Base, Core) ? name : "$mod.$name")
151149
else
152-
"atom://julia-client/?docs=true&mod=$(mod)&word=$(name)"
150+
uridocs(mod, name)
153151
end
154152
end
155-
completionurl(c::REPLCompletions.MethodCompletion) =
156-
"atom://julia-client/?docs=true&mod=$(c.method.module)&word=$(c.method.name)"
157-
completionurl(c::REPLCompletions.PackageCompletion) =
158-
"atom://julia-client/?moduleinfo=true&mod=$(c.package)"
159-
completionurl(c::REPLCompletions.KeywordCompletion) =
160-
"atom://julia-client/?docs=true&mod=Main&word=$(c.keyword)"
153+
completionurl(c::REPLCompletions.MethodCompletion) = uridocs(c.method.module, c.method.name)
154+
completionurl(c::REPLCompletions.PackageCompletion) = urimoduleinfo(c.package)
155+
completionurl(c::REPLCompletions.KeywordCompletion) = uridocs("Main", c.keyword)
161156

162157
completionmodule(mod, c) = shortstr(mod)
163158
completionmodule(mod, c::REPLCompletions.ModuleCompletion) = shortstr(c.parent)

src/docs.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ function renderitem(x)
3232

3333
mod = getmodule(x.mod)
3434
name = Symbol(x.name)
35-
r[:typ], r[:icon], r[:nativetype] = if (name !== :ans || mod === Base) && name keys(Docs.keywords)
35+
r[:typ], r[:icon], r[:nativetype] = if (name !== :ans || mod === Base) && iskeyword(name)
3636
"keyword", "k", x.typ
3737
else
3838
val = getfield′(mod, name)

src/refactor.jl

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
handle("renamerefactor") do data
2+
@destruct [
3+
old,
4+
full,
5+
new,
6+
path,
7+
# local context
8+
column || 1,
9+
row || 1,
10+
startRow || 0,
11+
context || "",
12+
# module context
13+
mod || "Main",
14+
] = data
15+
renamerefactor(old, full, new, path, column, row, startRow, context, mod)
16+
end
17+
18+
function renamerefactor(
19+
old, full, new, path,
20+
column = 1, row = 1, startrow = 0, context = "",
21+
mod = "Main",
22+
)
23+
# catch keyword renaming
24+
iskeyword(old) && return Dict(:warning => "Keywords can't be renamed: `$old`")
25+
26+
mod = getmodule(mod)
27+
head = first(split(full, '.'))
28+
headval = getfield′(mod, head)
29+
30+
# catch field renaming
31+
head old && !isa(headval, Module) && return Dict(
32+
:warning => "Rename refactoring on a field isn't available: `$obj.$old`"
33+
)
34+
35+
expr = CSTParser.parse(context)
36+
items = toplevelitems(expr, context)
37+
ind = findfirst(item -> item isa ToplevelBinding, items)
38+
bind = ind === nothing ? nothing : items[ind].bind
39+
40+
# local rename refactor if `old` isn't a toplevel binding
41+
if islocalrefactor(bind, old)
42+
try
43+
refactored = localrefactor(old, new, path, column, row, startrow, context, expr)
44+
return isempty(refactored) ?
45+
# NOTE: global refactoring not on definition, e.g.: on a call site, will be caught here
46+
Dict(:info => contextdescription(old, mod, context)) :
47+
Dict(
48+
:text => refactored,
49+
:success => "_Local_ rename refactoring `$old` ⟹ `$new` succeeded"
50+
)
51+
catch err
52+
return Dict(:error => errdescription(old, new, err))
53+
end
54+
end
55+
56+
# global rename refactor if the local rename refactor didn't happen
57+
try
58+
kind, desc = globalrefactor(old, new, mod, expr)
59+
60+
# make description
61+
if kind === :success
62+
val = getfield′(mod, full)
63+
moddesc = if (headval isa Module && headval mod) ||
64+
(applicable(parentmodule, val) && (headval = parentmodule(val)) mod)
65+
moduledescription(old, headval)
66+
else
67+
""
68+
end
69+
70+
desc = join(("_Global_ rename refactoring `$mod.$old` ⟹ `$mod.$new` succeeded.", moddesc, desc), "\n\n")
71+
end
72+
73+
return Dict(kind => desc)
74+
catch err
75+
return Dict(:error => errdescription(old, new, err))
76+
end
77+
end
78+
79+
islocalrefactor(bind, name) = bind === nothing || name bind.name
80+
81+
# local refactor
82+
# --------------
83+
84+
function localrefactor(old, new, path, column, row, startrow, context, expr)
85+
bindings = local_bindings(expr, context)
86+
line = row - startrow
87+
scope = current_scope(old, bindings, byteoffset(context, line, column))
88+
scope === nothing && return ""
89+
90+
current_context = scope.bindstr
91+
oldsym = Symbol(old)
92+
newsym = Symbol(new)
93+
new_context = MacroTools.textwalk(current_context) do sym
94+
sym === oldsym ? newsym : sym
95+
end
96+
97+
replace(context, current_context => new_context)
98+
end
99+
100+
function current_scope(name, bindings, byteoffset)
101+
for binding in bindings
102+
isa(binding, LocalScope) || continue
103+
104+
# first looks for innermost scope
105+
childscope = current_scope(name, binding.children, byteoffset)
106+
childscope !== nothing && return childscope
107+
108+
if byteoffset in binding.span &&
109+
any(bind -> bind isa LocalBinding && name == bind.name, binding.children)
110+
return binding
111+
end
112+
end
113+
114+
return nothing
115+
end
116+
117+
# global refactor
118+
# ---------------
119+
120+
function globalrefactor(old, new, mod, expr)
121+
entrypath, line = if mod == Main
122+
MAIN_MODULE_LOCATION[]
123+
else
124+
moduledefinition(mod)
125+
end
126+
files = modulefiles(entrypath)
127+
128+
nonwritablefiles = filter(f -> Int(Base.uperm(f)) 6, files)
129+
if !isempty(nonwritablefiles)
130+
return :warning, nonwritabledescription(mod, nonwritablefiles)
131+
end
132+
133+
with_logger(JunoProgressLogger()) do
134+
refactorfiles(old, new, mod, files, expr)
135+
end
136+
end
137+
138+
function refactorfiles(old, new, mod, files, expr)
139+
ismacro = CSTParser.defines_macro(expr)
140+
oldsym = ismacro ? Symbol("@" * old) : Symbol(old)
141+
newsym = ismacro ? Symbol("@" * new) : Symbol(new)
142+
143+
total = length(files)
144+
# TODO: enable line location information (the upstream needs to be enhanced)
145+
refactoredfiles = Set{String}()
146+
147+
id = "global_rename_refactor_progress"
148+
@info "Start global rename refactoring" progress=0 _id=id
149+
150+
for (i, file) enumerate(files)
151+
@info "Refactoring: $file ($i / $total)" progress=i/total _id=id
152+
153+
MacroTools.sourcewalk(file) do ex
154+
if ex === oldsym
155+
push!(refactoredfiles, fullpath(file))
156+
newsym
157+
# handle dot (module) accessor
158+
elseif @capture(ex, m_.$oldsym) && getfield′(mod, Symbol(m)) isa Module
159+
push!(refactoredfiles, fullpath(file))
160+
Expr(:., m, newsym)
161+
# macro case
162+
elseif ismacro && @capture(ex, macro $(Symbol(old))(args__) body_ end)
163+
push!(refactoredfiles, fullpath(file))
164+
Expr(:macro, :($(Symbol(new))($(args...))), :($body))
165+
else
166+
ex
167+
end
168+
end
169+
end
170+
171+
@info "Finish global rename refactoring" progress=1 _id=id
172+
173+
return if !isempty(refactoredfiles)
174+
:success, filedescription(mod, refactoredfiles)
175+
else
176+
:warning, "No rename refactoring occured on `$old` in `$mod` module."
177+
end
178+
end
179+
180+
# descriptions
181+
# ------------
182+
183+
function contextdescription(old, mod, context)
184+
gotouri = urigoto(mod, old)
185+
"""
186+
`$old` isn't found in local bindings in the current context:
187+
<details><summary>Context:</summary><pre><code>$(strip(context))</code></p></details>
188+
189+
If you want a global rename refactoring on `$mod.$old`, you need to run this command
190+
from its definition. <button>[Go to `$mod.$old`]($gotouri)</button>
191+
"""
192+
end
193+
194+
function moduledescription(old, parentmod)
195+
gotouri = urigoto(parentmod, old)
196+
"""
197+
**NOTE**: `$old` is defined in `$parentmod` -- you may need the same rename refactorings
198+
in that module as well. <button>[Go to `$parentmod.$old`]($gotouri)</button>
199+
"""
200+
end
201+
202+
function nonwritabledescription(mod, files)
203+
filelist = join(("<li>[$file]($(uriopen(file)))</li>" for file in files), '\n')
204+
"""
205+
Global rename refactor failed, since there are non-writable files detected in
206+
`$mod` module.
207+
208+
<details><summary>
209+
Non writable files (all in `$mod` module):
210+
</summary><ul>$(filelist)</ul></details>
211+
"""
212+
end
213+
214+
function filedescription(mod, files)
215+
filelist = join(("<li>[$file]($(uriopen(file)))</li>" for file in files), '\n')
216+
"""
217+
<details><summary>
218+
Refactored files (all in `$mod` module):
219+
</summary><ul>$(filelist)</ul></details>
220+
"""
221+
end
222+
223+
function errdescription(old, new, err)
224+
"""
225+
Rename refactoring `$old` ⟹ `$new` failed.
226+
227+
<details><summary>Error:</summary><pre><code>$(errmsg(err))</code></p></details>
228+
"""
229+
end

0 commit comments

Comments
 (0)