2
2
3
3
# editing and paging files
4
4
5
- import Base. shell_split
6
- using Base: find_source_file
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` and
11
+ `line` 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 three 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
+
30
+ Editors which cannot open to a specific line with a command may ignore the
31
+ `line` argument. The `fn` callback must return either an appropriate `Cmd`
32
+ object to open a file or `nothing` to indicate that they cannot edit this file.
33
+ Use `nothing` to indicate that this editor is not appropriate for the current
34
+ environment and another editor should be attempted. It is possible to add more
35
+ general editing hooks that need not spawn external commands by pushing a
36
+ callback directly to the vector `EDITOR_CALLBACKS`.
37
+
38
+ The `pattern` argument is a string, regular expression, or an array of strings
39
+ and regular expressions. For the `fn` to be called, one of the patterns must
40
+ match the value of `EDITOR`, `VISUAL` or `JULIA_EDITOR`. For strings, the string
41
+ must equal the [`basename`](@ref) of the first word of the editor command, with
42
+ its extension, if any, removed. E.g. "vi" doesn't match "vim -g" but matches
43
+ "/usr/bin/vi -m"; it also matches `vi.exe`. If `pattern` is a regex it is
44
+ matched against all of the editor command as a shell-escaped string. An array
45
+ pattern matches if any of its items match. If multiple editors match, the one
46
+ added most recently is used.
47
+
48
+ By default julia does not wait for the editor to close, running it in the
49
+ background. However, if the editor is terminal based, you will probably want to
50
+ set `wait=true` and julia will wait for the editor to close before resuming.
51
+
52
+ If one of the editor environment variables is set, but no editor entry matches it,
53
+ the default editor entry is invoked:
54
+
55
+ (cmd, path, line) -> `\$ cmd \$ path`
56
+
57
+ Note that many editors are already defined. All of the following commands should
58
+ already work:
59
+
60
+ - emacs
61
+ - vim
62
+ - nvim
63
+ - nano
64
+ - textmate
65
+ - mate
66
+ - kate
67
+ - subl
68
+ - atom
69
+ - notepad++
70
+ - Visual Studio Code
71
+ - open
72
+
73
+ # Example:
74
+
75
+ The following defines the usage of terminal-based `emacs`:
76
+
77
+ define_editor(
78
+ r"\\ bemacs\\ b.*\\ s(-nw|--no-window-system)\\ b", wait=true) do cmd, path, line
79
+ `\$ cmd +\$ line \$ path`
80
+ end
81
+
82
+ !!! compat "Julia 1.4"
83
+ `define_editor` was introduced in Julia 1.4.
84
+ """
85
+ function define_editor (fn:: Function , pattern; wait:: Bool = false )
86
+ callback = function (cmd:: Cmd , path:: AbstractString , line:: Integer )
87
+ editor_matches (pattern, cmd) || return false
88
+ editor = fn (cmd, path, line)
89
+ if editor isa Cmd
90
+ if wait
91
+ run (editor) # blocks while editor runs
92
+ else
93
+ run (pipeline (editor, stderr = stderr ), wait= false )
94
+ end
95
+ return true
96
+ elseif editor isa Nothing
97
+ return false
98
+ end
99
+ @warn " invalid editor value returned" pattern= pattern editor= editor
100
+ return false
101
+ end
102
+ pushfirst! (EDITOR_CALLBACKS, callback)
103
+ end
104
+
105
+ editor_matches (p:: Regex , cmd:: Cmd ) = occursin (p, shell_escape (cmd))
106
+ editor_matches (p:: String , cmd:: Cmd ) = p == splitext (basename (first (cmd)))[1 ]
107
+ editor_matches (ps:: AbstractArray , cmd:: Cmd ) = any (editor_matches (p, cmd) for p in ps)
108
+
109
+ function define_default_editors ()
110
+ # fallback: just call the editor with the path as argument
111
+ define_editor (r" .*" ) do cmd, path, line
112
+ ` $cmd $path `
113
+ end
114
+ define_editor ([
115
+ " vim" , " vi" , " nvim" , " mvim" , " nano" ,
116
+ r" \b emacs\b .*\s (-nw|--no-window-system)\b " ,
117
+ r" \b emacsclient\b .\s *-(-?nw|t|-?tty)\b " ], wait= true ) do cmd, path, line
118
+ ` $cmd +$line $path `
119
+ end
120
+ define_editor ([r" \b emacs" , " gedit" , r" \b gvim" ]) do cmd, path, line
121
+ ` $cmd +$line $path `
122
+ end
123
+ define_editor ([" textmate" , " mate" , " kate" ]) do cmd, path, line
124
+ ` $cmd $path -l $line `
125
+ end
126
+ define_editor ([r" \b subl" , r" \b atom" ]) do cmd, path, line
127
+ ` $cmd $path :$line `
128
+ end
129
+ define_editor (" code" ) do cmd, path, line
130
+ ` $cmd -g $path :$line `
131
+ end
132
+ define_editor (r" \b notepad++" ) do cmd, path, line
133
+ ` $cmd $path -n$line `
134
+ end
135
+ if Sys. iswindows ()
136
+ define_editor (r" \b CODE\. EXE\b " i ) do cmd, path, line
137
+ ` $cmd -g $path :$line `
138
+ end
139
+ callback = function (cmd:: Cmd , path:: AbstractString , line:: Integer )
140
+ cmd == ` open` || return false
141
+ # don't emit this ccall on other platforms
142
+ @static if Sys. iswindows ()
143
+ result = ccall ((:ShellExecuteW , " shell32" ), stdcall,
144
+ Int, (Ptr{Cvoid}, Cwstring, Cwstring,
145
+ Ptr{Cvoid}, Ptr{Cvoid}, Cint),
146
+ C_NULL , " open" , path, C_NULL , C_NULL , 10 )
147
+ systemerror (:edit , result ≤ 32 )
148
+ end
149
+ return true
150
+ end
151
+ pushfirst! (EDITOR_CALLBACKS, callback)
152
+ elseif Sys. isapple ()
153
+ define_editor (" open" ) do cmd, path, line
154
+ ` open -t $path `
155
+ end
156
+ end
157
+ end
7
158
8
159
"""
9
160
editor()
10
161
11
- Determine the editor to use when running functions like `edit`. Return an `Array` compatible
12
- for use within backticks. You can change the editor by setting `JULIA_EDITOR`, `VISUAL` or
13
- `EDITOR` as an environment variable .
162
+ Determine the editor to use when running functions like `edit`. Returns a `Cmd`
163
+ object. Change editor by setting `JULIA_EDITOR`, `VISUAL` or `EDITOR`
164
+ environment variables .
14
165
"""
15
166
function editor ()
16
- if Sys. iswindows () || Sys. isapple ()
17
- default_editor = " open"
18
- elseif isfile (" /etc/alternatives/editor" )
19
- default_editor = realpath (" /etc/alternatives/editor" )
20
- else
21
- default_editor = " emacs"
22
- end
23
167
# Note: the editor path can include spaces (if escaped) and flags.
24
- args = shell_split (get (ENV ," JULIA_EDITOR" , get (ENV ," VISUAL" , get (ENV ," EDITOR" , default_editor))))
25
- isempty (args) && error (" editor is empty" )
26
- return args
168
+ for var in [" JULIA_EDITOR" , " VISUAL" , " EDITOR" ]
169
+ str = get (ENV , var, nothing )
170
+ str === nothing && continue
171
+ isempty (str) && error (" invalid editor \$ $var : $(repr (str)) " )
172
+ return Cmd (shell_split (str))
173
+ end
174
+ editor_file = " /etc/alternatives/editor"
175
+ editor = (Sys. iswindows () || Sys. isapple ()) ? " open" :
176
+ isfile (editor_file) ? realpath (editor_file) : " emacs"
177
+ return Cmd ([editor])
27
178
end
28
179
29
180
"""
32
183
Edit a file or directory optionally providing a line number to edit the file at.
33
184
Return to the `julia` prompt when you quit the editor. The editor can be changed
34
185
by setting `JULIA_EDITOR`, `VISUAL` or `EDITOR` as an environment variable.
186
+
187
+ See also: (`define_editor`)[@ref]
35
188
"""
36
189
function edit (path:: AbstractString , line:: Integer = 0 )
37
- command = editor ()
38
- name = basename ( first (command ))
190
+ isempty (EDITOR_CALLBACKS) && define_default_editors ()
191
+ path isa String || (path = convert (String, path ))
39
192
if endswith (path, " .jl" )
40
- f = find_source_file (path)
41
- f != = nothing && (path = f )
193
+ p = find_source_file (path)
194
+ p != = nothing && (path = p )
42
195
end
43
- background = true
44
- line_unsupported = false
45
- if startswith (name, " vim." ) || name == " vi" || name == " vim" || name == " nvim" ||
46
- name == " mvim" || name == " nano" ||
47
- name == " emacs" && any (c -> c in [" -nw" , " --no-window-system" ], command) ||
48
- name == " emacsclient" && any (c -> c in [" -nw" , " -t" , " -tty" ], command)
49
- cmd = line != 0 ? ` $command +$line $path ` : ` $command $path `
50
- background = false
51
- elseif startswith (name, " emacs" ) || name == " gedit" || startswith (name, " gvim" )
52
- cmd = line != 0 ? ` $command +$line $path ` : ` $command $path `
53
- elseif name == " textmate" || name == " mate" || name == " kate"
54
- cmd = line != 0 ? ` $command $path -l $line ` : ` $command $path `
55
- elseif name == " rmate"
56
- cmd = line != 0 ? ` $command $path -l $line -f` : ` $command $path -f`
57
- elseif startswith (name, " subl" ) || startswith (name, " atom" )
58
- cmd = line != 0 ? ` $command $path :$line ` : ` $command $path `
59
- elseif name == " code" || (Sys. iswindows () && (uppercase (name) == " CODE.EXE" || uppercase (name) == " CODE.CMD" ))
60
- cmd = line != 0 ? ` $command -g $path :$line ` : ` $command -g $path `
61
- elseif startswith (name, " notepad++" )
62
- cmd = line != 0 ? ` $command $path -n$line ` : ` $command $path `
63
- elseif Sys. isapple () && name == " open"
64
- cmd = ` open -t $path `
65
- line_unsupported = true
66
- else
67
- cmd = ` $command $path `
68
- background = false
69
- line_unsupported = true
196
+ cmd = editor ()
197
+ for callback in EDITOR_CALLBACKS
198
+ callback (cmd, path, line) && return
70
199
end
71
-
72
- if Sys. iswindows () && name == " open"
73
- @static Sys. iswindows () && # don't emit this ccall on other platforms
74
- systemerror (:edit , ccall ((:ShellExecuteW , " shell32" ), stdcall, Int,
75
- (Ptr{Cvoid}, Cwstring, Cwstring, Ptr{Cvoid}, Ptr{Cvoid}, Cint),
76
- C_NULL , " open" , path, C_NULL , C_NULL , 10 ) ≤ 32 )
77
- elseif background
78
- run (pipeline (cmd, stderr = stderr ), wait= false )
79
- else
80
- run (cmd)
81
- end
82
- line != 0 && line_unsupported && println (" Unknown editor: no line number information passed.\n The method is defined at line $line ." )
83
-
84
- nothing
200
+ # shouldn't happen unless someone has removed fallback entry
201
+ error (" no editor found" )
85
202
end
86
203
87
204
"""
@@ -95,8 +212,8 @@ method to edit. For modules, open the main source file. The module needs to be l
95
212
!!! compat "Julia 1.1"
96
213
`edit` on modules requires at least Julia 1.1.
97
214
98
- The editor can be changed by setting `JULIA_EDITOR`, `VISUAL` or `EDITOR` as an environment
99
- variable .
215
+ To ensure that the file can be opened at the given line, you may need to call
216
+ `define_editor` first .
100
217
"""
101
218
edit (f) = edit (functionloc (f)... )
102
219
edit (f, @nospecialize t) = edit (functionloc (f,t)... )
0 commit comments