Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Any way to get output back to emacs ? #6

Open
girip opened this issue Nov 3, 2018 · 13 comments
Open

Any way to get output back to emacs ? #6

girip opened this issue Nov 3, 2018 · 13 comments

Comments

@girip
Copy link

girip commented Nov 3, 2018

Thank you very much for writing this very nice package.

Is there a way to get kind of screenshot of the latest state as seen in the tmux window back to org-mode ?
I could not find any such option in tmux manual, anyone knows ?, but it would be helpful to make this even more interactive.

@ahendriksen
Copy link
Owner

Hi,

Very good question! I have looked into this, but not implemented anything.

It might be worthwile to look into the emamux package. I do not use it (yet), but this package might contain the functionality you need :)

Moreover, there is a programmatic way to get the output from tmux using bash. From the tmux man page:

     capture-pane [-aepPq] [-b buffer-name] [-E end-line] [-S start-line] [-t
             target-pane]
                   (alias: capturep)
             Capture the contents of a pane.  If -p is given, the output goes
             to stdout, otherwise to the buffer specified with -b or a new
             buffer if omitted.  If -a is given, the alternate screen is used,
             and the history is not accessible.  If no alternate screen
             exists, an error will be returned unless -q is given.  If -e is
             given, the output includes escape sequences for text and back‐
             ground attributes.  -C also escapes non-printable characters as
             octal \xxx.  -J joins wrapped lines and preserves trailing spaces
             at each line's end.  -P captures only any output that the pane
             has received that is the beginning of an as-yet incomplete escape
             sequence.

             -S and -E specify the starting and ending line numbers, zero is
             the first line of the visible pane and negative numbers are lines
             in the history.  ‘-’ to -S is the start of the history and to -E
             the end of the visible pane.  The default is to capture only the
             visible contents of the pane.

Could you explain how you would want to use this functionality?

@girip
Copy link
Author

girip commented Nov 14, 2018

Thanks for responding.
I have few uses for this functionality:

  1. For long standing jobs, it would help to check its status.
  2. For commands that will yield immediate results, I can get the output back to emacs, without having to go check the results in tmux session and continue working within emacs.

I use tmux primarily for remote machines, and this way it won't break my flow when working within emacs for setup, run commands and document what I did.

@ahendriksen
Copy link
Owner

I have been thinking about this quite a bit for the past few days.

I think I have a function that might begin to scratch your itch:

(defun ob-tmux-get-output ()
  (interactive)
  (let ((info (org-babel-get-src-block-info 'light)))
    (when (and info (string-equal "tmux" (nth 0 info)))
      (with-help-window (help-buffer)
	(let* ((lang (nth 0 info))
	       (block-content (nth 1 info))
	       (params (nth 2 info))
	       (org-session (cdr (assq :session params)))
	       (socket (cdr (assq :socket params)))
	       (socket (when socket (expand-file-name socket)))
	       (ob-session (ob-tmux--from-org-session org-session socket)))
	  (princ (ob-tmux--execute-string ob-session
					  "capture-pane" "-p"
					  "-t" (ob-tmux--session ob-session))))))))

If you execute this function with point in an ob-tmux source block, it will get the output of the tmux pane and put it in a help-buffer. This is not yet perfect, but at least it is a start.

I can think of the following improvements:

  • Auto-highlight the region in the output buffer from the end to the last command that was executed.
    This can be done by searching back for the last line in the ob-tmux source block, for instance.
  • Add a 'g' keyboard shortcut to update the output buffer (for long running jobs)
  • Add 'C-c C-c' and 'C-c C-k' shortcuts to add the active region as a result in the org buffer or cancel
    and close the output buffer.

To be honest, I am not sure when I will get around to implementing this soon.

What do you think?

@dariuskramer
Copy link
Contributor

This feature would be perfect!
For now ob-tmux does not allow to do literate devops, as the result of executing the source block is not inserted in the buffer.

@ahendriksen
Copy link
Owner

I agree ob-tmux shows what you executed, but not the result.

@dkrm0: do you think the manual workflow I have described above would be a good fit for you?

  • What would you use this feature for? I either have highly interactive sessions where I sometimes correct things in the terminal as I go along, or long running jobs the output of which I absolutely do not want in my org files. Both cases do not seem to benefit much from this feature.
  • Do you often keep a terminal window with the tmux session open next to emacs, or do you try to do everything in emacs?

I am trying to understand why people want this feature, so I do not implement something that people do not want. Moreover, I do not think I feel the need for this feature, although once implemented I will probably use it often ;)

@girip
Copy link
Author

girip commented Dec 7, 2018

Thanks for taking the time to posting the function.
I have been trying to use it before I gave my feedback.

It works very well, I can now get the state of the tmux pane from emacs!

The improvements you suggested would be very useful if implimented.
The only other feature that would be helpful to add, is to have an option to switch between output directly to org-mode versus output to output buffer. This way, ob-tmux workflow can also work as literate devops especially to get some output inside org-mode for some commands.

@dariuskramer
Copy link
Contributor

@ahendriksen This is an example for a couple of days ago:

I've benchmarked my new build and documented the whole procedures. I've used kcbench and wanted to tweak CPU settings after each benchmark. kcbench take 5min each time and I needed the result to be in the org file, for future comparaison.

This is just a simple example from the top of my head :)

I don't keep a terminal session open next to emacs.

@ahendriksen
Copy link
Owner

I am noticing this is an often requested feature. That's great! It means that people are using this package and find it useful.

Unfortunately, I have little time at the moment to work on this (even during Christmas ;-) ). I will try to get to this in February at the earliest. I will also have to improve my elisp skills, since I have never popped up buffers before and never written a minor mode before.

Again, I appreciate all your input. I am going to implement this. Just not very soon :)

@aseltmann
Copy link

Hi @ahendriksen, I just started using ob-tmux and I am happy to say that I got it to run after a bit of tweaking and it works wonderfully. I would still be interested in the feature of getting output back to emacs, and wanted to give this thread a little bump :)

@ahendriksen
Copy link
Owner

Hi, thanks for the kind words :)

While looking for a good keybinding, I found out that org mode allows jumping to the result of a code block using C-c C-o and C-c C-v C-o. It is possible to override this functionality, and add the results into the Emacs buffer just before org-mode jumps to it.

Perhaps you could evaluate the following snippet (copy it to the scratch buffer, select the text and execute eval-region), and tell me if this is moving in the right direction.

  (defun ob-tmux--insert-result ()
    (interactive)
    (let ((info (org-babel-get-src-block-info 'light)))
      (when (and info (string-equal "tmux" (nth 0 info)))
        (let* ((params (nth 2 info))
               (org-session (cdr (assq :session params)))
               (socket (cdr (assq :socket params)))
               (socket (when socket (expand-file-name socket)))
               (ob-session (ob-tmux--from-org-session org-session socket)))
          (org-babel-insert-result
               (ob-tmux--execute-string ob-session
                                        "capture-pane" "-p"
                                        "-t" (ob-tmux--session ob-session))
               '("replace"))))))

  (defun ob-tmux--open-src-block-result (orig-fun &rest args)
    (ob-tmux--insert-result)
    (apply orig-fun args))
    
  (advice-add 'org-babel-open-src-block-result
                 :around #'ob-tmux--open-src-block-result)

This code should modify org-mode so that pressing C-c C-o on tmux source block should insert the entire contents of the source block's session into the results drawer.

This is still work in progress, and I would like to hold off adding this feature to ob-tmux until it is actually pleasant to use :)

@aseltmann
Copy link

aseltmann commented Apr 13, 2020

Thanks for this implementation, I just tried it out and for me it works the following way:

  1. it opens a Org-Babel Results Buffer displaying the last 32 lines of the tmux session
  2. a #+begin_example-Block is inserted below #+RESULTS: containing the last 32 lines of the tmux session. (Functionality like a :results output verbatim header argument)

So for me this is quite good already. I could see the following improvements (but only from a user's perspective, since I do not really know elisp programming and emacs internals...):

  • inserting the output as in 2. without opening the Buffer as in 1. On the other hand, people who want to use C-c C-o in the canonical way would lose functionality - so maybe it is the best keeping both.
  • We always get 32 lines of output. That means if there is only a echo test which is sent to a new tmux session, we get 2 lines containing the output followed by 30 empty lines before #+end_example. Possible solutions could be to be able to specify the number of output lines or to only capture the output of the last command. Being able to capture an arbitrary amount of output would be probably needed for applications like what @dkrm0 described here, as well.
  • More output options would be nice, e.g. displaying the results as an org-table, or other output options which can be specified by header arguments like :results. But I guess this is not so easy.
  • Another thing I tried was: I named a ob-tmux block and called it later via #+CALL: . In this case the C-c C-o method does not work

@ahendriksen
Copy link
Owner

ahendriksen commented Apr 15, 2020

Thanks for the feedback. I appreciate the time you put into testing this :)

Thanks for this implementation, I just tried it out and for me it works the following way:

1. it opens a _Org-Babel Results_ Buffer displaying the last 32 lines of the tmux session

2. a `#+begin_example`-Block is inserted below `#+RESULTS:` containing the last 32 lines of the tmux session. (Functionality like a `:results output verbatim` header argument)

So for me this is quite good already. I could see the following improvements (but only from a user's perspective, since I do not really know elisp programming and emacs internals...):

* inserting the output as in 2. without opening the Buffer as in 1. On the other hand, people who want to use `C-c C-o` in the canonical way would lose functionality - so maybe it is the best keeping both.

I reckon that editing will be necessary, so for now I will stay on the track that a new buffer is opened.

* We always get 32 lines of output. That means if there is only a `echo test` which is sent to a new tmux session, we get 2 lines containing the output followed by 30 empty lines before `#+end_example`. Possible solutions could be to be able to specify the number of output lines or to only capture the output of the last command. Being able to capture an arbitrary amount of output would be probably needed for applications like what @dkrm0 described [here](https://github.com/ahendriksen/ob-tmux/issues/6#issuecomment-448168473), as well.

I fixed this in the snippet below. There is an "-S" option to the tmux capture-pane command to ensure that everything from the start of history is captured.

* More output options would be nice, e.g. displaying the results as an org-table, or other output options which can be specified by header arguments like `:results`. But I guess this is not so easy.

This will be out of scope for ob-tmux.

* Another thing I tried was: I named a `ob-tmux` block and called it later via `#+CALL: `. In this case the `C-c C-o` method does not work

Does this work for other kind of source blocks?


I updated the snippet with the following improvements:

  1. The capture starts at the start of history (rather than just capturing the visible contents on screen)
  2. The results open in an org-edit-special buffer, which allows immediate editing of the results.
  3. The trailing newlines (and trailing whitespace) are automatically removed from this buffer.

To test, please first execute this snippet a couple times to ensure the previous implementation is removed:

  (advice-remove 'org-babel-open-src-block-result
                 #'ob-tmux--open-src-block-result)

This should

  (defun ob-tmux--insert-result ()
    (interactive)
    (let ((info (org-babel-get-src-block-info 'light)))
      (when (and info (string-equal "tmux" (nth 0 info)))
        (let* ((params (nth 2 info))
               (org-session (cdr (assq :session params)))
               (socket (cdr (assq :socket params)))
               (socket (when socket (expand-file-name socket)))
               (ob-session (ob-tmux--from-org-session org-session socket)))
          (org-babel-insert-result
               (ob-tmux--execute-string ob-session
                                        "capture-pane"
                                        "-p" ;; print to stdout
                                        "-S" "-" ;; start at beginning of history
                                        "-t" (ob-tmux--session ob-session))
               '("replace"))))))

  (defun ob-tmux--edit-result ()
    (interactive)
    (pcase (org-babel-get-src-block-info 'light)
      (`(,_ ,_ ,arguments ,_ ,_ ,start ,_)
       (save-excursion
         ;; Go to the results, if there aren't any then run the block.
         (goto-char start)
         (goto-char (or (org-babel-where-is-src-block-result)
                        (progn (org-babel-execute-src-block)
                               (org-babel-where-is-src-block-result))))
         (end-of-line)
         (skip-chars-forward " \r\t\n")
         (org-edit-special)
         (delete-trailing-whitespace)
         (end-of-buffer)
         t))
      (_ nil)))

  (defun ob-tmux--open-src-block-result (orig-fun &rest args)
    (let ((info (org-babel-get-src-block-info 'light)))
      (if (and info (string-equal "tmux" (nth 0 info)))
          (progn 
            (ob-tmux--insert-result)
            (ob-tmux--edit-result))
        (apply orig-fun args))))

  (advice-add 'org-babel-open-src-block-result
                 :around #'ob-tmux--open-src-block-result)

@aseltmann
Copy link

aseltmann commented Apr 23, 2020

I used the function now for some time and it works nicely. I noticed one shortcoming: After C-c C-o (org-babel-open-src-block-result) we end up in the org-edit-special buffer, as you mentioned. Now, we can edit it, then there are two options:

  • C-c ' (org-edit-src-exit) will close this buffer and the tmux output will be displayed with edits and trailing whitespaces removedgood
  • C-c C-k (org-edit-src-abort) will close this buffer and the tmux output will be displayed without edits and trailing whitespace removalbad
    I think org-edit-src-abort should in this case rather not display any results at all. But again I'm not sure if this is implementable?

Another thing I tried was: I named a ob-tmux block and called it later via #+CALL: . In this case the C-c C-o method does not work

According this you are right: this does not work with other source blocks as well. It aborts with user-error: No link found

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants