2

My goal is to write a function which behaves similarily to what happens when invoking PsReadline's Ctrl-R functionality: insert text on the current command line, but don't accept the line, so that when the function returns the cursor is still on the same line. I tried the following

function Invoke-Foo() {
  [Microsoft.PowerShell.PSConsoleReadLine]::Insert('foo')
}

But when calling this, the result is:

PS> Invoke-Foo foo
PS> <cursor position>

What I want instead if that after inserting the text, the cursor stays on that line so

PS> Invoke-Foo foo<cursor position>

I looked into other PSConsoleReadLine functions (as I assume they are the way to go since PsReadline handles the console?) like AddLine/CancelLine/... but none of the combinations do the trick. What does work is calling the function as a PsReadline key handler using Set-PSReadlineKeyHandler -Key 'Ctrl+P' -ScriptBlock {Invoke-Foo} then hitting Ctrl-P does exactly what I want. So the question is probably: how to hook into PsReadline to mimic a function being called as it's key handler?

update Solution is fairly simple, thanks to Jason for the tip: just bind Enter, and don't accept the line if Invoke-Foo is being called

function DealWithEnterKey() {
  $line = $null
  $cursor = $null
  [Microsoft.PowerShell.PSConsoleReadline]::GetBufferState([ref]$line, [ref]$cursor)

  if($line -match 'Invoke-Foo') {
    Invoke-Foo
  } else {
    [Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()
  }
}

Set-PSReadlineKeyHandler -Key 'Enter' -ScriptBlock {DealWithEnterKey}
stijn
  • 34,664
  • 13
  • 111
  • 163
  • I'm not sure I understand the use case. Do you enter `Invoke-Foo`, hit Enter, and then want PowerShell to execute `Invoke-Foo` in such a way that the existing line becomes `Invoke-Foo foo`? What should happen if you hit Enter again -- do you get `Invoke-Foo foo foo`? It seems like a convoluted way to do custom argument completion, for which there are better solutions. – Jeroen Mostert May 03 '17 at 10:00
  • @JeroenMostert I kept the example as simple as possible, in practice I want to enter `Invoke-Foo`, PS executes it (which will call fzf.exe underneath allowing to select a directory or file) and the existing line becomes just the selected file/dir, so I can edit it further (e.g. insert 'cd' or 'cat' in front of it). So yes I could just use a function like `Invoke-Foo -Command { cd $_ }`, but since I saw Ctrl-R or any other PSReadLine keyhandler basically does what I want (edit commandline in-place) I thought maybe there's a way to have a function do that as well. – stijn May 03 '17 at 10:14
  • So you're really after a function that produces a *new* command line, ready for further editing, and it would be neat if it did that by replacing itself? Presumably you only care about the case where it exists on its own then? Because making cases like `Copy-Item Invoke-Foo Invoke-Foo` work would require altering the parser. – Jeroen Mostert May 03 '17 at 10:20
  • indeed, a new commandline which can be edited at will. – stijn May 03 '17 at 10:22
  • Seems very hard. The problem is not simply mimicking a key handler, because your function is actually already parsed and being executed. You're asking for a PowerShell function to manipulate the input buffer of a prompt that doesn't exist yet. If this is possible at all, it'll require some nasty groveling in internals. ...how was this better than setting a key handler again? Do you enjoy typing that much? :-) – Jeroen Mostert May 03 '17 at 10:33
  • Well there's also the input buffer of the current prompt which does exist and can easily be modified, it's just that hitting Enter also moves it to the next prompt which is unwanted in this case. Anyway, it's not per se better, but I do find it easier to memorize commands/aliases than keyhandlers. – stijn May 03 '17 at 11:34

1 Answers1

1

You'll need to either use a custom key handler or simulate keyboard input I think.

I suggest rebinding Enter. If the command line matches your command, run your command and replace the command line. If not, just call the normal handler for Enter.

Jason Shirk
  • 7,734
  • 2
  • 24
  • 29
  • Might work indeed; did you try it and/or do you have sample code? – stijn May 03 '17 at 18:44
  • Look at SamplePSReadlineProfile.ps1 in your PSReadline module directory or on GitHub. I included different samples that cover your scenario, look for AcceptLine and GetBufferState to give you some ideas. – Jason Shirk May 03 '17 at 21:51
  • Thanks, it was indeed as simple as rebinding Enter. And that SamplePSReadlineProfile was also a good read. – stijn May 04 '17 at 08:08