[ANN] TerminalPager.jl: REPL inline help added

I think that both displayed methods are valid. Similar to tuples, there can be a trailing comma after a non-empty argument list:

julia> abs(-1,)
1

Thanks, I didn’t know this. Hmm, I wonder whether this is a feature or an anti-feature. I understand why we have it for tuples and arrays as you might have your elements in separate lines and just want to add another line without caring whether it is the last line.
However, for callables, this only seem to make sense for varargs, which are the exception and not the norm, as otherwise you hardly add or remove some arguments.

Does anyone use this syntax in the REPL and would expect help for the single-argument method after writing the comma?

No I think if you’ve typed f('a', and then press TAB it’s reasonable for help to assume you want at least 2 arguments.

Definitely a feature. You want to be able to add and remove lines from calls like this:

foo(
    bar,
    baz,
)

Lua has the rule that you can’t have a trailing comma in function calls and it’s awful.

Besides, as the manual says

Tuples are an abstraction of the arguments of a function – without the function itself.

so it would be weird if their syntax were incompatible.

That’s not to say anything about what the REPL help mode should do, I don’t have a strong opinion on that.

2 Likes

The reason that the arguments are effectively a Tuple and if it is supported there, it should also be supported for callables, is a strong one.

Could you provide a real-life example for foo where you actually use it? As adding an element changes the method, I would really only expect it to be used for Varargs methods.

Most commonly when there are optional keyword args, which is perhaps less relevant for the help mode discussion (though keyword only-methods do exist). Also when iterating on my own code and signatures change (also less relevant for help mode).

1 Like

This is implemented in the newly released version 0.6.7 of TerminalPager.jl. So you will always get the extended help with <F1> and <alt> + <h>.

1 Like

Does this have to be enabled somehow, or have some known limitations? When I do using TerminalPager, then type IOContext (for example), and press Alt-h, it does nothing (this is on Windows, on both the Terminal app and on Wezterm). Pressing F1 for some reason types an A at that location.

I can open an issue if needed, just wanted to make sure I’m not misunderstanding something or running into known limitations.

It should be enabled after you loaded the package. We already had reports of working correctly on Windows, so it should work.

Is your text cursor above or at the very end of IOContext when you hit <Alt>+<h> (i.e. not after a trailing space)?

Which version of TerminalPager.jl do you currently use (TerminalPager |> pkgversion)?

1 Like

Alt+h not working was indeed a vesion issue. I had an older locally checked out version of TerminalPager added, so it hadn’t been updated with the other packages. Now after adding the registry version, Alt+h works great.

F1 still produces an A at the location (whether at the end of the text or in the middle of it), both on Wezterm and Terminal.

The Alt-h is a great usability improvement btw, a major step forward for the Julia REPL and makes TerminalPager.jl an even more essential part of the REPL for me. Thank you for adding it.

1 Like

Great that you have found and fixed the main issue. I am glad that you like the functionality.

Regarding <F1>: Could it be that your keyboard is sending the multimedia keycode instead of the <F1> keycode? Could you try using <Fn>+<F1> and report what this is doing?

I think for everyone with advanced typing skills, <alt>+<h> is the superior solution (at least on a Linux and Windows keyboard), but <F1> is the more common shortcut and therefore more discoverable. Therefore it would still nice to understand why that one is not working, although not strictly necessary.

It’s F1 on an external keyboard that does this (and Fn+F1 on the main laptop keyboard also leads to A). It seems to be a general Julia REPL thing btw, happens without TerminalPager loaded too. Searching it up, I found an old obscure issue Function keys i.e. F5 and F6 input random characters on windows console Ā· Issue #28799 Ā· JuliaLang/julia Ā· GitHub so it looks like this has been this way since Julia pre-1.0 days!

There’s a lot going on in julia/stdlib/REPL/src/LineEdit.jl at master Ā· JuliaLang/julia Ā· GitHub and I’m guessing somewhere in there, some part of the input codes generated for F1-F5 keys on Windows is getting gobbled up, leaving only an A (or B C D or E) behind - probably something unintentionally written with Linux assumptions early on.

1 Like

I’ve updated the issue #28799 with a bit more detail.

Meanwhile, I’ll see if F10 or something can be made to work on Windows as a temporary alternative until this is fixed - F6 and above don’t have this particular issue, but I don’t know if the REPL code is still processing them incorrectly in some other way, but I’ll give it a try sometime in the next week.

1 Like

Thanks for nailing it down to a general Julia Windows problem.
So this is beyond what we can fix in TerminalPager.jl.

Let’s see whether someone has fun (or is being payed for) fixing the problem for a proprietary system. I assume that escape_defaults and default_keymap might be good starting points.

I updated the issue thread on this, but basically it looks like this was fixed a few months ago (possibly inadvertently, while fixing pasting) and will work fine in the 1.13 Julia release.

Meanwhile, for 1.12, LTS, etc., I have this in my startup.jl to make F10 work like F1 was supposed to (since the original issue only affects F1-F5 keys, not the rest of them):

startup.jl
module Startup

# Configure F10 as pager-help key since F1 doesn't work on Windows on Julia 1.12 or lower
if isinteractive()
    F10_MAPPED::Bool = false
    # This section of the code is largely from BasicAutoloads.jl
    is_repl_ready() = isdefined(Base, :active_repl_backend) && isdefined(Base.active_repl_backend, :ast_transforms)
    function register_ast_transform_when_ready()
        iter = 0
        while !is_repl_ready() && iter < 120
            iter += 1
            sleep(0.02 * iter)
        end
        if is_repl_ready()
            push!(Base.active_repl_backend.ast_transforms, map_f10_pager)
        else
            @warn "Timed out waiting for `Base.active_repl_backend.ast_transforms` to become available. F10 for paged help will not work."
        end
    end

    function map_f10_pager(ast)
        if !F10_MAPPED && isdefined(Main, :TerminalPager) && Main.TerminalPager isa Module &&
           isdefined(Main.TerminalPager, :_show_pager_extended_help)
            LE = Base.REPL_MODULE_REF[].LineEdit
            main_modes = filter(Base.active_repl.interface.modes) do m
                m isa LE.Prompt && LE.prompt_string(m) in ["julia> ", "pager> "]
            end
            for m in main_modes
                escapes = m.keymap_dict['\e']
                escapes['[']['2']['1'] = Dict{Char,Any}()
                escapes['[']['2']['1']['~'] = Main.TerminalPager._show_pager_extended_help  # <F10>
            end
            global F10_MAPPED = true
        end
        return ast
    end

    @async register_ast_transform_when_ready()
end

end

Kinda hacky to use ast_transforms for this (among other internals), but that’s the best way I could think of and it seems to work reliably.

Version 0.6.8 of TerminalPager.jl adds an inline about functionality close in spirit to the inline help of this Discourse topic.

After loading both About.jl and TerminalPager.jl (e.g. in your startup) and positioning your REPL text cursor on any identifier you cannot only get help with <alt>+<h>, but you get the output from about with <alt>+<a>. It’s both shown in a pager, so you get back to where you were after pressing <q>.

If, for example, you want to use a lazy iterator, you might be tempted to enter Iterators followed by <alt>+<h> just to be disappointed by this return

search: Iterators Base.IteratorSize numerator iterate Base.IteratorEltype Base.Generator

  Methods for working with Iterators.

which creates appetite for more, but can’t satisfy it.

You could have entered Iterators. followed by <tab>, but then you get

julia> Iterators.

Accumulate                 Base                       Count                      Cycle
Drop                       DropWhile                  Enumerate                  Filter
Flatten                    IterableStatePairs         IterationCutShort          PartitionIterator
ProductIterator            Repeated                   Rest                       Reverse
Stateful                   Take                       TakeWhile                  Zip
_approx_iter_type          _diff_length               _flatten_iteratorsize      _flatteneltype
_min_length                _only                      _pairs_elt                 _pisdone
_piterate                  _piterate1                 _prod_axes1                _prod_eltype
_prod_indices              _prod_size                 _prod_size1                _promote_tuple_shape
_zip_any_isdone            _zip_isdone                _zip_iterate_all           _zip_iterate_interleave
_zip_iterate_some          _zip_lengths_finite_equal  _zip_min_length            accumulate
and_iteratoreltype         and_iteratorsize           approx_iter_type           convert
countfrom                  cycle                      doiterate                  drop
drop_iteratorsize          dropwhile                  enumerate                  filter
flatmap                    flatten                    flatten_iteratorsize       flatten_length
map                        only                       partition                  partition_iteratorsize
peel                       prod_iteratorsize          product                    repeated
reset!                     rest                       rest_iteratorsize          reverse
take                       take_iteratorsize          takewhile                  zip
zip_iteratoreltype         zip_iteratorsize

which

  • is hard to grasp due to the amount of information
  • contains a lot of internal helper functions which should rarely be useful to the user
  • does not show what is public API
  • will typically scroll at least parts or your previous session out of view.

I think it’s better to enter Iterators followed by <alt>+<a> to get this:

Module Base.Iterators

Re-exports 21 names (from Base):
• Iterators   • drop       • flatmap    • peel      • reverse    • Stateful
• accumulate  • dropwhile  • flatten    • product   • take     
• countfrom   • enumerate  • map        • repeated  • takewhile
• cycle       • filter     • partition  • rest      • zip

This does not even do it justice, as in the REPL it will be nicely formatted and colored.

You can, of course, use it on all the other things About.jl supports, which is basically everything in Julia.

Technically, I implemented it as a package extension for About.jl inside of TerminalPager.jl using a bit of glue code to connect the functionality of both.

Thanks to @tecosaur and to @Ronis_BR for creating great packages with About.jl and TerminalPager.jl, respectively, doing the majority of the work!

14 Likes

Thank you for the amazing work @PatrickHaecker ! TerminalPager.jl is way more useful now after your contributions!

2 Likes