unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* [ELPA] New package: project-shells
@ 2017-02-24 12:36 Huang, Ying
  2017-02-24 16:21 ` raman
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: Huang, Ying @ 2017-02-24 12:36 UTC (permalink / raw)
  To: emacs-devel

Hi, All,

This is to manage multiple shell (or terminal) buffers for each
project.  For example, to develop for Linux kernel, I usually use one
shell buffer to configure and build kernel, one shell buffer to run
some git command not supported by magit, one shell buffer to run qemu
for built kernel, one shell buffer to ssh into guest system to test.
Different set of commands is used by the shell in each buffer, so each
shell should have different command history configuration, and for
some shell, I may need different setup.  And I have several projects
to work on.  In addition to project specific shell buffers, I want
some global shell buffers, so that I can use them whichever project I
am working on.  Project shells is an Emacs program to let my life
easier via helping me to manage all these shell/terminal buffers.

If you think this may be helpful for someone and there is no existing
package has same functionality, could you help me to upload this into
ELPA archive?  The package is as follow.

;;; project-shells.el --- Manage the shell buffers of each project -*- lexical-binding: t -*-

;; Copyright (C) 2017 "Huang, Ying" <huang.ying.caritas@gmail.com>

;; Author: "Huang, Ying" <huang.ying.caritas@gmail.com>
;; Maintainer: "Huang, Ying" <huang.ying.caritas@gmail.com>
;; URL: https://github.com/hying-caritas/project-shells
;; Version: 20170222
;; Package-Version: 20170222
;; Package-Type: simple
;; Keywords: project, shell, terminal
;; Package-Requires: ((pkg-info "0.4"))

;; This file is NOT part of GNU Emacs.

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.

;;; Commentary:

;; Manage multiple shell/terminal buffers for each project.  For
;; example, to develop for Linux kernel, I usually use one shell
;; buffer to configure and build kernel, one shell buffer to run some
;; git command not supported by magit, one shell buffer to run qemu
;; for built kernel, one shell buffer to ssh into guest system to
;; test.  Different set of commands is used by the shell in each
;; buffer, so each shell should have different command history
;; configuration, and for some shell, I may need different setup.  And
;; I have several projects to work on.  In addition to project
;; specific shell buffers, I want some global shell buffers, so that I
;; can use them whichever project I am working on.  Project shells is
;; an Emacs program to let my life easier via helping me to manage all
;; these shell/terminal buffers.

(require 'cl-lib)

(defvar-local project-shells-project-name nil)
(defvar-local project-shells-project-root nil)

;;; Customization
(defgroup project-shells nil
  "Manage shells of projects"
  :group 'tools
  :link '(url-link :tag "Github" "https://github.com/hying-caritas/project-shells"))

(defcustom project-shells-default-shell-name "sh"
  "The default shell buffer name"
  :group 'project-shells
  :type 'string)

(defcustom project-shells-empty-project "-"
  "Specify the name of the empty project.

This is used to create non-project specific shells."
  :group 'project-shells
  :type 'string)

(defcustom  project-shells-setup `((,project-shells-empty-project .
				      (("1" .
					(,project-shells-default-shell-name
					 "~/" 'shell nil)))))
  "Specify the setup for shells of each project.

Including name, initial directory, type, function to intialize,
etc."
  :group 'project-shells
  :type '(alist :key-type (string :tag "Project") :value-type
		(alist :tag "Project setup"
		       :key-type  (string :tag "Key")
		       :value-type (list :tag "Shell setup"
					 (string :tag "Name")
					 (string :tag "Directory")
					 (choice :tag "Type" (const term) (const shell))
					 (choice :tag "Function" (const nil) function)))))

(defcustom project-shells-keys '("1" "2" "3" "4" "5" "6" "7" "8" "9" "0" "-" "=")
  "Specify keys for shells, one shell will be created for each key.

Usually these key will be bound in a non-global keymap."
  :group 'project-shells
  :type '(repeat string))

(defcustom project-shells-term-keys '("-" "=")
  "Specify keys to create terminal.

By default shell mode will be used, but for keys in
project-shells-term-keys, ansi terminal mode will be used.  This
should be a subset of *poject-shells-keys*."
  :group 'project-shells
  :type '(repeat string))

(defcustom project-shells-project-name-func 'projectile-project-name
  "Specify function to get project name."
  :group 'project-shells
  :type 'function)

(defcustom project-shells-project-root-func 'projectile-project-root
  "Specify function to get project root directory"
  :group 'project-shells
  :type 'function)

(defcustom project-shells-histfile-env "HISTFILE"
  "Specify environment variable to set shell history file"
  :group 'project-shells
  :type 'string)

(defcustom project-shells-histfile-name ".shell_history"
  "Specify shell history file name"
  :group 'project-shells
  :type 'string)

(defcustom project-shells-term-args nil
  "Specify shell argument used in terminal"
  :group 'project-shells
  :type 'string)

(let ((saved-shell-buffer-list nil)
      (last-shell-name))
  (cl-defun shell-buffer-list ()
    (setf saved-shell-buffer-list
	  (cl-remove-if-not #'buffer-live-p saved-shell-buffer-list)))
  (cl-defun project-shells-switch (&optional name to-create)
    (interactive "bShell: ")
    (let* ((name (or name last-shell-name))
	   (buffer-list (shell-buffer-list))
	   (buf (when name
		  (cl-find-if (lambda (b) (string= name (buffer-name b)))
			      buffer-list))))
      (when (and (or buf to-create)
		 (cl-find (current-buffer) buffer-list))
	(setf last-shell-name (buffer-name (current-buffer))))
      (if buf
	  (progn
	    (select-window (display-buffer buf))
	    buf)
	(unless to-create
	  (message "No such shell: %s" name)
	  nil))))
  (cl-defun project-shells-switch-to-last ()
    (interactive)
    (let ((name (or (and last-shell-name (get-buffer last-shell-name)
			 last-shell-name)
		    (and (shell-buffer-list)
			 (buffer-name (first (shell-buffer-list)))))))
      (if name
	  (project-shells-switch name)
	(message "No more shell buffers!"))))
  (cl-defun project-shells-create (name dir &optional (type 'shell) func)
    (let ((default-directory (expand-file-name (or dir "~/"))))
      (cl-ecase type
	('term (ansi-term "/bin/sh"))
	('shell (shell)))
      (rename-buffer name)
      (push (current-buffer) saved-shell-buffer-list)
      (when func (funcall func)))))

(cl-defun project-shells-create-switch (name dir &optional (type 'shell) func)
  (unless (project-shells-switch name t)
    (project-shells-create name dir type func)))

(cl-defun project-shells-send-shell-command (cmdline)
  (insert cmdline)
  (comint-send-input))

(cl-defun project-shells-project-name ()
  (or project-shells-project-name (funcall project-shells-project-name-func)
      project-shells-empty-project))

(cl-defun project-shells-project-root (proj-name)
  (if (string= proj-name project-shells-empty-project)
      "~/"
    (or project-shells-project-root
	(funcall project-shells-project-root-func))))

(cl-defun project-shells-set-histfile-env (val)
  (when (and project-shells-histfile-env
	     project-shells-histfile-name)
    (setenv project-shells-histfile-env val)))

(cl-defun project-shells-escape-sh (str)
  (replace-regexp-in-string
   "\"" "\\\\\""
   (replace-regexp-in-string "\\\\" "\\\\\\\\" str)))

(cl-defun project-shells-command-string (args)
  (mapconcat
   #'identity
   (cl-loop
    for arg in args
    collect (concat "\"" (project-shells-escape-sh arg) "\""))
   " "))

(cl-defun project-shells-term-command-string ()
  (let* ((prog (or explicit-shell-file-name
		   (getenv "ESHELL") shell-file-name)))
    (concat "exec " (project-shells-command-string
		     (cons prog project-shells-term-args)) "\n")))

(cl-defun project-shells-activate (key &optional proj proj-root)
  (let* ((proj (or proj (project-shells-project-name)))
	 (proj-root (or proj-root (project-shells-project-root proj)))
	 (proj-shells (cdr (assoc proj project-shells-setup)))
	 (shell-info (cdr (assoc key proj-shells)))
	 (name (or (first shell-info) project-shells-default-shell-name))
	 (dir (or (second shell-info) proj-root))
	 (type (or (third shell-info)
		   (if (member key project-shells-term-keys)
		       'term 'shell)))
	 (func (fourth shell-info))
	 (shell-name (format "*%s.%s.%s*" key name proj))
	 (session-dir (expand-file-name (format "~/.sessions/%s/%s" proj key))))
    (mkdir session-dir t)
    (project-shells-set-histfile-env
     (format "%s/%s" session-dir project-shells-histfile-name))
    (project-shells-create-switch
     shell-name dir type
     (lambda ()
       (when func
	 (funcall func session-dir))
       (when (eq type 'term)
	 (term-send-raw-string (project-shells-term-command-string)))
       (setf project-shells-project-name proj
	     project-shells-project-root proj-root)))
    (project-shells-set-histfile-env nil)))

(cl-defun project-shells-setup (map &optional setup)
  (when setup
    (setf project-shells-setup setup))
  (cl-loop
   for key in project-shells-keys
   do (define-key map (kbd key)
	(let* ((key key))
	  (lambda (p)
	    (interactive "p")
	    (project-shells-activate
	     key (and (/= p 1) project-shells-empty-project)))))))

(provide 'project-shells)




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

* Re: [ELPA] New package: project-shells
  2017-02-24 12:36 [ELPA] New package: project-shells Huang, Ying
@ 2017-02-24 16:21 ` raman
  2017-02-24 16:44 ` John Yates
  2017-02-25  8:46 ` Thien-Thi Nguyen
  2 siblings, 0 replies; 6+ messages in thread
From: raman @ 2017-02-24 16:21 UTC (permalink / raw)
  To: Huang, Ying; +Cc: emacs-devel

sounds useful.
-- 



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

* Re: [ELPA] New package: project-shells
  2017-02-24 12:36 [ELPA] New package: project-shells Huang, Ying
  2017-02-24 16:21 ` raman
@ 2017-02-24 16:44 ` John Yates
  2017-02-25  8:46 ` Thien-Thi Nguyen
  2 siblings, 0 replies; 6+ messages in thread
From: John Yates @ 2017-02-24 16:44 UTC (permalink / raw)
  To: Huang, Ying; +Cc: Emacs developers

[-- Attachment #1: Type: text/plain, Size: 480 bytes --]

On Fri, Feb 24, 2017 at 7:36 AM, Huang, Ying <huang_ying_caritas@163.com>
wrote:

> one shell buffer to run
> some git command not supported by magit


Might magit-git-command (bound to ':') allow you to dispense with one of
your shells?

​(magit-git-command ARGS DIRECTORY)

Execute a Git subcommand asynchronously, displaying the output.
With a prefix argument run Git in the root of the current
repository, otherwise in ‘default-directory’.
​
​/john

[-- Attachment #2: Type: text/html, Size: 1843 bytes --]

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

* Re: [ELPA] New package: project-shells
  2017-02-24 12:36 [ELPA] New package: project-shells Huang, Ying
  2017-02-24 16:21 ` raman
  2017-02-24 16:44 ` John Yates
@ 2017-02-25  8:46 ` Thien-Thi Nguyen
  2017-03-03 12:46   ` Huang, Ying
  2 siblings, 1 reply; 6+ messages in thread
From: Thien-Thi Nguyen @ 2017-02-25  8:46 UTC (permalink / raw)
  To: Huang, Ying; +Cc: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 4304 bytes --]


() "Huang, Ying" <huang_ying_caritas@163.com>
() Fri, 24 Feb 2017 20:36:47 +0800

   ;;; Commentary:

   ;; [...]

   (require 'cl-lib)

This is missing ";;; Code:" prior to the ‘require’ form.

     "Specify the name of the empty project.
     "Specify the setup for shells of each project.
     "Specify keys for shells, one shell will be created for each key.
     [and so on]

These docstrings can be improved mostly by dropping the
"specify" and (perhaps) the article.  For example:

     "Name of the empty project.
     "Default configuration form for shells of each project.
     "Keys used for shells.
     [and so on]

In the second one, i ‘s/Setup/Default configuration form/’ and
suggest you describe form's format in the docstring.  In the
last one, i suggest you find a more descriptive term than
"used".  Also, i dropped the latter half of the run-on sentence.
You can add that back on the next line as a new sentence.

   (defcustom  project-shells-setup

Nit: extra space between ‘defcustom’ and ‘project-shells-setup’.

   (let ((saved-shell-buffer-list nil)
         (last-shell-name))

Both ‘saved-shell-buffer-list’ and ‘last-shell-name’ have
initial binding ‘nil’.  However, this is expressed in two
different ways (both valid).  Why?

     (cl-defun shell-buffer-list ()
       (setf saved-shell-buffer-list
             (cl-remove-if-not #'buffer-live-p saved-shell-buffer-list)))

Nit: Some blank lines between the ‘cl-defun’ forms would help
readability, especially important as these are not at top-level.

     (cl-defun project-shells-switch (&optional name to-create)
       (interactive "bShell: ")

All commands should have a docstring (even generated ones).

         (cl-ecase type
           ('term (ansi-term "/bin/sh"))
           ('shell (shell)))

Is there a reason you quote the key in the cl-ecase KEYLIST?
(Just curious.)

   (cl-defun project-shells-create-switch (name dir &optional (type 'shell) func)
     (unless (project-shells-switch name t)
       (project-shells-create name dir type func)))

Needs documentation, especially for arg ‘func’ (if, when, how it
is called; what happens if it is ill-formed or throws error, etc).

   (cl-defun project-shells-escape-sh (str) ...)

   (cl-defun project-shells-command-string (args) ...)

   (cl-defun project-shells-term-command-string () ...)

   (cl-defun project-shells-activate ...)

Each of these is called solely by the next.  Have you considered
using ‘cl-flet*’ to incorporate the first three into the last?

            (session-dir (expand-file-name (format "~/.sessions/%s/%s" proj key)))

This directory needs to be documented.  It would be nice if it
could be made customizable (e.g., to save under ~/.emacs.d/).
Also, what happens if ‘key’ is slash (or backslash, ‘M-g’, ...)?

        (format "%s/%s" session-dir project-shells-histfile-name)

More idiomatic to use ‘expand-file-name’ here.

   (cl-defun project-shells-setup (map &optional setup)
     (when setup
       (setf project-shells-setup setup))
     (cl-loop
      for key in project-shells-keys
      do (define-key map (kbd key)
           (let* ((key key))
             (lambda (p)
               (interactive "p")
               (project-shells-activate
                key (and (/= p 1) project-shells-empty-project)))))))

Have you considered using ‘this-command-keys’ and a single
command definition instead of defining a command per key?
Also, needs documentation.

One way to sidestep the need for documentation is to distinguish
between "private" and "public" elements, conventionally by using
a double-hyphen for internal (private) names.  Another way is to
incorporate single-caller funcs into that caller (as i mentioned
above).  Yet another (most poor IMHO) way is to never share your
code, but i'm very happy to see you've not chosen that way.  :-D

-- 
Thien-Thi Nguyen -----------------------------------------------
 (defun responsep (query)
   (pcase (context query)
     (`(technical ,ml) (correctp ml))
     ...))                              748E A0E8 1CB8 A748 9BFA
--------------------------------------- 6CE4 6703 2224 4C80 7502


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 197 bytes --]

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

* Re: [ELPA] New package: project-shells
  2017-02-25  8:46 ` Thien-Thi Nguyen
@ 2017-03-03 12:46   ` Huang, Ying
  2017-03-09  8:09     ` Thien-Thi Nguyen
  0 siblings, 1 reply; 6+ messages in thread
From: Huang, Ying @ 2017-03-03 12:46 UTC (permalink / raw)
  To: emacs-devel

Hi, All,

Below is the second version with all comments addressed.

Best Regards,
Huang, Ying

------------------------------------->
;;; project-shells.el --- Manage the shell buffers of each project -*- lexical-binding: t -*-

;; Copyright (C) 2017 "Huang, Ying" <huang.ying.caritas@gmail.com>

;; Author: "Huang, Ying" <huang.ying.caritas@gmail.com>
;; Maintainer: "Huang, Ying" <huang.ying.caritas@gmail.com>
;; URL: https://github.com/hying-caritas/project-shells
;; Version: 20170222
;; Package-Version: 20170222
;; Package-Type: simple
;; Keywords: processes, terminals
;; Package-Requires: ((emacs "24.3") (seq "2.19"))

;; This file is NOT part of GNU Emacs.

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.

;;; Commentary:

;; Manage multiple shell/terminal buffers for each project.  For
;; example, to develop for Linux kernel, I usually use one shell
;; buffer to configure and build kernel, one shell buffer to run some
;; git command not supported by magit, one shell buffer to run qemu
;; for built kernel, one shell buffer to ssh into guest system to
;; test.  Different set of commands is used by the shell in each
;; buffer, so each shell should have different command history
;; configuration, and for some shell, I may need different setup.  And
;; I have several projects to work on.  In addition to project
;; specific shell buffers, I want some global shell buffers, so that I
;; can use them whichever project I am working on.  Project shells is
;; an Emacs program to let my life easier via helping me to manage all
;; these shell/terminal buffers.

;;; Code:

(require 'cl-lib)
(require 'shell)
(require 'term)
(require 'seq)

(defvar-local project-shells-project-name nil)
(defvar-local project-shells-project-root nil)

;;; Customization
(defgroup project-shells nil
  "Manage shell buffers of each project"
  :group 'tools
  :link '(url-link :tag "Github" "https://github.com/hying-caritas/project-shells"))

(defcustom project-shells-default-shell-name "sh"
  "Default shell buffer name."
  :group 'project-shells
  :type 'string)

(defcustom project-shells-empty-project "-"
  "Name of the empty project.

This is used to create non-project specific shells."
  :group 'project-shells
  :type 'string)

(defcustom project-shells-setup `((,project-shells-empty-project .
				   (("1" .
				     (,project-shells-default-shell-name
				      "~/" shell nil)))))
  "Configration form for shells of each project.

The format of the variable is an alist which maps the project
name (string) to the project shells configuration.  Which is an
alist which maps the key (string) to the shell configuration.
Which is a list of shell name (string), initial
directory (string), type ('shell or 'term), and intialization
function (symbol or lambda)."
  :group 'project-shells
  :type '(alist :key-type (string :tag "Project") :value-type
		(alist :tag "Project setup"
		       :key-type  (string :tag "Key")
		       :value-type (list :tag "Shell setup"
					 (string :tag "Name")
					 (string :tag "Directory")
					 (choice :tag "Type" (const term) (const shell))
					 (choice :tag "Function" (const nil) function)))))

(defcustom project-shells-keys '("1" "2" "3" "4" "5" "6" "7" "8" "9" "0" "-" "=")
  "Keys used to create shell buffers.

One shell will be created for each key.  Usually these key will
be bound in a non-global keymap."
  :group 'project-shells
  :type '(repeat string))

(defcustom project-shells-term-keys '("-" "=")
  "Keys used to create terminal buffers.

By default shell mode will be used, but for keys in
‘project-shells-term-keys’, ansi terminal mode will be used.  This
should be a subset of *poject-shells-keys*."
  :group 'project-shells
  :type '(repeat string))

(defcustom project-shells-session-root "~/.sessions"
  "The root directory for the shell sessions."
  :group 'project-shells
  :type 'string)

(defcustom project-shells-project-name-func 'projectile-project-name
  "Function to get project name."
  :group 'project-shells
  :type 'function)

(defcustom project-shells-project-root-func 'projectile-project-root
  "Function to get project root directory."
  :group 'project-shells
  :type 'function)

(defcustom project-shells-histfile-env "HISTFILE"
  "Environment variable to set shell history file."
  :group 'project-shells
  :type 'string)

(defcustom project-shells-histfile-name ".shell_history"
  "Shell history file name used to set environment variable."
  :group 'project-shells
  :type 'string)

(defcustom project-shells-term-args nil
  "Shell arguments used in terminal."
  :group 'project-shells
  :type 'string)

(let ((saved-shell-buffer-list nil)
      (last-shell-name nil))
  (cl-defun project-shells--buffer-list ()
    (setf saved-shell-buffer-list
	  (cl-remove-if-not #'buffer-live-p saved-shell-buffer-list)))

  (cl-defun project-shells--switch (&optional name to-create)
    (let* ((name (or name last-shell-name))
	   (buffer-list (project-shells--buffer-list))
	   (buf (when name
		  (cl-find-if (lambda (b) (string= name (buffer-name b)))
			      buffer-list))))
      (when (and (or buf to-create)
		 (cl-find (current-buffer) buffer-list))
	(setf last-shell-name (buffer-name (current-buffer))))
      (if buf
	  (progn
	    (select-window (display-buffer buf))
	    buf)
	(unless to-create
	  (message "No such shell: %s" name)
	  nil))))

  (cl-defun project-shells-switch-to-last ()
    "Switch to the last shell buffer."
    (interactive)
    (let ((name (or (and last-shell-name (get-buffer last-shell-name)
			 last-shell-name)
		    (and (project-shells--buffer-list)
			 (buffer-name (cl-first (project-shells--buffer-list)))))))
      (if name
	  (project-shells--switch name)
	(message "No more shell buffers!"))))

  (cl-defun project-shells--create (name dir &optional (type 'shell) func)
    (let ((default-directory (expand-file-name (or dir "~/"))))
      (cl-ecase type
	(term (ansi-term "/bin/sh"))
	(shell (shell)))
      (rename-buffer name)
      (push (current-buffer) saved-shell-buffer-list)
      (when func (funcall func)))))

(cl-defun project-shells--create-switch (name dir &optional (type 'shell) func)
  (unless (project-shells--switch name t)
    (project-shells--create name dir type func)))

(cl-defun project-shells-send-shell-command (cmdline)
  "Send the command line to the current (shell) buffer.  Can be
used in shell initialized function."
  (insert cmdline)
  (comint-send-input))

(cl-defun project-shells--project-name ()
  (or project-shells-project-name (funcall project-shells-project-name-func)
      project-shells-empty-project))

(cl-defun project-shells--project-root (proj-name)
  (if (string= proj-name project-shells-empty-project)
      "~/"
    (or project-shells-project-root
	(funcall project-shells-project-root-func))))

(cl-defun project-shells--set-histfile-env (val)
  (when (and project-shells-histfile-env
	     project-shells-histfile-name)
    (setenv project-shells-histfile-env val)))

(cl-defun project-shells--escape-sh (str)
  (replace-regexp-in-string
   "\"" "\\\\\""
   (replace-regexp-in-string "\\\\" "\\\\\\\\" str)))

(cl-defun project-shells--command-string (args)
  (mapconcat
   #'identity
   (cl-loop
    for arg in args
    collect (concat "\"" (project-shells--escape-sh arg) "\""))
   " "))

(cl-defun project-shells--term-command-string ()
  (let* ((prog (or explicit-shell-file-name
		   (getenv "ESHELL") shell-file-name)))
    (concat "exec " (project-shells--command-string
		     (cons prog project-shells-term-args)) "\n")))

;;;###autoload
(cl-defun project-shells-activate-for-key (key &optional proj proj-root)
  "Create or switch to the shell buffer for the key, the project
name, and the project root directory."
  (let* ((key (replace-regexp-in-string "/" "slash" key))
	 (proj (or proj (project-shells--project-name)))
	 (proj-root (or proj-root (project-shells--project-root proj)))
	 (proj-shells (cdr (assoc proj project-shells-setup)))
	 (shell-info (cdr (assoc key proj-shells)))
	 (name (or (cl-first shell-info) project-shells-default-shell-name))
	 (dir (or (cl-second shell-info) proj-root))
	 (type (or (cl-third shell-info)
		   (if (member key project-shells-term-keys)
		       'term 'shell)))
	 (func (cl-fourth shell-info))
	 (shell-name (format "*%s.%s.%s*" key name proj))
	 (session-dir (expand-file-name (format "%s/%s" proj key)
					project-shells-session-root)))
    (mkdir session-dir t)
    (project-shells--set-histfile-env
     (expand-file-name project-shells-histfile-name session-dir))
    (unwind-protect
	(project-shells--create-switch
	 shell-name dir type
	 (lambda ()
	   (when func
	     (funcall func session-dir))
	   (when (eq type 'term)
	     (term-send-raw-string (project-shells--term-command-string)))
	   (setf project-shells-project-name proj
		 project-shells-project-root proj-root)))
      (project-shells--set-histfile-env nil))))

;;;###autoload
(cl-defun project-shells-activate (p)
  "Create or switch to the shell buffer for the key just typed"
  (interactive "p")
  (let* ((keys (this-command-keys-vector))
	 (key (seq-subseq keys (1- (seq-length keys))))
	 (key-desc (key-description key)))
    (project-shells-activate-for-key
     key-desc (and (/= p 1) project-shells-empty-project))))

;;;###autoload
(cl-defun project-shells-setup (map &optional setup)
  "Configure the project shells with the prefix keymap and the
setup, for format of setup, please refer to document of
project-shells-setup."
  (when setup
    (setf project-shells-setup setup))
  (cl-loop
   for key in project-shells-keys
   do (define-key map (kbd key) 'project-shells-activate)))

(provide 'project-shells)

;;; project-shells.el ends here




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

* Re: [ELPA] New package: project-shells
  2017-03-03 12:46   ` Huang, Ying
@ 2017-03-09  8:09     ` Thien-Thi Nguyen
  0 siblings, 0 replies; 6+ messages in thread
From: Thien-Thi Nguyen @ 2017-03-09  8:09 UTC (permalink / raw)
  To: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 4032 bytes --]


() "Huang, Ying" <huang_ying_caritas@163.com>
() Fri, 03 Mar 2017 20:46:40 +0800

   ;; Version: 20170222
   ;; Package-Version: 20170222

Reminder: Don't forget to update these fields.

     "Manage shell buffers of each project"

Needs period (?., U+2E) at end.  Similarly for other instances.

   type ('shell or 'term)

This is slightly unconventional.  You explicitly name the type
of the other alist components, why not this one as well?  Maybe:

 TYPE (a symbol, either ‘shell’ or ‘term’)

Another convention is to use all uppercase for metavariables.
For example, see ‘auto-mode-alist’.  (Thus, "TYPE" above.)

   One shell will be created for each key.  Usually these key will
   be bound in a non-global keymap."

I suggest naming the function that does this action.  For
example, "Normally, ‘project-shells-setup’ arranges for these
keys to open a project-specific shell.".  Naming the function
(w/ proper quotes) creates a hyperlink in the *Help* buffer that
helps the reader form a mental model of the package operation.
The "Normally" serves two purposes:

 (a) It sidesteps the decision to choose between capitalizing
     "project-shells-setup", the first word in the sentence
     (correct from a grammar pov) and leaving it as-is (correct
     from an accuracy pov).

 (b) It invites follow-on documentation for any specific
     customization tips.

I think (a) is a nice (technical writing) hack only, but (b) is
crucial to the user experience (and maintenance burden) of this
package.

   (defcustom project-shells-term-keys '("-" "=")
     "Keys used to create terminal buffers.

   By default shell mode will be used, but for keys in
   ‘project-shells-term-keys’, ansi terminal mode will be used.  This
   should be a subset of *poject-shells-keys*."

Spelling error: "poject".  Also, don't use asterisk (?*, U+2A)
to quote API elements.  Instead, use either backtick (?`, U+60)
and apostrophe (?', U+27) or Unicode chars LEFT SINGLE QUOTATION
MARK (?‘, U+2018) and RIGHT SINGLE QUOTATION MARK (?’, U+2019).

Lastly, design questions (rhetorical): What happens if "should
be a subset" condition does not hold?  Have you considered
combining ‘project-shells-term-keys’ and ‘project-shell-keys’
somehow?  What if my project supports live-hacking via REPL?

   (let ((saved-shell-buffer-list nil)
         (last-shell-name nil))

     (cl-defun project-shells--buffer-list ...)

     (cl-defun project-shells--switch ...)

     (cl-defun project-shells-switch-to-last ...)

     (cl-defun project-shells--create ...))

Nice, much less scary for old eyes to read now.  (I added
another blank line prior to ‘project-shells--buffer-list’, btw.)

   (cl-defun project-shells-send-shell-command (cmdline)
     "Send the command line to the current (shell) buffer.  Can be
   used in shell initialized function."

Move the "Can be..." sentence to the next line.  More tips at:
(info "(elisp) Documentation Tips")

     (let* ((key (replace-regexp-in-string "/" "slash" key))
            ...
            (session-dir (expand-file-name (format "%s/%s" proj key)
                                           project-shells-session-root)))

This is is why (b) above is important.  This code special-cases
slash (?/, U+2F), but will probably give surprising results for
‘\M-g’ or ‘\C-/’ and so on.  You can either add handling for
those here, or document the range of "acceptable" ‘key’ values.
If you don't, you will receive complaints from users who try to
use project-shells.el w/ strange (but valid) keys.  I don't know
about you, but i get enough complaints as it is...  :-D

-- 
Thien-Thi Nguyen -----------------------------------------------
 (defun responsep (query)
   (pcase (context query)
     (`(technical ,ml) (correctp ml))
     ...))                              748E A0E8 1CB8 A748 9BFA
--------------------------------------- 6CE4 6703 2224 4C80 7502


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 197 bytes --]

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

end of thread, other threads:[~2017-03-09  8:09 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-02-24 12:36 [ELPA] New package: project-shells Huang, Ying
2017-02-24 16:21 ` raman
2017-02-24 16:44 ` John Yates
2017-02-25  8:46 ` Thien-Thi Nguyen
2017-03-03 12:46   ` Huang, Ying
2017-03-09  8:09     ` Thien-Thi Nguyen

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

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).