this post was submitted on 29 Aug 2023
34 points (100.0% liked)

Linux

48052 readers
679 users here now

From Wikipedia, the free encyclopedia

Linux is a family of open source Unix-like operating systems based on the Linux kernel, an operating system kernel first released on September 17, 1991 by Linus Torvalds. Linux is typically packaged in a Linux distribution (or distro for short).

Distributions include the Linux kernel and supporting system software and libraries, many of which are provided by the GNU Project. Many Linux distributions use the word "Linux" in their name, but the Free Software Foundation uses the name GNU/Linux to emphasize the importance of GNU software, causing some controversy.

Rules

Related Communities

Community icon by Alpár-Etele Méder, licensed under CC BY 3.0

founded 5 years ago
MODERATORS
 

Some time ago I found xonsh which is a python-based shell. It had really good multiline support, and I am searching for a shell with sameish multiline support as xonsh. Fish shell also has good multiline support, it is around the same level, but it is not posix compatible. I want a shell that has that kind of level of multiline, but zsh (bash is also fine) compatible.

Does anyone know of one?

edit: based on the replies, I get this is unclear. My problem with zsh is that if i press enter and it starts a new line, I can't get back to the prevous line, because a new prompt is started. In fish this is possible, all lines are one prompt. But, fish is not posix compatible. So, I guess I want a posix-compatible shell with fish-like lines (multiple line) editing. I wanted zsh support to keep using my custom oh-my-zsh prompt, but remaking it for a new shell is not a big problem. Sorry for being unclear.

edit 2: solution is here! Thanks to @[email protected] I started thinking and made the following: When on the first line, enter accepts and alt-enter inserts a newline. When not on the first line, enter inserts a newline and alt-enter accepts. Here is the code to put in your .zshrc:

# MULTILINE!!!
bindkey '^[e' push-line-or-edit

# enter accepts when only one line found, else creates newline
function _zle_ml_enter {
    if ! [[ $BUFFER == *$'\n'* ]]; then
        zle accept-line
    else
        zle self-insert-unmeta
    fi
}
zle -N _zle_ml_enter
bindkey '^M' _zle_ml_enter

# alt-enter accepts when more than one line found, else creates newline
function _zle_ml_meta_enter {
    if [[ $BUFFER == *$'\n'* ]]; then
        zle accept-line
    else
        zle self-insert-unmeta
    fi
}
zle -N _zle_ml_meta_enter
bindkey '^[^M' _zle_ml_meta_enter

edit:
changed if [[ "$BUFFERLINES" -le 1 ]]; then to if ! [[ $BUFFER == *$'\n'* ]]; then and if [[ "$BUFFERLINES" -gt 1 ]]; then to if [[ $BUFFER == *$'\n'* ]]; then for improving detection. Also added alt-e shortcut because I had that configured myself but forgot to add here.

you are viewing a single comment's thread
view the rest of the comments
[–] [email protected] 4 points 1 year ago (1 children)

What behavior are you looking for? Could Zsh with some configuration work?

[–] [email protected] 1 points 1 year ago (1 children)

I updated my post to be clearer.

[–] [email protected] 1 points 1 year ago* (last edited 1 year ago) (1 children)

OK, well FWIW in Zsh you can use a keybind to trigger ZLE functions that turn your already-entered lines back into in-progress lines.

The most straightforward built-in function for this is push-line-or-edit:

At the top-level (PS1) prompt, equivalent to push-line. At a secondary (PS2) prompt, move the entire current multiline construct into the editor buffer. The latter is equivalent to push-input followed by get-line.

So let's say you want to trigger this with ctrl+e, all you need is:

bindkey '^e' push-line-or-edit
[–] [email protected] 1 points 1 year ago (2 children)

Thanks for your reply! This made me think: could I bind enter to newline and alt-enter to accept, which then made me think if it was possible to have enter to accept and alt-enter to newline on first line only, after that in reverse. That made me think of the script I added to the post.

But serious, without this I would have never thought of doing what I did.

[–] [email protected] 1 points 1 year ago

Ooh very cool! Thanks for sharing back your solution.

[–] [email protected] 1 points 1 year ago* (last edited 1 year ago) (1 children)

I started using this, it makes a lot of sense and I like it, thanks!

I can imagine myself forgetting how to accept multiline input with alt+enter, so I added a help message to _zle_ml_enter in the multiline case after the second line. It assumes setopt interactivecomments is already set:

EDIT: note that lemmy mangles the less-than symbol

# -- Run input if single line, otherwise insert newline --
# Key: enter
# Assumes: setopt interactivecomments
# Credit: https://programming.dev/comment/2479198
.zle_accept-except-multiline () {
  if (( BUFFERLINES <= 1 )) {
    zle accept-line
  } else {
    zle self-insert-unmeta
    if (( BUFFERLINES == 2 )) {
      LBUFFER+="# Use alt+enter to submit this multiline input"
      zle self-insert-unmeta
    }
  }
}
zle -N .zle_accept-except-multiline
bindkey '^M' .zle_accept-except-multiline  # Enter
[–] [email protected] 2 points 1 year ago* (last edited 1 year ago) (1 children)

So I changed some other things to improve detection. I will update the post. But nice idea adding a hint! I personaly did that in a hint command also specifying other shortcuts and aliases about my zsh config.

[–] [email protected] 1 points 1 year ago* (last edited 1 year ago)

Thanks again for posting your improvements! I will have them!

The idea here, checking for newline characters rather than counting lines, is to prevent it treating one line that is so long it wraps to the next as counting as a multiline input, right? So now I'm looking like

EDIT: lemmy is at least mangling ampersands here. Hard to believe it doesn't have proper code blocks yet...

# -- Run input if single line, otherwise insert newline --
# Key: enter
# Assumes: setopt interactivecomments
# Credit: https://programming.dev/comment/2479198
.zle_accept-except-multiline () {
  if [[ $BUFFER != *$'\n'* ]] {
    zle accept-line
    return
  } else {
    zle self-insert-unmeta
    if [[ $BUFFER == *$'\n'*$'\n'* ]] {
      local hint="# Use alt+enter to submit this multiline input"
      if [[ $BUFFER != *${hint}* ]] {
        LBUFFER+=$hint
        zle self-insert-unmeta
      }
    }
  }
}
zle -N .zle_accept-except-multiline
bindkey '^M' .zle_accept-except-multiline  # Enter

# -- Run input if multiline, otherwise insert newline --
# Key: alt+enter
# Credit: https://programming.dev/comment/2479198
.zle_accept_only_multiline () {
  if [[ $BUFFER == *$'\n'* ]] {
    zle accept-line
  } else {
    zle self-insert-unmeta
  }
}
zle -N .zle_accept_only_multiline
bindkey '^[^M' .zle_accept_only_multiline  # Enter

For pushing the line/multiline, I combine it with my clear function (ctrl+l):

# -- Refresh prompt, rerunning any hooks --
# Credit: romkatv/z4h
.zle_redraw-prompt () {
  for 1 ( chpwd $chpwd_functions precmd $precmd_functions ) {
    if (( $+functions[$1] ))  $1 &>/dev/null
  }
  zle .reset-prompt
  zle -R
}

# -- Better Screen Clearing --
# Clear line and redraw prompt, restore line at next prompt
# Key: ctrl+l
# Depends: .zle_redraw-prompt
.zle_push-line-and-clear () { zle push-input; zle clear-screen; .zle_redraw-prompt }
zle -N       .zle_push-line-and-clear
bindkey '^L' .zle_push-line-and-clear  # ctrl+l