Closed mjcarman closed 4 weeks ago
:chaincommand()
means "the next word begins a new CMD command line". That doesn't seem relevant for text that comes after perl
.
I don't think :chaincommand()
is useful for whatever you're trying to do, but I'm not sure yet what it is that you're trying to make work.
What version of Clink are you using?
What wasn't working in "wrote parsers for some Perl scripts that I run via doskey
aliases"?
Can you share a sample that reproduces whatever problems were encountered?
(Also, the exec.path setting is what controls whether command completions include matches from the PATH.)
Are you trying to get the perl parser to show filename completions?
If so, then that would be :addarg(clink.filematches)
(not :chaincommand()
).
Are you trying to get the perl parser to show filename completions? If so, then that would be
:addarg(clink.filematches)
(not:chaincommand()
).
No. I'm trying to define completions for a custom Perl script the same way I would for any other command-line executable.
I'm using clink 1.6.20.
:chaincommand()
means "the next word begins a new CMD command line". That doesn't seem relevant for text that comes afterperl
.
That's fair enough with regard to the intent of :chaincommand()
. I'm using (abusing?) it for its "use another parser for the rest of the command" behavior.
Scenario:
doskey foo=perl C:\path\to\foo.pl $*
so I can easily run it from anywhere.require("arghelper")
clink.argmatcher("foo")
:_addexflags({
{ "--help", "Show built-in documentation" },
{ "--version", "Print version number and exit" },
{ "--verbose", "Toggle verbose output" },
})
Tangential, but important: I added ".PL" to my PATHEXT
environment variable (along with creating a file type for perl scripts and associating it with the perl executable). Also, my exec.aliases
setting is set to True
.
What I want to do is be able to type (e.g.) foo --v<ctrl+space>
and see --version
and --verbose
as suggestions. I can make that work by adding :chaincommand()
to my parser for perl
, but doing so means that typing something like perl b<tab>
(to complete perl bar.pl
(where bar.pl is in the CWD) instead offers completions for executables beginning with "b" anywhere on my PATH
.
The general case would be clink supporting chaining parsers for runtimes (perl, python, java, etc.) and scripts executed via those runtimes where the overall command might end up looking like this:
perl -Ilib -MMy::Module foo.pl --verbose abc.txt
Scenario:
- I wrote a script named "foo.pl" that takes some arguments.
- I created an alias to the script as
doskey foo=perl C:\path\to\foo.pl $*
so I can easily run it from anywhere.- I created a "foo.lua" file in my clink completions folder. e.g.
require("arghelper") clink.argmatcher("foo") :_addexflags({ { "--help", "Show built-in documentation" }, { "--version", "Print version number and exit" }, { "--verbose", "Toggle verbose output" }, })
Tangential, but important: I added ".PL" to my
PATHEXT
environment variable (along with creating a file type for perl scripts and associating it with the perl executable). Also, myexec.aliases
setting is set toTrue
.What I want to do is be able to type (e.g.)
foo --v<ctrl+space>
and see--version
and--verbose
as suggestions. I can make that work by adding:chaincommand()
to my parser forperl
, but doing so means that typing something likeperl b<tab>
(to completeperl bar.pl
(where bar.pl is in the CWD) instead offers completions for executables beginning with "b" anywhere on myPATH
.
Thank you for describing what you've tried, that helped very much.
I understand what you're trying to do now:
foo
to delay-load an argmatcher, but you want to delay-load logic to include doing a lookup of the doskey alias name before expanding it. Currently it only tries a lookup of the doskey alias itself if it couldn't find an argmatcher for the command inside the expanded doskey alias.scriptengine scriptname
to look up an argmatcher for the scriptname
.Full solution is at the bottom; it's small and simple.
- I created a "foo.lua" file in my clink completions folder. e.g.
That's the source of the problem.
If you move the "foo.lua" file to a normal Clink script directory so it's loaded at startup, instead of in a delay-load completions directory, then # 1 isn't needed, because the argmatcher is already loaded and so the delay-load lookup isn't even reached.
Or if you use the approach described further below for dealing with # 2, then # 1 isn't needed.
I can maybe consider exploring the possibility of adding another lookup step which tries using the doskey alias name before expanding it. But it would only partially address your needs, and it isn't needed at all if the following is used, which fully addresses your needs. (And I think it may break other things, so I wouldn't be in a hurry to experiment with it.)
Btw, here is a screenshot showing that if the argmatcher is loaded at startup, instead of delay-load, then the argmatcher for the doskey alias works how you wanted.
The general case would be clink supporting chaining parser for runtimes (perl, python, java, etc.) and scripts executed via those runtimes where the overall command might end up looking like this:
perl -Ilib -MMy::Module foo.pl --verbose abc.txt
For # 2 i.e. the "general case" you describe, that's much more problematic for Clink to do automagically. What about python foo.pl
or perl taskmgr.exe
and other nonsensical combinations of "chain command" input? You would have to somehow tell Clink what things are allowed as script names, to restrict chaining. To try to do that through the :chaincommand()
function would get very messy, very quickly.
Instead of trying to use :chaincommand()
and restricting it to become a different kind of feature, I would recommend to use clink.filematchesexact()
, the onlink
callback function, and clink.getargmatcher()
.
Try this:
First, insert these functions before the clink.argmatcher("perl")
line:
local function perl_scripts(word)
return clink.filematchesexact(word.."*.pl")
end
local function perl_onlink(link, arg_index, word) -- luacheck: no unused
if word then
local ext = path.getextension(word)
if ext and ext:lower() == ".pl" then
local argmatcher = clink.getargmatcher(word)
return argmatcher
end
end
end
Then, replace the :chaincommand()
line with this:
- :chaincommand()
+ :addarg({ perl_scripts, onlink=perl_onlink })
Thank you! This is definitely better than what I'd been doing. Is there a way to get filematchesexact()
to return directory names as partial matches the way filematches()
does so I can incrementally search for scripts in sub-directories? Maybe it would be better to use filematches()
and a filter instead, though it isn't clear to me how to hook one in or if I'd have to write a generator.
In onlink
, what's the difference between returning nil
and returning false
? Ideally, after clink sees a script name it should stop using the perl
argmatcher even if there isn't an argmatcher for the matched script. I tweaked your solution a bit and am using this at the moment. It works well enough for my purposes but I'm wondering if there's something better.
local argmatcher = clink.getargmatcher(word)
if argmatcher ~= nil then
return argmatcher
else
return file_matches
end
Thank you! This is definitely better than what I'd been doing. Is there a way to get
filematchesexact()
to return directory names as partial matches the wayfilematches()
does so I can incrementally search for scripts in sub-directories? Maybe it would be better to usefilematches()
and a filter instead, though it isn't clear to me how to hook one in or if I'd have to write a generator.
Use clink.dirmatches.
:addarg({ perl_scripts, clink.dirmatches, onlink=perl_onlink })
In
onlink
, what's the difference between returningnil
and returningfalse
?
I think you're asking what this means:
false
to override subsequent parsing and continue using the current argmatcher.I had to read the source code to make sure I understand what I meant when I wrote that text. I understand the general concept I had in mind, but I'm not entirely sure specifically what I intended to happen. Anyway, how it actually works is that returning false
or nil
do exactly the same thing. 🙄 I'll adjust the documentation accordingly.
Ideally, after clink sees a script name it should stop using the
perl
argmatcher even if there isn't an argmatcher for the matched script. I tweaked your solution a bit and am using this at the moment. It works well enough for my purposes but I'm wondering if there's something better.local argmatcher = clink.getargmatcher(word) if argmatcher ~= nil then return argmatcher else return file_matches end
Yes. The sample code I shared just demonstrated how to parse a word and then link to a specific argmatcher.
The tweak you shared will end up making perl script_with_no_argmatcher.pl
assume that the named script accepts one argument, which is a file name. Further arguments after that will pop back to the perl
argmatcher.
I wonder if you want something more like this:
local file_matches_loop = clink.argmatcher():addarg(clink.filematches):loop()
and
local argmatcher = clink.getargmatcher(word)
if argmatcher ~= nil then
return argmatcher
else
return file_matches_loop
end
The [:loop()
]https://chrisant996.github.io/clink/clink.html#_argmatcher:loop() will make it assume that the script accepts multiple file arguments.
If you want it to complete one file argument and then stop without popping back to the perl
argmatcher, then use :nofiles()
instead of :loop()
.
local argmatcher = clink.getargmatcher(word) if argmatcher ~= nil then return argmatcher else return file_matches end
Just a little note about Lua syntax:
That's a perfectly fine way to implement that "if ... else" logic.
It can also be shortened to the following, and behave exactly the same:
return clink.getargmatcher(word) or file_matches
Either way is perfectly fine, but Lua authors often use the more concise version. There is a small difference between them while stepping through code in the Lua debugger, though: the concise form gets executed as a single expression, so you lose the ability to single step through and inspect the value of argmatcher
partway through.
See 3.4.4 Logical Operators for details on the logical and
, or
, not
operators in Lua. Lua doesn't have quite the same "short circuit Boolean evaluation" as C/C++ do, but x and y or z
is essentially like x ? y : z
.
But a subtle but important note: x and y
evaluates to x
if x is true otherwise y
if x is false. It does NOT force the result to be true
or false
like C/C++ do.
x | y | x and y |
---|---|---|
true |
false |
false |
true |
true |
true |
true |
nil |
nil |
true |
"hello" |
"hello" |
false |
false |
false |
false |
true |
false |
false |
nil |
false |
false |
"hello" |
false |
nil |
false |
nil |
nil |
true |
nil |
nil |
nil |
nil |
nil |
"hello" |
nil |
"world" |
false |
false |
"world" |
true |
true |
"world" |
nil |
nil |
"world" |
"hello" |
"hello" |
Use clink.dirmatches.
:addarg({ perl_scripts, clink.dirmatches, onlink=perl_onlink })
Perfect .
how it actually works is that returning
false
ornil
do exactly the same thing. 🙄 I'll adjust the documentation accordingly.
That's what I saw when experimenting, which is why I asked.
I wonder if you want something more like this:
local file_matches_loop = clink.argmatcher():addarg(clink.filematches):loop()
Yeah, that's better. I threw in the non-looping file matcher as a quick test but hadn't really used it yet. Thanks for the suggestion.
Just a little note about Lua syntax [...]
LOL. When it comes to Lua I'm firmly in the "hack who doesn't know the idioms" category. After looking at the documentation you linked, Lua's and
and or
operators have the same behavior as Perl's so it's familiar to me, but not something I assume when coding in an unfamiliar language.
My scenario/use case: I wrote a simple parser for perl. Enjoying clink's support for command-line completion suggestions, I then wrote parsers for some Perl scripts that I run via
doskey
aliases. In order to get the latter working I added:chaincommand()
to my perl parser. That worked, but had the very undesirable side-effect of making completions for other perl scripts (ones not using doskey aliases) search my path for executables instead of searching the current working directory for files/directories. e.g. typingperl con<tab>
in a folder containing a script named "convert.pl" will suggestperl conhost.exe
instead of (really, before) "perl convert.pl."I experimented with the different
chaincommand
modes (particularlydoskey
) without success. I'm requesting a new "files" mode to allow chaining parsers but to otherwise act like the normal file/directory completion.There may well be a different way to do this that I just haven't been clever enough to figure out yet.