/
githubmirror
/
julia
Обзор
Документация
Войти
/
githubmirror
/
julia
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
stdlib/InteractiveUtils/src/editless.jl
372 строки
12 KB
Jeff Bezanson
uniform methods for `edit` and `less` (#62020)
24 июл 2026, 02:17
Не верифицирован
24 июл 2026, 02:17
ed07f2e
Код
Авторство
О чём код?
# This file is a part of Julia. License is MIT: https://julialang.org/license # editing and paging files 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`, `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. """ const EDITOR_CALLBACKS = Function[] """ define_editor(fn, pattern; wait=false) 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 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 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 match the value of `EDITOR`, `VISUAL` or `JULIA_EDITOR`. For strings, the string must equal the [`basename`](@ref) of the first word of the editor command, with its extension, if any, removed. E.g. "vi" doesn't match "vim -g" but matches "/usr/bin/vi -m"; it also matches `vi.exe`. If `pattern` is a regex it is matched against all of the editor command as a shell-escaped string. An array pattern matches if any of its items match. If multiple editors match, the one added most recently is used. By default julia does not wait for the editor to close, running it in the background. However, if the editor is terminal based, you will probably want to 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, column) -> `\$cmd \$path` Note that many editors are already defined. All of the following commands should already work: - emacs - emacsclient - vim - nvim - nano - micro - kak - helix - textmate - mate - kate - subl - atom - notepad++ - Visual Studio Code - open - pycharm - bbedit # Examples The following defines the usage of terminal-based `emacs`: define_editor( r"\\bemacs\\b.*\\s(-nw|--no-window-system)\\b", wait=true) do cmd, path, line, column `\$cmd +\$line:\$column \$path` end !!! compat "Julia 1.4" `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, column::Integer) editor_matches(pattern, cmd) || return false 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 else run(pipeline(editor, stderr=stderr), wait=false) end return true elseif editor isa Nothing return false end @warn "invalid editor value returned" pattern=pattern editor=editor return false end pushfirst!(EDITOR_CALLBACKS, callback) end editor_matches(p::Regex, cmd::Cmd) = occursin(p, shell_escape(cmd)) editor_matches(p::String, cmd::Cmd) = p == splitext(basename(first(cmd)))[1] editor_matches(ps::AbstractArray, cmd::Cmd) = any(editor_matches(p, cmd) for p in ps) function define_default_editors() # fallback: just call the editor with the path as argument define_editor(r".*") do cmd, path, line, column `$cmd $path` end # vim family for (editors, wait) in [ [["vim", "vi", "nvim", "mvim"], true], [[r"\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 # emacs (must check that emacs not running in -t/-nw # before regex match for general emacs) for (editors, wait) in [ [[r"\bemacs"], false], [[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(["micro", "kak"]; wait=true) do cmd, path, line, column `$cmd +$line $path` end define_editor(["hx", "helix"]; wait=true) do cmd, path, line, column `$cmd $path:$line:$column` end define_editor(["textmate", "mate", "kate"]) do cmd, path, line, column `$cmd $path -l $line` end define_editor([r"\bsubl", r"\batom", "pycharm", "bbedit"]) do cmd, path, line, column `$cmd $path:$line` end define_editor(["code", "code-insiders"]) do cmd, path, line, column `$cmd -g $path:$line:$column` end 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, column `$cmd -g $path:$line:$column` end callback = function (cmd::Cmd, path::AbstractString, line::Integer) cmd == `open` || return false # don't emit this ccall on other platforms @static if Sys.iswindows() result = ccall((:ShellExecuteW, "shell32"), stdcall, Int, (Ptr{Cvoid}, Cwstring, Cwstring, Ptr{Cvoid}, Ptr{Cvoid}, Cint), C_NULL, "open", path, C_NULL, C_NULL, 10) systemerror(:edit, result ≤ 32) end return true end pushfirst!(EDITOR_CALLBACKS, callback) elseif Sys.isapple() define_editor("open") do cmd, path, line, column `open -t $path` end end end define_default_editors() """ editor() Determine the editor to use when running functions like `edit`. Returns a `Cmd` object. Change editor by setting `JULIA_EDITOR`, `VISUAL` or `EDITOR` environment variables. """ function editor() # Note: the editor path can include spaces (if escaped) and flags. for var in ["JULIA_EDITOR", "VISUAL", "EDITOR"] str = get(ENV, var, nothing) str === nothing && continue isempty(str) && error("invalid editor \$$var: $(repr(str))") return Cmd(shell_split(str)) end editor_file = "/etc/alternatives/editor" editor = (Sys.iswindows() || Sys.isapple()) ? "open" : isfile(editor_file) ? realpath(editor_file) : "emacs" return Cmd([editor]) end function find_source(path::AbstractString) path isa String || (path = convert(String, path)) if endswith(path, ".jl") p = find_source_file(path) p !== nothing && return p end return path end """ 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. !!! compat "Julia 1.9" The `column` argument requires at least Julia 1.9. See also [`InteractiveUtils.define_editor`](@ref). """ function edit(path::AbstractString, line::Integer=1, column::Integer=1) path = find_source(path) cmd = editor() for callback in EDITOR_CALLBACKS 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") end # terminal pager if Sys.iswindows() function less(file::AbstractString, line::Integer) file = find_source(file) pager = shell_split(get(ENV, "PAGER", "more")) if pager[1] == "more" g = "" line -= 1 else g = "g" end run(Cmd(`$pager +$(line)$(g) \"$file\"`, windows_verbatim = true)) nothing end else function less(file::AbstractString, line::Integer) file = find_source(file) pager = shell_split(get(ENV, "PAGER", "less")) run(`$pager +$(line)g $file`) nothing end end """ less(file::AbstractString, [line::Integer]) Show a file using the default pager, optionally providing a starting line number. Returns to the `julia` prompt when you quit the pager. """ less(file::AbstractString) = less(file, 1) # methods shared by edit and less """ edit(f, [types]) Edit the definition of a function, optionally specifying a tuple of types to indicate which method to edit. edit(::Method) Edit the definition of a method by specifying a method object. edit(f, n::Integer) Edit the nth method in `methods(f)`. edit(argtypes::Union{Tuple, Type{<:Tuple}}) Edit a method specified as a tuple of types or Tuple type. edit(::Module) Edit the main source file of a module. The module must be loaded with `using` or `import` first. edit(::Module, ::Symbol) Edit the definition of a documented global variable or type. !!! compat "Julia 1.1" `edit` on modules requires at least Julia 1.1. To ensure that the file can be opened at the given line, you may need to call `InteractiveUtils.define_editor` first. """ function edit end """ less(arguments...) Show a definition using the default pager. Accepted arguments are the same as for [`edit`](@ref). """ function less end let _editless = Union{typeof(edit), typeof(less)} function (el::_editless)(@nospecialize f) ms = methods(f).ms length(ms) == 1 && el(functionloc(ms[1])...) length(ms) > 1 && return ms length(ms) == 0 && functionloc(f) # throws nothing end (el::_editless)(m::Method) = el(functionloc(m)...) (el::_editless)(@nospecialize(f), idx::Integer) = el(methods(f).ms[idx]) (el::_editless)(f, t) = (@nospecialize; el(functionloc(f, t)...)) (el::_editless)(@nospecialize argtypes::Union{Tuple, Type{<:Tuple}}) = el(functionloc(argtypes)...) (el::_editless)(file::Nothing, line::Integer) = error("could not find source file for function") function (el::_editless)(m::Module) path = pathof(m) path === nothing && error("could not find source file for module: $m") el(path) end (el::_editless)(m::Module, n::Symbol) = el(varloc(m, n)...) end # find where a (documented) global is defined using the doc system function varloc(mod::Module, name::Symbol) m = Base.Docs.meta(mod) if m !== nothing bnd = Base.Docs.Binding(mod, name) if haskey(m, bnd) docstr = m[bnd] if docstr isa Base.Docs.MultiDoc length(docstr.docs) != 1 && @goto err docstr = only(docstr.docs).second end if docstr isa Base.Docs.DocStr mm = docstr.data if haskey(mm, :path) && haskey(mm, :linenumber) p = mm[:path] return (something(find_source_file(p), p), mm[:linenumber]) end end end end @label err error("could not determine location of variable $mod.$name") end