2
2
3
3
# editing and paging files
4
4
5
- import Base. shell_split
6
- using Base: find_source_file
7
-
8
- struct EditorEntry{P,Fn}
9
- pattern:: P
10
- fn:: Fn
11
- wait:: Bool
12
- takesline:: Bool
13
- end
14
- const editors = Vector {EditorEntry} (undef, 0 )
15
-
16
- struct EditorCommand{C,E}
17
- parsed_command:: C
18
- entry:: E
19
- end
20
- function parse_command (entry:: EditorEntry , command, path, line)
21
- if entry. takesline
22
- cmd = entry. fn (command, path, line)
23
- if ! isnothing (cmd)
24
- return EditorCommand (cmd, entry), true
25
- end
26
- end
5
+ using Base: shell_split, shell_escape, find_source_file
27
6
28
- cmd = entry. fn (command, path)
29
- if ! isnothing (cmd)
30
- return EditorCommand (cmd, entry), false
31
- end
32
-
33
- nothing , false
34
- end
7
+ """
8
+ EDITOR_CALLBACKS :: Vector{Function}
35
9
36
- function Base. run (x:: EditorCommand{Cmd} )
37
- if ! x. entry. wait
38
- run (pipeline (x. parsed_command, stderr = stderr ), wait= false )
39
- else
40
- run (x. parsed_command)
41
- end
42
- end
43
- Base. run (x:: EditorCommand{Function} ) = x. parsed_command ()
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[]
44
16
45
17
"""
46
- define_editor(fn, pattern;wait=false)
18
+ define_editor(fn, pattern; wait=false)
47
19
48
- Define a new editor matching `pattern` that can be used to open a file
49
- (possibly at a given line number) using `fn`.
20
+ Define a new editor matching `pattern` that can be used to open a file (possibly
21
+ at a given line number) using `fn`.
50
22
51
23
The `fn` argument is a function that determines how to open a file with the
52
- given editor. It should take 2 or 3 arguments, as follows:
24
+ given editor. It should take three arguments, as follows:
53
25
54
- * `command` - an array of strings representing the editor command. It can be
55
- safely interpolated into a command created using backtick notation.
56
- * `path` - the path to the source file to open
57
- * `line` - the optional line number to open to; if specified the returned
58
- command must open the file at the given line.
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
59
29
60
- `fn` must return either an appropriate `Cmd` object to open a file, a
61
- zero-argument function that will open the file directly, or `nothing`.
62
- Returning a `Cmd` is preferred over returning a function. Use `nothing` to
63
- indicate that this editor is not appropriate for the current environment and
64
- another editor should be attempted.
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`.
65
37
66
38
The `pattern` argument is a string, regular expression, or an array of strings
67
- and regular expressions. For the `fn` to be called one of the patterns must
68
- match the value of `EDITOR`, `VISUAL` or `JULIA_EDITOR`. For strings, only
69
- whole words can match (i.e. "vi" doesn't match "vim -g" but will match
70
- "/usr/bin/vi -m").
71
-
72
- If multiple defined editors match, the one most recently defined will be
73
- used.
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.
74
47
75
48
By default julia does not wait for the editor to close, running it in the
76
49
background. However, if the editor is terminal based, you will probably want to
77
50
set `wait=true` and julia will wait for the editor to close before resuming.
78
51
79
- If no editor entry can be found, then a file is opened by running
80
- ` \$ command \$ path`.
52
+ If one of the editor environment variables is set, but no editor entry matches it,
53
+ the default editor entry is invoked:
81
54
82
- Note that many editors are already defined. All of the following commands
83
- should already work:
55
+ (cmd, path, line) -> `\$ cmd \$ path`
56
+
57
+ Note that many editors are already defined. All of the following commands should
58
+ already work:
84
59
85
60
- emacs
86
61
- vim
@@ -96,45 +71,59 @@ should already work:
96
71
- open
97
72
98
73
# Example:
74
+
99
75
The following defines the usage of terminal-based `emacs`:
100
76
101
- define_editor(r" \b emacs \b .*(-nw|--no-window-system)",
102
- wait=true) do cmd, path, line
77
+ define_editor(
78
+ r" \\ bemacs \\ b.* \\ s(-nw|--no-window-system) \\ b", wait=true) do cmd, path, line
103
79
`\$ cmd +\$ line \$ path`
104
80
end
81
+
82
+ !!! compat "Julia 1.4"
83
+ `define_editor` was introduced in Julia 1.4.
105
84
"""
106
- function define_editor (fn, pattern; wait= false , priority= 0 )
107
- nargs = map (x -> x. nargs - 1 , methods (fn). ms)
108
- has3args = 3 ∈ nargs
109
- has2args = 2 ∈ nargs
110
- entry = EditorEntry (pattern, fn, wait, has3args)
111
- pushfirst! (editors, entry)
112
-
113
- if ! (has3args || has2args)
114
- error (" Editor function must take 2 or 3 arguments" )
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
115
101
end
102
+ pushfirst! (EDITOR_CALLBACKS, callback)
116
103
end
117
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
+
118
109
function define_default_editors ()
119
- define_editor ([" vim" ," vi" ," nvim" ," mvim" ," nano" ],
120
- wait= true ) do cmd, path, line
121
- ` $cmd +$line $path `
122
- end
123
- define_editor ([r" \b emacs" ," gedit" ,r" \b gvim" ]) do cmd, path, line
124
- ` $cmd +$line $path `
110
+ # fallback: just call the editor with the path as argument
111
+ define_editor (r" .*" ) do cmd, path, line
112
+ ` $cmd $path `
125
113
end
126
- define_editor (r" \b emacs\b .*(-nw|--no-window-system)" ,
127
- wait= true ) do cmd, path, line
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
128
118
` $cmd +$line $path `
129
119
end
130
- define_editor (r" \b emacsclient\b .*(-nw|-t|-tty)" ,
131
- wait= true ) do cmd, path, line
120
+ define_editor ([r" \b emacs" , " gedit" , r" \b gvim" ]) do cmd, path, line
132
121
` $cmd +$line $path `
133
122
end
134
- define_editor ([" textmate" ," mate" ," kate" ]) do cmd, path, line
123
+ define_editor ([" textmate" , " mate" , " kate" ]) do cmd, path, line
135
124
` $cmd $path -l $line `
136
125
end
137
- define_editor ([r" \b subl" ,r" \b atom" ]) do cmd, path, line
126
+ define_editor ([r" \b subl" , r" \b atom" ]) do cmd, path, line
138
127
` $cmd $path :$line `
139
128
end
140
129
define_editor (" code" ) do cmd, path, line
@@ -147,20 +136,21 @@ function define_default_editors()
147
136
define_editor (r" \b CODE\. EXE\b " i ) do cmd, path, line
148
137
` $cmd -g $path :$line `
149
138
end
150
- define_editor (" open" ) do cmd, path, line
151
- function ()
152
- # don't emit this ccall on other platforms
153
- @static if Sys. iswindows ()
154
- result = ccall ((:ShellExecuteW , " shell32" ), stdcall,
155
- Int, (Ptr{Cvoid}, Cwstring, Cwstring,
156
- Ptr{Cvoid}, Ptr{Cvoid}, Cint),
157
- C_NULL , " open" , path, C_NULL , C_NULL , 10 )
158
- systemerror (:edit , result ≤ 32 )
159
- 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 )
160
148
end
149
+ return true
161
150
end
151
+ pushfirst! (EDITOR_CALLBACKS, callback)
162
152
elseif Sys. isapple ()
163
- define_editor (" open" ) do cmd, path
153
+ define_editor (" open" ) do cmd, path, line
164
154
` open -t $path `
165
155
end
166
156
end
@@ -169,69 +159,46 @@ end
169
159
"""
170
160
editor()
171
161
172
- Determine the editor to use when running functions like `edit`. Return an `Array` compatible
173
- for use within backticks. You can change the editor by setting `JULIA_EDITOR`, `VISUAL` or
174
- `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 .
175
165
"""
176
166
function editor ()
177
- if Sys. iswindows () || Sys. isapple ()
178
- default_editor = " open"
179
- elseif isfile (" /etc/alternatives/editor" )
180
- default_editor = realpath (" /etc/alternatives/editor" )
181
- else
182
- default_editor = " emacs"
183
- end
184
167
# Note: the editor path can include spaces (if escaped) and flags.
185
- args = shell_split (get (ENV , " JULIA_EDITOR" ,
186
- get (ENV ," VISUAL" , get (ENV ," EDITOR" , default_editor))))
187
- isempty (args) && error (" editor is empty" )
188
- 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])
189
178
end
190
179
191
- editormatches (pattern:: String , command) =
192
- occursin (Regex (" \\ b" * pattern* " \\ b" ), command)
193
- editormatches (pattern:: Regex , command) =
194
- occursin (pattern, command)
195
- editormatches (pattern:: AbstractArray , command) =
196
- any (x -> editormatches (x, command), pattern)
197
- findeditors (command) =
198
- filter (e -> editormatches (e. pattern,join (command," " )), editors)
199
-
200
180
"""
201
181
edit(path::AbstractString, line::Integer=0)
202
182
203
183
Edit a file or directory optionally providing a line number to edit the file at.
204
184
Return to the `julia` prompt when you quit the editor. The editor can be changed
205
185
by setting `JULIA_EDITOR`, `VISUAL` or `EDITOR` as an environment variable.
206
- To ensure that the file can be opened at the given line, you may need to
207
- call `define_editor` first.
186
+
187
+ See also: ( `define_editor`)[@ref]
208
188
"""
209
189
function edit (path:: AbstractString , line:: Integer = 0 )
210
- ! isempty (editors) || define_default_editors ()
211
- command = editor ( )
190
+ isempty (EDITOR_CALLBACKS) && define_default_editors ()
191
+ path isa String || (path = convert (String, path) )
212
192
if endswith (path, " .jl" )
213
- f = find_source_file (path)
214
- f != = nothing && (path = f)
215
- end
216
-
217
- parsed = nothing
218
- line_supported = false
219
- for entry in findeditors (command)
220
- parsed, line_supported = parse_command (entry, command, path, line)
221
- isnothing (parsed) || break
222
- end
223
- if isnothing (parsed)
224
- parsed = ` $command $path `
225
- line_supported = false
193
+ p = find_source_file (path)
194
+ p != = nothing && (path = p)
226
195
end
227
-
228
- if line != 0 && ! line_supported
229
- @info (" Unknown editor: no line number information passed.\n " *
230
- " The method is defined at line $line ." )
196
+ cmd = editor ()
197
+ for callback in EDITOR_CALLBACKS
198
+ callback (cmd, path, line) && return
231
199
end
232
- run (parsed)
233
-
234
- nothing
200
+ # shouldn't happen unless someone has removed fallback entry
201
+ error (" no editor found" )
235
202
end
236
203
237
204
"""
0 commit comments