1

I am using zsh with the prezto configuration framework. I want to be able to type in terms like 1 + 2 * (3 / 4) and get "2.5" out of it.

Those terms could be recognized because they only consist of certain characters: 0123456789 +-*/(). For everything more complicated I am fine with opening a dedicated program (like a python shell).

solutions that are not good enough

  1. Use a program: I don't want to type python -c before my term or pipe it into bc. That's too much typing and I hope it can be done better.
  2. Use a function or an alias: There are some solutions out there that allow to do these calculation when prefing it with a c or = or calc, like in = 1 + 2 * (3 / 4). That is nice but basically the same thing as point 1.
  3. Use another shell: I know about xonsh and I am sure there are other shells that can do that. But I like zsh and I don't want to change shells.

what might work

  1. zsh plugin: I could imagine a zsh-plugin that finds if a command matches a certain regex and then pipes it into python/bc/whatever. Somewhat similar to what the automatic cd thing does when you enter just the name of a directory. I don't know if this is possible though. I would love to hear some hints so I could write that.
  2. Intercept command not found: Similar to 4. perhaps it is possible to intercept the behaviour of zsh when a command is not found. But again, I don't know how I would do that.
  3. alias all the numbers: I guess you could write an alias for all the numbers so that they are commands that take the rest of the math-string as arguments. That might work, but seems pretty hacky. Also 'all the numbers' is quite a lot aliases, even when autogenerating the code for it.

I would like to hear ideas on how to tackle this problem. I am not afraid of writing code, but I would prefere a clean solution over a hacky one.

2
  • You realize that you don't have to pipe anything into bc, right? That you can just run it interactively? I hope three keystrokes (five if you count ^D to exit bc) isn't going too far out of your way. Or (since you mention it first) that you can alias py python3 and do maths interactively there in the same number of keystrokes? Commented Apr 29, 2022 at 22:48
  • @DopeGhoti I understand, but typing anything more then the maths term is an extra mental step that I would like to avoid. Similar like autocd, where it is enough just to type in the directory you want, not the cd command. Commented May 3, 2022 at 15:01

1 Answer 1

2

Intercepting a command before it's executed is possible, but that's too late: the command has already been parsed and aliases have been expanded. So for example 2 * 3 would appear as the command not found 2 (assuming there is no command by that name) followed by the list of files in the current directory.

Instead you need to intercept the command as soon as it's submitted, before it's even parsed. So you need to hook into the command line interface, not into the command execution engine. I don't think there's a generic way to do that: the only way I can think of is to override the enter key command and its siblings, and make them rewrite the command line. That's not pretty, but it meets your requirements.

The main enter key command is accept-line, bound to ^J and ^M. You can override it as follows (you'll need to do this for the other similar widgets that you use, e.g. accept-and-hold, accept-line-and-down-history, etc.):

function my-accept-line {
    my-rewrite-accepted-line "$@"
    zle accept-line "$@"
}
zle -N my-accept-line
bindkey '^J' my-accept-line
bindkey '^M' my-accept-line

Now, for the function to rewrite the accepted line. The line content is in BUFFER. The only difficulty is the logic to do the rewriting. Maybe something like this, which requires the line to start with a number (not a variable, that would be too ambiguous) optionally preceded by unary operators and parentheses, followed by a binary operator:

function my-rewrite-accepted-line {
  emulate -L zsh
  setopt extended_glob
  if [[ ${BUFFER// /} == [\(+\-]#([0-9]##(|.[0-9]#)(|e(-|+|)[0-9]##)|0x[0-9A-Fa-f]##)[-+*/%\&\|\^]* ]]; then
    BUFFER="echo \$(($BUFFER))"
  fi
}

Please note that I have only done minimal testing with a bare zsh. I haven't investigated whether this may interfere with Prezto.

1

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.