emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/dtache f6c9710c1b 017/158: Merge develop branch into ma


From: ELPA Syncer
Subject: [elpa] externals/dtache f6c9710c1b 017/158: Merge develop branch into master
Date: Wed, 19 Jan 2022 18:57:40 -0500 (EST)

branch: externals/dtache
commit f6c9710c1b100adb5bbb58b3f648b2a2e649c3b6
Author: Niklas Eklund <niklas.eklund@posteo.net>
Commit: Niklas Eklund <niklas.eklund@posteo.net>

    Merge develop branch into master
    
    This commit contains:
    - An updated README.org
    - A documentation folder with a demo.org file
    - Functionality to determine status of a session
    - Add diff session command
    - Improve string representations
    - Introduce dtache-env script
---
 README.org             | 280 ++++++++++++++++++++--------------
 documentation/demo.org | 204 +++++++++++++++++++++++++
 dtache-shell.el        |   2 +-
 dtache.el              | 405 ++++++++++++++++++++++++++-----------------------
 embark-dtache.el       |   9 +-
 marginalia-dtache.el   |  14 +-
 test/dtache-test.el    |  74 +++++----
 7 files changed, 643 insertions(+), 345 deletions(-)

diff --git a/README.org b/README.org
index e106a27c14..405fdded31 100644
--- a/README.org
+++ b/README.org
@@ -7,33 +7,114 @@
   :description: Why Dtache?
   :end:
 
-  =Dtache= allows a program to be seamlessly executed in an environment that 
is isolated from =Emacs= which made possible by the 
[[https://github.com/crigler/dtach][dtach]] program. This package makes sure 
that, even though programs are running in isolated sessions, they get tightly 
integrated with Emacs.
-
-  The =dtache= package is split up into two, =dtache.el= and 
=dtache-shell.el=. The former provides the backend implementation, whilst the 
former provides the integration with =M-x shell=.
-
-** Screenshots
-
-TBD
+  =Dtache=, or =Detach Emacs=, is a package to run shell commands in sessions 
that are isolated from Emacs. =Dtache= also provides a convenient user 
interface to interact with the sessions. 
+
+  The package sprung out of the realization that I would run certain shell 
commands in a terminal outside of Emacs instead of using =M-x shell=. These 
situations were I didn't chose Emacs was either:
+  - Because of performance reasons, the built in shell is not the fastest when 
there is a lot of output
+  - Because of stability concerns, I didn't want to have Emacs freeze or crash 
have an effect on the shell command I was running
+  - Because of remote capabilities, I would be using 
[[https://github.com/tmux/tmux][Tmux]] on a remote host in order to run a 
command and detach from it
+  But running shell commands outside of Emacs meant missing out on all the 
nice features that comes from an Emacs workflow.
+ 
+  The solution for me has been =dtache=. The package allows users to start 
sessions, which will keep on running, even if Emacs itself is shut down. This 
is a feature provided by the program 
[[https://github.com/crigler/dtach][dtach]]. The package takes all of the 
previous benefits of using an external terminal and provides them in the 
package and at the same time offers a lot of other capabilities, provided by 
Emacs.
+
+** Features
+
+Here is a list of features that =dtache= provides for a session.
+
+  - =Output=: The user have access to all output from a session
+  - =Notification=: The user gets a notification when a session has finished
+  - =Status=: Sessions are automatically labeled success or failure
+  - =Remote=: Sessions can be started on remote hosts, effectively becoming a 
lightweight alternative to Tmux
+  - =Duration=: The user is informed about for how long a session has run, or 
ran, if the session has finished
+  - =Compile=: The user can chose to compile the output from a session, which 
effectively mimics =M-x compile=
+  - =Tail=: The user can chose to tail the output of an active session
+  - =Metadata=: The user can add annotators which are used to capture metadata 
about the environment from which the session was started
+  - =Diff=: The user can compare two sessions in order to see differences in 
the session outputs
+  - =Rerun=: The user can chose to rerun a session, which will rerun the 
command on the right host, in the correct working directory
+  - =Open=: The user can quickly open a session in a Do What I Mean fashion:
+    + If the session is active the output is tailed
+    + If the session has the status success the output is opened
+    + If the session has the status failure the session is compiled
+
+  To see examples of the features listed above please see the following 
presentation =PLACEHOLDER=.
   
 * Configuration
-** Use-package examples
-*** Dtache
+** Dtache
 
-Configuration for the =dtache= package. This package provides the backend for 
=dtache=.
+A minimal configuration for =dtache=.
 
 #+begin_src elisp
   (use-package dtache
     :hook (after-init . dtache-initialize)
     :config
     ;; Configure `dtache'
-    (setq dtache-db-directory (no-littering-expand-var-file-name "dtache"))
-    (setq dtache-session-directory (expand-file-name "dtache" 
(temporary-file-directory)))
+    (setq dtache-db-directory user-emacs-directory)
+    (setq dtache-session-directory (expand-file-name "dtache" 
(temporary-file-directory))))
+#+end_src
+
+** The dtache-env script
+
+Create an executable shell script, named =dtache-env=, which will be used as a 
wrapper for the shell commands we are running.
+
+#+begin_src sh
+  #!/usr/bin/env bash
+
+  dtache_command="$*"
+
+  if eval "$dtache_command"; then
+      echo -e "\nDtache session finished"
+  else
+      echo -e "\nDtache exited abnormally with code $?"
+  fi
+#+end_src
+
+Either put it somewhere in path or customize the =dtache-env= variable so that 
it refers to the script.
 
-    ;; Exclude dtache log files from `recentf'
-    (add-to-list 'recentf-exclude (rx (regexp "dtache.*\.log"))))
+#+begin_src elisp
+  (setq dtache-env "/path/to/dtache-env")
 #+end_src
 
-*** Dtache-shell
+This script is necessary in order for =dtache= to get information about the 
exit status of a session.
+
+* Commands
+** Creating a session
+
+There are tree different ways to create a dtache session.
+
+| Function                      | Description                 |
+|-------------------------------+-----------------------------|
+| =dtache-shell-command=        | Call with  M-x              |
+| =dtache-start-session=        | Call from within a function |
+| =dtache-shell-create-session= | Call from inside M-x shell  |
+
+** List sessions
+
+There are two convenient ways of listing sessions and acting upon one.
+
+| Function               | Description                     |
+|------------------------+---------------------------------|
+| =dtache-list-sessions= | Tabulated list view of sessions |
+| =dtache-open-session=  | Completion based selection      |
+
+** Actions on sessions
+
+General commands that can be used either in =dtache-list-sessions= or in 
=dtache-open-session= (using the embark extension).
+
+| Command                       | Description                                 |
+|-------------------------------+---------------------------------------------|
+| dtache-open-output            | Open a session's output                     |
+| dtache-tail-output            | Tail the output of an active session        |
+| dtache-diff-session           | Diff a session with another session         |
+| dtache-compile-session        | Open the session output in compilation mode |
+| dtache-rerun-session          | Rerun a session                             |
+| dtache-insert-session-command | Insert the session's command at point       |
+| dtache-copy-session-command   | Copy the session's shell command            |
+| dtache-copy-session-output    | Copy the session's output                   |
+| dtache-kill-session           | Kill an active session                      |
+| dtache-remove-session         | Remove an inactive session                  |
+
+* Extensions
+** Dtache-shell
 
 Configuration for the =dtache-shell= package. This package provides the 
integration with =M-x shell=.
 
@@ -48,40 +129,26 @@ Configuration for the =dtache-shell= package. This package 
provides the integrat
     (setq dtache-shell-history-file "~/.bash_history"))
 #+end_src
 
-*** Metadata annotators
-
-Create a custom function that captures the branch name if the session is 
started in a git repository.
-
-#+begin_src elisp
-  (defun dtache--session-git-branch ()
-    "Return current git branch."
-    (let ((git-directory (locate-dominating-file "." ".git")))
-      (when git-directory
-        (let ((args '("name-rev" "--name-only" "HEAD")))
-          (with-temp-buffer
-            (apply #'process-file `("git" nil t nil ,@args))
-            (string-trim (buffer-string)))))))
-#+end_src
-
-Configure the metadata annotators list so that it runs your annotator.
+Commands to be used in shell buffers.
 
-#+begin_src elisp
-  (setq dtache-metadata-annotators-alist '((branch . 
dtache--session-git-branch))
-#+end_src
+| Command                 | Description                 |
+|-------------------------+-----------------------------|
+| dtache-shell-create     | Create a session            |
+| dtache-shell-attach     | Attach to a session         |
+| dtache-shell-detach     | Detach from a session       |
 
-** Integration with other packages
-*** Embark
+** Embark
 
-Add [[https://github.com/oantolin/embark/][embark]] actions to =dtache= 
session commands.
+Add [[https://github.com/oantolin/embark/][embark]] actions to 
=dtache-open-session=.
 
 #+begin_src elisp
   (use-package embark-dtache
     :after (dtache embark))
 #+end_src
 
-*** Marginalia
+** Marginalia
 
- Add [[https://github.com/minad/marginalia/][marginalia]] annotations to 
enrich the =dtache= session commands.
+ Add [[https://github.com/minad/marginalia/][marginalia]] annotations to 
enrich =dtache-open-session=.
 
 #+begin_src elisp
   (use-package marginalia-dtache
@@ -90,105 +157,87 @@ Add [[https://github.com/oantolin/embark/][embark]] 
actions to =dtache= session
     (add-to-list 'marginalia-annotators-heavy '(dtache . 
marginalia-dtache-annotate)))
 #+end_src
 
-** Remote support
+* Tips & Tricks
+** System notifications
 
-The =dtache= package supports 
[[https://www.gnu.org/software/emacs/manual/html_node/elisp/Connection-Local-Variables.html][Connection
 Local Variables]] which allows the user to change the variables used by 
=dtache= when running on a remote host.
+By default =dtache= uses the echo area to notify the user when a session has 
finished. An alternative is to utilize the 
[[https://github.com/jwiegley/alert][alert]] package to get a system 
notification instead.
 
 #+begin_src elisp
-  (connection-local-set-profile-variables
-   'remote-dtache
-   '((dtache-shell . "/bin/bash")
-     (dtache-shell-history-file . "~/.bash_history")
-     (dtache-session-directory . "~/tmp")
-     (dtache-dtach-program . "/home/user/.local/bin/dtach")))
-
-  (connection-local-set-profiles
-   '(:application tramp :protocol "ssh") 'remote-dtache)
+  (defun dtache-session-finish-alert (session)
+    "Send an alert notification when SESSION finish."
+    (let ((status (dtache--session-status session))
+          (title
+           (pcase (dtache--session-status session)
+             ('success "Dtache finished!")
+             ('failure "Dtache failed!"))))
+      (alert (dtache--session-command session)
+             :title title
+             :severity (pcase status
+                         ('success 'moderate)
+                         ('failure 'high))
+             :category 'compile
+             :id (pcase status
+                   ('success 'compile-ok)
+                   ('failure 'compile-fail)))))
 #+end_src
 
-** Degraded mode
-
-Occasionally the =dtache= user might run into shell commands that don't play 
well with the package. The symptom is that the logs are not updated until the 
session finish, which removes a lot of the use-case for dtache. To mitigate the 
problem dtache can be instructed to run in a degraded mode for those commands. 
This is done by adding a regexp that matches the troublesome command and add it 
to the list =dtache-degraded-list=.
+With the usage of =advice= the user can override the default implantation with 
the alert version.
 
 #+begin_src elisp
-  (setq dtache-degraded-list '("foo"))
+  (advice-add 'dtache-session-finish-notification :override 
#'dtache-session-finish-alert)
 #+end_src
 
-In degraded mode =dtache= will skip the usage of =tee= and instead redirect 
all the outputs to the dtache log. Use =dtache-tail-log= to tail the output 
from the session. It's still possible to attach in the terminal but there is no 
output forwarded to it.
-
-** Notification
+** Metadata annotators
 
-Make =dtache= send a notification once a session is finished. This would only 
make sense to add for sessions on the localhost. Add the following advice to 
the config.
+The user can configure any number of annotators to run upon creation of a 
session. Here is an example of an annotator which captures the branch name if 
the session is started in a git repository.
 
 #+begin_src elisp
-  (defun dtache-session-finish-notification-a (session)
-    "Send a notification when SESSION finish."
-    (let* ((min-duration 5.0)
-           (send-alert (> (dtache--session-duration session) min-duration)))
-      (if send-alert
-          (alert (format "Command: %s" (dtache--session-command session))
-                 :title (format "Dtache session finished!")
-                 :severity 'moderate
-                 :category 'compile
-                 :id 'compile-ok)
-        (message "Dtache finished session: %s"
-                 (dtache--session-command session)))))
+  (defun dtache--session-git-branch ()
+    "Return current git branch."
+    (let ((git-directory (locate-dominating-file "." ".git")))
+      (when git-directory
+        (let ((args '("name-rev" "--name-only" "HEAD")))
+          (with-temp-buffer
+            (apply #'process-file `("git" nil t nil ,@args))
+            (string-trim (buffer-string)))))))
 #+end_src
 
+The user only needs to add this function to the list of annotators.
+
 #+begin_src elisp
-  (advice-add 'dtache-session-finish-notification :override 
#'dtache-session-finish-notification-a)
+  (setq dtache-metadata-annotators-alist '((branch . 
dtache--session-git-branch))
 #+end_src
 
-** Evil bindings
+** Remote support
 
-#+begin_src elisp
-  (general-def '(normal motion) dtache-sessions-mode-map
-    "<return>" #'dtache-open-session
-    "e" #'dtache-open-stderr
-    "c" #'dtache-compile-session
-    "d" #'dtache-remove-session
-    "gr" #'dtache-list-sessions
-    "K" #'dtache-kill-session
-    "L" #'dtache-open-log
-    "o" #'dtache-open-stdout
-    "r" #'dtache-rerun-session
-    "t" #'dtache-tail-log
-    "w" #'dtache-copy-session-command
-    "W" #'dtache-copy-session-log)
-  (general-def '(motion normal) dtache-log-mode-map
-    "q" #'kill-buffer-and-window)
-  (general-def '(motion normal) dtache-tail-mode-map
-    "q" #'dtache-quit-tail-log)
-#+end_src
+The =dtache= package supports 
[[https://www.gnu.org/software/emacs/manual/html_node/elisp/Connection-Local-Variables.html][Connection
 Local Variables]] which allows you to change the variables used by =dtache= 
when running on a remote host. This useful when the user needs to alter dtache 
settings when running on a remote host.
 
-* Commands
+#+begin_src elisp
+  (connection-local-set-profile-variables
+   'remote-dtache
+   '((dtache-env . "~/bin/dtache-env")
+     (dtache-shell-program . "/bin/bash")
+     (dtache-shell-history-file . "~/.bash_history")
+     (dtache-session-directory . "~/tmp")
+     (dtache-dtach-program . "/home/user/.local/bin/dtach")))
 
-The following is a list of commands that can be run on =dtache= sessions.
+  (connection-local-set-profiles
+   '(:application tramp :protocol "ssh") 'remote-dtache)
+#+end_src
 
-** Dtache-shell
+** Redirect only
 
-Commands to be used in shell buffers.
+Some programs doesn't play well with =tee= which =dtache= relies upon to 
redirect the output both to standard out as well as to file. If you encounter a 
situation where output from a session is only visible once it has finished 
running, the command you launched should instead be using =redirect only=. To 
list a command to run with redirect only in the future can be done by adding a 
regexp to =dtache-redirect-only-regexps=.
 
-| Command                 | Description                 |
-|-------------------------+-----------------------------|
-| dtache-shell-create     | Create a session            |
-| dtache-shell-attach     | Attach to a session         |
-| dtache-shell-detach     | Detach from a session       |
+#+begin_src elisp
+  (setq dtache-read-only-list '("^ls"))
+#+end_src
 
-** Dtache
+Here the command beginning with =ls= would from now on be using redirect only.
 
-General commands that can be used anywhere.
+** Evil bindings
 
-| Command                     | Description                                 |
-|-----------------------------+---------------------------------------------|
-| dtache-open-log             | Open the output log for a session           |
-| dtache-open-stdout          | Open the stdout for a session               |
-| dtache-open-stderr          | Open the stderr for a session               |
-| dtache-copy-session-command | Copy the session command                    |
-| dtache-copy-session-log     | Copy the log output of a session            |
-| dtache-kill-session         | Kill a session                              |
-| dtache-remove-session       | Remove a session                            |
-| dtache-compile-session      | Open the session output in compilation mode |
+For inspiration on how to configure =dtache-list-sessions= to use evil 
bindings see 
[[https://gitlab.com/niklaseklund/dotfiles/blob/master/.config/emacs/init.el#L1393][Niklas
 Eklund's Emacs config]].
 
 * Tips & Tricks
 ** Advice functions
@@ -213,9 +262,4 @@ The following two functions are examples on how to create 
functions that can be
 
 * Credits
 
-The inspiration for the package comes from ~ambrevar's~ 
[[https://github.com/Ambrevar/dotfiles/blob/master/.emacs.d/lisp/package-eshell-detach.el][package-eshell-detach]].
-
-* TODO Things to do before next release
-- [X] Update header documentation in files
-- [ ] Squash the development from the branch and merge to master
-- [ ] Update the README.org file
+I got inspired by =Ambrevar's= pursuits on 
[[https://ambrevar.xyz/emacs-eshell/][using eshell as his main shell]], and his 
[[https://github.com/Ambrevar/dotfiles/blob/master/.emacs.d/lisp/package-eshell-detach.el][package-eshell-detach]]
 got me into the idea of using =dtach= as a base for detachable shell commands.
diff --git a/documentation/demo.org b/documentation/demo.org
new file mode 100644
index 0000000000..df520614a3
--- /dev/null
+++ b/documentation/demo.org
@@ -0,0 +1,204 @@
+#+title: dtache.el - Dtach Emacs
+#+author: Niklas Eklund
+#+language: en
+
+* COMMENT Preparations
+
+A simple script that runs for 20 seconds.
+
+#+begin_src sh :tangle /tmp/dtache/loop.sh :tangle-mode (identity #o755)
+  for i in {1..20} ; do sleep 1; echo "$i" ; done
+#+end_src
+
+Tangle the code block above into a shell script.
+
+#+begin_src elisp :results none
+  (call-interactively #'org-babel-tangle-file)
+#+end_src
+
+Need to make sure =ediff= behaves.
+
+#+begin_src elisp :results none
+  (let ((window-conf))
+    (add-hook 'ediff-before-setup-hook
+              (defun ne/ediff-save-window-conf ()
+                (setq window-conf (current-window-configuration))))
+    (dolist (hook '(ediff-quit-hook ediff-suspended-hook))
+      (add-hook hook (defun ne/ediff-restore-window-conf ()
+                       (set-window-configuration window-conf)))))
+#+end_src
+
+* Introduction
+
+  =Dtache=, or =Detach Emacs=, is a package to run shell commands in sessions 
that are isolated from Emacs. The name of the package comes from 
[[https://github.com/crigler/dtach][dtach]], which is the program that makes 
this package possible.
+
+  The inspiration for the package comes from those situations where I would 
chose to run commands in a terminal outside of Emacs.
+
+* How do I?
+** Start a session
+*** With M-x
+
+A session can be started with the command =dtache-shell-command=.
+
+#+begin_src elisp :results none
+  (call-interactively #'dtache-shell-command)
+#+end_src
+
+*** With a function
+
+A session can also be started with the =dtache-start-session= function. Which 
can be included in custom commands.
+
+#+begin_src elisp :results none :dir ~/code/python
+  (dtache-start-session "pylint demo.py ")
+#+end_src
+
+*** With shell
+
+A session can also be started from =M-x shell=, making it a dispatcher for 
=dtache= sessions.
+
+#+begin_src elisp :results none
+  (call-interactively #'shell)
+#+end_src
+
+Run the following command.
+
+#+begin_src sh
+  sleep 10 && ls -la
+#+end_src
+
+** Interact with a session
+
+To list all sessions.
+
+#+begin_src elisp :results none
+  (dtache-list-sessions)
+#+end_src
+
+The commands that are available.
+
+| Command                       | Description                  | Keybinding |
+|-------------------------------+------------------------------+------------|
+| =dtache-open-session=         | A DWIM function              | Return     |
+| =dtache-compile-session=      | Post compilation             | c          |
+| =dtache-remove-session=       | Remove a session             | d          |
+| =dtache-kill-session=         | Kill an active session       | k          |
+| =dtache-open-output=          | Open sessions output         | o          |
+| =dtache-rerun-session=        | Rerun a session              | r          |
+| =dtache-tail-session=         | Tail the output of a session | t          |
+| =dtache-copy-session-command= | Copy command                 | w          |
+| =dtache-copy-session-output=  | Copy the output              | W          |
+
+* Configuration
+** Annotators
+
+An annotator function runs when a session is started. Its intent is to capture 
metadata information.
+
+#+begin_src elisp :results none
+  (defun dtache--session-git-branch ()
+    "Return current git branch."
+    (let ((git-directory (locate-dominating-file "." ".git")))
+      (when git-directory
+        (let ((args '("name-rev" "--name-only" "HEAD")))
+          (with-temp-buffer
+            (apply #'process-file `("git" nil t nil ,@args))
+            (string-trim (buffer-string)))))))
+#+end_src
+
+Register the annotator function.
+
+#+begin_src elisp :results none
+  (setq dtache-metadata-annotators-alist '((branch . 
dtache--session-git-branch)))
+#+end_src
+
+Create a session.
+
+#+begin_src elisp :results none :dir ~/src/emacs-packages/dtache
+  (dtache-start-session "sleep 2 && ls")
+#+end_src
+
+** External package integration
+
+Here are some examples on how =dtache= can be further improved with external 
packages.
+
+*** Embark & Marginalia
+
+The command =dtache-open-session= is an alternative to the 
=dtache-list-sessions=.
+
+#+begin_src elisp :results none
+  (call-interactively #'dtache-open-session)
+#+end_src
+
+The =dtache-open-session= can be enhanced with 
[[https://github.com/minad/marginalia/][marginalia]] annotations.
+
+#+begin_src elisp :results none
+  (use-package marginalia-dtache
+    :after (dtache marginalia)
+    :config
+    (setq dtache-max-command-length 50)
+    (add-to-list 'marginalia-annotator-registry '(dtache 
marginalia-dtache-annotate builtin none)))
+#+end_src
+
+The =dtache-open-session= can also be given actions through 
[[https://github.com/oantolin/embark/][embark]].
+
+#+begin_src elisp :results none
+  (use-package embark-dtache
+    :after (dtache embark))
+#+end_src
+
+*** Alert
+
+By default =dtache= uses the echo area to notify the user when a session has 
finished. An alternative is to utilize the 
[[https://github.com/jwiegley/alert][alert]] package to get a system 
notification instead.
+
+#+begin_src elisp :results none
+  (defun dtache-session-finish-alert (session)
+    "Send an alert notification when SESSION finish."
+    (let ((status (dtache--session-status session))
+          (title
+           (pcase (dtache--session-status session)
+             ('success "Dtache finished!")
+             ('failure "Dtache failed!"))))
+      (alert (dtache--session-command session)
+             :title title
+             :severity (pcase status
+                         ('success 'moderate)
+                         ('failure 'high))
+             :category 'compile
+             :id (pcase status
+                   ('success 'compile-ok)
+                   ('failure 'compile-fail)))))
+
+  (advice-add 'dtache-session-finish-notification :override 
#'dtache-session-finish-alert)
+#+end_src
+
+A successful session.
+
+#+begin_src elisp :results none :dir ~/src/emacs-packages/dtache
+  (dtache-start-session "ls")
+#+end_src
+
+A failing session.
+
+#+begin_src elisp :results none :dir ~/src/emacs-packages/dtache
+  (dtache-start-session "lsl")
+#+end_src
+
+* Other use cases
+** Remote execution
+
+=Dtache= has support for remote execution which is made possible through 
=TRAMP=. The only difference from a users perspective is that there might be 
some delay before the notification is issued. 
+
+#+begin_src elisp :dir /ssh:pi:~/bin :results none
+  (dtache-start-session "sleep 5 && ls -la")
+#+end_src
+
+** Duration
+
+The duration becomes very valuable when the shell commands are deterministic.
+
+** Diff two sessions
+
+In combination with the =git-branch= annotator the =dtache-diff-session= 
command becomes useful in comparing two sessions.
+
+** Transient combo
+
+[[https://github.com/magit/transient][Transient]] and =dtache= plays very well 
together. At work I use the power of transient to compose shell commands and 
=dtache= to run them.
diff --git a/dtache-shell.el b/dtache-shell.el
index 74fa248c8c..cb0b3db74e 100755
--- a/dtache-shell.el
+++ b/dtache-shell.el
@@ -151,7 +151,7 @@ cluttering the comint-history with dtach commands."
           (concat
            (dtache--session-session-directory dtache-shell--current-session)
            (dtache--session-id dtache-shell--current-session)
-           dtache--socket-ext))
+           ".socket"))
          (input
           (concat dtache-dtach-program " " (dtache--dtach-arg) " " socket)))
     (comint-simple-send proc input)))
diff --git a/dtache.el b/dtache.el
index a4d201cc23..17e8795066 100644
--- a/dtache.el
+++ b/dtache.el
@@ -1,4 +1,4 @@
-;;; dtache.el --- Run and manage detached commands -*- lexical-binding: t -*-
+;;; dtache.el --- Dispatch and interact with dtache sessions -*- 
lexical-binding: t -*-
 
 ;; Copyright (C) 2020-2021 Niklas Eklund
 
@@ -59,10 +59,12 @@
   "The name of the `dtach' program.")
 (defvar dtache-shell-program "bash"
   "Shell to run the dtach command in.")
-(defvar dtache-max-command-length 95
+(defvar dtache-env "dtache-env"
+  "The name of the `dtache' program.")
+(defvar dtache-max-command-length nil
   "Maximum length of displayed command.")
-(defvar dtache-degraded-list '()
-  "Regexps for commands that should be run in dedgraded mode.")
+(defvar dtache-redirect-only-regexps '()
+  "Regexps for commands that should be run with redirect only.")
 (defvar dtache-tail-interval 2
   "Interval in seconds for the update rate when tailing a session.")
 (defvar dtache-session-type nil
@@ -71,11 +73,10 @@
   "Custom function to use to open a session.")
 (defvar dtache-session-callback-function nil
   "Custom function to callback when a session finish.")
+(defvar dtache-session-status-function #'dtache-session-exit-code-status
+  "Custom function to deduce the status of a session.")
 (defvar dtache-compile-hooks nil
   "Hooks to run when compiling a session.")
-(defvar dtache-dispatch-alist `((standard . dtache-tail-log)
-                                (shell . dtache-tail-log))
-  "Specify function to open session with based on type.")
 (defvar dtache-metadata-annotators-alist nil
   "An alist of annotators for metadata.")
 
@@ -84,21 +85,12 @@
 (defvar dtache--sessions-initialized nil
   "Sessions are initialized.")
 (defvar dtache--dtach-mode nil
-  "Mode of operation.")
+  "Mode of operation for dtach.")
 (defvar dtache--sessions nil
   "A list of sessions.")
 (defvar dtache--remote-session-timer nil
   "Timer object for remote polling.")
 
-(defconst dtache--socket-ext ".socket"
-  "The file name extension for the socket.")
-(defconst dtache--log-ext ".log"
-  "The file name extension for combined stdout and stderr.")
-(defconst dtache--stdout-ext ".stdout"
-  "The file name extension for stdout.")
-(defconst dtache--stderr-ext ".stderr"
-  "The file name extension for stderr.")
-
 ;;;; Data structures
 
 (cl-defstruct (dtache-session (:constructor dtache--session-create)
@@ -108,25 +100,23 @@
   (type nil :read-only t)
   (open-function nil :read-only t)
   (callback-function nil :read-only t)
+  (status-function nil :read-only t)
   (working-directory nil :read-only t)
   (creation-time nil :read-only t)
   (session-directory nil :read-only t)
   (metadata nil :read-only t)
   (host nil :read-only t)
-  (degraded nil :read-only t)
+  (redirect-only nil :read-only t)
+  (status nil)
   (duration nil)
-  (log-size nil)
+  (output-size nil)
   (active nil))
 
 ;;;; Commands
 
 ;;;###autoload
-(defun dtache-shell-command (command &optional suppress-output)
-  "Execute COMMAND asynchronously with `dtache'.
-
-The input parameters are kept in sync with `async-shell-command'.  If
-the optional parameters SUPPRESS-OUTPUT has a value the output buffer
-is not opened and the command will run in the background."
+(defun dtache-shell-command (command)
+  "Execute COMMAND asynchronously with `dtache'."
   (interactive
    (list
     (read-shell-command (if shell-command-prompt-show-cwd
@@ -134,11 +124,10 @@ is not opened and the command will run in the background."
                                             (abbreviate-file-name
                                              default-directory))
                           "Dtache shell command: ")
-                        nil nil)
-    current-prefix-arg))
+                        nil nil)))
   (let* ((inhibit-message t)
          (dtache-session-type 'standard))
-    (dtache-start-session command (not suppress-output))))
+    (dtache-start-session command)))
 
 ;;;###autoload
 (defun dtache-list-sessions ()
@@ -159,12 +148,10 @@ is not opened and the command will run in the background."
    (list (if (eq major-mode 'dtache-sessions-mode)
              (tabulated-list-get-id)
            (dtache-select-session))))
-  (let* ((dispatch-function
-          (or (dtache--session-open-function session)
-              (alist-get (dtache--session-type session)
-                         dtache-dispatch-alist)
-              #'dtache-open-log)))
-    (funcall dispatch-function session)))
+  (if-let ((open-function
+            (dtache--session-open-function session)))
+      (funcall open-function session))
+  (dtache-open-dwim session))
 
 ;;;###autoload
 (defun dtache-compile-session (session)
@@ -183,10 +170,11 @@ is not opened and the command will run in the background."
       (with-current-buffer (get-buffer-create buffer-name)
         (setq-local buffer-read-only nil)
         (erase-buffer)
-        (insert-file-contents file)
+        (insert (dtache-session-output session))
         (setq-local default-directory
                     (dtache--session-working-directory session))
         (run-hooks 'dtache-compile-hooks)
+        (dtache-log-mode)
         (compilation-minor-mode)
         (setq-local font-lock-defaults '(compilation-mode-font-lock-keywords 
t))
         (font-lock-mode)
@@ -209,14 +197,15 @@ is not opened and the command will run in the background."
     (dtache-start-session (dtache--session-command session))))
 
 ;;;###autoload
-(defun dtache-copy-session-log (session)
+(defun dtache-copy-session-output (session)
   "Copy SESSION's log."
   (interactive
    (list (if (eq major-mode 'dtache-sessions-mode)
              (tabulated-list-get-id)
            (dtache-select-session))))
-  (dtache--file-content
-   (dtache-session-file session 'log)))
+  (with-temp-buffer
+    (insert (dtache-session-output session))
+    (kill-new (buffer-string))))
 
 ;;;###autoload
 (defun dtache-copy-session-command (session)
@@ -259,45 +248,62 @@ is not opened and the command will run in the background."
       (dtache--kill-processes pid))))
 
 ;;;###autoload
-(defun dtache-open-log (session)
-  "Open SESSION's log."
+(defun dtache-open-output (session)
+  "Open SESSION's output."
   (interactive
    (list (if (eq major-mode 'dtache-sessions-mode)
              (tabulated-list-get-id)
            (dtache-select-session))))
-  (dtache--open-file session 'log))
+  (let* ((buffer-name
+          (format "*dtache-output-%s*"
+                  (dtache--session-short-id session)))
+         (file-path
+          (dtache-session-file session 'log))
+         (tramp-verbose 1))
+    (if (file-exists-p file-path)
+        (progn
+          (with-current-buffer (get-buffer-create buffer-name)
+            (insert (dtache-session-output session))
+            (setq-local default-directory (dtache--session-working-directory 
session))
+            (dtache-log-mode)
+            (goto-char (point-max)))
+          (pop-to-buffer buffer-name))
+      (message "Dtache can't find file: %s" file-path))))
 
 ;;;###autoload
-(defun dtache-tail-log (session)
-  "Tail SESSION's log."
+(defun dtache-tail-output (session)
+  "Tail SESSION's output."
   (interactive
    (list (if (eq major-mode 'dtache-sessions-mode)
              (tabulated-list-get-id)
            (dtache-select-session))))
   (if (dtache--session-active-p session)
-      (dtache--tail-file session 'log)
-    (dtache--open-file session 'log)))
-
-;;;###autoload
-(defun dtache-open-stdout (session)
-  "Open SESSION's stdout."
-  (interactive
-   (list (if (eq major-mode 'dtache-sessions-mode)
-             (tabulated-list-get-id)
-           (dtache-select-session))))
-  (dtache--open-file session 'stdout))
+      (let* ((file-path
+              (dtache-session-file session 'log))
+             (tramp-verbose 1))
+        (when (file-exists-p file-path)
+          (find-file-other-window file-path)
+          (dtache-tail-mode)
+          (goto-char (point-max))))
+    (dtache-open-output session)))
 
 ;;;###autoload
-(defun dtache-open-stderr (session)
-  "Open SESSION's stderr."
+(defun dtache-diff-session (session1 session2)
+  "Diff SESSION1 with SESSION2."
   (interactive
-   (list (if (eq major-mode 'dtache-sessions-mode)
-             (tabulated-list-get-id)
-           (dtache-select-session))))
-  (dtache--open-file session 'stderr))
+   (list
+    (dtache-select-session)
+    (dtache-select-session)))
+  (dtache--create-diff-buffer session1)
+  (dtache--create-diff-buffer session2)
+  (let ((buffer1 (format "*dtache-diff-%s*"
+                         (dtache--session-short-id session1)))
+        (buffer2 (format "*dtache-diff-%s*"
+                         (dtache--session-short-id session2))))
+    (ediff-buffers buffer1 buffer2)))
 
 ;;;###autoload
-(defun dtache-quit-tail-log ()
+(defun dtache-quit-tail-output ()
   "Quit `dtache' tail log.
 
 The log can have been updated, but that is not done by the user but
@@ -311,23 +317,12 @@ nil before closing."
 
 ;;;;; Session
 
-(defun dtache-start-session (command &optional show-output)
-  "Start a `dtache' session running COMMAND optionally SHOW-OUTPUT."
+(defun dtache-start-session (command)
+  "Start a `dtache' session running COMMAND."
   (let* ((dtache--dtach-mode 'new)
          (session (dtache--create-session command))
          (dtache-command (dtache-dtach-command session)))
     (dtache-setup-notification session)
-    (when show-output
-      (if (file-remote-p default-directory)
-          (run-with-timer 0.1 nil (lambda () (dtache-tail-log session)))
-        (file-notify-add-watch
-         (dtache-session-file session 'log)
-         '(change)
-         (lambda (event)
-           (pcase-let ((`(,_ ,action ,_) event))
-             (when (eq action 'created)
-               (dtache-tail-log session)))))))
-
     (apply #'start-file-process
            `("dtache" nil ,dtache-dtach-program ,@dtache-command))))
 
@@ -359,10 +354,8 @@ Sessions running on  current host or localhost are 
updated."
          (concat
           (dtache--session-id session)
           (pcase file
-            ('socket dtache--socket-ext)
-            ('log dtache--log-ext)
-            ('stdout dtache--stdout-ext)
-            ('stderr dtache--stderr-ext))))
+            ('socket ".socket")
+            ('log ".log"))))
         (directory (concat
                     (file-remote-p (dtache--session-working-directory session))
                     (dtache--session-session-directory session))))
@@ -384,10 +377,12 @@ Sessions running on  current host or localhost are 
updated."
       (setf (dtache--session-active session) nil)
       (setf (dtache--session-duration session)
             (dtache--duration session))
+      (when-let ((status (dtache--session-status-function session)))
+        (setf (dtache--session-status session) (funcall status session)))
       (dtache-session-finish-notification session)
       (when-let ((callback (dtache--session-callback-function session)))
         (funcall callback))))
-  (setf (dtache--session-log-size session)
+  (setf (dtache--session-output-size session)
         (file-attribute-size (file-attributes
                               (dtache-session-file session 'log))))
   session)
@@ -405,7 +400,7 @@ Sessions running on  current host or localhost are updated."
                        ;; Remove missing local sessions
                        (seq-remove (lambda (it)
                                      (and (string= "localhost" 
(dtache--session-host it))
-                                          (dtache--session-dead-p it))))
+                                          (dtache--session-missing-p it))))
                        ;; Update local active sessions
                        (seq-map (lambda (it)
                                   (if (and (string= "localhost" 
(dtache--session-host it))
@@ -443,9 +438,49 @@ Sessions running on  current host or localhost are 
updated."
    (seq-remove
     (lambda (it)
       (and (string= host (dtache--session-host it))
-           (dtache--session-dead-p it)))
+           (dtache--session-missing-p it)))
     dtache--sessions)))
 
+(defun dtache-session-exit-code-status (session)
+  "Return status based on exit-code in SESSION."
+  (with-temp-buffer
+    (insert-file-contents (dtache-session-file session 'log))
+    (goto-char (point-max))
+    (if (string-match "Dtache session finished" (thing-at-point 'line t))
+        'success
+      'failure)))
+
+(defun dtache-session-output (session)
+  "Return content of SESSION's output."
+  (let* ((filename (dtache-session-file session 'log))
+         (status (dtache--session-status session))
+         (remove-dtache-message (not (eq status 'unknown))))
+    (with-temp-buffer
+      (insert-file-contents filename)
+      (goto-char (point-max))
+      (when remove-dtache-message
+        (line-move -3)
+        (end-of-line))
+      (buffer-substring (point-min) (point)))))
+
+(defun dtache-session-finish-notification (session)
+  "Send a notification when SESSION finish."
+  (let ((status (pcase (dtache--session-status session)
+                  ('success "Dtache finished")
+                  ('failure "Dtache failed")) ))
+    (message "%s: %s" status (dtache--session-command session))))
+
+(defun dtache-open-dwim (session)
+  "Open SESSION in a do what I mean fashion."
+  (cond ((dtache--session-active session)
+         (dtache-tail-output session))
+        ((eq 'success (dtache--session-status session))
+         (dtache-open-output session))
+        ((eq 'failure (dtache--session-status session))
+         (dtache-compile-session session))
+        (t (progn (message "Unknown status of session.")
+                  (dtache-open-output session)))))
+
 ;;;;; Other
 
 (defun dtache-completing-read (sessions)
@@ -477,17 +512,17 @@ Sessions running on  current host or localhost are 
updated."
   (with-connection-local-variables
    (let* ((directory (dtache--session-session-directory session))
           (file-name (dtache--session-id session))
-          (socket (concat directory file-name dtache--socket-ext))
+          (socket (concat directory file-name ".socket"))
           ;; Construct the command line
-          (commandline (dtache--output-command session))
-          (dtache--dtach-mode (if (dtache--session-degraded session)
+          (command (dtache--magic-command session))
+          (dtache--dtach-mode (if (dtache--session-redirect-only session)
                                   'new
                                 dtache--dtach-mode)))
-     `(,(dtache--dtach-arg) ,socket "-z" ,dtache-shell-program "-c" 
,commandline))))
+     `(,(dtache--dtach-arg) ,socket "-z" ,dtache-shell-program "-c" 
,command))))
 
-(defun dtache-degraded-p (command)
+(defun dtache-redirect-only-p (command)
   "Return t if COMMAND should run in degreaded mode."
-  (if (thread-last dtache-degraded-list
+  (if (thread-last dtache-redirect-only-regexps
         (seq-filter (lambda (regexp)
                       (string-match-p regexp command)))
         (length)
@@ -511,6 +546,15 @@ Sessions running on  current host or localhost are 
updated."
     (unless (file-exists-p directory)
       (make-directory directory t))))
 
+(defun dtache-get-working-directory ()
+  "Return an abreviated working directory path."
+  (let* ((remote (file-remote-p default-directory))
+         (full-home (if remote (expand-file-name remote) (expand-file-name 
"~")))
+         (short-home (if remote (concat remote "~/") "~")))
+    (replace-regexp-in-string full-home
+                              short-home
+                              (expand-file-name default-directory))))
+
 ;;;; Support functions
 
 ;;;;; Session
@@ -524,10 +568,12 @@ Sessions running on  current host or localhost are 
updated."
                                  :type dtache-session-type
                                  :open-function dtache-open-session-function
                                  :callback-function 
dtache-session-callback-function
-                                 :working-directory default-directory
-                                 :degraded (dtache-degraded-p command)
+                                 :status-function 
dtache-session-status-function
+                                 :working-directory 
(dtache-get-working-directory)
+                                 :redirect-only (dtache-redirect-only-p 
command)
                                  :creation-time (time-to-seconds 
(current-time))
-                                 :log-size 0
+                                 :status 'unknown
+                                 :output-size 0
                                  :session-directory (file-name-as-directory 
dtache-session-directory)
                                  :host (dtache--host)
                                  :metadata (dtache-metadata)
@@ -544,7 +590,7 @@ Sessions running on  current host or localhost are updated."
           (concat
            (dtache--session-session-directory session)
            (dtache--session-id session)
-           dtache--socket-ext))
+           ".socket"))
          (regexp (rx-to-string `(and "dtach " (or "-n " "-c ") ,socket)))
          (ps-args '("aux" "-w")))
     (with-temp-buffer
@@ -567,20 +613,22 @@ Sessions running on  current host or localhost are 
updated."
 
 (defun dtache--session-truncate-command (session)
   "Return a truncated string representation of SESSION's command."
-  (let ((command (dtache--session-command session))
-        (part-length (- dtache-max-command-length 3)))
-    (if (<= (length command) dtache-max-command-length)
-        (let ((padding-length (- dtache-max-command-length (length command))))
-          (concat command (make-string padding-length ?\s)))
-      (concat
-       (substring  command 0 (/ part-length 2))
-       "..."
-       (substring command (- (length command) (/ part-length 2)) (length 
command))))))
+  (if (null dtache-max-command-length)
+      (dtache--session-command session)
+    (let ((command (dtache--session-command session))
+          (part-length (- dtache-max-command-length 3)))
+      (if (<= (length command) dtache-max-command-length)
+          (let ((padding-length (- dtache-max-command-length (length 
command))))
+            (concat command (make-string padding-length ?\s)))
+        (concat
+         (substring command 0 (/ part-length 2))
+         "..."
+         (substring command (- (length command) (/ part-length 2)) (length 
command)))))))
 
 (defun dtache--session-update (session)
   "Update the `dtache' SESSION."
   (setf (dtache--session-active session) (dtache--session-active-p session))
-  (setf (dtache--session-log-size session) (file-attribute-size
+  (setf (dtache--session-output-size session) (file-attribute-size
                                             (file-attributes
                                              (dtache-session-file session 
'log))))
   session)
@@ -601,8 +649,8 @@ Sessions running on  current host or localhost are updated."
    (dtache--session-active session)
    (not (file-exists-p (dtache-session-file session 'socket)))))
 
-(defun dtache--session-dead-p (session)
-  "Return t if SESSION is dead."
+(defun dtache--session-missing-p (session)
+  "Return t if SESSION is missing."
   (not
    (file-exists-p
     (dtache-session-file session 'log))))
@@ -613,6 +661,23 @@ Sessions running on  current host or localhost are 
updated."
     (setq dtache--remote-session-timer
           (run-with-timer 10 60 #'dtache-update-remote-sessions))))
 
+(defun dtache--create-diff-buffer (session)
+  "Create a diff buffer for SESSION."
+  (let ((buffer-name
+         (format "*dtache-diff-%s*"
+                 (dtache--session-short-id session))))
+    (with-current-buffer (get-buffer-create buffer-name)
+      (erase-buffer)
+      (insert (format "Command: %s\n" (dtache--session-command session)))
+      (insert (format "Working directory: %s\n" (dtache--working-dir-str 
session)))
+      (insert (format "Status: %s\n" (dtache--session-status session)))
+      (insert (format "Created at: %s\n" (dtache--creation-str session)))
+      (insert (format "Id: %s\n" (dtache--session-id session)))
+      (insert (format "Metadata: %s\n" (dtache--metadata-str session)))
+      (insert (format "Duration: %s\n" (dtache--duration-str session)))
+      (insert "\n")
+      (insert (dtache-session-output session)))))
+
 ;;;;; Database
 
 (defun dtache--db-select-sessions ()
@@ -661,11 +726,6 @@ Sessions running on  current host or localhost are 
updated."
     ('attach "-a")
     (_ "-n")))
 
-(defun dtache-session-finish-notification (session)
-  "Send a notification when SESSION finish."
-  (message "Dtache finished session: %s"
-           (dtache--session-command session)))
-
 (defun dtache--add-end-of-session-notification (session)
   "Trigger an event when SESSION is stopped."
   (file-notify-add-watch
@@ -675,9 +735,10 @@ Sessions running on  current host or localhost are 
updated."
      (pcase-let ((`(,_ ,action ,_) event))
        (when (eq action 'deleted)
          ;; Update session
-         (setf (dtache--session-log-size session) (file-attribute-size
+         (setf (dtache--session-output-size session) (file-attribute-size
                                                    (file-attributes
                                                     (dtache-session-file 
session 'log))))
+
          (setf (dtache--session-active session) nil)
          (setf (dtache--session-duration session)
                (- (time-to-seconds) (dtache--session-creation-time session)))
@@ -685,6 +746,10 @@ Sessions running on  current host or localhost are 
updated."
          ;; Update session in database
          (dtache--db-update-session session)
 
+         ;; Update status
+         (when-let ((status (dtache--session-status-function session)))
+           (setf (dtache--session-status session) (funcall status session)))
+
          ;; Send notification
          (dtache-session-finish-notification session)
 
@@ -711,37 +776,21 @@ Sessions running on  current host or localhost are 
updated."
     (seq-do (lambda (pid) (dtache--kill-processes pid)) child-processes)
     (apply #'process-file `("kill" nil nil nil ,pid))))
 
-(defun dtache--output-command (session)
-  "Return output command for SESSION."
-  (if (dtache--session-degraded session)
-      (dtache--output-to-file-command session)
-    (dtache--output-to-both-command session)))
+(defun dtache--magic-command (session)
+  "Return the magic dtache command for SESSION.
 
-(defun dtache--output-to-file-command (session)
-  "Return a command to send SESSION's output directly to log."
-  (let* ((command (dtache--session-command session))
-         (directory (dtache--session-session-directory session))
-         (file-name (dtache--session-id session))
-         (log (concat directory file-name dtache--log-ext)))
-    ;; Construct the command line
-    ;;   echo &> log
-    (format "{ %s; } &> %s" command log)))
-
-(defun dtache--output-to-both-command (session)
-  "Return a command to send SESSION's output to both shell and log."
-  (let* ((command (dtache--session-command session))
+If SESSION is redirect-only fallback to a command that doesn't rely on tee.
+Otherwise use tee to log stdout and stderr individually."
+  (let* ((command (string-join
+                   `(,dtache-env
+                     ,dtache-shell-program "-c" "-i"
+                     ,(shell-quote-argument (format "\"%s\"" 
(dtache--session-command session)))) " "))
          (directory (dtache--session-session-directory session))
          (file-name (dtache--session-id session))
-         (stdout (concat directory file-name dtache--stdout-ext))
-         (stderr (concat directory file-name dtache--stderr-ext))
-         (log (concat directory file-name dtache--log-ext)))
-    ;; Construct the command line
-    ;;   { { echo stdout; echo stderr >&2; } >>(tee stdout ); } 2>>(tee 
stderr) | tee log
-    (format "{ { %s; }%s }%s %s"
-            (format "%s" command)
-            (format " > >(tee %s );" stdout)
-            (format " 2> >(tee %s )" stderr)
-            (format " | tee %s" log))))
+         (log (concat directory file-name ".log")))
+    (if (dtache--session-redirect-only session)
+        (format "{ %s; } &> %s" command log)
+      (format "{ %s; } 2>&1 | tee %s" command log))))
 
 (defun dtache--host ()
   "Return name of host."
@@ -749,12 +798,6 @@ Sessions running on  current host or localhost are 
updated."
    (file-remote-p default-directory 'host)
    "localhost"))
 
-(defun dtache--file-content (file)
-  "Copy FILE's content."
-  (with-temp-buffer
-    (insert-file-contents file)
-    (kill-new (buffer-string))))
-
 (defun dtache--duration (session)
   "Return the time duration of the SESSION.
 
@@ -766,29 +809,6 @@ the current time is used."
         (dtache-session-file session 'log))))
      (dtache--session-creation-time session)))
 
-(defun dtache--open-file (session file)
-  "Oen SESSION's FILE."
-  (let* ((file-path
-          (dtache-session-file session file))
-         (tramp-verbose 1))
-    (if (file-exists-p file-path)
-        (progn
-          (find-file-other-window file-path)
-          (setq-local default-directory (dtache--session-working-directory 
session))
-          (dtache-log-mode)
-          (goto-char (point-max)))
-      (message "Dtache can't find file: %s" file-path))))
-
-(defun dtache--tail-file (session file)
-  "Tail SESSION's FILE."
-  (let* ((file-path
-          (dtache-session-file session file))
-         (tramp-verbose 1))
-    (when (file-exists-p file-path)
-      (find-file-other-window file-path)
-      (dtache-tail-mode)
-      (goto-char (point-max)))))
-
 (defun dtache--create-id (command)
   "Return a hash identifier for COMMAND."
   (let ((current-time (current-time-string)))
@@ -826,21 +846,30 @@ the current time is used."
    (dtache--session-creation-time session)))
 
 (defun dtache--size-str (session)
-  "Return the size of SESSION's log."
+  "Return the size of SESSION's output."
   (file-size-human-readable
-   (dtache--session-log-size session)))
+   (dtache--session-output-size session)))
 
-(defun dtache--degraded-str (session)
-  "Return string if SESSION is degraded."
-  (if (dtache--session-degraded session)
-      "!"
-    ""))
+(defun dtache--status-str (session)
+  "Return string if SESSION has failed."
+  (pcase (dtache--session-status session)
+    ('failure "!")
+    ('success " ")
+    ('unknown " ")))
 
 (defun dtache--active-str (session)
   "Return string if SESSION is active."
   (if (dtache--session-active session)
       "*"
-    ""))
+    " "))
+
+(defun dtache--working-dir-str (session)
+  "Return working directory of SESSION."
+  (let ((working-directory
+         (dtache--session-working-directory session)))
+    (if-let ((remote (file-remote-p working-directory)))
+        (string-remove-prefix remote working-directory)
+        working-directory)))
 
 ;;;; Major modes
 
@@ -856,7 +885,7 @@ the current time is used."
 
 (defvar dtache-tail-mode-map
   (let ((map (make-sparse-keymap)))
-    (define-key map (kbd "q") #'dtache-quit-tail-log)
+    (define-key map (kbd "q") #'dtache-quit-tail-output)
     map)
   "Keymap for `dtache-tail-mode'.")
 
@@ -877,13 +906,15 @@ the current time is used."
 (define-derived-mode dtache-sessions-mode tabulated-list-mode "Dtache Sessions"
   "Dtache sessions."
   (setq tabulated-list-format
-        `[("Command" ,dtache-max-command-length nil)
+        `[("Command" ,(or dtache-max-command-length 50) nil)
           ("Active" 10 nil)
-          ("Directory" 30 nil)
+          ("Status" 10 nil)
           ("Host" 20 nil)
+          ("Directory" 40 nil)
+          ("Metadata" 30 nil)
           ("Duration" 10 nil)
-          ("Created" 20 nil)
-          ("ID" 8 nil)])
+          ("Size" 10 nil)
+          ("Created" 20 nil)])
   (setq tabulated-list-padding 2)
   (setq tabulated-list-sort-key nil)
   (tabulated-list-init-header))
@@ -893,24 +924,24 @@ the current time is used."
   `(,session
     [,(dtache--session-command session)
      ,(dtache--active-str session)
-     ,(dtache--session-working-directory session)
+     ,(dtache--status-str session)
      ,(dtache--session-host session)
+     ,(dtache--working-dir-str session)
+     ,(dtache--metadata-str session)
      ,(dtache--duration-str session)
-     ,(dtache--creation-str session)
-     ,(dtache--session-short-id session)]))
+     ,(dtache--size-str session)
+     ,(dtache--creation-str session)]))
 
 (let ((map dtache-sessions-mode-map))
   (define-key map (kbd "<return>") #'dtache-open-session)
   (define-key map (kbd "c") #'dtache-compile-session)
   (define-key map (kbd "d") #'dtache-remove-session)
-  (define-key map (kbd "e") #'dtache-open-stderr)
   (define-key map (kbd "k") #'dtache-kill-session)
-  (define-key map (kbd "l") #'dtache-open-log)
-  (define-key map (kbd "o") #'dtache-open-stdout)
+  (define-key map (kbd "o") #'dtache-open-output)
   (define-key map (kbd "r") #'dtache-rerun-session)
-  (define-key map (kbd "t") #'dtache-tail-log)
+  (define-key map (kbd "t") #'dtache-tail-output)
   (define-key map (kbd "w") #'dtache-copy-session-command)
-  (define-key map (kbd "W") #'dtache-copy-session-log))
+  (define-key map (kbd "W") #'dtache-copy-session-output))
 
 (provide 'dtache)
 
diff --git a/embark-dtache.el b/embark-dtache.el
index 1f214c7d84..537fe970e6 100644
--- a/embark-dtache.el
+++ b/embark-dtache.el
@@ -40,15 +40,14 @@
   "Keymap for Embark dtache actions."
   ("c" dtache-compile-session)
   ("d" dtache-remove-session)
-  ("e" dtache-open-stderr)
   ("i" dtache-insert-session-command)
   ("k" dtache-kill-session)
-  ("l" dtache-open-log)
-  ("o" dtache-open-stdout)
+  ("o" dtache-open-output)
   ("r" dtache-rerun-session)
-  ("t" dtache-tail-log)
+  ("t" dtache-tail-output)
   ("w" dtache-copy-session-command)
-  ("W" dtache-copy-session-log))
+  ("W" dtache-copy-session-output)
+  ("=" dtache-diff-session))
 
 (add-to-list 'embark-keymap-alist '(dtache . embark-dtache-map))
 
diff --git a/marginalia-dtache.el b/marginalia-dtache.el
index c1889be8f4..e1c82f8a4d 100644
--- a/marginalia-dtache.el
+++ b/marginalia-dtache.el
@@ -39,8 +39,10 @@
 
 (defvar marginalia-dtache-metadata-length 30)
 (defvar marginalia-dtache-duration-length 10)
+(defvar marginalia-dtache-working-dir-length 50)
 (defvar marginalia-dtache-size-length 8)
 (defvar marginalia-dtache-date-length 12)
+(defvar marginalia-dtache-host-length 10)
 
 ;;;; Faces
 
@@ -73,6 +75,14 @@
   '((t :inherit marginalia-date))
   "Face used to highlight date in `marginalia-mode'.")
 
+(defface marginalia-dtache-working-dir
+  '((t :inherit marginalia-symbol))
+  "Face used to highlight working directory in `marginalia-mode'.")
+
+(defface marginalia-dtache-host
+  '((t :inherit marginalia-function))
+  "Face used to highlight host in `marginalia-mode'.")
+
 ;;;; Functions
 
 (defun marginalia-dtache-annotate (candidate)
@@ -81,7 +91,9 @@
           (get-text-property 0 'dtache--data candidate)))
     (marginalia--fields
      ((dtache--active-str session) :width 3 :face 'marginalia-dtache-active)
-     ((dtache--degraded-str session) :width 3 :face 'marginalia-dtache-error)
+     ((dtache--status-str session) :width 3 :face 'marginalia-dtache-error)
+     ((dtache--session-host session) :truncate marginalia-dtache-host-length 
:face 'marginalia-dtache-host)
+     ((dtache--working-dir-str session) :truncate 
marginalia-dtache-working-dir-length :face 'marginalia-dtache-working-dir)
      ((dtache--metadata-str session) :truncate 
marginalia-dtache-metadata-length :face 'marginalia-dtache-metadata)
      ((dtache--duration-str session) :truncate 
marginalia-dtache-duration-length :face 'marginalia-dtache-duration)
      ((dtache--size-str session) :truncate marginalia-dtache-size-length :face 
'marginalia-dtache-size)
diff --git a/test/dtache-test.el b/test/dtache-test.el
index e85a815215..b5edada4d2 100644
--- a/test/dtache-test.el
+++ b/test/dtache-test.el
@@ -37,7 +37,10 @@
   "Initialize a dtache database and evaluate BODY."
   `(let* ((temp-directory (make-temp-file "dtache" t))
           (dtache-db-directory (expand-file-name "dtache.db" temp-directory))
-          (dtache-session-directory (expand-file-name "sessions" 
temp-directory)))
+          (dtache-session-directory (expand-file-name "sessions" 
temp-directory))
+          (dtache--sessions)
+          (dtache--sessions-initialized)
+          (dtache--remote-session-timer))
      (unwind-protect
          (progn
            (dtache-initialize)
@@ -56,19 +59,18 @@
   "Set STATE of SESSION."
   (pcase state
     ('activate
-     (dolist (type `(socket log stderr))
+     (dolist (type `(socket log))
        (with-temp-file (dtache-session-file session type))))
     ('deactivate
      (delete-file (dtache-session-file session 'socket)))
     ('kill
      (delete-file (dtache-session-file session 'socket))
-     (delete-file (dtache-session-file session 'log))
-     (delete-file (dtache-session-file session 'stderr)))))
+     (delete-file (dtache-session-file session 'log)))))
 
 ;;;; Tests
 
 (ert-deftest dtache-test-dtach-command ()
-  (cl-letf* (((symbol-function #'dtache--output-command) (lambda (_) 
"command"))
+  (cl-letf* (((symbol-function #'dtache--magic-command) (lambda (_) "command"))
              (dtache-shell-program "zsh")
              (dtache-dtach-program "/usr/bin/dtach")
              (dtache--dtach-mode 'create)
@@ -97,8 +99,6 @@
              ((symbol-function #'file-remote-p) (lambda (_directory) nil))
              (session (dtache--session-create :id "12345" :session-directory 
"/home/user/tmp/")))
     (should (string= "/home/user/tmp/12345.log" (dtache-session-file session 
'log)))
-    (should (string= "/home/user/tmp/12345.stderr" (dtache-session-file 
session 'stderr)))
-    (should (string= "/home/user/tmp/12345.stdout" (dtache-session-file 
session 'stdout)))
     (should (string= "/home/user/tmp/12345.socket" (dtache-session-file 
session 'socket))))
 
   ;; Remote files
@@ -106,8 +106,6 @@
              ((symbol-function #'file-remote-p) (lambda (_directory) 
"/ssh:foo:"))
              (session (dtache--session-create :id "12345" :session-directory 
"/home/user/tmp/")))
     (should (string= "/ssh:foo:/home/user/tmp/12345.log" (dtache-session-file 
session 'log)))
-    (should (string= "/ssh:foo:/home/user/tmp/12345.stderr" 
(dtache-session-file session 'stderr)))
-    (should (string= "/ssh:foo:/home/user/tmp/12345.stdout" 
(dtache-session-file session 'stdout)))
     (should (string= "/ssh:foo:/home/user/tmp/12345.socket" 
(dtache-session-file session 'socket)))))
 
 (ert-deftest dtache-test-session-short-id ()
@@ -139,11 +137,11 @@
 (ert-deftest dtache-test-session-dead-p ()
   (dtache-test--with-temp-database
    (let ((session (dtache-test--create-session :command "foo" :host 
"localhost")))
-     (should (not (dtache--session-dead-p session)))
+     (should (not (dtache--session-missing-p session)))
      (dtache-test--change-session-state session 'deactivate)
-     (should (not (dtache--session-dead-p session)))
+     (should (not (dtache--session-missing-p session)))
      (dtache-test--change-session-state session 'kill)
-     (should (dtache--session-dead-p session)))))
+     (should (dtache--session-missing-p session)))))
 
 (ert-deftest dtache-test-cleanup-host-sessions ()
   (dtache-test--with-temp-database
@@ -185,27 +183,27 @@
      (dtache--db-update-session session)
      (should (equal session (car (dtache--db-select-sessions)))))))
 
-(ert-deftest dtache-test-output-command ()
-  ;; Degraded
-  (let* ((dtache-notify-send nil)
+(ert-deftest dtache-test-magic-command ()
+  ;; Redirect only
+  (let* ((dtache-shell-program "bash")
          (actual
-          (dtache--output-command
-           (dtache--session-create :id "12345" :session-directory 
"/tmp/dtache/" :command "ls" :degraded t)))
-         (expected "{ ls; } &> /tmp/dtache/12345.log"))
+          (dtache--magic-command
+           (dtache--session-create :id "12345" :session-directory 
"/tmp/dtache/" :command "ls" :redirect-only t)))
+         (expected "{ dtache-env bash -c -i \\\"ls\\\"; } &> 
/tmp/dtache/12345.log"))
     (should (string= actual expected)))
 
   ;; Normal
-  (let* ((dtache-notify-send nil)
+  (let* ((dtache-shell-program "bash")
          (actual
-          (dtache--output-command
+          (dtache--magic-command
            (dtache--session-create :id "12345" :session-directory 
"/tmp/dtache/" :command "ls")))
-         (expected "{ { ls; } > >(tee /tmp/dtache/12345.stdout ); } 2> >(tee 
/tmp/dtache/12345.stderr )  | tee /tmp/dtache/12345.log"))
+         (expected "{ dtache-env bash -c -i \\\"ls\\\"; } 2>&1 | tee 
/tmp/dtache/12345.log"))
     (should (string= actual expected))))
 
-(ert-deftest dtache-test-degraded-p ()
-  (let ((dtache-degraded-list '("ls")))
-    (should (not (dtache-degraded-p "cd")))
-    (should (dtache-degraded-p "ls -la"))))
+(ert-deftest dtache-test-redirect-only-p ()
+  (let ((dtache-redirect-only-regexps '("ls")))
+    (should (not (dtache-redirect-only-p "cd")))
+    (should (dtache-redirect-only-p "ls -la"))))
 
 (ert-deftest dtache-test-session-pid ()
   (cl-letf* (((symbol-function #'process-file) (lambda (_program _infile 
_buffer _display &rest _args)
@@ -213,8 +211,7 @@
 
              (session1 (dtache--session-create :id "foo" :session-directory 
"/tmp/"))
              (session2 (dtache--session-create :id "bar" :session-directory 
"/tmp/"))
-             (session3 (dtache--session-create :id "baz" :session-directory 
"/tmp/"))
-             (dtache--socket-ext ".socket"))
+             (session3 (dtache--session-create :id "baz" :session-directory 
"/tmp/")))
     (should (string= "6699" (dtache--session-pid session1)))
     (should (string= "6698" (dtache--session-pid session2)))
     (should (not (dtache--session-pid session3)))))
@@ -233,16 +230,27 @@
     (should (string= "May 08 08:49" (dtache--creation-str 
(dtache--session-create :creation-time 1620463748.7636228))))))
 
 (ert-deftest dtache-test-size-str ()
-  (should (string= "100" (dtache--size-str (dtache--session-create :log-size 
100))))
-  (should (string= "1k" (dtache--size-str (dtache--session-create :log-size 
1024)))))
+  (should (string= "100" (dtache--size-str (dtache--session-create 
:output-size 100))))
+  (should (string= "1k" (dtache--size-str (dtache--session-create :output-size 
1024)))))
 
-(ert-deftest dtache-test-degraded-str ()
-  (should (string= "!" (dtache--degraded-str (dtache--session-create :degraded 
t))))
-  (should (string= "" (dtache--degraded-str (dtache--session-create :degraded 
nil)))))
+(ert-deftest dtache-test-status-str ()
+  (should (string= "!" (dtache--status-str (dtache--session-create :status 
'failure))))
+  (should (string= " " (dtache--status-str (dtache--session-create :status 
'success))))
+  (should (string= " " (dtache--status-str (dtache--session-create :status 
'unknown)))))
 
 (ert-deftest dtache-test-active-str ()
   (should (string= "*" (dtache--active-str (dtache--session-create :active 
t))))
-  (should (string= "" (dtache--active-str (dtache--session-create :active 
nil)))))
+  (should (string= " " (dtache--active-str (dtache--session-create :active 
nil)))))
+
+(ert-deftest dtache-test-working-dir-str ()
+  (should
+   (string= "/home/user/repo"
+            (dtache--working-dir-str
+             (dtache--session-create :working-directory 
"/ssh:remote:/home/user/repo"))))
+  (should
+   (string= "~/repo"
+            (dtache--working-dir-str
+             (dtache--session-create :working-directory "~/repo")))))
 
 (provide 'dtache-test)
 



reply via email to

[Prev in Thread] Current Thread [Next in Thread]