Skip to content

Commit

Permalink
Merge pull request JuliaLang#33760 from JuliaLang/rf/edit_choose_column
Browse files Browse the repository at this point in the history
edit(): allow specifying the column for some editors
  • Loading branch information
KristofferC authored May 17, 2022
2 parents bd85247 + bda9eaa commit f2a2664
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 41 deletions.
91 changes: 58 additions & 33 deletions stdlib/InteractiveUtils/src/editless.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ using Base: shell_split, shell_escape, find_source_file
"""
EDITOR_CALLBACKS :: Vector{Function}
A vector of editor callback functions, which take as arguments `cmd`, `path` and
`line` and which is then expected to either open an editor and return `true` to
A vector of editor callback functions, which take as arguments `cmd`, `path`, `line`
and `column` and which is then expected to either open an editor and return `true` to
indicate that it has handled the request, or return `false` to decline the
editing request.
"""
Expand All @@ -21,19 +21,20 @@ Define a new editor matching `pattern` that can be used to open a file (possibly
at a given line number) using `fn`.
The `fn` argument is a function that determines how to open a file with the
given editor. It should take three arguments, as follows:
given editor. It should take four arguments, as follows:
* `cmd` - a base command object for the editor
* `path` - the path to the source file to open
* `line` - the line number to open the editor at
* `column` - the column number to open the editor at
Editors which cannot open to a specific line with a command may ignore the
`line` argument. The `fn` callback must return either an appropriate `Cmd`
object to open a file or `nothing` to indicate that they cannot edit this file.
Use `nothing` to indicate that this editor is not appropriate for the current
environment and another editor should be attempted. It is possible to add more
general editing hooks that need not spawn external commands by pushing a
callback directly to the vector `EDITOR_CALLBACKS`.
Editors which cannot open to a specific line with a command or a specific column
may ignore the `line` and/or `column` argument. The `fn` callback must return
either an appropriate `Cmd` object to open a file or `nothing` to indicate that
they cannot edit this file. Use `nothing` to indicate that this editor is not
appropriate for the current environment and another editor should be attempted.
It is possible to add more general editing hooks that need not spawn
external commands by pushing a callback directly to the vector `EDITOR_CALLBACKS`.
The `pattern` argument is a string, regular expression, or an array of strings
and regular expressions. For the `fn` to be called, one of the patterns must
Expand All @@ -52,7 +53,7 @@ set `wait=true` and julia will wait for the editor to close before resuming.
If one of the editor environment variables is set, but no editor entry matches it,
the default editor entry is invoked:
(cmd, path, line) -> `\$cmd \$path`
(cmd, path, line, column) -> `\$cmd \$path`
Note that many editors are already defined. All of the following commands should
already work:
Expand Down Expand Up @@ -88,9 +89,14 @@ The following defines the usage of terminal-based `emacs`:
`define_editor` was introduced in Julia 1.4.
"""
function define_editor(fn::Function, pattern; wait::Bool=false)
callback = function (cmd::Cmd, path::AbstractString, line::Integer)
callback = function (cmd::Cmd, path::AbstractString, line::Integer, column::Integer)
editor_matches(pattern, cmd) || return false
editor = fn(cmd, path, line)
editor = if !applicable(fn, cmd, path, line, column)
# Be backwards compatible with editors that did not define the newly added column argument
fn(cmd, path, line)
else
fn(cmd, path, line, column)
end
if editor isa Cmd
if wait
run(editor) # blocks while editor runs
Expand All @@ -113,35 +119,50 @@ editor_matches(ps::AbstractArray, cmd::Cmd) = any(editor_matches(p, cmd) for p i

function define_default_editors()
# fallback: just call the editor with the path as argument
define_editor(r".*") do cmd, path, line
define_editor(r".*") do cmd, path, line, column
`$cmd $path`
end
define_editor(Any[r"\bemacs", "gedit", r"\bgvim"]) do cmd, path, line
`$cmd +$line $path`
# vim family
for (editors, wait) in [[Any["vim", "vi", "nvim", "mvim"], true],
[Any["\bgvim"], false]]
define_editor(editors; wait) do cmd, path, line, column
cmd = line == 0 ? `$cmd $path` :
column == 0 ? `$cmd +$line $path` :
`$cmd "+normal $(line)G$(column)|" $path`
end
end
define_editor("nano"; wait=true) do cmd, path, line, column
cmd = `$cmd +$line,$column $path`
end
# Must check that emacs not running in -t/-nw before regex match for general emacs
define_editor(Any[
"vim", "vi", "nvim", "mvim", "nano", "micro", "kak",
r"\bemacs\b.*\s(-nw|--no-window-system)\b",
r"\bemacsclient\b.\s*-(-?nw|t|-?tty)\b",
], wait=true) do cmd, path, line
# emacs (must check that emacs not running in -t/-nw before regex match for general emacs)
for (editors, wait) in [[Any[r"\bemacs"], false],
[Any[r"\bemacs\b.*\s(-nw|--no-window-system)\b", r"\bemacsclient\b.\s*-(-?nw|t|-?tty)\b"], true]]
define_editor(editors; wait) do cmd, path, line, column
`$cmd +$line:$column $path`
end
end
# Other editors
define_editor("gedit") do cmd, path, line, column
`$cmd +$line:$column $path`
end
define_editor(Any["micro", "kak"]; wait=true) do cmd, path, line, column
`$cmd +$line $path`
end
define_editor(["textmate", "mate", "kate"]) do cmd, path, line
define_editor(["textmate", "mate", "kate"]) do cmd, path, line, column
`$cmd $path -l $line`
end
define_editor(Any[r"\bsubl", r"\batom", "pycharm", "bbedit"]) do cmd, path, line
define_editor(Any[r"\bsubl", r"\batom", "pycharm", "bbedit"]) do cmd, path, line, column
`$cmd $path:$line`
end
define_editor(["code", "code-insiders"]) do cmd, path, line
`$cmd -g $path:$line`
define_editor(["code", "code-insiders"]) do cmd, path, line, column
`$cmd -g $path:$line:$column`
end
define_editor(r"\bnotepad++") do cmd, path, line
define_editor(r"\bnotepad++") do cmd, path, line, column
`$cmd $path -n$line`
end
if Sys.iswindows()
define_editor(r"\bCODE\.EXE\b"i) do cmd, path, line
`$cmd -g $path:$line`
define_editor(r"\bCODE\.EXE\b"i) do cmd, path, line, column
`$cmd -g $path:$line:$column`
end
callback = function (cmd::Cmd, path::AbstractString, line::Integer)
cmd == `open` || return false
Expand All @@ -157,7 +178,7 @@ function define_default_editors()
end
pushfirst!(EDITOR_CALLBACKS, callback)
elseif Sys.isapple()
define_editor("open") do cmd, path, line
define_editor("open") do cmd, path, line, column
`open -t $path`
end
end
Expand Down Expand Up @@ -186,23 +207,27 @@ function editor()
end

"""
edit(path::AbstractString, line::Integer=0)
edit(path::AbstractString, line::Integer=0, column::Integer=0)
Edit a file or directory optionally providing a line number to edit the file at.
Return to the `julia` prompt when you quit the editor. The editor can be changed
by setting `JULIA_EDITOR`, `VISUAL` or `EDITOR` as an environment variable.
See also [`define_editor`](@ref).
"""
function edit(path::AbstractString, line::Integer=0)
function edit(path::AbstractString, line::Integer=0, column::Integer=0)
path isa String || (path = convert(String, path))
if endswith(path, ".jl")
p = find_source_file(path)
p !== nothing && (path = p)
end
cmd = editor()
for callback in EDITOR_CALLBACKS
callback(cmd, path, line) && return
if !applicable(callback, cmd, path, line, column)
callback(cmd, path, line) && return
else
callback(cmd, path, line, column) && return
end
end
# shouldn't happen unless someone has removed fallback entry
error("no editor found")
Expand Down
23 changes: 20 additions & 3 deletions stdlib/REPL/src/LineEdit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1314,7 +1314,7 @@ function guess_current_mode_name(s)
end

# edit current input in editor
function edit_input(s, f = (filename, line) -> InteractiveUtils.edit(filename, line))
function edit_input(s, f = (filename, line, column) -> InteractiveUtils.edit(filename, line, column))
mode_name = guess_current_mode_name(s)
filename = tempname()
if mode_name == :julia
Expand All @@ -1325,9 +1325,26 @@ function edit_input(s, f = (filename, line) -> InteractiveUtils.edit(filename, l
buf = buffer(s)
pos = position(buf)
str = String(take!(buf))
line = 1 + count(==(_newline), view(str, 1:pos))
lines = readlines(IOBuffer(str); keep=true)

# Compute line
line_start_offset = 0
line = 1
while line < length(lines) && line_start_offset + sizeof(lines[line]) <= pos
line_start_offset += sizeof(lines[line])
line += 1
end

# Compute column
col = 0
off = line_start_offset
while off <= pos
off = nextind(str, off)
col += 1
end

write(filename, str)
f(filename, line)
f(filename, line, col)
str_mod = readchomp(filename)
rm(filename)
if str != str_mod # something was changed, run the input
Expand Down
9 changes: 4 additions & 5 deletions stdlib/REPL/test/repl.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1506,11 +1506,10 @@ fake_repl() do stdin_write, stdout_read, repl
end
repl.interface = REPL.setup_interface(repl)
s = LineEdit.init_state(repl.t, repl.interface)
LineEdit.edit_insert(s, "1234")
@show buffercontents(LineEdit.buffer(s))
input_f = function(filename, line)
write(filename, "123456\n")
LineEdit.edit_insert(s, "1234αβ")
input_f = function(filename, line, column)
write(filename, "1234αβ56γ\n")
end
LineEdit.edit_input(s, input_f)
@test buffercontents(LineEdit.buffer(s)) == "123456"
@test buffercontents(LineEdit.buffer(s)) == "1234αβ56γ"
end

0 comments on commit f2a2664

Please sign in to comment.