unofficial mirror of help-gnu-emacs@gnu.org
 help / color / mirror / Atom feed
* Advice on custom packages that conflict with tramp
@ 2023-03-07 21:30 David Karr
  2023-03-10 16:19 ` Stefan Monnier via Users list for the GNU Emacs text editor
  0 siblings, 1 reply; 2+ messages in thread
From: David Karr @ 2023-03-07 21:30 UTC (permalink / raw)
  To: help-gnu-emacs

I've been using Emacs for a very long time, although I don't consider
myself a sophisticated user.  I mostly use it for shell buffers and viewing
files, and occasionally editing.

Just about 25 years ago, I wrote two very small packages that I use to
manage a ring of shell buffers, which provides the following primary
features:
* When I'm in a shell buffer, executing the "main function" wilI create a
new shell that goes to the end of the ring
* When I'm not in a shell buffer, executing that function just goes to the
first shell buffer in the ring
* When I'm in a shell buffer, I can press one key sequence to go to the
next (or previous) buffer in the ring, going to the next buffer from the
last buffer in the ring goes to the first one
* When I'm in a shell buffer, I can execute one function that lets me enter
a search string representing a directory, and it will put me in the first
shell buffer whose current directory contains that substring

The resulting shell buffer names are just "shell", "shell-1", "shell-2", et
cetera.

As I said, I'm not really a sophisticated elisper.  I think it's likely
that what I wrote violates any number of conventions and rules, but for
what I mainly use it for, it works fine.

However, I'm now trying to see if the Tramp package can make some of the
ssh things I have to do a little easier.  I've been talking to the Tramp
team, and it appears that something I'm doing in my shell package is
conflicting with Tramp.  If I start Emacs and immediately use Tramp, it
works fine. If I first create a shell buffer and then try to use Tramp, the
connection times out.

One of the Tramp team members took a brief look at my packages and said
that he did see at least one oddity in how I'm creating the shell buffers,
being that it creates the shell buffer (so I can control the name) and then
calls "shell" for that buffer. I'm not sure how to fix that so my shell
package still works and Tramp also works.  If I had to lose the control
over the buffer name, I could live with that, but the basic features that I
listed up above have to work.

I'm going to include here the two packages that implement this.  I'd
appreciate any constructive advice on refactoring this so the original
functionality still works, and Tramp also works.

----cycle-shell.el-----------
;;;
;;; cycle-shell.el --- Package to allow creation and cycling between
multiple
;;;                    "shell" buffers.  This requires the "buffer-cycle"
;;;                    package and the "shell-for-cycle" package (which is
just
;;;                    a barely modified version of shell.el from the v19
;;;                    distribution).
;;;
;; Author: David M. Karr <dkarr@nmo.gtegsc.com>
;; Keywords: buffers shell

;;; This package allows the user to create multiple shell buffers and
quickly
;;; move between them.

(require 'buffer-cycle)

(defvar last-shell-buffer nil "BUFFER object, last shell buffer")

(defun cycle-make-shell ()
  "Implements a ring of *shell* buffers.  If current buffer is not a shell
buffer, then it will just execute 'shell'.  If it IS a shell buffer, then
create a new shell buffer, starting with '*shell-1*', but skipping to the
next
number if that already exists."
  (interactive)
  (let* ((bufname (buffer-name (current-buffer))))
    (if (string-match "\*shell\\(\*\\|-\\([0-9][0-9]*\\)\*\\)" bufname)
        (progn
          (setq change-dir default-directory)
          (setq done nil)
          (while (not done)
            (progn
              (setq new-bufname (next-bufname bufname "shell"))
              (if (bufferp (get-buffer new-bufname))
                  (setq bufname new-bufname)
                (setq done t)
                )
              )
            )
          (if (bufferp (get-buffer "*shell*"))
              (progn
                (set-buffer "*shell*")
                (rename-uniquely)
                (setq tmp-bufname (buffer-name))
                ))
          (shell)
 (sit-for 1)
          (set-buffer "*shell*")
          (rename-buffer new-bufname)
          ;; Now we need to somehow change the directory to "change-dir".
          (set-buffer tmp-bufname)
          (rename-buffer "*shell*")
          (set-buffer new-bufname)
          )
      (progn
        ;; check for existence of buffer last-shell-buffer.  If it exists,
        ;; go to it.  If not, then execute "shell".

        (if (and (bufferp last-shell-buffer)
                 (not (killed-buffer-p last-shell-buffer)))
            (switch-to-buffer last-shell-buffer)
          (shell))
        )
      )
    )
  )

(defun cycle-go-shell (&optional arg)
  "If in a shell buffer, go to the next shell buffer in the ring."
  (interactive "P")
  (if arg
      (if (numberp arg)
 (cycle-go-buffer "shell" arg)
(cycle-prev-buffer "shell"))
    (cycle-next-buffer "shell"))
  (if (not (eq major-mode 'shell-mode))
      (shell))
  (setq last-shell-buffer (current-buffer))
  )

(defun cycle-find-shell (&optional string)
  (interactive "sEnter directory substring: ")
  (let* ((buflist (buffer-list))
(next-buffer nil)
(chosen-buffer nil)
)
    (while (setq next-buffer (car buflist))
      (setq buflist (cdr buflist))
      (setq bufname (buffer-name next-buffer))
      (setq substring "*shell")
      (if (string-equal
  (substring
   bufname 0 (min (length substring) (length bufname))) substring)
 (progn
   (set-buffer next-buffer)
   (if (string-match string default-directory)
(setq chosen-buffer next-buffer)
     )
   )
)
      )
    (if chosen-buffer
(progn
 (switch-to-buffer chosen-buffer)
 (setq last-shell-buffer chosen-buffer)
 )
      (error (concat "PWD with \"" string "\" not found."))
      )
    )
  )

(defun killed-buffer-p (buffer)
  "Return t if BUFFER is killed."
  (not (buffer-name buffer)))

(defun shells-buffer-list ()
  (interactive)
  (electric-buffer-list "^\*shell")
  )

(defun cycle-find-shell-or-shells-buffer-list (&optional arg)
  (interactive "P")
  (if arg
                (shells-buffer-list)
         (call-interactively 'cycle-find-shell)
                )
  )
----cycle-shell.el-----------

-----buffer-cycle.el-----------
;;;
;;; buffer-cycle.el --- defuns to cycle between several buffers of the same
;;;                     type.
;;;
;; Author: David M. Karr <dkarr@nmo.gtegsc.com>
;; Keywords: buffers

;;; This package defines a small number of utility defuns to be used by
other
;;; packages to provide the facility of cycling quickly between several
buffers
;;; of the same type.  It assumes the first buffer of the type will be named
;;; "*something*", and following ones will be named "*something-1*", etc.
;;;
;;; The defun "next-bufname" takes a BUFNAME and a CORESTRING and returns
what
;;; would be the next buffer name in the cycle.
;;;
;;; The defun "cycle-next-buffer" takes a CORESTRING.  It looks at the
current
;;; buffer and gets the result of "next-bufname" given the current buffer
and
;;; the CORESTRING.  If that buffer exists, then it tries to switch to that
;;; new buffer.  If buffers in the middle of the ring are deleted, then the
;;; buffers at the end are unreachable by this method.  It could be made
more
;;; robust by storing the buffer names in a list and tracking buffer
deletes,
;;; etc., but this is probably good enough.

(provide 'buffer-cycle)

(defun next-bufname (bufname corestring)
  "Given a particular BUFNAME and a CORESTRING (like \"*CORESTRING*\"),
return
the next buffer in line."
  (if (string-equal (concat "*" corestring "*") bufname)
      (concat "*" corestring "-1*")
    (progn
      (string-match (concat "\*" corestring
"\\(\*\\|-\\([0-9][0-9]*\\)\*\\)")
                    bufname)
      (concat "*" corestring "-"
 (int-to-string
(+ (string-to-number
(substring bufname (match-beginning 2) (match-end 2)))
1))
 "*")
      )
    )
  )

(defun prev-bufname (bufname corestring)
  "Given a particular BUFNAME and a CORESTRING (like \"*CORESTRING*\"),
return
the previous buffer in line."
  (string-match (concat "\*" corestring "\\(\*\\|-\\([0-9][0-9]*\\)\*\\)")
bufname)
  (setq new-bufname
 (concat "*" corestring "-"
(int-to-string
 (- (string-to-number
(substring bufname
 (match-beginning 2) (match-end 2)))
 1))
"*"))
  (if (string= new-bufname (concat "\*" corestring "-0\*"))
      (concat "\*" corestring "\*")
    new-bufname)
  )

(defun cycle-next-buffer (corestring)
  (let* ((bufname (buffer-name (current-buffer))))
    (if (string-match
         (concat "\*" corestring "\\(\*\\|-\\([0-9][0-9]*\\)\*\\)") bufname)
        (progn
          (setq new-bufname (next-bufname bufname corestring))
          (if (bufferp (get-buffer new-bufname))
              (switch-to-buffer new-bufname)
            (switch-to-buffer (concat "*" corestring "*"))
            )
          )
      (switch-to-buffer (concat "*" corestring "*"))
      )
    )
  )

(defun cycle-prev-buffer (corestring)
  (let* ((bufname (buffer-name (current-buffer))))
    (if (string-match
         (concat "\*" corestring "\\(\*\\|-\\([0-9][0-9]*\\)\*\\)") bufname)
        (progn
 (if (string= (concat "\*" corestring "\*") bufname)
     (let ((last-bufname bufname))
(while (bufferp (get-buffer
(setq new-bufname
      (next-bufname last-bufname corestring))))
 (setq last-bufname new-bufname)
 )
(switch-to-buffer last-bufname)
)
   (let ((new-bufname (prev-bufname bufname corestring)))
     (if (bufferp (get-buffer new-bufname))
 (switch-to-buffer new-bufname)
(switch-to-buffer (concat "*" corestring "*"))
)
     )
   )
 )
      (switch-to-buffer (concat "*" corestring "*"))
      )
    )
  )

(defun cycle-go-buffer (corestring bufnum)
  (setq new-bufname
(concat "\*" corestring
(if (not (eq bufnum 0))
 (concat "-" (number-to-string bufnum)))
"\*"))
  (if (bufferp (get-buffer new-bufname))
      (switch-to-buffer new-bufname)
    (error (concat "No " corestring " buffer in " new-bufname ".")))
  )
-----buffer-cycle.el-----------


^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: Advice on custom packages that conflict with tramp
  2023-03-07 21:30 Advice on custom packages that conflict with tramp David Karr
@ 2023-03-10 16:19 ` Stefan Monnier via Users list for the GNU Emacs text editor
  0 siblings, 0 replies; 2+ messages in thread
From: Stefan Monnier via Users list for the GNU Emacs text editor @ 2023-03-10 16:19 UTC (permalink / raw)
  To: help-gnu-emacs

> One of the Tramp team members took a brief look at my packages and said
> that he did see at least one oddity in how I'm creating the shell buffers,
> being that it creates the shell buffer (so I can control the name) and then
> calls "shell" for that buffer.

I can't see where it "creates the shell buffer" before calling `shell`.

> (defun cycle-make-shell ()
>   "Implements a ring of *shell* buffers.  If current buffer is not a shell
> buffer, then it will just execute 'shell'.  If it IS a shell buffer, then
> create a new shell buffer, starting with '*shell-1*', but skipping to the
> next
> number if that already exists."
>   (interactive)
>   (let* ((bufname (buffer-name (current-buffer))))
>     (if (string-match "\*shell\\(\*\\|-\\([0-9][0-9]*\\)\*\\)" bufname)
                         ^         ^                      ^
                         ineffective

BTW, why not check (derived-mode-p 'shell-mode) instead of checking the name?

>         (progn
>           (setq change-dir default-directory)
>           (setq done nil)

Here you change those two *global* variables.  Better define them
locally with `let`.  *Never* use `setq` on a variable unless you've
locally defined it with `let` or you globally defined it with `defvar`.

I recommend you regularly byte-compile your code (or use `flymake-mode`)
to benefit from the compiler's warnings.

>           (while (not done)
>             (progn
>               (setq new-bufname (next-bufname bufname "shell"))
>               (if (bufferp (get-buffer new-bufname))
>                   (setq bufname new-bufname)
>                 (setq done t)
>                 )
>               )
>             )
>           (if (bufferp (get-buffer "*shell*"))
>               (progn
>                 (set-buffer "*shell*")
>                 (rename-uniquely)
>                 (setq tmp-bufname (buffer-name))
>                 ))
>           (shell)
>  (sit-for 1)
>           (set-buffer "*shell*")
>           (rename-buffer new-bufname)
>           ;; Now we need to somehow change the directory to "change-dir".
>           (set-buffer tmp-bufname)
>           (rename-buffer "*shell*")
>           (set-buffer new-bufname)

If I were you, I'd change that function so that all your buffers are
named `*shell-N*` (i.e. you won't have one called `*shell*` any more).
Then you can just replace the above code with something like:

    (shell)
    (rename-uniquely)

and call it a day.  It makes the other case (i.e. when you call that
command from a non-shell buffer) slightly more complicated since you
have to look for a shell buffer, but nothing too bad.
IOW something like:

    (defun my-cycle-make-shell ()
      (interactive)
      (let ((dest nil))
        (unless (derived-mode-p 'shell-mode)
          (let ((bufs (buffer-list)))
            (while (and bufs (not dest))
              (if (with-current-buffer (car bufs)
                    (derived-mode-p 'shell-mode))
                  (setq dest (car bufs))
                (setq bufs (cdr bufs))))))
        (if dest
            (pop-to-buffer-same-window dest)
          (shell)
          (rename-uniquely))))

Of course, you can use something else than `rename-uniquely` if you
don't like the names it chooses :-)

> (defun cycle-find-shell (&optional string)
>   (interactive "sEnter directory substring: ")

AFAICT `string` is actually not searched as a substring but as a regexp.


        Stefan




^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2023-03-10 16:19 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-03-07 21:30 Advice on custom packages that conflict with tramp David Karr
2023-03-10 16:19 ` Stefan Monnier via Users list for the GNU Emacs text editor

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).