Skip to content

Commit f2a2664

Browse files
authored
Merge pull request #33760 from JuliaLang/rf/edit_choose_column
edit(): allow specifying the column for some editors
2 parents bd85247 + bda9eaa commit f2a2664

File tree

3 files changed

+82
-41
lines changed

3 files changed

+82
-41
lines changed

stdlib/InteractiveUtils/src/editless.jl

Lines changed: 58 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ using Base: shell_split, shell_escape, find_source_file
77
"""
88
EDITOR_CALLBACKS :: Vector{Function}
99
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
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
1212
indicate that it has handled the request, or return `false` to decline the
1313
editing request.
1414
"""
@@ -21,19 +21,20 @@ Define a new editor matching `pattern` that can be used to open a file (possibly
2121
at a given line number) using `fn`.
2222
2323
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:
24+
given editor. It should take four arguments, as follows:
2525
2626
* `cmd` - a base command object for the editor
2727
* `path` - the path to the source file to open
2828
* `line` - the line number to open the editor at
29+
* `column` - the column number to open the editor at
2930
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`.
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`.
3738
3839
The `pattern` argument is a string, regular expression, or an array of strings
3940
and regular expressions. For the `fn` to be called, one of the patterns must
@@ -52,7 +53,7 @@ set `wait=true` and julia will wait for the editor to close before resuming.
5253
If one of the editor environment variables is set, but no editor entry matches it,
5354
the default editor entry is invoked:
5455
55-
(cmd, path, line) -> `\$cmd \$path`
56+
(cmd, path, line, column) -> `\$cmd \$path`
5657
5758
Note that many editors are already defined. All of the following commands should
5859
already work:
@@ -88,9 +89,14 @@ The following defines the usage of terminal-based `emacs`:
8889
`define_editor` was introduced in Julia 1.4.
8990
"""
9091
function define_editor(fn::Function, pattern; wait::Bool=false)
91-
callback = function (cmd::Cmd, path::AbstractString, line::Integer)
92+
callback = function (cmd::Cmd, path::AbstractString, line::Integer, column::Integer)
9293
editor_matches(pattern, cmd) || return false
93-
editor = fn(cmd, path, line)
94+
editor = if !applicable(fn, cmd, path, line, column)
95+
# Be backwards compatible with editors that did not define the newly added column argument
96+
fn(cmd, path, line)
97+
else
98+
fn(cmd, path, line, column)
99+
end
94100
if editor isa Cmd
95101
if wait
96102
run(editor) # blocks while editor runs
@@ -113,35 +119,50 @@ editor_matches(ps::AbstractArray, cmd::Cmd) = any(editor_matches(p, cmd) for p i
113119

114120
function define_default_editors()
115121
# fallback: just call the editor with the path as argument
116-
define_editor(r".*") do cmd, path, line
122+
define_editor(r".*") do cmd, path, line, column
117123
`$cmd $path`
118124
end
119-
define_editor(Any[r"\bemacs", "gedit", r"\bgvim"]) do cmd, path, line
120-
`$cmd +$line $path`
125+
# vim family
126+
for (editors, wait) in [[Any["vim", "vi", "nvim", "mvim"], true],
127+
[Any["\bgvim"], false]]
128+
define_editor(editors; wait) do cmd, path, line, column
129+
cmd = line == 0 ? `$cmd $path` :
130+
column == 0 ? `$cmd +$line $path` :
131+
`$cmd "+normal $(line)G$(column)|" $path`
132+
end
133+
end
134+
define_editor("nano"; wait=true) do cmd, path, line, column
135+
cmd = `$cmd +$line,$column $path`
121136
end
122-
# Must check that emacs not running in -t/-nw before regex match for general emacs
123-
define_editor(Any[
124-
"vim", "vi", "nvim", "mvim", "nano", "micro", "kak",
125-
r"\bemacs\b.*\s(-nw|--no-window-system)\b",
126-
r"\bemacsclient\b.\s*-(-?nw|t|-?tty)\b",
127-
], wait=true) do cmd, path, line
137+
# emacs (must check that emacs not running in -t/-nw before regex match for general emacs)
138+
for (editors, wait) in [[Any[r"\bemacs"], false],
139+
[Any[r"\bemacs\b.*\s(-nw|--no-window-system)\b", r"\bemacsclient\b.\s*-(-?nw|t|-?tty)\b"], true]]
140+
define_editor(editors; wait) do cmd, path, line, column
141+
`$cmd +$line:$column $path`
142+
end
143+
end
144+
# Other editors
145+
define_editor("gedit") do cmd, path, line, column
146+
`$cmd +$line:$column $path`
147+
end
148+
define_editor(Any["micro", "kak"]; wait=true) do cmd, path, line, column
128149
`$cmd +$line $path`
129150
end
130-
define_editor(["textmate", "mate", "kate"]) do cmd, path, line
151+
define_editor(["textmate", "mate", "kate"]) do cmd, path, line, column
131152
`$cmd $path -l $line`
132153
end
133-
define_editor(Any[r"\bsubl", r"\batom", "pycharm", "bbedit"]) do cmd, path, line
154+
define_editor(Any[r"\bsubl", r"\batom", "pycharm", "bbedit"]) do cmd, path, line, column
134155
`$cmd $path:$line`
135156
end
136-
define_editor(["code", "code-insiders"]) do cmd, path, line
137-
`$cmd -g $path:$line`
157+
define_editor(["code", "code-insiders"]) do cmd, path, line, column
158+
`$cmd -g $path:$line:$column`
138159
end
139-
define_editor(r"\bnotepad++") do cmd, path, line
160+
define_editor(r"\bnotepad++") do cmd, path, line, column
140161
`$cmd $path -n$line`
141162
end
142163
if Sys.iswindows()
143-
define_editor(r"\bCODE\.EXE\b"i) do cmd, path, line
144-
`$cmd -g $path:$line`
164+
define_editor(r"\bCODE\.EXE\b"i) do cmd, path, line, column
165+
`$cmd -g $path:$line:$column`
145166
end
146167
callback = function (cmd::Cmd, path::AbstractString, line::Integer)
147168
cmd == `open` || return false
@@ -157,7 +178,7 @@ function define_default_editors()
157178
end
158179
pushfirst!(EDITOR_CALLBACKS, callback)
159180
elseif Sys.isapple()
160-
define_editor("open") do cmd, path, line
181+
define_editor("open") do cmd, path, line, column
161182
`open -t $path`
162183
end
163184
end
@@ -186,23 +207,27 @@ function editor()
186207
end
187208

188209
"""
189-
edit(path::AbstractString, line::Integer=0)
210+
edit(path::AbstractString, line::Integer=0, column::Integer=0)
190211
191212
Edit a file or directory optionally providing a line number to edit the file at.
192213
Return to the `julia` prompt when you quit the editor. The editor can be changed
193214
by setting `JULIA_EDITOR`, `VISUAL` or `EDITOR` as an environment variable.
194215
195216
See also [`define_editor`](@ref).
196217
"""
197-
function edit(path::AbstractString, line::Integer=0)
218+
function edit(path::AbstractString, line::Integer=0, column::Integer=0)
198219
path isa String || (path = convert(String, path))
199220
if endswith(path, ".jl")
200221
p = find_source_file(path)
201222
p !== nothing && (path = p)
202223
end
203224
cmd = editor()
204225
for callback in EDITOR_CALLBACKS
205-
callback(cmd, path, line) && return
226+
if !applicable(callback, cmd, path, line, column)
227+
callback(cmd, path, line) && return
228+
else
229+
callback(cmd, path, line, column) && return
230+
end
206231
end
207232
# shouldn't happen unless someone has removed fallback entry
208233
error("no editor found")

stdlib/REPL/src/LineEdit.jl

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1314,7 +1314,7 @@ function guess_current_mode_name(s)
13141314
end
13151315

13161316
# edit current input in editor
1317-
function edit_input(s, f = (filename, line) -> InteractiveUtils.edit(filename, line))
1317+
function edit_input(s, f = (filename, line, column) -> InteractiveUtils.edit(filename, line, column))
13181318
mode_name = guess_current_mode_name(s)
13191319
filename = tempname()
13201320
if mode_name == :julia
@@ -1325,9 +1325,26 @@ function edit_input(s, f = (filename, line) -> InteractiveUtils.edit(filename, l
13251325
buf = buffer(s)
13261326
pos = position(buf)
13271327
str = String(take!(buf))
1328-
line = 1 + count(==(_newline), view(str, 1:pos))
1328+
lines = readlines(IOBuffer(str); keep=true)
1329+
1330+
# Compute line
1331+
line_start_offset = 0
1332+
line = 1
1333+
while line < length(lines) && line_start_offset + sizeof(lines[line]) <= pos
1334+
line_start_offset += sizeof(lines[line])
1335+
line += 1
1336+
end
1337+
1338+
# Compute column
1339+
col = 0
1340+
off = line_start_offset
1341+
while off <= pos
1342+
off = nextind(str, off)
1343+
col += 1
1344+
end
1345+
13291346
write(filename, str)
1330-
f(filename, line)
1347+
f(filename, line, col)
13311348
str_mod = readchomp(filename)
13321349
rm(filename)
13331350
if str != str_mod # something was changed, run the input

stdlib/REPL/test/repl.jl

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1506,11 +1506,10 @@ fake_repl() do stdin_write, stdout_read, repl
15061506
end
15071507
repl.interface = REPL.setup_interface(repl)
15081508
s = LineEdit.init_state(repl.t, repl.interface)
1509-
LineEdit.edit_insert(s, "1234")
1510-
@show buffercontents(LineEdit.buffer(s))
1511-
input_f = function(filename, line)
1512-
write(filename, "123456\n")
1509+
LineEdit.edit_insert(s, "1234αβ")
1510+
input_f = function(filename, line, column)
1511+
write(filename, "1234αβ56γ\n")
15131512
end
15141513
LineEdit.edit_input(s, input_f)
1515-
@test buffercontents(LineEdit.buffer(s)) == "123456"
1514+
@test buffercontents(LineEdit.buffer(s)) == "1234αβ56γ"
15161515
end

0 commit comments

Comments
 (0)