• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / #37641

03 Oct 2023 08:35PM UTC coverage: 86.293% (-0.06%) from 86.356%
#37641

push

local

web-flow
Revert "Don't mark nonlocal symbols as hidden" (#51571)

72863 of 84437 relevant lines covered (86.29%)

12901453.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

25.97
/stdlib/InteractiveUtils/src/editless.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
# editing and paging files
4

5
using Base: shell_split, shell_escape, find_source_file
6

7
"""
8
    EDITOR_CALLBACKS :: Vector{Function}
9

10
A vector of editor callback functions, which take as arguments `cmd`, `path`, `line`
11
and `column` and which is then expected to either open an editor and return `true` to
12
indicate that it has handled the request, or return `false` to decline the
13
editing request.
14
"""
15
const EDITOR_CALLBACKS = Function[]
16

17
"""
18
    define_editor(fn, pattern; wait=false)
19

20
Define a new editor matching `pattern` that can be used to open a file (possibly
21
at a given line number) using `fn`.
22

23
The `fn` argument is a function that determines how to open a file with the
24
given editor. It should take four arguments, as follows:
25

26
* `cmd` - a base command object for the editor
27
* `path` - the path to the source file to open
28
* `line` - the line number to open the editor at
29
* `column` - the column number to open the editor at
30

31
Editors which cannot open to a specific line with a command or a specific column
32
may ignore the `line` and/or `column` argument. The `fn` callback must return
33
either an appropriate `Cmd` object to open a file or `nothing` to indicate that
34
they cannot edit this file. Use `nothing` to indicate that this editor is not
35
appropriate for the current environment and another editor should be attempted.
36
It is possible to add more general editing hooks that need not spawn
37
external commands by pushing a callback directly to the vector `EDITOR_CALLBACKS`.
38

39
The `pattern` argument is a string, regular expression, or an array of strings
40
and regular expressions. For the `fn` to be called, one of the patterns must
41
match the value of `EDITOR`, `VISUAL` or `JULIA_EDITOR`. For strings, the string
42
must equal the [`basename`](@ref) of the first word of the editor command, with
43
its extension, if any, removed. E.g. "vi" doesn't match "vim -g" but matches
44
"/usr/bin/vi -m"; it also matches `vi.exe`. If `pattern` is a regex it is
45
matched against all of the editor command as a shell-escaped string. An array
46
pattern matches if any of its items match. If multiple editors match, the one
47
added most recently is used.
48

49
By default julia does not wait for the editor to close, running it in the
50
background. However, if the editor is terminal based, you will probably want to
51
set `wait=true` and julia will wait for the editor to close before resuming.
52

53
If one of the editor environment variables is set, but no editor entry matches it,
54
the default editor entry is invoked:
55

56
    (cmd, path, line, column) -> `\$cmd \$path`
57

58
Note that many editors are already defined. All of the following commands should
59
already work:
60

61
- emacs
62
- emacsclient
63
- vim
64
- nvim
65
- nano
66
- micro
67
- kak
68
- helix
69
- textmate
70
- mate
71
- kate
72
- subl
73
- atom
74
- notepad++
75
- Visual Studio Code
76
- open
77
- pycharm
78
- bbedit
79

80
# Example:
81

82
The following defines the usage of terminal-based `emacs`:
83

84
    define_editor(
85
        r"\\bemacs\\b.*\\s(-nw|--no-window-system)\\b", wait=true) do cmd, path, line
86
        `\$cmd +\$line \$path`
87
    end
88

89
!!! compat "Julia 1.4"
90
    `define_editor` was introduced in Julia 1.4.
91
"""
92
function define_editor(fn::Function, pattern; wait::Bool=false)
13✔
93
    callback = function (cmd::Cmd, path::AbstractString, line::Integer, column::Integer)
13✔
94
        editor_matches(pattern, cmd) || return false
95
        editor = if !applicable(fn, cmd, path, line, column)
96
            # Be backwards compatible with editors that did not define the newly added column argument
97
            fn(cmd, path, line)
98
        else
99
            fn(cmd, path, line, column)
100
        end
101
        if editor isa Cmd
102
            if wait
103
                run(editor) # blocks while editor runs
104
            else
105
                run(pipeline(editor, stderr=stderr), wait=false)
106
            end
107
            return true
108
        elseif editor isa Nothing
109
            return false
110
        end
111
        @warn "invalid editor value returned" pattern=pattern editor=editor
112
        return false
113
    end
114
    pushfirst!(EDITOR_CALLBACKS, callback)
13✔
115
end
116

117
editor_matches(p::Regex, cmd::Cmd) = occursin(p, shell_escape(cmd))
×
118
editor_matches(p::String, cmd::Cmd) = p == splitext(basename(first(cmd)))[1]
×
119
editor_matches(ps::AbstractArray, cmd::Cmd) = any(editor_matches(p, cmd) for p in ps)
×
120

121
function define_default_editors()
1✔
122
    # fallback: just call the editor with the path as argument
123
    define_editor(r".*") do cmd, path, line, column
1✔
124
        `$cmd $path`
125
    end
126
    # vim family
127
    for (editors, wait) in [
1✔
128
        [["vim", "vi", "nvim", "mvim"], true],
129
        [[r"\bgvim"], false],
130
    ]
131
        define_editor(editors; wait) do cmd, path, line, column
2✔
132
            cmd = line == 0 ? `$cmd $path` :
133
                column == 0 ? `$cmd +$line $path` :
134
                `$cmd "+normal $(line)G$(column)|" $path`
135
        end
136
    end
3✔
137
    define_editor("nano"; wait=true) do cmd, path, line, column
1✔
138
        cmd = `$cmd +$line,$column $path`
139
    end
140
    # emacs (must check that emacs not running in -t/-nw
141
    # before regex match for general emacs)
142
    for (editors, wait) in [
1✔
143
        [[r"\bemacs"], false],
144
        [[r"\bemacs\b.*\s(-nw|--no-window-system)\b",
145
          r"\bemacsclient\b.\s*-(-?nw|t|-?tty)\b"], true],
146
    ]
147
        define_editor(editors; wait) do cmd, path, line, column
2✔
148
            `$cmd +$line:$column $path`
149
        end
150
    end
3✔
151
    # other editors
152
    define_editor("gedit") do cmd, path, line, column
1✔
153
        `$cmd +$line:$column $path`
154
    end
155
    define_editor(["micro", "kak"]; wait=true) do cmd, path, line, column
2✔
156
        `$cmd +$line $path`
157
    end
158
    define_editor(["hx", "helix"]; wait=true) do cmd, path, line, column
2✔
159
        `$cmd $path:$line:$column`
160
    end
161
    define_editor(["textmate", "mate", "kate"]) do cmd, path, line, column
3✔
162
        `$cmd $path -l $line`
163
    end
164
    define_editor([r"\bsubl", r"\batom", "pycharm", "bbedit"]) do cmd, path, line, column
4✔
165
        `$cmd $path:$line`
166
    end
167
    define_editor(["code", "code-insiders"]) do cmd, path, line, column
2✔
168
        `$cmd -g $path:$line:$column`
169
    end
170
    define_editor(r"\bnotepad++") do cmd, path, line, column
1✔
171
        `$cmd $path -n$line`
172
    end
173
    if Sys.iswindows()
×
174
        define_editor(r"\bCODE\.EXE\b"i) do cmd, path, line, column
×
175
            `$cmd -g $path:$line:$column`
176
        end
177
        callback = function (cmd::Cmd, path::AbstractString, line::Integer)
×
178
            cmd == `open` || return false
179
            # don't emit this ccall on other platforms
180
            @static if Sys.iswindows()
181
                result = ccall((:ShellExecuteW, "shell32"), stdcall,
182
                               Int, (Ptr{Cvoid}, Cwstring, Cwstring,
183
                                     Ptr{Cvoid}, Ptr{Cvoid}, Cint),
184
                               C_NULL, "open", path, C_NULL, C_NULL, 10)
185
                systemerror(:edit, result ≤ 32)
186
            end
187
            return true
188
        end
189
        pushfirst!(EDITOR_CALLBACKS, callback)
×
190
    elseif Sys.isapple()
×
191
        define_editor("open") do cmd, path, line, column
1✔
192
            `open -t $path`
193
        end
194
    end
195
end
196
define_default_editors()
197

198
"""
199
    editor()
200

201
Determine the editor to use when running functions like `edit`. Returns a `Cmd`
202
object. Change editor by setting `JULIA_EDITOR`, `VISUAL` or `EDITOR`
203
environment variables.
204
"""
205
function editor()
×
206
    # Note: the editor path can include spaces (if escaped) and flags.
207
    for var in ["JULIA_EDITOR", "VISUAL", "EDITOR"]
×
208
        str = get(ENV, var, nothing)
×
209
        str === nothing && continue
×
210
        isempty(str) && error("invalid editor \$$var: $(repr(str))")
×
211
        return Cmd(shell_split(str))
×
212
    end
×
213
    editor_file = "/etc/alternatives/editor"
×
214
    editor = (Sys.iswindows() || Sys.isapple()) ? "open" :
×
215
        isfile(editor_file) ? realpath(editor_file) : "emacs"
216
    return Cmd([editor])
×
217
end
218

219
"""
220
    edit(path::AbstractString, line::Integer=0, column::Integer=0)
221

222
Edit a file or directory optionally providing a line number to edit the file at.
223
Return to the `julia` prompt when you quit the editor. The editor can be changed
224
by setting `JULIA_EDITOR`, `VISUAL` or `EDITOR` as an environment variable.
225

226
See also [`define_editor`](@ref).
227
"""
228
function edit(path::AbstractString, line::Integer=0, column::Integer=0)
×
229
    path isa String || (path = convert(String, path))
×
230
    if endswith(path, ".jl")
×
231
        p = find_source_file(path)
×
232
        p !== nothing && (path = p)
×
233
    end
234
    cmd = editor()
×
235
    for callback in EDITOR_CALLBACKS
×
236
        if !applicable(callback, cmd, path, line, column)
×
237
            callback(cmd, path, line) && return
×
238
        else
239
            callback(cmd, path, line, column) && return
×
240
        end
241
    end
×
242
    # shouldn't happen unless someone has removed fallback entry
243
    error("no editor found")
×
244
end
245

246
"""
247
    edit(function, [types])
248
    edit(module)
249

250
Edit the definition of a function, optionally specifying a tuple of types to indicate which
251
method to edit. For modules, open the main source file. The module needs to be loaded with
252
`using` or `import` first.
253

254
!!! compat "Julia 1.1"
255
    `edit` on modules requires at least Julia 1.1.
256

257
To ensure that the file can be opened at the given line, you may need to call
258
`define_editor` first.
259
"""
260
function edit(@nospecialize f)
×
261
    ms = methods(f).ms
×
262
    length(ms) == 1 && edit(functionloc(ms[1])...)
×
263
    length(ms) > 1 && return ms
×
264
    length(ms) == 0 && functionloc(f) # throws
×
265
    nothing
×
266
end
267
edit(m::Method) = edit(functionloc(m)...)
×
268
edit(@nospecialize(f), idx::Integer) = edit(methods(f).ms[idx])
×
269
edit(f, t)  = (@nospecialize; edit(functionloc(f, t)...))
×
270
edit(file::Nothing, line::Integer) = error("could not find source file for function")
×
271
edit(m::Module) = edit(pathof(m))
×
272

273
# terminal pager
274

275
if Sys.iswindows()
276
    function less(file::AbstractString, line::Integer)
×
277
        pager = shell_split(get(ENV, "PAGER", "more"))
×
278
        if pager[1] == "more"
×
279
            g = ""
×
280
            line -= 1
×
281
        else
282
            g = "g"
×
283
        end
284
        run(Cmd(`$pager +$(line)$(g) \"$file\"`, windows_verbatim = true))
×
285
        nothing
×
286
    end
287
else
288
    function less(file::AbstractString, line::Integer)
×
289
        pager = shell_split(get(ENV, "PAGER", "less"))
×
290
        run(`$pager +$(line)g $file`)
×
291
        nothing
×
292
    end
293
end
294

295
"""
296
    less(file::AbstractString, [line::Integer])
297

298
Show a file using the default pager, optionally providing a starting line number. Returns to
299
the `julia` prompt when you quit the pager.
300
"""
301
less(file::AbstractString) = less(file, 1)
×
302

303
"""
304
    less(function, [types])
305

306
Show the definition of a function using the default pager, optionally specifying a tuple of
307
types to indicate which method to see.
308
"""
309
less(f)                   = less(functionloc(f)...)
×
310
less(f, @nospecialize t)  = less(functionloc(f,t)...)
×
311
less(file, line::Integer) = error("could not find source file for function")
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc