emacs-libvterm
emacs-libvterm copied to clipboard
Using a tmp buffer instead of less when piping the output of a command
I thought it would be nice to pipe the output of commands to a tmp buffer. I came up with this solution.
#.bashrc
function menos()
{content="$(</dev/stdin)";
vterm_cmd menos "$content"; }
}
;; init.el
(defun my/menos (content)
(switch-to-buffer (make-temp-name "menos-"))
(insert content))
(push (list "menos" 'my/menos)
vterm-eval-cmds)
Now I can do something like this ls -l | menos and a buffer will open with the output of that command. menos is less in spanish.
The problem with this solution
It does not respect the format of command output. How to make it to respect the format of the command output.
You can reproduce the problem with the function say. E.g., say $(ls -l). I think that we are not correctly handling the newlines when executing elisp. The string arrives at vterm--eval already without the newlines, so probably we drop them in the where we create elisp_code in vterm-module.c (but I might be wrong).
I think that this is a bug.
@ram535 I found a solution which works well for piping commands to emacs:
The bash function uses mapcar which reads from stdin into an array, the newlines are replaced with '#nl#' before being sent to vterm_cmd.
vp() {
# If receiving from stdin
if [ -p /dev/stdin ]; then
pipe_input=()
# read all lines into an array
mapfile -n 0 -t pipe_input || {
echo "ERROR: mapfile failed in" "${FUNCNAME}"
exit 1
}
# using the array directly fails for long outputs
# vterm_cmd bash_pipe "${pipe_input[@]}"
# replace newlines with '#nl#'
nl_arr=$(printf '%b#nl#' "${pipe_input[@]}")
vterm_cmd bash_pipe "${nl_arr}"
# the preceding fails for files containing escape chars like ''
# maybe use the '%q' which escapes non-printable chars instead?
# null_arr=$(printf '%q\0' "${pipe_input[@]}")
else
vterm_cmd bash_pipe "$*"
fi
}
The receiving function then replaces '#nl#' with newlines and copies the result to a temporary buffer and to the clipboard.
(defun vterm-bash-pipe(vterm-out)
"Read the output of bash `vp` function defined in ~/.bash_vterm.
The output is appended to the *vterm-out* buffer and copied to the clipboard."
(let ((buf (get-buffer-create "*vterm-out*"))
;;`vp` replaces newlines with '#nl#'
(out (replace-regexp-in-string "#nl#" "\n" vterm-out))
(now (format-time-string "%Y%d%m-%H%M")))
(set-buffer buf)
;; add date + time header
(goto-char (point-max))
(insert "\n## " now "\n")
(let ((beg (point)))
(insert out)
(let ((end (point)))
(insert "################")
;; copy the region to clipboard
(clipboard-kill-ring-save beg end)))))
https://gist.github.com/phammar/421982159ad1995e89d7ea645fb76927
This is what I've come up with for my setup:
In ~/.bashrc:
vterm_less() {
printf "\e]51;Eless \""
cat | base64 -w0
printf "\"\e\\"
}
if [[ "$INSIDE_EMACS" = 'vterm' ]]; then
alias less=vterm_less
fi
In ~/.emacs.d/init.el:
(defun vterm-less (content)
(let ((less-buffer (get-buffer-create (make-temp-name "vterm-less-"))))
(with-current-buffer less-buffer
(switch-to-buffer less-buffer)
(special-mode)
(insert (base64-decode-string content)))
)
)
(defun my-vterm-mode-hook ()
(add-to-list 'vterm-eval-cmds (list "less" #'vterm-less))
)
(add-hook 'vterm-mode-hook #'my-vterm-mode-hook)
It works fine for up to 100k lines. After 10k lines it takes quite a bit of time to pass the content though. I suspect this is because how the event loop is written. Nevertheless, it works and preserves all formatting.
This works awesome for getting command output into Emacs! Thank you all, @ram535, @phammar, @knazarov! I think this would be an awesome feature to have built-in which would greatly enhance Emacs/vterm integration.
I'm using @knazarov's solution which works great so far, with some minor annoyances:
- I get some special chars as codes in the buffer, eg. Catalan apostrophe
’as\342\200\231, accents such asías\303\255and some emojis and Nerd icons. ANSI colors are correctly ignored. No big deal, so far. I'll get to it if I have time (any advice appreciated). - I've found the hook code is not needed.
@Sbozzolo What would be needed to integrate this in the package in order to submit a PR? Some ideas:
- A
.bashrccommand documented in the readme, such asvterm-pipe. - Add the Elisp function, such as
vterm-pipe-read. - No clipboard copying (at least by default). Maybe a
vterm-pipe-copyfunction based on the above to get the piped text straight into the kill ring. - Anything else?
Ideally, vterm-pipe-read would be basic plumbing, so it can be used to pipe to buffer, clipboard, etc. depending on some parameter, which would have corresponding shell commands as vterm-pipe-{buffer,copy,etc}. Not sure how to implement and expose it, thou.
Comments/feedback welcome.
None of the above worked for me in all my use cases, so I went for a simpler approach: use cat as the PAGER and rely on vterm builtin navigation functions.
Main drawback is that it fills up you vterm buffer. Should be as fast as vterm is able to display lines. Didn't try it out for huge outputs, feedback is very welcome.
Just add this to .bashrc
if [[ "$INSIDE_EMACS" = 'vterm' ]]; then
export PAGER=cat
fi
and use C-c C-p to jump to the previous prompt. Then navigate the output using copy-mode.
Jumping back and going to copy-mode can be done automatically if you add these utils:
In some script ~/my-pager:
#!/usr/bin/env sh
if [[ "$INSIDE_EMACS" = 'vterm' ]]; then
cat
printf "\e]51;Epager"
printf "\e\\"
else
less
fi
in .bashrc:
export PAGER=~/my-pager
in init.el:
(defun vterm-pager ()
(vterm-previous-prompt 1)
(vterm-copy-mode))
(add-to-list 'vterm-eval-cmds (list "pager" #'vterm-pager))
~@ram535~ @jixiuf @Sbozzolo Any feedback from maintainers about the possibility of accepting a PR for this feature as discussed in my prior comment?
EDIT: mentioned ram535 as maintainer originally by mistake, as he was the issue closer. Apologies for the noise.