From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!.POSTED!not-for-mail From: "Huang\, Ying" Newsgroups: gmane.emacs.devel Subject: Re: [ELPA] New package: project-shells Date: Fri, 03 Mar 2017 20:46:40 +0800 Message-ID: <8737eu1qa7.fsf@163.com> References: <87mvdbojds.fsf@163.com> <87varyd5eb.fsf@zigzag.favinet> NNTP-Posting-Host: blaine.gmane.org Mime-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Trace: blaine.gmane.org 1488548116 12879 195.159.176.226 (3 Mar 2017 13:35:16 GMT) X-Complaints-To: usenet@blaine.gmane.org NNTP-Posting-Date: Fri, 3 Mar 2017 13:35:16 +0000 (UTC) User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1 (gnu/linux) To: emacs-devel@gnu.org Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Fri Mar 03 14:35:10 2017 Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by blaine.gmane.org with esmtp (Exim 4.84_2) (envelope-from ) id 1cjnMS-0002a6-Ge for ged-emacs-devel@m.gmane.org; Fri, 03 Mar 2017 14:35:09 +0100 Original-Received: from localhost ([::1]:58103 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cjnMY-0005O7-FI for ged-emacs-devel@m.gmane.org; Fri, 03 Mar 2017 08:35:14 -0500 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:46223) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cjmbx-0003QB-LZ for emacs-devel@gnu.org; Fri, 03 Mar 2017 07:47:07 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1cjmbu-00065Z-G1 for emacs-devel@gnu.org; Fri, 03 Mar 2017 07:47:05 -0500 Original-Received: from m12-14.163.com ([220.181.12.14]:38920) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cjmbr-0005w6-9z for emacs-devel@gnu.org; Fri, 03 Mar 2017 07:47:02 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=163.com; s=s110527; h=From:Subject:Date:Message-ID:MIME-Version; bh=2cJjG B1PSR7+o6l4Sfd1cl8JD4tmqK0tEyRTUtXkFuA=; b=cgdxbvBDfi1rKwEAwgrWn 4ilkRJcTj9FfA3KDoM4inwZY5Ua0WGCOFLlyfKrKePVyPoOnC7+vrl7sytWud/9a FPOc5iNjDHJQJfNfblxQRxTgr+UV1YKY5oucDoa12MDJZydCAID2aJ8NebPoM5IU /mTb8c1mgNBSs9FH0GLaZ4= Original-Received: from yhuang-guixsd (unknown [222.65.4.40]) by smtp10 (Coremail) with SMTP id DsCowAC3frG3ZblYeOOxKg--.35138S2; Fri, 03 Mar 2017 20:46:49 +0800 (CST) In-Reply-To: <87varyd5eb.fsf@zigzag.favinet> (Thien-Thi Nguyen's message of "Sat, 25 Feb 2017 09:46:36 +0100") X-CM-TRANSID: DsCowAC3frG3ZblYeOOxKg--.35138S2 X-Coremail-Antispam: 1Uf129KBjvJXoW3tw17Ar4fCF43Kw1rWrW3KFg_yoWDZrWkpa nxK39rGrWkCF18JFyDJF13W3s3Grn2q34IkF15Gw4UXa45Ww47Xrnxtr95WFyjyrnFg345 X3yv9r1kG3W7JaUanT9S1TB71UUUUUUqnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDUYxBIdaVFxhVjvjDU0xZFpf9x07b5KZXUUUUU= X-Originating-IP: [222.65.4.40] X-CM-SenderInfo: xkxd0wxb1l0wxbfd2xxwdvqiywtou0bp/1tbiShmPtlO-4JnfEgAAsy X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x [fuzzy] X-Received-From: 220.181.12.14 X-Mailman-Approved-At: Fri, 03 Mar 2017 08:33:19 -0500 X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.org gmane.emacs.devel:212738 Archived-At: 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" ;; Author: "Huang, Ying" ;; Maintainer: "Huang, Ying" ;; 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