romkatv / powerlevel10k

A Zsh theme
MIT License
44.26k stars 2.12k forks source link

Inconsistent space between segments depending on the background color of a custom p10k segment #2673

Closed ddribin closed 1 month ago

ddribin commented 1 month ago

Hello. I am trying to add a custom segment called drd_dev_env. I have it at the end of my POWERLEVEL9K_LEFT_PROMPT_ELEMENTS like this:

    # =========================[ Line #2 ]=========================
    drd_dev_env             # Development environment: prod/staging/test
    context                 # user@hostname
    prompt_char             # prompt symbol
)

If I define an empty background color, there is a space between this segment and context:

  typeset -g POWERLEVEL9K_DRD_DEV_ENV_BACKGROUND=
  function prompt_drd_dev_env() {
    if [[ ! -z $DRD_DEV_ENV ]]; then
      p10k segment -f white -t "$DRD_DEV_ENV"
    fi
  }
p10k-empy-bg

However, if I define a non-empty background color, there is not a space between this segment and context:

  typeset -g POWERLEVEL9K_DRD_DEV_ENV_BACKGROUND='black'
  function prompt_drd_dev_env() {
    if [[ ! -z $DRD_DEV_ENV ]]; then
      p10k segment -f white -t "$DRD_DEV_ENV"
    fi
  }
p10k-black-bg

Is there any way to explicitly control this behavior?

I am using the P10k from git at commit 16e5848.

romkatv commented 1 month ago

Left prompt segments with the same background color are separated by POWERLEVEL9K_LEFT_SUBSEGMENT_SEPARATOR. Left prompt segments with different background colors are separated by POWERLEVEL9K_LEFT_SEGMENT_SEPARATOR. I suppose in your config the former is set to ' ' and the latter to ''. You can verify with the following command:

typeset -p POWERLEVEL9K_LEFT_SUBSEGMENT_SEPARATOR POWERLEVEL9K_LEFT_SEGMENT_SEPARATOR

I also suppose that your config is based on Lean style. It's a good choice. I also use Lean. In this style you don't use background colors and convey information by content, icons and foreground colors only.

ddribin commented 1 month ago

Yup! Those are set, like you suspected:

> typeset -p POWERLEVEL9K_LEFT_SUBSEGMENT_SEPARATOR POWERLEVEL9K_LEFT_SEGMENT_SEPARATOR
typeset POWERLEVEL9K_LEFT_SUBSEGMENT_SEPARATOR=' '
typeset POWERLEVEL9K_LEFT_SEGMENT_SEPARATOR=''

And yes, I also use Lean:

# Generated by Powerlevel10k configuration wizard on 2021-12-29 at 10:47 CST.
# Based on romkatv/powerlevel10k/config/p10k-lean.zsh, checksum 54401.
# Wizard options: compatible, ascii, lean, 1 line, compact, concise, transient_prompt,
# instant_prompt=verbose.

And yeah, I have not used any background colors until now, which is why I've probably not noticed. I probably want POWERLEVEL9K_LEFT_SEGMENT_SEPARATOR set to a space, too. I'll try that out tomorrow.

romkatv commented 1 month ago

I probably want POWERLEVEL9K_LEFT_SEGMENT_SEPARATOR set to a space, too.

Sure, you can do that, although as I mentioned above, I don't think it's a good idea to use background colors with Lean style.

ddribin commented 1 month ago

Yeah, I understand. I want background colors really only in this one case. I almost removed a backup on a prod machine, because the prompt between prod and test were exactly the same. The prod machine is named example.com and the test one is example.test, so they both have the same machine name in context. So I kinda want the dev environment it to stick out a bit. ;-)

Is adding a space to the text/content expansion asking for trouble? I'm really going to use -s state for this, but I just wanted to simplify an example case.

romkatv commented 1 month ago

My suggestion is to use foreground colors and/or icons to call attention to a segment.

ddribin commented 1 month ago

You're advice hasn't lead me astray, yet. ;-) I've switched over to only setting the foreground:

  typeset -g POWERLEVEL9K_DRD_DEV_ENV_PROD_FOREGROUND=green
  typeset -g POWERLEVEL9K_DRD_DEV_ENV_PROD_CONTENT_EXPANSION='%BPRD'
  typeset -g POWERLEVEL9K_DRD_DEV_ENV_PROD_VISUAL_IDENTIFIER_EXPANSION='●'

  typeset -g POWERLEVEL9K_DRD_DEV_ENV_STAGING_FOREGROUND=33 # shade of blue
  typeset -g POWERLEVEL9K_DRD_DEV_ENV_STAGING_CONTENT_EXPANSION='%BSTG'
  typeset -g POWERLEVEL9K_DRD_DEV_ENV_STAGING_VISUAL_IDENTIFIER_EXPANSION='◆'

  typeset -g POWERLEVEL9K_DRD_DEV_ENV_TEST_FOREGROUND=purple
  typeset -g POWERLEVEL9K_DRD_DEV_ENV_TEST_CONTENT_EXPANSION='%BTST'
  typeset -g POWERLEVEL9K_DRD_DEV_ENV_TEST_VISUAL_IDENTIFIER_EXPANSION='■'

  function prompt_drd_dev_env() {
    if [[ ! -z $DRD_DEV_ENV ]]; then
      p10k segment -s $DRD_DEV_ENV
    fi
  }
p10k-no-bg

I may get rid of the visual identifier expansion, as I'm not sure the two characters (the icon and the separator) or worth the real estate. And I may move it up the line above, just before the dir segment. But this gets me what I wanted.

romkatv commented 1 month ago

Looks good. I'd implement it like this:

typeset -gA drd_dev_env_colors=(
  PROD    green
  STAGING 33
  TEST    purple
)

function prompt_drd_dev_env() {
  local color=${drd_dev_env_colors[$DRD_DEV_ENV]}
  [[ -n $color ]] || return
  p10k segment -f $color -t "%B$DRD_DEV_ENV"
}

You don't have to use POWERLEVEL9K_* overrides when working with your own prompt segment. No need to override what you've custom made for yourself.

ddribin commented 1 month ago

This is why I posted what I had. I knew you would have an improvement! 🤣 Here's what I've got now (I like to have the prompt text only be three characters):

  typeset -gA drd_dev_env_colors=(
    PROD    green
    STAGING 33
    TEST    purple
  )
  typeset -gA drd_dev_env_text=(
    PROD    '%BPRD'
    STAGING '%BSTG'
    TEST    '%BTST'
  )

  function prompt_drd_dev_env() {
    local color=${drd_dev_env_colors[$DRD_DEV_ENV]}
    local text=${drd_dev_env_text[$DRD_DEV_ENV]}
    [[ -n "$text" ]] || return
    p10k segment -f $color -t "$text"
  }

And I think we can close this out. Thanks again for all your help and feedback! P10k is truly a gift.

romkatv commented 1 month ago

I like to have the prompt text only be three characters

typeset -gA drd_dev_env=(
  PROD    '%F{002}PRD'
  STAGING '%F{033}STG'
  TEST    '%F{129}TST'
)

function prompt_drd_dev_env() {
  p10k segment -t "${drd_dev_env[$DRD_DEV_ENV]}"
}
ddribin commented 1 month ago

And just for anyone landing here via search, in the future, it's good to add instant prompt support for this segment with:

  function instant_prompt_drd_dev_env() {
    prompt_drd_dev_env
  }

Without it, the environment would always "pop" in a few milliseconds later, on new shells.

romkatv commented 1 month ago

There is a way to add instant prompt support to this segment but that's not it. I'll post the right way when I get to a machine with a keyboard.

romkatv commented 1 month ago

This should work correctly:

function instant_prompt_drd_dev_env() {
  local k v
  for k v in ${(kv)drd_dev_env}; do
    p10k segment -t $v -c '${(M)DRD_DEV_ENV:#'$k'}'
  done
}

You can also do this:

typeset -gA drd_dev_env=(
  PROD    '%F{002}PRD'
  STAGING '%F{033}STG'
  TEST    '%F{129}TST'
)

function prompt_drd_dev_env() {
  local k v
  for k v in ${(kv)drd_dev_env}; do
    p10k segment -t $v -c '${(M)DRD_DEV_ENV:#'$k'}'
  done
}

function instant_prompt_drd_dev_env() {
  prompt_drd_dev_env
}

The important part is that instant_prompt_drd_dev_env makes the same p10k segment calls every time it is invoked. More precisely, the calls can depend on $PWD, $USER and a few other things, but they cannot depend on arbitrary environment variables.

ddribin commented 1 month ago

The important part is that instant_prompt_drd_dev_env makes the same p10k segment calls every time it is invoked. More precisely, the calls can depend on $PWD, $USER and a few other things, but they cannot depend on arbitrary environment variables.

Maybe I'm using an environment variable incorrectly here, but $DRD_DEV_ENV never really changes. The screen shots showed setting it via export, but that was for demo purposes, only. In reality, it is set in /etc/zshenv on a per-machine basis and never changes.

romkatv commented 1 month ago

I hope future readers with a similar goal will understand that my code is correct in general and yours is correct only under very specific conditions.

ddribin commented 1 month ago

FWIW, I took this code as inspiration:

  # User-defined prompt segments may optionally provide an instant_prompt_* function. Its job
  # is to generate the prompt segment for display in instant prompt. See
  # https://github.com/romkatv/powerlevel10k/blob/master/README.md#instant-prompt.
  #
  # Powerlevel10k will call instant_prompt_* at the same time as the regular prompt_* function
  # and will record all `p10k segment` calls it makes. When displaying instant prompt, Powerlevel10k
  # will replay these calls without actually calling instant_prompt_*. It is imperative that
  # instant_prompt_* always makes the same `p10k segment` calls regardless of environment. If this
  # rule is not observed, the content of instant prompt will be incorrect.
  #
  # Usually, you should either not define instant_prompt_* or simply call prompt_* from it. If
  # instant_prompt_* is not defined for a segment, the segment won't be shown in instant prompt.
  function instant_prompt_example() {
    # Since prompt_example always makes the same `p10k segment` calls, we can call it from
    # instant_prompt_example. This will give us the same `example` prompt segment in the instant
    # and regular prompts.
    prompt_example
  }

And this comment is relevant:

It is imperative that instantprompt* always makes the same p10k segment calls regardless of environment.`

But yes, I would hope future readers would always take your advice, over mine! ;-)