wez / wezterm

A GPU-accelerated cross-platform terminal emulator and multiplexer written by @wez and implemented in Rust
https://wezfurlong.org/wezterm/
Other
17.87k stars 801 forks source link

Change color of tab depending on pane feature #647

Closed jankatins closed 3 years ago

jankatins commented 3 years ago

Is your feature request related to a problem? Please describe.

I use toolbox to run certain things at work and it looks more or less the same as my regular account. I do use starship to change the prompt, but it would be amazing if the tab could also indicate that this tab runs in toolbox.

Describe the solution you'd like It would be amazing if wezterm could change to color/add a prefix of the tab based on the tab title or an env variable or a file or so (the actual flag is the existence of a file but I think I can set a env variable or use a title command to change the tab title).

Describe alternatives you've considered As far as I understand the config (https://wezfurlong.org/wezterm/config/lua/config/tab_bar_style.html) does not allow for context based styling of specific panes.

One alternative is using the right status, as pane:get_title() exists. But that doesn't show non-active tab(s) which run in that environment.

jankatins commented 3 years ago

Tab title would not completely work: the title is set before the command is run, so in my usecase, it would not work as only after pressing enter within the new environment, it would show the correct thing. See #711 where you can see the empty prompts which I used to let the title show the right char.

wez commented 3 years ago

nightly builds now have a format-tab-title event that you can use to format individual tab titles using the same FormatItem syntax used in wezterm.format.

jankatins commented 3 years ago

@wez Works as intended, thank you! :-)

Is there any way to get an environment variable from the current active pane when in format-tab-title?

wez commented 3 years ago

inspecting the environment doesn't have a defined interface; the pane may not be local and the foreground process may not be the one that the terminal started. If you know that everything is local you could potentially snoop around in something like the proc filesystem if you're on linux.

There's an escape sequence that iTerm2 defined that wezterm is technically able to parse, but doesn't do anything with:

https://github.com/wez/wezterm/blob/main/termwiz/src/escape/osc.rs#L821-L825

I haven't done anything with these so far, but it seems like this might be the thing you need for your use-case; you could have your shell emit something like this, but derived from the environment that you want to act upon:

printf "\e]1337;SetUserVar=%s=%s\a" NAME $(echo VALUE | base64)

then wezterm could add the dictionary of user vars as an available item in PaneInformation

jankatins commented 3 years ago

Another thing: if you do a different color on hover, this cannot be done from the current format-tab-title, as it misses the hover information. E.g.

return {
...
      -- You can configure some alternate styling when the mouse pointer
      -- moves over inactive tabs
      inactive_tab_hover = {
        bg_color = "#3c1361",
        fg_color = "#909090",
        --italic = true,
        intensity = "Bold",
        underline = "Single",
}
wezterm.on("format-tab-title", function(tab, tabs, panes, config)
  if tab.is_active then
    --return "! " .. tab.active_pane.title
    return {
      {Background={Color="red"}},
      {Text="! " .. tab.active_pane.title},
    }
  end
  return {
      {Background={Color="green"}},
      {Text="* " .. tab.active_pane.title},
    }
end)

-> will show the inactive part green, but on hover will switch to underlined text, but not to the purple-ly colors defined in inactive_tab_hover.

wez commented 3 years ago

I was hoping you wouldn't ask about hover :-p. To support that, we'll need to call the event one extra time for the hovered tab. I'll add that :)

jankatins commented 3 years ago

then wezterm could add the dictionary of user vars as an available item in PaneInformation

This sounds amazing! (I would add a postcmd to zsh, which would send this :-))

jankatins commented 3 years ago

Re hover: actually I'm fine with the current state (my hoover is only making it underline), but just wanted to state it for completeness :-)

jankatins commented 3 years ago

One more comment: it seems strange that the tab_bar_style thingies are drawn around a format-tab-title which contains format information. The result is something like this:

image

I can work around that, by basically making all old values be nothing and really do everything in the event, but this looks a bit messy.

-> maybe don't use the tab-bar-style if the returned value is a table (=has formatting) or expose the default formatting via the "old" mechanism as a lua functions so one could call that as a fallback (and the default format-tab-title would be just delegating it to this func)?

There also seems something in the code which adds spaces to too short tab titles, which again use the old mechanism.

wez commented 3 years ago

re: the tab bar style decoration, my thinking was that more casual users would like to focus on having format-tab-title produce just the label portion. If you want more precise control, I think setting the relevant pieces of tab_bar_style to empty strings is reasonable.

If we think that most people using format-tab-style just don't want tab_bar_style, then maybe a simpler end state is to remove tab_bar_style and have folks migrate to format-tab-title instead?

wez commented 3 years ago

With the most recent commit you can do:

printf "\e]1337;SetUserVar=%s=%s\a" foo $(echo -n value | base64)
wezterm.on("format-tab-title", function(tab, tabs, panes, config, hover)
   local foo = tab.active_pane.user_vars.foo or "";
   return {
      {Text=foo..tab.active_pane.title},
   }
end)
jankatins commented 3 years ago

Nice, I have now converted my setup to this (will add more colors for toolbox + hover later, my style foo is weak...):

-- https://wezfurlong.org/wezterm/config/
local wezterm = require 'wezterm';

-- The powerline </> symbol
local LEFT_ARROW = utf8.char(0xe0b3);
local SOLID_LEFT_ARROW = utf8.char(0xe0b2);
local SOLID_RIGHT_ARROW = utf8.char(0xe0b0);

local color_tabbar_background = "#0b0022"
-- background, forground, intensity, underline
local color_tab_inactive={"#3c1361", "#808080", "Bold", "None"};
local color_tab_hoover={"#3c1361", "#909090", "Bold", "Single"};
local color_tab_active={"#52307c", "#c0c0c0", "Bold", "None"};
-- meh: chars and width are only broadly related...
local tab_max_width = 50
local tab_max_chars = 30

wezterm.on("format-tab-title", function(tab, tabs, panes, config, hover)
  local tab_is_toolbox = (tab.active_pane.user_vars.in_toolbox or "false") == "true";
  local tab_is_hover = hover;
  local tab_is_active = tab.is_active;

  if tab_is_active then
    c = color_tab_active;
  elseif tab_is_hover then
    c=color_tab_hoover;
  else
    c=color_tab_inactive;
  end

  bg=c[1]
  fg=c[2]
  b=c[3]
  u=c[4]

  title = tab.active_pane.title;
  -- we can at maximum only display some predefined chars and have to add 
  -- the numbering ("xx: ", 4 chars) and maybe a dot + space (3 chars))
  available_chars = tab_max_chars - 4 - 3;
  title_chars = string.len(title)
  if title_chars > available_chars then
    title = "…" .. string.sub(title, title_chars - available_chars) ;
  end

  if tab_is_toolbox then
    title = "🔴 " .. title
  end

  if #tabs > 1 then
    title = string.format("%d: %s", tab.tab_index + 1, title);
  end

  return {
    {Background={Color=color_tabbar_background}},
    {Foreground={Color=bg}},
    {Text=SOLID_LEFT_ARROW},
    {Background={Color=bg}},
    {Foreground={Color=fg}},
    {Text=" "},
    {Attribute={Underline=u}},
    {Attribute={Intensity=b}},
    {Text=title},
    {Attribute={Intensity="Normal"}},
    {Attribute={Underline="None"}},
    {Text=" "},
    {Background={Color=color_tabbar_background}},
    {Foreground={Color=bg}},
    {Text=SOLID_RIGHT_ARROW},

  }

end)

local empty = ""

return {
  ...
  tab_max_width = tab_max_width,
  -- intentionally both empty as we use the title bar function
  tab_bar_style = {
    active_tab_left = empty,
    active_tab_right = empty,
    inactive_tab_left = empty,
    inactive_tab_right = empty,
    inactive_tab_hover_left = empty,
    inactive_tab_hover_right = empty,
  },
  colors = {
    tab_bar = {},
  }
}

And in my zshrc i have:

export IN_TOOLBOX="false"
# Toolbox specific things
if [[ -f /run/.containerenv && -f /run/.toolboxenv ]]; then
  # If we are inside a toolbox...
  IN_TOOLBOX="true"
fi

# copied and adapted from https://github.com/starship/starship/blob/master/src/init/starship.zsh
# Will be run before every prompt draw
toolbox_wezterm_precmd() {
  # makes this available in the pane info of wezterm
  printf "\e]1337;SetUserVar=%s=%s\a" in_toolbox $(echo -n ${IN_TOOLBOX} | base64)
}
(( ! ${+precmd_functions} )) && precmd_functions=()
if [[ -z ${precmd_functions[(re)toolbox_wezterm_precmd]} ]]; then
    precmd_functions+=(toolbox_wezterm_precmd)
fi

Works nicely! Thanks a lot for implementing this!

Re old-sytle styling vs new event/func: It does feel a bit clumsy to explicitly disable all old-style styling via some empty string/table. The + icon also still needs to be styled the old way, but that's also fine, just a bit inconsistent.

It's also a bit strange that the "tab title shortner" is basically now some heuristic, as the real string width is not available in lua. If this heuristic is wrong, it removes the powerline > endings in my case: image.

But apart from making a string_width function available in lua, I wouldn't know a better way (e.g. in my case, I would want the title to be shortened between the red ball and the original title...). So to be honest: it's good enough for me, I will just set the width do much bigger than the char length will always catch it earlier :-)

jankatins commented 3 years ago

Oh, and you still need to press enter twice for some reason: going into the toolbox -> new prompt has the decoaration, but tab not yet -> enter -> both prompt and tab have the toolbox decoration. Might be a timing issue between when the precmd thingies are run and when the pane info is computed? image

On the other hand: exiting the toolbox (and therfore sending the false state) will imidiately remove the dot and manually overriding it will not work at all (flicker, but directly back to the old state).

wez commented 3 years ago

So it sounds like a couple of things need to be tidied up:

wez commented 3 years ago

wezterm.truncate_to_width and the new max_width parameter should help manage the tab titles; there's an example of this at the bottom of https://wezfurlong.org/wezterm/config/lua/window-events/format-tab-title.html

jankatins commented 3 years ago

Any chance to truncate at the beginning? In paths, the end is the important part and that is was is displayed most if the time in my case.

jankatins commented 3 years ago

BTW: just realized that the title is also not set (or only set after one more ENTER) as oh-my-zsh is setting it in precmd (https://github.com/ohmyzsh/ohmyzsh/blob/master/lib/termsupport.zsh). Unfortunately, I cannot record, as my gif recorder does not work on wayland anymore :-(

Basically:

  1. Open wezterm, precmd should set title to ~, wezterm shows "wezterm"
  2. Press enter without a command -> wezterm shows ~ in the tab title
jankatins commented 3 years ago

Running wezterm 20210501-112939-faaf6dea, and I noticed that tab titles are really short somehow.

I traced that down that I somehow get two different tab title max_width passed in somehow:

I've set my tab width to 40 and when I use the following tab function:

wezterm.on("format-tab-title", function(tab, tabs, panes, config, hover, max_width)
  wezterm.log_warn(string.format("%s , %s, %s, %s", tab.active_pane.title, max_width, hover, tabs)); 
...

Log looks like this (two tabs) -> two runs for each tab!

 2021-05-01T23:00:21.473Z WARN  config::lua                    > lua: ~ , 40, false, table: 0x555ffe90f800
 2021-05-01T23:00:21.474Z WARN  config::lua                    > lua: wezterm , 40, false, table: 0x555ffe92bd00
 2021-05-01T23:00:21.474Z WARN  config::lua                    > lua: ~ , 14, false, table: 0x555ffde34120
 2021-05-01T23:00:21.475Z WARN  config::lua                    > lua: wezterm , 14, false, table: 0x555ffe8938b0

I'm not quite sure why sometimes I only see short titles and sometimes longer. I also saw that when the title is different than "wezterm" or "~", I get higher numbers passed in. E.g. this is what I get as output for a single tab in a different folder:

 2021-05-01T23:08:04.412Z WARN  config::lua                    > lua: /var/lib/AccountsService , 40, false, table: 0x555ffec18a00
 2021-05-01T23:08:04.413Z WARN  config::lua                    > lua: /var/lib/AccountsService , 31, false, table: 0x555ffe8b0540
wez commented 3 years ago

It's expected that there are two passes; it does one first with config-specified size to see what the ideal tab width might be. If the ideal width won't fit, it takes the available width, less some space for the new tab button, then divides it by the number of tabs to get the equal width per tab that will fit.

Then a second pass is made with this adjusted max and whether we're hovering in the now-known width of that tab.

Is there something about the width of the window or the table that is being generated and returned that correlates with the weird widths?

jankatins commented 3 years ago

One more thing: the truncate thingy would benefit from a "truncate marker" (I'm really bad with names): I've this pattern and it seems redundant given what truncate has to compute:

  if wezterm.column_width(title) > available_width then
    title = "…" ..   wezterm.truncate_left(title, available_width-2);
  end
jankatins commented 3 years ago

and a big thanks for the refactoring: this looks much cleaner than munching it together :-) .. and also for the explanation: it was surprising: seems I missed that when I read the docs, but rereading it, the behaviour makes total sense.

jankatins commented 3 years ago

Just realized that there is a small gap here between the (foreground) edge and the (background) title: image

Relevant code should be this:

  return {
    {Background={Color=color_tabbar_background}},
    {Foreground={Color=bg}},
    {Text=SOLID_LEFT_ARROW},
    {Background={Color=bg}},
    {Foreground={Color=fg}},
    {Text=" "},
    {Attribute={Underline=u}},
    {Attribute={Intensity=b}},
    {Text=title},
    {Attribute={Intensity="Normal"}},
    {Attribute={Underline="None"}},
    {Text=" "},
    {Background={Color=color_tabbar_background}},
    {Foreground={Color=bg}},
    {Text=SOLID_RIGHT_ARROW},
  }
wez commented 3 years ago

The gaps are due to a font hinting bug in freetype. https://github.com/wez/wezterm/issues/584 and https://github.com/wez/wezterm/pull/588 are relevant. The latter is a PR that seems to have stalled.

wez commented 3 years ago

I think this is complete now; are there any outstanding issues?

MuhammedZakir commented 3 years ago

2 more requests related to current discussion. But if you want, I can open a new issue.

  1. Is it possible to add "scale-factor"/"scale-weight" for tabs?
    • E.g. setting weight of tab 1 to 0.5 - tab 1 will take 50% of total space and all other tabs take the remaining tab bar space equally.
    • My use case: I use first 1-3 tabs as main tabs and the all others as "temporary". I want my main tab buttons to show as much content as possible. (dir + running program + ...).
  2. An option to make tab buttons fill the whole tab bar (like in iTerm2):
    • Current: | Tab1 | Tab2 | + | <remaining space> | <right status> |
    • What I want: | Tab1 | Tab2 | + | <right status> |
no-response[bot] commented 3 years ago

This issue has been automatically closed because there has been no response to the request for more information from the original author. With only the information that is currently in the issue, there isn't enough information to take further action. Please reach out if you have or find the answers we need so that we can investigate further.

github-actions[bot] commented 1 year ago

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.