romkatv / zsh4humans

A turnkey configuration for Zsh
MIT License
1.81k stars 116 forks source link

Feedback #35

Closed maximbaz closed 2 years ago

maximbaz commented 4 years ago

Hey @romkatv! I'm giving z4h/v3 a spin, and would like to share some ideas and ask some questions at the same time 🙂

  1. Tab completion: traversing hidden folders

You mentioned it is possible to add an option to traverse hidden folders as well when glob_dots is set, could you please add it or give me a hint how to do this locally?

Turns out I edit files in hidden folders too often, it would be helpful if fzf included everything.

  1. Tab completion: ignoring some patterns

The standard completion system respects ignored-patterns.

This was a great hint, just as one idea it allows to ignore .zwc files, for example with zstyle ':completion:*:*:kak:*:*' ignored-patterns '*.zwc' opening .p10k.zsh in editor is simpler, as kak .p<Tab> now has only one match instead of two, so fzf doesn't show up at all and I go straight to .p10k.zsh.

Currently recursive directory listing doesn't respect it but it's on my TODO list to fix this.

Would be awesome! Could you please suggest what would be a proper zstyle syntax that you want to support for ignoring folders for cd? Say for example I wanted to ignore ~/.cache/ and ~/.docker/ and everything underneath them.

  1. Tab completion: do not require space to search different dirs

Suppose I just cloned zsh4humans, I press <Alt-Down> and I want to navigate to zsh4humans/fn. Typing z fn will find it, but typing zfn will not. I am used to just typing in fzf without having to think where I should put spaces... Could we at least have an option, if the current behavior is something you prefer?

By the way, while <Alt-Down> shows

zsh4humans
zsh4humans/fn

But cd <Tab> shows

zsh4humans
./zsh4humans/fn

Both work, but the latter is less aesthetic :)

  1. Tab completion: fzf-tab continuous-trigger

Am I right that it will become unnecessary when everything will be traversed? If so, this could be cleaned up I presume:

https://github.com/romkatv/zsh4humans/blob/6af0bfc8727b5e5ed72643ce5c457e9cbc513afb/.zshrc#L56-L58

  1. Tab completion: multiple selection doesn't seem to work

Or I'm doing something wrong :) What I expected to work is to type $ cp .zsh<Tab> and press Ctrl+Space to select more than one entry

  1. Tab completion: add grouping and multiple coloring

Have you considered adding groups and using different colors to fzf completion? I have some half-broken leftovers from prezto, but they will suffice to show a difference:

zstyle ':completion:*:*:*:*:*' menu select
zstyle ':completion:*:matches' group 'yes'
zstyle ':completion:*:options' description 'yes'
zstyle ':completion:*:options' auto-description '%d'
zstyle ':completion:*:corrections' format ' %F{green}-- %d (errors: %e) --%f'
zstyle ':completion:*:descriptions' format ' %F{yellow}-- %d --%f'
zstyle ':completion:*:messages' format ' %F{purple} -- %d --%f'
zstyle ':completion:*:warnings' format ' %F{red}-- no matches found --%f'
zstyle ':completion:*:default' list-prompt '%S%M matches%s'
zstyle ':completion:*' format ' %F{yellow}-- %d --%f'
zstyle ':completion:*' group-name ''
zstyle ':completion:*' verbose yes

Current style:

image

With grouping and coloring:

image

I don't care about group names too much, we could just hide them, but I do think coloring different types of autocomplete is useful and nice.

  1. "Additional Zsh startup files" in README

I would suggest to rephrase this a little, let me give you an example when editing those files makes sense: it is a nice and easy way to start GUI. Here's how I do it:

Let me know if this makes sense. Right now I simply put all my variables on top of .zshenv generated by z4h, but I'm wondering if the README needs to be adjusted to relax the bold phrase a bit, maybe just say that everything that z4h generates must be preserved without modifications?

  1. Avoid exporting XDG_CACHE_HOME

I would suggest to drop this line:

https://github.com/romkatv/zsh4humans/blob/6af0bfc8727b5e5ed72643ce5c457e9cbc513afb/.zshenv#L11

I believe every software knows how to fallback to $HOME/.cache in the absence of that variable, and it should be a very conscious user decision to export all those XDG_* variables, let's not do it for them.

  1. Add TIMEFMT='user=%U system=%S cpu=%P total=%*E' to z4h

First saw it in your dotfiles, and instantly fell in love. I believe it's exactly one of those things that makes zsh for humans and should be done for everyone 🙂

  1. Terminal title to contain timestamp

Because transient prompt (often) removes the timestamp of when a command was started, you once recommended me to add the time to the terminal title instead, to be able to see how long the command is already running. I still think it was a great idea, might be worth adding directly to z4h? Time is only needed for when a command is running (not when title shows current dir), and maybe only when transient prompt is enabled (but I'd probably just show the time always when command is running). What do you think?

  1. Support moooooore LS_COLORS

Have you seen this project? https://github.com/trapd00r/LS_COLORS

I think it would be very cool to tap into their work and have an integration with it, just like you do with zsh-syntax-highlight and others. Currently I can source their file and it will affect my ls, but I don't think tab completion respects this variable, maybe because colors are set immediately after a built-in LS_COLORS is defined?

https://github.com/romkatv/zsh4humans/blob/6af0bfc8727b5e5ed72643ce5c457e9cbc513afb/fn/-z4h-init#L392

  1. Various small ideas

I have had a few small snippets taken from here and there that I use quite frequently, want to show them and see if you would want to take anything directly in z4h:

typeset -g POWERLEVEL9K_TRANSIENT_PROMPT=off
p10k-on-pre-prompt()  { p10k display '1'=show }
p10k-on-post-prompt() { p10k display '1'=hide }
my-pound-toggle() {
    if [[ "$BUFFER" = '#'* ]]; then
        if [[ $CURSOR != $#BUFFER ]]; then
            (( CURSOR -= 1 ))
        fi
        BUFFER="${BUFFER:1}"
    else
        BUFFER="#$BUFFER"
        (( CURSOR += 1 ))
    fi
}
zle -N my-pound-toggle
autoload -Uz edit-command-line
zle -N edit-command-line
my-ctrl-z() {
    if [[ $#BUFFER -eq 0 ]]; then
        BUFFER="fg"
        zle accept-line -w
    else
        zle push-input -w
        zle clear-screen -w
    fi
}
zle -N my-ctrl-z

That's enough for now 😄

UPDATE: OK, one more 😁 Have you considered to add these options to the list of default options in z4h?

romkatv commented 4 years ago

Hey @romkatv! I'm giving z4h/v3 a spin, and would like to share some ideas and ask some questions at the same time

Thanks!

  1. Tab completion: traversing hidden folders

You mentioned it is possible to add an option to traverse hidden folders as well when glob_dots is set, could you please add it or give me a hint how to do this locally?

This should do it:

diff --git a/fn/-z4h-comp-files b/fn/-z4h-comp-files
index 9c63668..02ea709 100644
--- a/fn/-z4h-comp-files
+++ b/fn/-z4h-comp-files
@@ -14,13 +14,13 @@

   if (( only_dirs )); then
     if (( dot_glob )); then
-      local dirs=($path_prefix${^${(Q)words:#.*}}/*(D-/Y1N:h:t))
+      local dirs=($path_prefix${^${(Q)words}}/*(D-/Y1N:h:t))
     else
       local dirs=($path_prefix${^${(Q)words:#.*}}/*(-/Y1N:h:t))
     fi
   else
     if (( dot_glob )); then
-      local dirs=($path_prefix${^${(Q)words:#.*}}/*(DY1N:h:t))
+      local dirs=($path_prefix${^${(Q)words}}/*(DY1N:h:t))
     else
       local dirs=($path_prefix${^${(Q)words:#.*}}/*(Y1N:h:t))
     fi
@@ -36,7 +36,6 @@
       cmd+=(-path ./${(b)dir}/'*' -o)
     done
     cmd[-1]=(')' -prune)
-    cmd+=(-o -name '.*' -prune)
     (( dot_glob )) && cmd+=(-print)
     cmd+=(-o -print)
   fi
diff --git a/fn/z4h-cd-down b/fn/z4h-cd-down
index ba47b08..ee71d35 100644
--- a/fn/z4h-cd-down
+++ b/fn/z4h-cd-down
@@ -9,7 +9,7 @@

   if (( dot_glob )); then
     local dirs=(./*(-/DN))
-    local non_empty=(${^${dirs:#./.*}}/*(D-/Y1N:h:t))
+    local non_empty=(${^dirs}/*(D-/Y1N:h:t))
   else
     local dirs=(./*(-/N))
     local non_empty=(${^dirs}/*(-/Y1N:h:t))
@@ -23,7 +23,6 @@
       cmd+=(-path ./${(b)dir}/'*' -o)
     done
     cmd[-1]=(')' -prune)
-    cmd+=(-o -name '.*' -prune)
     (( dot_glob )) && cmd+=(-print)
     cmd+=(-o -print)
   fi

The standard completion system respects ignored-patterns.

This was a great hint, just as one idea it allows to ignore .zwc files, for example with zstyle ':completion:*:*:kak:*:*' ignored-patterns '*.zwc' opening .p10k.zsh in editor is simpler, as kak .p<Tab> now has only one match instead of two, so fzf doesn't show up at all and I go straight to .p10k.zsh.

I like this! I added this zstyle to zsh4humans.

Currently recursive directory listing doesn't respect it but it's on my TODO list to fix this.

Would be awesome! Could you please suggest what would be a proper zstyle syntax that you want to support for ignoring folders for cd? Say for example I wanted to ignore ~/.cache/ and ~/.docker/ and everything underneath them.

I don't know yet.

  1. Tab completion: do not require space to search different dirs

Suppose I just cloned zsh4humans, I press <Alt-Down> and I want to navigate to zsh4humans/fn. Typing z fn will find it, but typing zfn will not. I am used to just typing in fzf without having to think where I should put spaces... Could we at least have an option, if the current behavior is something you prefer?

You are using an unreleased version of zsh4humans, which is essentially my personal dotfiles. I'm experimenting with --exact fzf flag right now, which turns off fuzzy matching by default. You can still get fuzzy matching for a term with 'zfn. You can also search for --exact in zsh4humans and remove it.

FWIW, after a few days of adjusting to --exact I very much prefer it. Fuzzy matching in fzf is its main weakness. All other fuzzy finders I've tried have much better fuzzy matching logic.

By the way, while <Alt-Down> shows

zsh4humans
zsh4humans/fn

But cd <Tab> shows

zsh4humans
./zsh4humans/fn

That's a bug. Fixed.

  1. Tab completion: fzf-tab continuous-trigger

Am I right that it will become unnecessary when everything will be traversed?

It's still necessary for two reasons.

  1. Not all directories are traversed by default. E.g., .git is not traversed but sometimes I want to type something like cat .git/HEAD or ls .git/refs/remotes/*/*.
  2. If you tab-complete in a giant directory tree with the goal of retrieving x/blah, you can quickly find x/ and press Tab instead of waiting for everything before x/ to be traversed first.
  1. Tab completion: multiple selection doesn't seem to work

Works for me.

docker run -e TERM -e COLORTERM -w /root -it --rm alpine sh -uec '
  apk add zsh curl
  echo POWERLEVEL9K_DISABLE_CONFIGURATION_WIZARD=true >~/.p10k.zsh
  sh -c "$(curl -fsSL https://raw.githubusercontent.com/romkatv/zsh4humans/v3/install)"'

image

And then, after pressing Enter:

image

(Superfluous spaces between arguments are the result of a bug in fzf-tab. I don't know yet if it's fixable.)

Have you considered adding groups and using different colors to fzf completion?

Maybe. In general I don't like group colors because it can look awful without sorting (and I don't want to enable sorting of completion candidates) and interferes with fzf's highlighting of matched terms. If fzf supported highlting via reverse video, that would help.

  1. "Additional Zsh startup files" in README

I would suggest to rephrase this a little

I haven't updated docs in v3 yet. You may have noticed that v3 has two rc files but the docs say there is just one.

  • In .zprofile I have [[ -z $DISPLAY && "$(tty)" == "/dev/tty1" ]] && exec sway

This is awful. Does you OS come without init system? Using zsh rc files to start services isn't something I would recommend or consider a valid use case.

  • In .zshenv I have a list of environment variables that I want to be exported both in terminal and also for GUI

This is equally awful. Does you system come without /etc/environment, ~/.pam_environment or equivalent? .zshenv is for setting up environment specific to zsh. It's evaluated in every zsh process (e.g., zsh -c 'echo hi'). What you are looking for is environment that gets set up when a user logs in.

  1. Avoid exporting XDG_CACHE_HOME

I would suggest to drop this line:

https://github.com/romkatv/zsh4humans/blob/6af0bfc8727b5e5ed72643ce5c457e9cbc513afb/.zshenv#L11

Removed. This line was originally in .zshrc so that users who do want to override XDG_* knew where to put them. In v3 I've moved the top of .zshrc to .zshenv because that code is very tricky and because 99.9% of users shouldn't change it. I also envision that some users will want to have ZDOTDIR=~/.zsh or similar, and this setup has explicit support for it.

  1. Add TIMEFMT='user=%U system=%S cpu=%P total=%*E' to z4h

Done.

  1. Terminal title to contain timestamp

I plan to support it via zstyle where you can specify preexec and precmd terminal title formats similarly to prompt (with %-escapes and stuff).

Because transient prompt (often) removes the timestamp of when a command was started, you once recommended me to add the time to the terminal title instead, to be able to see how long the command is already running. I still think it was a great idea, might be worth adding directly to z4h? Time is only needed for when a command is running (not when title shows current dir), and maybe only when transient prompt is enabled (but I'd probably just show the time always when command is running). What do you think?

Maybe. I prefer to show less by default and let users add extra stuff if they want. I think it's better than showing stuff than users don't want (even if they have an option to turn it off).

  1. Support moooooore LS_COLORS

Have you seen this project? https://github.com/trapd00r/LS_COLORS

Yes. I used it in the past.

Currently I can source their file and it will affect my ls, but I don't think tab completion respects this variable, maybe because colors are set immediately after a built-in LS_COLORS is defined?

That's a bug. Fixed.

My position here is the same as w.r.t. to terminal title. zsh4humans by default should be usable and should have all vital features. Extra features should be easy to turn on. Every time users want to turn something off, it's worse than when users want to turn something on. Both of these imply that the defaults aren't optimal for the user but turning off is worse than on.

trapd00r/LS_COLORS is easy to enable for those who want it. If it was on by default, I would turn it off in my own dotfiles. The more colors you add to regular files, the more difficult it becomes to distinguish between regular and special files. That distinction is important to me.

I think some regular file colors would help me but I cannot think of a default that would work for everyone.

  • I still use your trick to have a new line added in transient prompt, just curious if noone else requested this to become a simple config variable? :)

I don't think anyone has asked for it. If this were to become popular, I could add an option to the configuration wizard.

  • Bind this to Ctrl+/ and it will toggle comment for the currently typed command, just like in VS Code and other editors :)

This looks like what I used to have in my .zshrc. Nowadays I use Alt+O instead. It requires fewer keystrokes to recover the stashed command. I also want to try push-input and see how that goes.

  • Bind this to Ctrl+V,V to edit the current command in EDITOR, very useful for multi-line commands

I used it for a while but I no longer do. Editing in $EDITOR means no completions and no syntax highlighting. I now edit multi-line commands directly in zle. It takes a little while to get used to pressing Alt+Enter instad of Enter but after that it's quite comfortable.

  • Make Ctrl+Z toggle pressing Ctrl+Z and fg, very useful for quickly checking terminal behind an editor and going back to the editor
my-ctrl-z() {
    if [[ $#BUFFER -eq 0 ]]; then
        BUFFER="fg"
        zle accept-line -w
    else
        zle push-input -w
        zle clear-screen -w
    fi
}
zle -N my-ctrl-z

The idea looks interesting although the specifics look rather unintuitive to me. How about this?

my-ctrl-z() {
  (( ${(%):-%j} > 0 )) && fg
}
zle -N my-ctrl-z
bindkey '^Z' my-ctrl-z

That's enough for now

UPDATE: OK, one more Have you considered to add these options to the list of default options in z4h?

  • HIST_IGNORE_ALL_DUPS

Currently if you press Ctrl+R, you'll see all commands you've typed in the current zsh session (except for consecutive dups). I find it valuable. HIST_IGNORE_ALL_DUPS will lose that.

to keep the history file smaller?

z4h sets HIST_SAVE_NO_DUPS, so dups are not saved to the history file.

HIST_SAVE_NO_DUPS

This is set.

  • RC_QUOTES to allow 'Henry''s Garage' instead of 'Henry'\''s Garage'?

This is too dangerous to enable by default as it changes the meaning of POSIX-compliant commands. Can be very surprising. Users who want this option can put it in their own .zshrc.

romkatv commented 4 years ago

I recalled there is one more case where I use continuous tab completion.

mkdir -p /tmp/a/b{1,2}/c{1,2}
cd /tmp/a/b/c<TAB>

This will first offer you to choose between b1 and b2. If you confirm your selection with Tab, it'll immediately present you with the choice between c1 and c2.

By the way, have you tried z4h ssh? Do you use ssh often? I can give you a short intro about setting it up to take full advantage of file transfers to the remote and then back to local host. For me z4h ssh (which I invoke simply as ssh) was one of the main reasons I've switched to z4h.

maximbaz commented 4 years ago

Tab completion: traversing hidden folders

This should do it...

Yup, works, thanks 🙂

I'm experimenting with --exact fzf flag right now, which turns off fuzzy matching by default. You can still get fuzzy matching for a term with 'zfn. You can also search for --exact in zsh4humans and remove it. FWIW, after a few days of adjusting to --exact I very much prefer it. Fuzzy matching in fzf is its main weakness. All other fuzzy finders I've tried have much better fuzzy matching logic.

Thanks for the hint! I do agree that the matching algorithm in fzf is worse than in competitors, but I still prefer the fuzzy flow 🙂 I removed --exact for now.

I guess long-term it would make sense to leave it up to users to put --exact to their $FZF_DEFAULT_OPTS, I imagine flags like --exact are very subjective to people's preferences and with these flags consistency is very important with how you are using fzf for everything else outside of z4h (in other words, not to confuse your muscle memory, if you use --exact outside of z4h you would want to have --exact also in z4h, and the opposite). What do you think?

Am I right that it will become unnecessary when everything will be traversed?

It's still necessary for two reasons... I recalled there is one more case where I use continuous tab completion.

Riiiight, makes total sense.

Tab completion: multiple selection doesn't seem to work

Works for me....

Uh, my bad, turns out Ctrl+Space was bound by another app. It's working now 🙂

What would be nice if Ctrl+Space would not only toggle the current element, but also move to the next one. Because when you multi-select, you usually select several items, usually consequent, so there is little reason to stay on the current item.

This is awful. Does you OS come without init system? This is equally awful. Does you system come without /etc/environment, ~/.pam_environment or equivalent?

Hehe 🙂 I'm still in search for a good way to manage this, but PAM environment is also not ideal as its syntax is limited in what you can do, and then people tend to have their variables declared all over the place... In any case, your point is accepted 👍

Every time users want to turn something off, it's worse than when users want to turn something on. Both of these imply that the defaults aren't optimal for the user but turning off is worse than on.

I like this logic!

Make Ctrl+Z toggle pressing Ctrl+Z and fg

The idea looks interesting although the specifics look rather unintuitive to me. How about this?

This one has two downsides:

image

To be frank, the snippet I posted is also not ideal, if you press Ctrl+Z 6 times your prompt will also contain useless garbage. The true ideal behavior would be like Alt+<arrow> bindings behave in z4h, where the prompt is being redrawn instead of being "submitted", i.e. the cursor stays on the same line in the terminal. I don't know if it's possible to achieve though...

By the way, have you tried z4h ssh? Do you use ssh often?

I use it often, I have tried z4h ssh and find it amazing! By the way, I confirm that with the latest zsh-bin everything works perfectly with kitty.

I can give you a short intro about setting it up to take full advantage of file transfers to the remote and then back to local host.

Would really appreciate this!


And thanks for all the fixes you have already made after this thread 🙂

romkatv commented 4 years ago

I guess long-term it would make sense to leave it up to users to put --exact to their $FZF_DEFAULT_OPTS

This is what worries me the most when you try a version of my code that isn't yet ready for others to use. On one hand it's great to have early feedback. On the other hand you find the product and the code behind it of worse quality than what you came to expect, and your feedback is also higher on noise than normal (but still very valuable) due to the observations of "I know" kind.

It's not my plan to have --exact hard-coded. It's just something I can do to my own dotfiles to see how it feels. When v3 is released, it will definitely have a way to control fzf flags, and it'll almost certainly be done with zstyle. Since z4h doesn't use any of the builtin fzf widgets, it doesn't make sense to respect FZF_* parameters. I've already yanked them out in preparation.

If v3 will have a default for fuzzy/not-fuzzy search, it'll be fuzzy. It'll be easy to change. Non-fuzzy isn't so much better to warrant going against the status quo established by fzf.

I'm not sure if I mentioned it before. If everything goes well, v4 will likely be the last version of zsh4humans. Or to put it the other way, v3 will be the last version that I don't want to become popular because I want to break backward compatibility one more time. Before I release v3, I'll address some major issues and clean things up. I'll try to keep the release low key just like the previous versions. I want feedback but only from intelligent users. v4 will have a setup wizard similar to the one in p10k where you'll be able to choose this vs that for the common options that I anticipate. Once v4 is out, I'll maintain backward compatibility for as long as I muster.

What would be nice if Ctrl+Space would not only toggle the current element, but also move to the next one.

Good idea. I don't control fzf but maybe it's something I can add upstream.

This one has two downsides:

  • It "breaks" transient prompt, as in if you open an editor and then press Ctrl+Z 6 times, you will see that part of the prompt will not become transient, it will have all the icons

Ah, you want something like Alt+Tab for full-screen apps in the terminal? That sounds cool! I'll see what I can do.

By the way, I confirm that with the latest zsh-bin everything works perfectly with kitty.

Thanks!

I can give you a short intro about setting it up to take full advantage of file transfers to the remote and then back to local host.

Would really appreciate this!

I've put it on my TODO list. Will ping you later.

And thanks for all the fixes you have already made after this thread 🙂

Thanks for the suggestions and bug reports! Invaluable as always.

maximbaz commented 4 years ago

This is what worries me the most when you try a version of my code that isn't yet ready for others to use. On one hand it's great to have early feedback. On the other hand you find the product and the code behind it of worse quality than what you came to expect, and your feedback is also higher on noise than normal (but still very valuable) due to the observations of "I know" kind.

Makes sense 🙂 Don't get me wrong, I knew what I subscribed for when I decided to try v3 despite your warning about it not being ready - feel free to just reply with "I know" on such comments 👍

Ah, you want something like Alt+Tab for full-screen apps in the terminal? That sounds cool! I'll see what I can do.

Precisely!


Found a small issue when testing history retrieval over ssh, on closing connection I get this error:

(anon):zf_mv:1: /tmp/zshxurItS: invalid cross-device link

It is coming from this line:

https://github.com/romkatv/zsh4humans/blob/93fc6e46ff5df17c4514b444eaba065858efedef/fn/-z4h-ssh#L360

My /tmp is on RAM (tmpfs), and it seems zf_mv cannot move files between different devices; changing zf_mv to mv on that line fixes the issue but I'm not sure it's the right approach.

romkatv commented 4 years ago

Found a small issue when testing history retrieval over ssh, on closing connection I get this error:

Thanks!

it seems zf_mv cannot move files between different devices

Yeah, this annoying limitation is even documented. It's really annoying when you are trying to write fast crash-safe code.

changing zf_mv to mv on that line fixes the issue but I'm not sure it's the right approach.

This isn't safe. Pick up the right fix here: https://github.com/romkatv/zsh4humans/commit/c200c733880a8ddcb6c9d58dd0f80073f6fdca79.

maximbaz commented 4 years ago

I found a... weirdness 🤔

Say I have a file aliases.zsh containing the following:

alias sayhello='echo hello'
greet() { sayhello world }

If I source aliases.zsh in .zshrc, open a new shell and run greet world, it will work as expected. But if I instead z4h source aliases.zsh in .zshrc, then greet world will not expand the alias and crash with command not found: sayhello

Seems like if you source a script from within a function, it does it... in a different context or something 🤔

It's easy to workaround (e.g. by replacing the alias with sayhello() { echo "$@" }), but maybe you'll have any ideas how to make the behavior of z4h source match source...

romkatv commented 4 years ago

I found a... weirdness

Aww, that sucks. I've asked on zsh-workers: https://www.zsh.org/mla/workers//2020/msg01019.html. I'm not holding my breath though.

maximbaz commented 4 years ago

Another small observation: bash completions seem to be loaded too late in the game, for example in order to enable completions for azure-cli we need to z4h source this file in .zshrc, and it would fail unless we also autoload and call bashcompinit manually. I'm not sure if this is just a matter of extracting bashcompinit out of -z4h-post-init(), because there is some other stuff related to the completions in that function... 🤔

romkatv commented 4 years ago

Another small observation: bash completions seem to be loaded too late in the game

Thanks for letting me know. To be honest, I haven't tested this feature and I don't use bash completions myself.

I've moved the call to bashcompinit to z4h init, so az completions should work. There are still cases where it won't work though. E.g., complete -p and complete -r won't work until zsh is fully initialized. I don't know if bash completion functions ever call these during initialization.

romkatv commented 4 years ago

Aww, that sucks. I've asked on zsh-workers: https://www.zsh.org/mla/workers//2020/msg01019.html. I'm not holding my breath though.

I received the reply I expected from reading the code of zcompile.

  1. zcompile foo && source foo is not equivalent to source foo in one aspect: the former won't expand aliases defined in foo while sourcing foo. This is by design.
  2. You shouldn't use aliases in scripts. That's not what they are meant for.

While I agree with (2), I cannot enforce it and I don't want to have this gotcha in z4h source. So I've changed z4h source to not compile by default. You can pass -c to enable compilation.

# Do not zcompile.
z4h source foo.zsh
# Do zcompile.
z4h source -c bar.zsh

Note that -c doesn't always speed things up. Compared to plain z4h source, it takes additional N - M * file_size seconds. If the file is very small, z4h source -c is slower than plain z4h source. The threshold is fairly low though.

maximbaz commented 4 years ago

Do you have any plans for merging <Alt-down> and cd <Tab> to have a common implementation underneath both features? Just thinking that it takes time to maintain both, and from a user perspective it is kinda annoying when you stumble upon minor inconsistencies between them (because over time you stop noticing which one of the two you use). I found myself using fzf-tab continuous-trigger quite often actually (for one of the cases you mentioned, to stop traversing everything and focus on a certain dir), and this feature is only available via cd <Tab>.

In general I've been extensively using v3 this entire week, testing different features, and I must say it is working really great, very solid work!

romkatv commented 4 years ago

Do you have any plans for merging <Alt-down> and cd <Tab> to have a common implementation underneath both features?

I think you want to ask a different question.

from a user perspective it is kinda annoying when you stumble upon minor inconsistencies between them

That's a good question. Or rather a good bug report / feature request.

I found myself using fzf-tab continuous-trigger quite often actually (for one of the cases you mentioned, to stop traversing everything and focus on a certain dir), and this feature is only available via cd <Tab>.

Fixed. Now Tab works in Alt+Down.

In general I've been extensively using v3 this entire week, testing different features, and I must say it is working really great, very solid work!

Thanks! I'm glad.

By the way, what do you think of word-based widgets? Do you use them? Have you noticed that they are different from the stock widgets? Do they behave the way you would expect?

Perhaps there are more widgets that you haven't tried? Here's the list of bindings that I recommend trying.

Cursor movement

Bindings Description
Ctrl+Left Previous word
Ctrl+Right Next word
Ctrl+Home Beginning
Ctrl+End End

Deletions

Bindings Description
Ctrl+Backspace Previous word
Ctrl+Delete Next word
Alt+K Everything left of cursor
Ctrl+K Everything right of cursor
Ctrl+U Current line
Alt+J Everything

History

Bindings Description
Up Previous local history event
Down Next local history event
Ctrl+Up Previous shared history event
Ctrl+Down Next shared history event

Miscellaneous

Bindings Description
Alt+M Accept autosuggestion without moving cursor
Alt+O Push command to history without executing
Ctrl+/ Undo
Alt+/ Redo
Alt+H Show help for command

Undo is super useful. I undo Tab completions often. For example, I might type something like rmdir -- ./**/*~*/.git/*(/^F), hit Tab to expand the glob, visually validate that it does what I expect (this one is supposed to match empty directories recursively except in .git), and then hit Ctrl+/ to undo the expansion before executing the command. This way I have the command with an unexpanded glob in my history in case I want to run it again.

Another common use case for Undo is after pasting form clipboard. I often find out that clipboard has something other than what I expect.

Local history (Up) vs global history (Ctrl+Up) is great if you use several zsh sessions at once.

The default behavior of prefix-based history widgets (like Up in z4h) is to move cursor to the end. This has some unintuitive consequences as it effectively introduces two cursors. One cursor is visible, and it's positioned at the end. The other cursor is invisible and it's positioned at the same place where it was before you pressed Up. The invisible cursor demarcates the command prefix used by the widget in case you press Up again. Here's an example of how it usually works.

The last point is tricky because it requires you to remember where the invisible cursor is. Many actions lead to the invisible cursor change its position to align with the visible cursor, so this can be tricky. E.g., if after the first Up you were to manually move cursor to the left and then back, the next Up wouldn't give you cd /bar. Instead, it would give you a command that starts with cd ~/foo.

In z4h there is always one cursor position. Or, to put it another way, the invisible cursor is always at the same position as the visible. This is achieved by not changing the cursor position when you press Up. This makes it much easier to reason about Up. This key always gives you a command that has the same prefix as the current command line up to the cursor.

I also noticed that when I press Up I'm more likely to change the command at the front rather than at the back (add sudo, or some flag), so it also saves keystrokes.

Alt+H is nice. Try typing typeset and hitting this key combo. Also try it on git clone.

I use Alt+M often. If you want to accept an autosuggestions and edit something close to the current cursor position, Alt+M can save many keystrokes.

Ctrl+K, Alt+K and Alt+J make command line editing much more efficient.

Let me know if you discover any issues with these or have improvement suggestions.

maximbaz commented 4 years ago

By the way, what do you think of word-based widgets? Do you use them? Have you noticed that they are different from the stock widgets? Do they behave the way you would expect?

There maybe are some subtle differences comparing to what I was used to (like $WORDCHARS, will get to that in a second), but in general they behave the way I expect, I think defaults are chosen well.

Perhaps there are more widgets that you haven't tried? Here's the list of bindings that I recommend trying.

Oh thanks for sharing so much details!

romkatv commented 4 years ago

There maybe are some subtle differences comparing to what I was used to (like $WORDCHARS, will get to that in a second), but in general they behave the way I expect, I think defaults are chosen well.

Let me show how z4h word-based widgets are different from the defaults. Suppose you have this command line:

ls / foo/bar

The default forward-word with the default WORDCHARS (which includes /) will go over the following cursor positions:

ls / foo/bar
^  ^ ^

This is OK-ish but too coarse most of the time. Ideally, there should be another position where the cursor stops -- somewhere between foo and bar.

Without / in WORDCHARS:

ls / foo/bar
^    ^   ^

Note how the cursor jumps over the first argument of ls. This problem becomes more apparent if you set WORDCHARS to empty (like most zsh users seem to do).

ls . / $_ &> foo/bar
^            ^   ^

That jump from ls straight to foo really sucks.

For comparison, in z4h it looks like this:

ls . / $_ &> foo/bar
^  ^ ^ ^  ^  ^   ^

sometimes you just want to get to the beginning of a long file path and it is annoying to press Ctrl+Left many times.

Indeed.

What do you think about implementing two additional shortcuts that would jump to a next/previous whitespace?

I've added 4 new widgets:

# Move cursor one zsh word forward.
bindkey '^[[1;6C' z4h-forward-zword        # ctrl+shift+right
# Move cursor one zsh word backward.
bindkey '^[[1;6D' z4h-backward-zword       # ctrl+shift+left
# Delete next zsh word.
bindkey '^[[3;6~' z4h-kill-zword           # ctrl+shift+del
# Delete previous zsh word.
bindkey '^[^H'    z4h-backward-kill-zword  # ctrl+alt+bs

Here's how they tokenize commands:

foo '  bar / baz  ' $(qux quux)
^   ^               ^

I'm running out of decent keys to bind things to, so you'll likely have to rebind these to whatever works for you.

Let me know what you think about these widgets.

I think it could also be a good idea to set WORDCHARS='' in z4h-init only if WORDCHARS var is not defined by that point yet, in case user does want to customize it before loading z4h.

You can set WORDCHARS in ~/.zshrc after z4h init.

  • History shortcuts Good idea, but I might be doing something wrong, they all seem to operate on a local history. Is my expectation correct in this example?

    • Open two terminals side by side
    • In terminal 1 run echo 1
    • In terminal 2 run echo 2
    • Back in terminal 1, if I press Ctrl+Up I should get echo 2, but I get echo 1

Zsh reads shared history before precmd, so you need to press Enter in terminal 1 to pick up the latest command from terminal 2.

@cyrinux uses Azerty and he has to press Shift+: in order to get / char, and so when you press Ctrl+Shift+<anything>, this binding is not being passed to zsh, terminal catches it.

Noted. Eventually z4h will have an interactive wizard for setting up bindings but for now I'm afraid the only solution is to rebind things in zshrc.

suggestions for run-help [...]

  1. It prints to stdout how it expands commands and aliases, I don't really need to see that info
  2. When I have alias gcl="git clone --recursive", $ gcl<Alt-h> shows help for git, not git clone (even without --recursive)

(1) doesn't seem terrible but (2) is something I'll try to fix. I also noticed what appears to be inconsistent behavior:

% alias ls='ls --color=auto'
% run-help ls
ls is an alias for ls --color=auto
Press any key for more help or q to quit

If I press any key other than q, it'll open man page on ls. Seems fine. However, with alias gcl="git clone --recursive" man gets opened automatically.

maximbaz commented 4 years ago

Let me show how z4h word-based widgets are different from the defaults

Uh, yeah definitely z4h approach is a lot better! And with the 4 new widgets the workflow is very nice, easy to choose if you want to make a short and precise jump vs long to the end of the zword jump.

I've added 4 new widgets: I'm running out of decent keys to bind things to, so you'll likely have to rebind these to whatever works for you.

Using Ctrl+Shift makes sense (also is consistent with text editors where you additionally press Shift to jump by WORD), I cheated and made kitty terminal send ^[^H when I press Ctrl+Shift+Backspace, so that all 4 new key bindings use Ctrl+Shift consistently. In kitty you also must "open up" Ctrl+Shift keys before they are available to zsh, so if you are interested, the full config is this:

map ctrl+shift+left      noop
map ctrl+shift+right     noop
map ctrl+shift+delete    noop
map ctrl+shift+backspace send_text all \x1b\x08

Other than that, these widgets behave exactly as I expect them to do, and I think they are a very nice addition to the toolkit.

Zsh reads shared history before precmd, so you need to press Enter in terminal 1 to pick up the latest command from terminal 2.

Ooh, I see! Yes now I can reproduce the difference between local and global history.

Would it make sense to make zsh read shared history on Ctrl+Up keypress as well? Maybe together with some smart way of detecting if it has to vs if history file hasn't changed since the last time it was read. Just to make this feature a bit more obvious for users who don't know how it is implemented internally.

UPDATE 1: I know you added bindkey -M emacs '^H' backward-kill-word to .zshrc for demo purposes, but would make sense to also have that binding in the core z4h (and be mapped to z4h-backward-kill-word instead)

UPDATE 2: There's also a small issue with z4h-backward-kill-word, it doesn't move the cursor upon removal. Type cat one.txt | grep something, put cursor right before the dot and press Ctrl+W to remove one (maybe with an intention to write two instead), but the cursor will no longer stay on the dot.

romkatv commented 4 years ago

Using Ctrl+Shift makes sense (also is consistent with text editors where you additionally press Shift to jump by WORD)

I think Ctrl is the standard key modifier on Windows and Linux to move/delete words. Holding shift usually enables selection, so Ctrl+Shift+Right selects the next word.

By the way, I've been contemplating implementing the same Shift behavior in Zsh. I think it can be reproduced exactly with just one difference. After selecting text with Shift+Arrows you'll have to use a shortcut other than the standard you've configured in the terminal to copy to clipboard. E.g., I normally copy text from the terminal to clipboard with Ctrl+Shift+C or Ctrl+Insert but here I'll need to press Ctrl+S or something like that. (Currently my Ctrl+S binding copies the whole command line to clipboard.)

What do you think of this?

I cheated and made kitty terminal send ^[^H when I press Ctrl+Shift+Backspace, so that all 4 new key bindings use Ctrl+Shift consistently. In kitty you also must "open up" Ctrl+Shift keys before they are available to zsh, so if you are interested, the full config is this:

map ctrl+shift+left      noop
map ctrl+shift+right     noop
map ctrl+shift+delete    noop
map ctrl+shift+backspace send_text all \x1b\x08

Nice!

Would it make sense to make zsh read shared history on Ctrl+Up keypress as well?

Yes, that would definitely be better but it's very difficult to achieve. z4h simply exposes the builtin zsh history via bindings. In order to read global history on Ctrl+Up I would need to write a ton of code.

In Zsh, anything that uses history can request either local or global history (there is also internal history but it's less interesting). I chose for Ctrl+R and for autosuggestions to use global history, but I could also choose to use local, or to provide Ctrl+Shift+R for local history. I didn't do this because it doesn't seem very useful.

On the other hand, I made Up and Down use local history. I think this is what everyone expects and what I prefer myself.

When I just added Ctrl+Up and Ctrl+Down I was using them a lot. Nowadays Ctrl+R without a query shows your whole global history, so most of the time I use Ctrl+R instead of Ctrl+Up. I'm thinking of removing Ctrl+Up and Ctrl+Down bindings. This will free up these keys for something more useful and I won't have to explain this local/global history distinction.

UPDATE 1: I know you added bindkey -M emacs '^H' backward-kill-word to .zshrc for demo purposes, but would make sense to also have that binding in the core z4h (and be mapped to z4h-backward-kill-word instead)

I don't want to overwrite the default bindings in z4h. ^H in emacs keymap is bound to backward-delete-char and some users expect it to work that way. If you have to define an extra binding, that's one thing. You are just adding more stuff on top z4h. But if z4h overrides the default binding that you like, that's quite different. In this case z4h is being overly aggressive and opinionated.

So, instead of following your suggestions I've moved the binding for z4h-backward-kill-zword to .zshrc (^[^H is bound to backward-kill-word by default). You'll need to add it to your own .zshrc.

UPDATE 2: There's also a small issue with z4h-backward-kill-word, it doesn't move the cursor upon removal. Type cat one.txt | grep something, put cursor right before the dot and press Ctrl+W to remove one (maybe with an intention to write two instead), but the cursor will no longer stay on the dot.

Thanks! Fixed.

maximbaz commented 4 years ago

I'm thinking of removing Ctrl+Up and Ctrl+Down bindings. This will free up these keys for something more useful and I won't have to explain this local/global history distinction.

I think this reasoning here, and your choice for Ctrl+R and autosuggestions to use global history, all make sense.

Just one question to confirm, I noticed that contrary to Ctrl+Up, Ctrl+R and autosuggestions both require not just pressing Enter, but actually running some other command to refresh the global history - is there a reason for why history is not refreshed for them in precmd?

Example:

By the way, I've been contemplating implementing the same Shift behavior in Zsh. I think it can be reproduced exactly with just one difference. After selecting text with Shift+Arrows you'll have to use a shortcut other than the standard you've configured in the terminal to copy to clipboard.

Interesting idea, before I comment too much I'd like to clarify my understanding first, my first impression is that it will only be useful for "copy region to clipboard" operation, because "select to delete" or "select to replace" would require same or more number of keypresses comparing to a simple deletion using Ctrl+{,Shift+}{Delete,Backspace} keys - is that right or am I missing something here?

romkatv commented 4 years ago

I noticed that contrary to Ctrl+Up, Ctrl+R and autosuggestions both require not just pressing Enter, but actually running some other command to refresh the global history - is there a reason for why history is not refreshed for them in precmd?

Example:

  • Open two terminals side by side
  • In terminal 1 run echo 1
  • In terminal 2 run echo 2
  • Back in terminal 1, press Enter, then try Ctrl+R or echo<Up>, both will not see echo 2 until you run something else first in terminal 1.

I confirm this. Looks like a zsh bug at the first glance. I'll dig into it when I get some free time.

Interesting idea, before I comment too much I'd like to clarify my understanding first, my first impression is that it will only be useful for "copy region to clipboard" operation, because "select to delete" or "select to replace" would require same or more number of keypresses comparing to a simple deletion using Ctrl+{,Shift+}{Delete,Backspace} keys - is that right or am I missing something here?

People use shift-select + delete in text editors even though the same logic applies there. If I were to implement this feature in zsh, I'd make delete and backspace work on selection so that it works the way people expect.

I should've mentioned that this is all very speculative. If I could create true terminal selection with Shift-Arrows (the same kind of selection you can produce with a mouse), I would definitely do it but zsh-only selection with custom keys to copy/cut/paste doesn't sound very exciting to me. But maybe am I'm wrong. Maybe I would like it if I tried it.

tumd commented 4 years ago

Sorry for hijacking this issue, but it's called 'Feedback' which is a very general topic, and that's what this post is about.

I was wondering if this function would be acceptable to include in z4h. It's quite subtle until you realize you've used it without knowing it. It does happen from time to time (at least for me) you find or grep for files, copying one of the results and pasting it to your prompt right after you've written cd.

Another thing; I just noticed that e93734c sets the $ZSH variable (?), the same variable-name I have been using for my own zsh-snippets located in ~/.zsh/. And (without actually test it yet by updating z4h) I guess there might be a collision.

ZSH=${ZSH:-${ZDOTDIR:-$HOME}/.zsh}

() {
  for config_file ($ZSH/rc/*.zsh) z4h source $config_file
}

What would be the recommended way to include your own zsh-folder similar to the above?

Thank you for your awesome work and inspiring code. :)

romkatv commented 4 years ago

Sorry for hijacking this issue, but it's called 'Feedback' which is a very general topic, and that's what this post is about.

Sure, feedback is welcome.

I was wondering if this function would be acceptable to include in z4h. It's quite subtle until you realize you've used it without knowing it. It does happen from time to time (at least for me) you find or grep for files, copying one of the results and pasting it to your prompt right after you've written cd.

Maybe, although the motivation is rather low. I don't recall ever having an error from cd because I'd passed an existing file that's not a directory as an argument.

If some feature cannot be simply enabled by the user through their own config, or if enabling it correctly is difficult, tricky or error prone, then it's a strong signal that it must be provided by z4h. This cd function is not of this kind. You can define it in your zshrc or whatever.

Another thing; I just noticed that e93734c sets the $ZSH variable (?)

If this is a question, the answer is yes, it does set ZSH parameter unless it was already set.

the same variable-name I have been using for my own zsh-snippets located in ~/.zsh/. And (without actually test it yet by updating z4h) I guess there might be a collision.

I guess you'd better test it.

What would be the recommended way to include your own zsh-folder similar to the above?

Not sure I understand what you are asking. If you want to create ~/.zsh and point ZSH to it, go ahead. If understand you currently, you've already done that, so 👍

(FWIW, I personally don't find ZSH pointing to ~/.zsh useful. It hardly saves typing and it increases complexity due to indirection and opens up an avenue for clashes. Note that ZSH is used by Oh My Zsh.)

maximbaz commented 4 years ago

Want to share a couple of more observations regarding how file completion works, would be nice if it these can be improved:

  1. I noticed that tab completion doesn't cross mountpoint boundaries, for example my /run/user/1000 is mounted on tmpfs, so $ vim /run/<Tab> will not list anything inside /run/user/1000/. I use btrfs with multiple subvolumes, so I stumble upon this quite often. Not critical, but slightly annoying 🙂

  2. In a git repo, try git log -p -- <Tab>, you will only see autocompletions for the files and folders in current dir, but not recursive like you would get with $ vim <Tab>.

romkatv commented 4 years ago
  1. I noticed that tab completion doesn't cross mountpoint boundaries

This is intentional. Weird and bad things can happen if you traverse all directories. To stay on the safe side I made tab completion in z4h stay within a single filesystem.

I've added a note to make it customizable although I don't know yet how exactly it should look like. Perhaps a blacklist of target filesystems into which it's not allowed to recurse if the search has started from another filesystem? The current behavior would be described as blacklist='*' (do not recurse into any filesystems that are different from the starting point). Setting blacklist to '' (empty) would allow recursing into everything. When you notice that recursing into devtmpfs is not something you want, you can add it to the blacklist.

Would that work for you?

  1. In a git repo, try git log -p -- <Tab>, you will only see autocompletions for the files and folders in current dir, but not recursive like you would get with $ vim <Tab>.

Yeah, this really sucks. Not only git completion is dead slow, it also drops most of the information it computes on the floor forcing you to repeatedly perform (slow!) completion.

To fix this one would have to change/patch/hack git completion. No changes in zsh4humans required. It's not possible to do it without changing git completion.

I've added it to my TODO list but it's a good task for anyone else to pick up. It's well isolated and independent from zsh4humans.

What would be really cool is to implement git completion on top of gitstatus. It has all the info. Then it would be super fast. This is just musings though. I suspect it's a ton of work.

maximbaz commented 4 years ago

Weird and bad things can happen if you traverse all directories. Setting blacklist to '' (empty) would allow recursing into everything. When you notice that recursing into devtmpfs is not something you want, you can add it to the blacklist.

I see. I think the use-case that I have (which would apply to btrfs, zfs, and maybe others) can be solved without any configuration, if z4h would by default traverse not only the filesystem of the starting point, but also filesystem of where the / is mounted (e.g. $ mount | awk '/on \/ / {print $5}'). I think this should be safe? And I doubt anyone would really ask for more (but let's see).

UPDATE: re-reading your message, I think what you meant by "blacklist='*' (do not recurse into any filesystems that are different from the starting point)" is not what I initially understood, if you are suggesting that if starting point filesystem if btrfs, and a folder is discovered during traversal is mounted also on btrfs, then that folder will be traversed - then this approach will work perfectly well as well, and again maybe configuration is not needed altogether.

If not, then I would suggest providing whitelist instead of blacklist, so that users could just add whitelist='btrfs' and still benefit from ignoring everything else in mount just like vanilla z4h does, without spending too much time on configuring blacklist very precisely.

To fix this one would have to change/patch/hack git completion. No changes in zsh4humans required. It's not possible to do it without changing git completion.

Makes sense!

romkatv commented 4 years ago

I see. I think the use-case that I have (which would apply to btrfs, zfs, and maybe others) can be solved without any configuration, if z4h would by default traverse not only the filesystem of the starting point, but also filesystem of where the / is mounted

That's an interesting idea. I implemented this if find is GNU. Let me know whether this works well for you.

maximbaz commented 4 years ago

Sadly find doesn't seem to know what btrfs is:

$ command find / . -maxdepth 0 -printf '%F\n'
unknown
unknown

While there are alternative ways to retrieving the filesystems, like:

$ command df --output=fstype . / | command sed -e 1d
btrfs
btrfs

We won't be able to plug them into find command to actually run the search, find . -fstype btrfs finds nothing 😞

I found some old bug mentioning this issue, but I don't think it is very trustworthy, e.g. contrary to what they say, df definitely returns correct info.

romkatv commented 4 years ago

Sadly find doesn't seem to know what btrfs is:

That's unfortunate. I've changed the code so that if / or . use "unknown" filesystem, the code reverts to the old behavior of using -xdev.

If you are feeling generous, mind submitting a bug against GNU find? Would be nice if it was fixed. Or maybe they'll tell you how to fix your system so that you get btrfs instead of unknown.

We won't be able to plug them into find command to actually run the search

Yeah, I know about this gotcha. That's why I retrieve file system type with find.

romkatv commented 4 years ago

If you are feeling generous, mind submitting a bug against GNU find?

The bug you've found is still open. Maybe post a comment there? Since now df displays btrfs filesystem type for subdirectories of btrfs mounts (this is true, right?), it's not entirely unreasonable to check how df does that and to do the same in find.

maximbaz commented 4 years ago

Yep, I'll take care of it and report back when there is an update 👍

romkatv commented 4 years ago
  1. In a git repo, try git log -p -- <Tab>, you will only see autocompletions for the files and folders in current dir, but not recursive like you would get with $ vim <Tab>.

Yeah, this really sucks. [...] I've added it to my TODO list [...]

I took a stab at it. If you update zsh4humans and type git add <TAB>, you'll see the list of all unstaged and untracked files. Let me know how it works for you.

There is one downside as far as I can see. If you create a directory with a thousand files in it and type git add <TAB>, you'll get a long list of files. If your goal is to add all of them, with normal git completions you would add the directory but it's not offered as a completion in zsh4humans. One way to improve the UI is to inject a bunch of directories into the list of completion candidates. There is a question of which directories to inject and I think it would be sensible to inject the smallest set of directories that partitions the set of files in the maximum number of ways. For example, given these files:

a/b/c/1
a/b/c/2
a/b/1
a/b/d/1

These would be the injected directories:

a/b
a/b/c

a/b allows you to select all files. a/b/c selects the first two files. There is no need to inject a because it selects the same files as a/b. There is no need to inject a/b/d because it selects the same files as a/b/d/1.

WDYT?

maximbaz commented 4 years ago

It is really really good! I tested so far git add, git checkout, git log, all behave perfectly.

Relative paths work too, i.e. if you are in a subfolder, <Tab> will only suggest children, while ../<Tab> will include parent and its nested files.

I completely agree with the proposed logic for injecting directories 👍

romkatv commented 4 years ago

FYI: You can now customize terminal title with zstyle. Here are the defaults:

if (( P9K_SSH )); then
  zstyle ':z4h' term-title-preexec '%n@%m: ${1//\%/%%}'
  zstyle ':z4h' term-title-precmd  '%n@%m: %~'
else
  zstyle ':z4h' term-title-preexec '${1//\%/%%}'
  zstyle ':z4h' term-title-precmd  '%~'
fi

Values get prompt-expanded. In preexec $1 is the original command and $2 is the same command after alias expansion.

If you explicitly set value to empty, title won't be set.

romkatv commented 4 years ago

@maximbaz Heads up: I've changed the API for specifying terminal title. Here are the new defaults (the values are the same as before but the context is different):

zstyle :z4h:term-title:ssh   precmd  '%n@%m: %~'
zstyle :z4h:term-title:ssh   preexec '%n@%m: ${1//\%/%%}'
zstyle :z4h:term-title:local precmd  '%~'
zstyle :z4h:term-title:local preexec '${1//\%/%%}'

I did this after seeing how you've used the previous API and realizing that it causes terminal title flickering when you start zsh. The new API no longer requires you to check P9K_SSH and thus you can place zstyle definitions at the top of ~/.zshrc above z4h init. This will remove flickering.

maximbaz commented 4 years ago

Thanks, this is awesome 👍

maximbaz commented 4 years ago

Would it be possible to support <Alt-Up> shortcut inside <Alt-Down>? It happened to me a few times that I would press <Alt-Down> and accidentally press Tab on a wrong entry (so I enter a wrong subdir), and it's then annoying that I need to press Esc, then go up with <Alt-Up>, and then restart <Alt-Down> from there. If I could just correct my mistake while I'm in <Alt-Down> menu without all that hassle... 😄

romkatv commented 4 years ago

That's a good idea. I like it.

Alt+Left should also work and it's a more natural choice to revert an accidental Tab on a wrong fzf entry. E.g., if you intend to go into foo/ but accidentally press Tab on bar/baz/, Alt+Left will take you back while Alt+Up will take you to bar/.

For consistency, Alt+Right should also work: Alt+Left followed by Alt+Right should be a no-op. It's possible to make Alt+Down behave identically to Tab but this makes sense only within fzf from Alt+Down and not within Tab completion or Ctrl+R (other Alt+Arrow bindings can be made to work consistency in all instances of fzf).

I've added this to my TODO list. Hopefully will get to it once I'm done with the Real Life backlog.

romkatv commented 4 years ago

Heads up: I've pointed stable channel of zsh-syntax-highlighting to my fork with a bunch of optimizations that make it about 2x faster. Once you run z4h update, you'll be using this code. If you notice anything weird or broken w.r.t. syntax highlighting, let me know.

I've sent a PR upstream but there is no ETA yet.

P.S.

You can define the channel for a package like this:

zstyle ':z4h:zsh-syntax-highlighting' channel stable

Available channels:

There is also a special channel called command that allows you to override the command used to install the package. For example, here's how you can instruct z4h to use zsh-syntax-highlighting from a local repo that you've manually cloned to ~/zsh-syntax-highlighting (I do this when testing local changes):

zstyle ':z4h:zsh-syntax-highlighting' channel command 'zf_ln -s -- ~/zsh-syntax-highlighting $Z4H_PACKAGE_DIR'

Channel changes come into effect only when you run z4h update.

maximbaz commented 4 years ago

Some early feedback on ssh and history retrieval, based on your own dotfiles (I realize it is very early feedback):

This way the history is synchronized bi-directionally, I can edit the history file locally and on next ssh it will be uploaded and used.

This is an cool feature! One other interesting scenario it allows is to share a single history file with a set of remotes, e.g. all servers in one datacenter, which is pretty easy to implement in a custom z4h-ssh-configure().

romkatv commented 4 years ago
  • To store history files locally I'd put them in some folder, not to clutter $HOME (a good candidate could be $XDG_DATA_HOME), so instead of:

    zstyle -e ':z4h:ssh:*' retrieve-history 'reply=($ZDOTDIR/.zsh_history.${(%):-%m}:$z4h_ssh_host)'

    I went for:

    zstyle -e ':z4h:ssh:*' retrieve-history 'reply=($XDG_DATA_HOME/zsh-history/${z4h_ssh_host##*:})'

LGTM. You can replace ${z4h_ssh_host##*:} with $z4h_ssh_host. Hostnames cannot contain colon.

(I personally don't care about cluttering $HOME.)

(this also cuts off my local hostname from the name of the file to make it a bit more clean)

I use several physical local machines for work and I store zsh history in a private Git repo. To avoid merge conflicts, local history on each machine is stored in ~/.zsh_history.${(%):-%m}. On startup, all ~/.zsh_history* files are read with fc -RI. This means that commands I type on machine A are available in the command history when I'm working on machine B.

Remote history (commands that I run over ssh) is also stored in the private GIt repo. Once again, to avoid merge conflicts, the file names contain local host name: ~/.zsh_history.${(%):-%m}:$z4h_ssh_host.

When ssh'ing to a remote, I assume the point of this exercise is to upload the local copy of the history file back to the ~/.zsh_history on remote, where remote $HISTFILE will point.

This will loose history when you open multiple simultaneous ssh connections, so it's now how I do it. Instead, I send over .zsh_history.*:$z4h_ssh_host and read these files with fc -RI on the remote host.

One other interesting scenario it allows is to share a single history file with a set of remotes, e.g. all servers in one datacenter

This will cause history loss and merge issues. What I do instead, is send multiple history files.


Here's how my configs work.

  1. HISTFILE is set in ~/.zshenv: https://github.com/romkatv/dotfiles-public/blob/d4bd5ac92ae89eb3beb1c5e2bdb2df861cbab720/.zshenv#L9. This override kicks in only on the local host. On the remote host HISTFILE is set by z4h to its default value of ~/.zsh_history. Note that setting HISTFILE is necessary only if you are using multiple local machines and want to synchronize history between them through a private Git repo.
  2. HISTFILE is read on startup automatically, while extra history files are read manually in ~/.zshrc: https://github.com/romkatv/dotfiles-public/blob/8334d8932eabddaf4569de4c3e617b2e911851b4/.zshrc#L68-L73.
  3. History is retrieved from remote hosts with this setting: https://github.com/romkatv/dotfiles-public/blob/8334d8932eabddaf4569de4c3e617b2e911851b4/.zshrc#L82. As noted above, ${(%):-%m} in the file name is necessary only in the case of multiple local hosts with synchronized history.
  4. All history for host X is sent to host X with this code: https://github.com/romkatv/dotfiles-public/blob/8334d8932eabddaf4569de4c3e617b2e911851b4/.zshrc#L84-L90.
  5. In my private dotfiles I have additional code that defines groups of machines. Whenever I'm connecting to a machine from group G, history from all machines in this group is sent to it. Here's a simplified version that assumes two groups of machines -- servers and routers -- with hard-coded hostnames of all machines:
    
    function my-ssh-configure() {
    emulate -L zsh
    (( $+functions[z4h-ssh-configure] )) && z4h-ssh-configure
    local -a servers=(foo bar)
    local -a routers=(baz qux)
    local -a hosts
    case $z4h_ssh_host in
    ${~${(j:|:)routers}}) hosts=($routers);;
    ${~${(j:|:)servers}}) hosts=($servers);;
    esac
    local file
    for file in $ZDOTDIR/.zsh_history.*:${^hosts}(N); do
    (( $+z4h_ssh_send_files[$file] )) && continue
    z4h_ssh_send_files[$file]='"$ZDOTDIR"/'${file:t}
    done
    }

zstyle ':z4h:ssh:*' configure my-ssh-configure



This is very flexible. If you like, you can send local history to some (or all) machines. You can assign more than one group to a machine. You can also define groups dynamically based on their host names (e.g., anything that matches `*.prod.microsoft.com` could be one group).

Basically, whenever you work on machine X (be it local or remote), you can make history from any set of machines available on it. It's up to you to decide what that set should be.
romkatv commented 4 years ago
  1. All history for host X is sent to host X with this code: https://github.com/romkatv/dotfiles-public/blob/8334d8932eabddaf4569de4c3e617b2e911851b4/.zshrc#L84-L90.

You might wonder what the point of this might be. Doesn't host X already have all its history in ~/.zsh_history?

The answer is that host X may have been reimaged. The only storage I consider persistent is Git, so history is ultimately stored in a Git repo. You can reimage a local or a remote machine and still have all configs, history, etc.

romkatv commented 4 years ago

Heads up: User rc files no longer get compiled by default. If you want them compiled to speed up Zsh startup slightly, add this to .zshrc:

z4h compile -- $ZDOTDIR/{.zshenv,.zprofile,.zshrc,.zlogin,.zlogout}

See z4h help compile for caveats.

romkatv commented 4 years ago

Heads up: Widgets that change the current working directory are now bound to Shift+Arrows and cd-key zstyle no longer exists. If you want to keep using Alt+Arrows, add this to your .zshrc:

z4h bindkey z4h-cd-back    Alt+Left
z4h bindkey z4h-cd-forward Alt+Right
z4h bindkey z4h-cd-up      Alt+Up
z4h bindkey z4h-cd-down    Alt+Down

You can also use plain bindkey. z4h bindkey is syntax sugar.

maximbaz commented 4 years ago

Thank you very much for the detailed replies, very interesting and educational! I didn't know about fc -RI, merging history files makes a lot of sense, your workflow is indeed very flexible.

Just out of curiosity, how do you keep zsh history in git repo, do you have a periodic timer that executes git commit, git pull, git push on all your physical local machines?

Heads up: I've pointed stable channel of zsh-syntax-highlighting to my fork with a bunch of optimizations that make it about 2x faster.

This works well, the only tiny regression I noticed is that newly installed binaries are not colored properly even after a rehash call. For example, add this to your .zshrc:

install-foobar() {
    echo '#!/bin/sh\necho foobar' > ~/bin/foobar
    chmod +x ~/bin/foobar
    rehash
}

(Assuming ~/bin exists and is in your $PATH)

Then in your terminal type:

$ install-foobar
$ foobar

Notice that foobar is colored in red, while on the upstream zsh-syntax-highlighting the color is correct after rehash call.

Heads up: Widgets that change the current working directory are now bound to Shift+Arrows

Out of curiosity, why? Are you planning some other functionality to Alt+Arrows?

You can also use plain bindkey. z4h bindkey is syntax sugar.

This is a neat syntax sugar! Helpful because otherwise you always end up writing a comment with human-readable key combination.

Not very important and might be out of scope, the only binding I couldn't convert to z4h bindkey is the one that consists of two consequent keypresses, namely ^V^V, I wonder if this should be supported as z4h bindkey edit-command-line Ctrl+V Ctrl+V?

romkatv commented 4 years ago

Just out of curiosity, how do you keep zsh history in git repo, do you have a periodic timer that executes git commit, git pull, git push on all your physical local machines?

I have two Git repos where I store my stuff: dotfiles-public and dotfiles-private. Both are overlaid over $HOME (that is, their worktree is $HOME), so I can version any file without moving or symlinking it. I sync with this: https://github.com/romkatv/dotfiles-public/blob/master/dotfiles/functions/sync-dotfiles, which I run manually. It synchronizes both repos.

The point of using a separate file for history on every machine is twofold. The first reason is that it gives local history precedence (when I hit Ctrl+R, commands that I ran on machine A are displayed before the commands from machine B). The second reason is that it avoids merge conflicts (every history files is modified only on one machine, so no conflicts when pulling).

My private Git repo doesn't contain just history. There are also various configs and scripts that I don't want to share publicly.

There are a few more important bits to my dotfiles management:

When I press Ctrl+P once, I get public showing up in prompt and Git status in prompt corresponds to dotfiles-public repo. All git commands also target this repo. So if I'm in ~/foo/bar and want to add ./baz to dotfiles-public, I hit Ctrl+P and type git add baz, git commit, etc. If I hit Ctrl+P another time, it activates dotfiles-private. Another Ctrl+P gets me to normal state.

It's very convenient. It's much better than anything else I've tried.

Heads up: I've pointed stable channel of zsh-syntax-highlighting to my fork with a bunch of optimizations that make it about 2x faster.

This works well, the only tiny regression I noticed is that newly installed binaries are not colored properly even after a rehash call. For example, add this to your .zshrc:

install-foobar() {
    echo '#!/bin/sh\necho foobar' > ~/bin/foobar
    chmod +x ~/bin/foobar
    rehash
}

(Assuming ~/bin exists and is in your $PATH)

Then in your terminal type:

$ install-foobar
$ foobar

Notice that foobar is colored in red, while on the upstream zsh-syntax-highlighting the color is correct after rehash call.

Cannot reproduce. I get green foobar.

Can you confirm that you get red foobar if you follow your own instructions? What if you run z4h update first?

Heads up: Widgets that change the current working directory are now bound to Shift+Arrows

Out of curiosity, why? Are you planning some other functionality to Alt+Arrows?

I want to release v3 next week, so I'm cleaning things up and making sure the basics work with the default .zshrc. Alt+Arrows is the most natural key binding for z4h-cd-* widgets for PC users because they mimic back/forward button bindings in web browsers and a few other apps. Unfortunately, Mac users are accustomed to having Alt+Arrows (or rather Option+Arrows) moving the cursor by words. This functionality is more important than z4h-cd-* widgets, so if I have to choose which one works by default I'll go with the cursor movement.

I think I've already mentioned that v4 will have a wizard that will take care of configuring key bindings. This is still my plan.

Not very important and might be out of scope, the only binding I couldn't convert to z4h bindkey is the one that consists of two consequent keypresses, namely ^V^V, I wonder if this should be supported as z4h bindkey edit-command-line Ctrl+V Ctrl+V?

The current version of z4h bindkey supports a small subset of key combinations supported by builtin bindkey. I'll add new ones later. That said, I just added two more, including the one you are asking for.

z4h bindkey edit-command-line 'Ctrl+V Ctrl+V'

Note the quotes. z4h bindkey allows you to bind more than one key sequence at once, so without quotes it's also a valid call.

# Bind Ctrl+E and End to end-of-line.
z4h bindkey end-of-line Ctrl+E End

I've also added support for single-key bindings.

z4h bindkey z4h-cd-up 'Ctrl+X U'

Note that z4h bindkey automatically defines lower and upper case bindings, so Ctrl+X U is translated to ^XU and ^Xu.

If you are binding Ctrl+V Ctrl+V, you might want to delete the standard Ctrl+V binding with bindkey -r '^V'. Otherwise pressing Ctrl+V will be ambiguous, so zsh will wait for 10ms * KEYTIMEOUT (this is 200ms by default in z4h) for you to press Ctrl+V a second time. If you don't do it fast enough, you'll get quoted-insert. Vi keymap users have to live with this (Esc is a prefix of pretty much every key binding) but emacs keymap users can quite easily avoid defining bindings with a shared prefix. I find Ctrl+V useful, so if I wanted to to have a binding for edit-command-line, I'd choose something other than Ctrl+V Ctrl+V (I'd most likely choose Alt+Something because it's easy to press and there are many free keys not bound to anything with Alt).

romkatv commented 4 years ago

@maximbaz Try updating z4h and removing this line from your config: https://github.com/maximbaz/dotfiles/blob/2a20b995bc9495a1866a1fd1faf98d71fa73dbff/.zshrc#L6

zstyle ':z4h:ssh:router' passthrough yes

Does it still work as before?

I'd like to have function ssh() { z4h ssh "$@" } in the default .zshrc, so I've implemented automatic passthrough logic so that ssh just works without requiring manual zstyle ... passthrough yes overrides.

maximbaz commented 4 years ago

Cannot reproduce. I get green foobar.

My apologies, I made a mistake while preparing the test example. Turns out the key here is that rehash is called in a subshell, i.e. the example should have used regular braces like so:

install-foobar() (
    echo '#!/bin/sh\necho foobar' > ~/bin/foobar
    chmod +x ~/bin/foobar
    rehash
)

To be honest it calls for refactoring on my side (there is really no reason for me to use subshell), but this still might be worth solving.

if I wanted to to have a binding for edit-command-line, I'd choose something other than Ctrl+V Ctrl+V (I'd most likely choose Alt+Something because it's easy to press and there are many free keys not bound to anything with Alt).

Good point... :D

Try updating z4h and removing this line from your config: Does it still work as before?

Not nicely, I can connect but I get a few lines of syntax error (line 1 column 5) when I ssh to my router if I remove this line.

romkatv commented 4 years ago

My apologies, I made a mistake while preparing the test example. Turns out the key here is that rehash is called in a subshell

rehash in a subshell has no effect.

So the regression in my zsh-syntax-highlighting fork is the following:

% touch ~/bin/foobar
% chmod +x ~/bin/foobar
% foobar

The original zsh-syntax-highlighting highlights foobar in green on the last line. My fork highlights it in red. I'll find a way to fix this.

Not nicely, I can connect but I get a few lines of syntax error (line 1 column 5) when I ssh to my router if I remove this line.

I need to fix this but this error message isn't enough to figure out what the problem is.

Could you add set -x at the top of $Z4H/zsh4humans/fn/-z4h-cmd-ssh and $Z4H/zsh4humans/sc/ssh-bootstrap (under the shebang), restart zsh and run ssh router?

maximbaz commented 4 years ago

There you go: https://paste.maximbaz.com/f5d5f7f501ef

UPDATE: wait, did't add set -x to the second file, stand by... UPDATE 2: adding set -x didn't make any difference but I updated the link with the new output anyway, you can look at it now

romkatv commented 4 years ago
+-z4h-cmd-ssh:266> ssh -T router '/bin/cat >/tmp/z4h-ssh.maximbaz.1999541.1598085653.13144'
syntax error (line 1 column 5)

Interesting. Does the following command also produce this error?

command ssh router '/bin/cat >/dev/null' </dev/null

Do you know why? What is the login shell on router?

cyrinux commented 4 years ago
+-z4h-cmd-ssh:266> ssh -T router '/bin/cat >/tmp/z4h-ssh.maximbaz.1999541.1598085653.13144'
syntax error (line 1 column 5)

Interesting. Does the following command also produce this error?

command ssh router '/bin/cat >/dev/null' </dev/null

Do you know why? What is the login shell on router?

Hi @romkatv , we got the same router with @maximbaz . Yes the above command produce the same error.

I don't know exactly what shell it is, but it is a mikrotik router with the last RouterOS (6.47).

$ command ssh router '/bin/cat >/dev/null' </dev/null
syntax error (line 1 column 5)