elvish
elvish copied to clipboard
A help system for the built-in commands
As rasied on the user group, I asked about the possibility of having the documentation for each command available via the command line, so we could type e.g. > help randint
and get the documentation that is already present in the go source code, e.g. https://github.com/elves/elvish/blob/master/pkg/eval/builtin_fn_num.go#L576
Currently this documentation is in markdown. The simplest solution would be to convert from markdown to plain text, resulting in something like (for this I used pandoc -t plain
):
elvdoc:fn randint
randint $low $high
Output a pseudo-random integer in the half-open interval [$low, $high).
Example:
~> # Emulate dice
randint 1 7
This would already be a big advance. Of course for cherries on top it would be nice if links could be preserved and code could get syntax highlighting. I do not know anything about go and how feasible this would be, I assume there are many markdown packages available...
We already have code written to extract the Elvish var and command documentation and convert it to HTML using pandoc
(see website/tools/md-to-html). Building on that to generate plain text embedded in the elvish binary and available to a hypothetical help
command should be straightforward.
Another approach is that taken by Fish. It converts its command documentation to "man" pages that are installed in a fish specific man dir. Fish then alters the MANPATH
env var so that directory is searched first. That leverages the mature man
ecosystem but has a couple of drawbacks. First, it requires installing not just the elvish
binary but also additional files. Second, it probably isn't (easily) portable to other platforms such as Windows or Plan 9. The first point could be considered a positive by anyone who wants the smallest possible Elvish binary, but that can also be addressed by a separate make target that doesn't generate the embedded help text.
More interesting questions involve the behavior of the hypothetical help
command. Ideally it would reflow text to match the terminal width but that could be omitted from the first implementation. Should it automatically present the text using a "pager" program like less
or simply assume most command help text is short enough that a pager won't usually add value? What should the output of help
be when invoked with no arguments? When invoked with a module name (e.g., help str:
) it should probably output a list of vars and commands in the module and nothing else. But if the output is automatically piped through a pager then something like the corresponding web reference page might be preferable.
Yes, please!
But also, being able to get just the command arguments would be really useful. Such as, randint $low? $high
or range &step=1 $low? $high
, as a simple reminder of command usage. Much of the time, that is all that is really needed.
As an added bonus, show that info automatically, in the place otherwise used for showing syntax errors.
Having an auto-popup of just the command line inputs would also be awesome. Perhaps that could be fleshed out as a separate feature once the help sources are available.
I suspect most help entries are nowhere near long enough to require a pager. help
alone should probably just give its own use and settings, a general introduction and link to the online docs.
Having an auto-popup of just the command line inputs would also be awesome. Perhaps that could be fleshed out as a separate feature once the help sources are available.
I'm reasonably certain any user could write this in elvish w/ clever use of the edit module, but it would only work for normal elvish functions.
Actually, would it be a better approach to attach the metadata we normally get with elvish fns (eg arg-names
, body
, def
, etc) to the builtins? Then add an optional docstring
key-value pair which normal users could also provide for their own functions?
Actually, would it be a better approach to attach the metadata we normally get with elvish fns (eg arg-names, body, def, etc) to the builtins? Then add an optional docstring key-value pair which normal users could also provide for their own functions?
Yes! I have wanted that for ages, but didn't ask because I was afraid of asking too much, maybe. But he who doesn't ask, doesn't get …
@crinklywrappr and @hanche: Supporting introspection of builtins has already been requested, see #680.
@hanche and @crinklywrappr, I decided to start working on this. Introspection of builtins as requested by issue #680 might be useful for providing a simple one line command "usage" help text. But it is inadequate as a mechanism for resolving this issue since we also want to be able to provide a detailed explanation of the variable or command. And that is only possible by parsing the //elvdoc:
comments in the source code. Also, obviously, issue #680 is not resolved. Whereas there is an established mechanism for extracting //elvdoc:
comments from the source code. Which can be leveraged to provide both a one-liner help text and a more detailed help text.
See issue #738 for some important background regarding the current situation.
I was hoping to leave the text in Markdown format and have Elvish directly render the Markdown. However, importing https://github.com/MichaelMure/mdr more than doubled the size of the elvish binary: 9.3MB to 22MB on my primary development server. I also tried https://pkg.go.dev/github.com/quackduck/go-term-markdown which has been updated recently. It was almost as bad in terms of the increase in the size of the Elvish binary at just 1MB smaller than the previous package. Clearly, it isn't going to be possible to have Elvish directly translate Markdown to the terminal since we can't accept such huge increases in the size of the Elvish binary. 😞
If we want to retain some formatting the obvious alternative is the legacy UNIX "man" format. That, however, won't work on Windows unless MSYS2 is installed (and it's not clear we can get it to work even then). I'm also not happy with having to transcode from Markdown to the ancient Man syntax. Not to mention dealing with the differences in command line switches and behavior of the various man
commands on BSD, AT&T, and GNU/Linux distros. Which is why I'd prefer a solution that doesn't involve shelling out to an external command.
A plain text solution as described in the opening comment isn't very difficult. But I would hope we could do better. We could use HTML but that would require the user install a CLI command like lynx
to render the text. Which is no better, and arguably worse, than the man
file format since we can reasonably expect a man
command to be installed (albeit not on Windows).
I considered using ReStructuredText, either on the fly using Pandoc or permanently. Note that the Fish shell (another popular non-POSIX shell) uses it as its preferred format. However, AFAICT there is no actively maintained Go ReStructuredText (RST) package that would be suitable for our needs. The Fish shell project relies on the Sphinx project for its support of ReStructuredText but doesn't seem like a good fit for this project.
So I'm wondering what other people think about how best to expose the existing Markdown based variable and command documentation interactively. Should we start with the modest goal of exposing the documentation as plain text without regard to the actual size of the user's terminal?
I have a couple of ideas, both of which may be dumb.
-
You can sidestep the problem of binary size by releasing a plug-in e.g. https://github.com/elves/sample-plugin
-
You can echo raw markdown to the terminal and instruct users to pipe the text to their markdown renderer of choice.
The second approach is appealing to me, personally. It's begging for other people to promote elvish in their personal projects.
@crinklywrappr, Thanks for the feedback. Plugins require Cgo and don't work on Windows. So that is not a viable solution. Requiring the user to feed the help
output through another program to render it is, shall we say, not friendly. 😄
I was hoping to avoid hardcoding the line length and highlighting. That way the output could be dynamically adapted to the terminal width and whether a light or dark color scheme is desired. But I think for the first implementation I'll simply generate text with a fixed line length (80 chars) and hardcoded highlighting escape sequences based on the current Elvish editor (basically, the highlighting you see in the elv.sh documentation).
Something not already discussed is what should be the result of help unknown-command
. Should it explicitly fail? Should it run man unknown-command
? Something else?
Documentation for command not found?
I think help unknown-command
should just print a (possibly styled red or something) text saying the command is unknown, rather than try to be super helpful and run man
. Once you go down that route, why not info
?
And it should not raise an error either. The resulting noise is just annoying, and it is primarily for interactive use anyhow, not for use in a program.
But it might be useful to have an option to make it output the styled help text to the value stream, in case some user wants to do something creative with it. And if that option is given, it should raise an error if the command is not known.
I've been chipping away at implementing a help
command by focusing on the transformation of Github flavored Markdown to raw text suitable for display in a terminal using ANSI X3.64 escape sequences. I was sort of happy with the results of adding a "raw" option to the website/cmd/elvdoc and website/cmd/highlight programs. Having found https://github.com/Orange-OpenSource/pandoc-terminal-writer to handle other Markdown constructs I'm very excited that the help
command will produce really good, rather than good enough, output.
@hanche, I'm ambivalent about special-casing help unknown-command
to not raise an exception and instead simply write an error message to stderr. Doing so is arguably the more user friendly option but is contrary to how every other command behaves that reports an invalid set of arguments.
P.S., I'm also inclined to implement a search option similar to man -k
in the initial help
command implementation. I'm still mulling over whether that behavior should be the default if the string doesn't match a known Elvish variable or function or require an option to enable it. Obviously, there should be an option to enable searching the documentation for matching a string. The question is whether the lack of an exact match of a variable or function should automatically enable searching the documentation. I'm inclined to not enable searching the documentation if an exact match is not found but am ambivalent.
What do you think about using the edit module to make the help searchable & scrollable? Maybe bind to Ctrl+h?
What do you think about using the edit module to make the help searchable & scrollable? Maybe bind to Ctrl+h?
That is a good question and proposal but outside the scope of simply implementing a help
command that can be used like this: help randint
or help &text=random
. Once the core data structures are implemented and a CLI help
command is available it is reasonable to extend the use of that information into a new "help mode" analogous to the "location" and "navigation" modes.
@krader1961 wrote:
I'm ambivalent about special-casing
help unknown-command
to not raise an exception and instead simply write an error message to stderr. Doing so is arguably the more user friendly option but is contrary to how every other command behaves that reports an invalid set of arguments.
Yes but … Elvish is a programming language as well as an interactive shell. All those other commands can and will be used in elvish programs, and so it is essential that they raise an exception when something goes wrong. The help
command, on the other hand, is purely interactive. It really does not make much sense to include it in a larger program. And so, I don't see the need for it to raise an exception if you ask it for a help text that does not exist. (Or, if I am wrong and there is such a need, it could come with an &err
option that will cause it to raise exceptions in this case. This should very rarely be needed, if ever.) If you use the command wrongly, for example, if you give it an option that it does not recognise, or too many arguments, or arguments of the wrong type, that is different. By all means, let it raise an exception in such cases.
By the way, it might be a good idea, once a help
command exists, to allow giving it no arguments at all, in which case it could provide some basic information for new users to help them get going.
I am under the impression the help
command is non-interactive.
I am under the impression the help command is non-interactive.
It's interactive in the sense the output is meant to be displayed on a terminal and read by a human. It is not expected that the output will be piped through any other Elvish command. Do you have some ideas for how the help
command would be used in a pipeline (other than possibly piping its output into a pager like the less
command) or in combination with other Elvish commands?
Do you have some ideas for how the help command would be used in a pipeline
I had the idea to pipe the builtin ns vars to fzf
, and display the help
result on the right.
I had the idea to pipe the builtin ns vars to fzf, and display the help result on the right.
That sounds as if it is equivalent piping the output into a pager; e.g., help compare | less
. Which is still interactive in the sense that the output is displayed in a terminal to be read by the user. As opposed to being processed by any of the other Elvish commands.
Yeah, I'm sure whatever you come up with will be great. I'm excited! 😄
Okay, I've got a help
implementation I'm finally happy with. There's still some work to do (mostly unit tests) but I think it's ready for testing by other people. You can build from https://github.com/krader1961/elvish/tree/issue-1432-help-command if you want to try it. Or contact me on the IM channels and I'll provide a binary for your platform if I can. The screenshot shows three ways to use the new help
command.
![Screen Shot 2022-07-10 at 18 52 25](https://user-images.githubusercontent.com/7545917/178176198-d212deac-a534-44a7-aaf6-74a4e16c5ef8.png)
Love it!
With the help epm:
example, is that tab completion or did you press <enter>
?
With the help epm: example, is that tab completion or did you press
?
I pressed Enter to actually execute the help
command. It should be straightforward to implement tab completion but I feel that should be a separate change since this is already huge (over 1100 lines if you exclude the generated documentation.go and over 2000 lines if you don't).
FWIW, I previously asked this question: Something not already discussed is what should be the result of help unknown-command.
I decided to treat that as if the &search=$true
option was used; i.e., perform an implicit search for functions/variables whose documentation includes the string. For example, help NoSuchText
is equivalent to help &search NoSuchText
(assuming, of course, that NoSuchText
is not a function or variable in the documentation).
Note that @xiaq wants a different solution than @krader1961's nice pull request, so it seems we will have to wait longer for a working help system. At least as an end user, @krader1961's system worked wonderfully, I used it daily, and I hope waiting for a "better" architecture will not sink this into the indefinite future…
Here is @xiaq's planning comment:
https://github.com/elves/elvish/pull/1583#issuecomment-1257311073
FWIW, I am cogitating on @xiaq's criticism of my pull-request. I disagree with the complaint about the added dependency on the glow
command since it only requires the go
command which is already a dependency to compile Elvish. However, it seems like the core complaint vis-a-vis the glow
dependency, is that you can't just do go install ....
. That should be easy to solve.
Regarding @xiaq's second point that my change doesn't address Elvish documentation from third-party modules that was explicitly a non-goal to keep the initial change a reasonable size. I also fail to see how implementing that capability solves the problem of rendering the //elvdoc:
comments embedded in the source for the builtins since there is no requirement a user has the Elvish source installed. My intent was to follow up my first change with another one that introduced a command for the user to run that would generate something like a ~/.local/share/elvish/elvdoc.json file the builtin help
command would incorporate in its output.
@xiaq also said "To be able to do 1, the hard part is teaching Elvish to render Markdown in the terminal." I wanted to do that because it would make it possible to respect the user's terminal width. However, as I noted in this comment the increase in size of the Elvish binary was prohibitive. I don't think trying to create a Markdown renderer limited to the current requirements of the Elvish builtins is
a) likely to result in significant decreases in the size of the Elvish binary due to supporting run-time rendering of Markdown, and
b) doing so means we're always going to have to deal with modifying the Elvish specific Markdown renderer to fix bugs and add support for Markdown features used by builtin documentation and third-party documentation.
The latter point seems like a good argument for not adding additional code to Elvish that is not central to its purpose. Rendering Markdown is something that many other projects, like glow
, have already solved and are actively maintained. It's not clear why Elvish should take on the burden of maintaining its own Markdown rendering code since that capability is not central to its behavior.
The argument my original change doesn't allow you to just run go build ./cmd/elvish
(or the equivalent go install
) can be solved in two ways:
a) Stub the variables fnHelpDocs
and varHelpDocs
so that a normal make get
only needs to generate code that populates those vars rather than define them.
b) Commit the artifacts created by make pkg/help/documentation.go
into the repository.
I was going to implement solution (a) but am leaning towards (b). Yes, option (b) means there could be drift between the pre-generated documentation and the actual documentation if people commit changes that change the documentation without running make pkg/help/documentation.go
. But it should be possible to guard against that via a CI step that verifies make pkg/help/documentation.go
doesn't change the relevant artifacts. The question is how to make it easy for contributors to generate the documentation artifacts and include them in their commits without having to wait for the CI environment to tell them their change has problems and they need to regenerate the documentation. In other words, adding support for the help
command shouldn't make it harder, or at least more error prone from a CI workflow perspective, for people to change the documentation of embedded commands.