Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Pane method that allows working with semantic zones #2968

Closed
piechologist opened this issue Jan 16, 2023 · 7 comments
Closed

Add Pane method that allows working with semantic zones #2968

piechologist opened this issue Jan 16, 2023 · 7 comments
Labels
enhancement New feature or request

Comments

@piechologist
Copy link

Is your feature request related to a problem? Please describe.
This enhancement would allow basic broadcasting. See issue #2658 and discussion #1130.

Describe the solution you'd like
I propose a function pane:get_current_input(). This is inspired by pane:get_logical_lines_as_text() and CopyMode { MoveBackwardSemanticZone = 'Input' }. The proposed function should return:

  1. the current command line buffer as a string, i.e. the text after the last prompt (as marked by the escape sequence \e]133;B\a). The string may contain multiple lines or may be empty. Leading and trailing whitespace should be trimmed. Note that the last prompt may have moved from the viewport into the scrollback (pane:get_logical_lines_as_text() returns the text from the viewport only).
  2. or nil if the alternate screen is active
  3. or nil if there's no prompt at all (no active shell integration or the prompt dropped out of the scrollback)

Describe alternatives you've considered
CopyMode offers the necessary actions but I couldn't figure out how to use it in a callback. I guess it's better left for interactive use as it's also visible in the GUI.

Additional context
The proposed function could be used:

  1. to send the currently composed command to other panes as discussed in Is there a way to broadcast key inputs to multiple panes? #1130, or
  2. to check if we are at a prompt and the command line is empty before attempting to paste text with pane:send_text().
@piechologist piechologist added the enhancement New feature or request label Jan 16, 2023
wez added a commit that referenced this issue Jan 19, 2023
@wez
Copy link
Owner

wez commented Jan 19, 2023

I've added a couple of new methods in main; it's not exactly what you asked for, but rather the building blocks from which you could build that.

For your use-case, I think you want to get the zone from the current cursor position:

local wezterm = require 'wezterm'

return {
  keys = {
    {key='e', mods='ALT', action = wezterm.action_callback(function(win, pane)
      local cursor = pane:get_cursor_position()
      wezterm.log_info("zones" , pane:get_semantic_zones())
      local zone = pane:get_semantic_zone_at(cursor.x-1, cursor.y)
      if zone then
        wezterm.log_info("zone is ", zone)
        local text = pane:get_text_from_semantic_zone(zone)
        wezterm.log_info("zone text is " .. text)
      end
    end)}
  }
}

Note that for me and my shell setup (which is using something slightly different from the shell integration script), my shell only shows me the Prompt zone without my current input. You may need to do a little bit of math based on that zone: for example, you could determine the zone, then pane:get_text_from_region(zone.end_x+1, zone.end_y, 0, zone.end_y+pane:get_dimensions().viewport_rows) to get the text just after it, or something like that.

It typically takes about an hour before commits are available as nightly builds for all platforms. Linux builds are the fastest to build and are often available within about 20 minutes. Windows and macOS builds take a bit longer.

Please take a few moments to try out the changes and let me know how that works out. You can find the nightly downloads for your system in the wezterm installation docs.

If you prefer to use packages provided by your distribution or package manager of choice and don't want to replace that with a nightly download, keep in mind that you can download portable packages (eg: a .dmg file on macOS, a .zip file on Windows and an .AppImage file on Linux) that can be run without permanently installing or replacing an existing package, and can then simply be deleted once you no longer need them.

If you are eager and can build from source then you may be able to try this out more quickly.

@piechologist
Copy link
Author

That's even better and more flexible! I added the following function to my helpers.lua and don't need to use the mouse for copying the last output anymore 🎉:

function module.copy_last_output(window, pane)
  local ozones = pane:get_semantic_zones('Output')
  local txt = pane:get_text_from_semantic_zone(ozones[#ozones])
  window:copy_to_clipboard(txt)
end

Secondly, it's now easy to reliably check if we are at a prompt and the command line is still empty:

local function can_i_paste(pane)
  if pane:is_alt_screen_active() then
    return false
  end
  local dims = pane:get_dimensions()
  local bottom = dims.physical_top + dims.viewport_rows - 1
  local pzones = pane:get_semantic_zones('Prompt')
  local zone = pzones[#pzones]
  if zone then
    local input = pane:get_text_from_region(zone.end_x+1, zone.end_y, dims.cols-1, bottom)
    if input == '' then
      return true
    end
  end
  return false
end

I'm still struggling with retrieving the command line though. I don't want to copy the input zone past the cursor because fish may append an autosuggestion. That's my attempt:

local function get_current_input(pane)
  if pane:is_alt_screen_active() then
    return nil
  end
  local cursor = pane:get_cursor_position()
  local zone = pane:get_semantic_zone_at(cursor.x-1, cursor.y)
  if zone and zone.semantic_type == 'Input' then
    wezterm.log_info('cursor:', cursor)
    wezterm.log_info('zone:', zone)
    local cmd = pane:get_text_from_region(zone.start_x, zone.start_y, cursor.x-1, cursor.y)
    wezterm.log_info('cmd: =='..cmd..'==')
    return cmd
  end
  return nil
end

I can't get pane:get_text_from_region() to extract just a part of a single line. Similar to your suggestion, getting a whole line works:

pane:get_text_from_region(zone.start_x, zone.start_y, 0, cursor.y+1)

Also, when writing a multi-line command and the cursor is on the last line (3), e.g.:

prompt> echo 'line 1
line 2
line 3'

calling pane:get_cursor_position() reports y as if the cursor was on line 1.

Is that behavior due to using logical lines?

@eugenesvk
Copy link
Contributor

Just bumped into this with some hyperlink parsing actions as I wanted to check whether a command line is empty before pasting another command and executing it.

Just out of curiosity, am I correct that it is impossible for the terminal to know the command line text without parsing output with the special semantic markers?

@wez
Copy link
Owner

wez commented Mar 16, 2023

At a fundamental level the only thing a terminal knows with certainty about a pane is that something will emit data as a byte stream that may have escape sequences in them. It doesn't really know for sure what program(s) might be running in the pane, whether they are local, or what the shape of that output might be.

Using semantic zones is the closest thing that we have to enable some basic understanding, and that requires a cooperating program to emit them in the right places.

Do you need to use semantic zones in your logic? No, but if you don't it will likely be a lot more complex and prone to false positives/negatives to try to scrape the terminal display to figure things out.

@eugenesvk
Copy link
Contributor

Ok, thanks for the clarification

@piechologist
Copy link
Author

I've added a couple of new methods in main; it's not exactly what you asked for, but rather the building blocks from which you could build that.

These methods have been working flawlessly for a year now. Thank you very much!

I guess we can close this issue?

Copy link
Contributor

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues. If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 12, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants