[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[nongnu] elpa/sesman 8f2784a4ba 001/100: Initial commit
From: |
ELPA Syncer |
Subject: |
[nongnu] elpa/sesman 8f2784a4ba 001/100: Initial commit |
Date: |
Tue, 28 Dec 2021 14:05:57 -0500 (EST) |
branch: elpa/sesman
commit 8f2784a4bab9d0c922635942aeec454f8bbbc2c8
Author: Vitalie Spinu <spinuvit@gmail.com>
Commit: Vitalie Spinu <spinuvit@gmail.com>
Initial commit
---
.gitignore | 2 +
sesman.el | 460 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 462 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000..57f70631ff
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+scratch.el
+test-sesman.el
\ No newline at end of file
diff --git a/sesman.el b/sesman.el
new file mode 100644
index 0000000000..d004f75272
--- /dev/null
+++ b/sesman.el
@@ -0,0 +1,460 @@
+;;; sesman.el --- Session and connection manager interface -*-
lexical-binding: t -*-
+;;
+;; Copyright (C) 2018, Vitalie Spinu
+;; Author: Vitalie Spinu
+;; URL: https://github.com/vspinu/sesman
+;; Keywords: process
+;; Version: 0.0.1
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; 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 this program; see the file COPYING. If not, write to
+;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth
+;; Floor, Boston, MA 02110-1301, USA.
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;;; Commentary:
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;;; Code:
+
+(require 'project)
+(require 'mule-util)
+(require 'seq)
+
+(defgroup sesman nil
+ "Session manager."
+ :prefix "sesman")
+
+(defvar sesman-sessions (make-hash-table :test #'equal)
+ "Hashtable of all sesman sessions.
+Key is a cons (system-name . session-name).")
+(defvar sesman-links nil
+ "An alist of all sesman associations.
+Each element is of the form (key cxt-type cxt-value) where
+\"key\" is of the form (system-name . session-name).")
+
+
+;;; User Interface
+
+(defcustom sesman-1-to-1-links '(directory buffer)
+ "List of context types for which links should be 1-to-1."
+ :group 'sesman
+ :type '(repeat symbol))
+
+(defun sesman-start ()
+ "Start sesman session."
+ (interactive)
+ (let ((session (sesman-start-session (sesman--system))))
+ (sesman-register session)
+ (message "Started %s" (car session))))
+
+(defun sesman-restart ()
+ "Restart sesman session."
+ (interactive)
+ (let* ((system (sesman--system))
+ (old-session (sesman-ensure-session "Restart session: "))
+ (old-session (sesman-unregister old-session system))
+ (new-session (sesman-restart-session system old-session)))
+ (sesman-register new-session system)
+ (message "Restarted %s" (car old-session))
+ new-session))
+
+(defun sesman-kill ()
+ "Kill sesman session."
+ (interactive)
+ (let ((sessions (sesman-ensure-session "Kill session: " nil 'ask-all))
+ (system (sesman--system)))
+ (mapc (lambda (s)
+ (sesman-unregister s system)
+ (sesman-kill-session system s))
+ sessions)
+ (message "Killed %s" (mapcar #'car sessions))))
+
+(defun sesman-link-with-buffer ()
+ "Associate a session with current buffer."
+ (interactive)
+ (sesman--link-session-interactively buffer))
+
+(defun sesman-link-with-directory ()
+ "Associate a session with current directory."
+ (interactive)
+ (sesman--link-session-interactively directory))
+
+(defun sesman-link-with-project ()
+ "Associate a session with current project."
+ (interactive)
+ (sesman--link-session-interactively project))
+
+(defun sesman-unlink (&optional arg)
+ "Break any of the previously formed associations."
+ (interactive "P")
+ (let* ((links (or (sesman--current-links)
+ (user-error "No %s associations found" (sesman--system)))))
+ (mapc #'sesman--unlink
+ (sesman--ask-for-link "Unlink: " links 'ask-all))))
+
+(defvar sesman-map
+ (let (sesman-map)
+ (define-prefix-command 'sesman-map)
+ (define-key sesman-map (kbd "C-s") 'sesman-start)
+ (define-key sesman-map (kbd "s") 'sesman-start)
+ (define-key sesman-map (kbd "C-r") 'sesman-restart)
+ (define-key sesman-map (kbd "r") 'sesman-restart)
+ (define-key sesman-map (kbd "C-k") 'sesman-kill)
+ (define-key sesman-map (kbd "k") 'sesman-kill)
+ (define-key sesman-map (kbd "C-b") 'sesman-link-with-buffer)
+ (define-key sesman-map (kbd "b") 'sesman-link-with-buffer)
+ (define-key sesman-map (kbd "C-d") 'sesman-link-with-directory)
+ (define-key sesman-map (kbd "d") 'sesman-link-with-directory)
+ (define-key sesman-map (kbd "C-p") 'sesman-link-with-project)
+ (define-key sesman-map (kbd "p") 'sesman-link-with-project)
+ (define-key sesman-map (kbd "C-u") 'sesman-unlink)
+ (define-key sesman-map (kbd " u") 'sesman-unlink)
+ sesman-map)
+ "Session management prefix keymap.")
+
+
+;;; System Interface
+
+(defvar-local sesman-system nil
+ "Name of the system managed by `sesman'.
+Can be either a symbol, or a function returning a symbol.")
+
+(cl-defgeneric sesman-context-types (system)
+ "Return a list of context types understood by SYSTEM."
+ '(buffer directory project))
+
+(cl-defgeneric sesman-start-session (system &optional session)
+ "Start and return SYSTEM SESSION.
+A session is a list with first element being a name. When
+present SESSION is an old session (typically during the session
+restart) and could be safely (re-)used.")
+
+(cl-defgeneric sesman-kill-session (system session)
+ "Kill SYSTEM SESSION.")
+
+(cl-defgeneric sesman-restart-session (system session)
+ "Restart SYSTEM SESSION.
+By default, calls `sesman-kill-session' and then
+`sesman-start-session'."
+ (let ((old-name (car session)))
+ (sesman-kill-session system session)
+ (let ((new-session (sesman-start-session system session)))
+ (setcar new-session old-name)
+ new-session)))
+
+(cl-defgeneric sesman-greater-p (system session1 session2)
+ "Return non-nil if SESSION1 should be sorted before SESSION2.
+By default, sort by session name. Systems should overwrite this
+method to provide a more meaningful ordering; ideally more
+recently used session should score higher."
+ (string-greaterp (car session1) (car session2)))
+
+(cl-defgeneric sesman-friendly-session-p (system session)
+ "Non-nil if SYSTEM's SESSION is friendly to current context.
+A friendly session is the one for which it makes sense to create
+an association with current contexts. For example, if the user
+is within the project A which is required (dependent upon) from
+project B, then a session opened within project B is a friendly
+session for current context. By default, there are no friendly
+sessions."
+ ;; by default no friendly sessions
+ nil)
+
+(defun sesman-ensure-session (&optional prompt ask-new ask-all)
+ "Ensure that at least one session is linked and return most relevant one.
+If there is an unambiguous link, return the linked session. In
+case of multiple associations, ask the user for a session with
+PROMPT. When ASK-NEW is non-nil, offer *new* option to start a
+new session. If ASK-ALL is non-nil offer *all* option to return
+the sessions. If ASK-ALL is non-nil, return a list of sessions."
+ (let ((prompt (or prompt "Session: "))
+ (sessions (sesman-linked-sessions)))
+ (cond
+ ;; 1. Single association; return
+ ((and (eq (length sessions) 1)
+ (not ask-new)
+ (not ask-all))
+ (car sessions))
+ ;; 2. Multiple associations; ask
+ (sessions
+ (sesman--ask-for-session prompt sessions ask-new ask-all))
+ ;; 3. No associations, get all friendly sessions and ask
+ (t (let ((sessions (sesman-friendly-sessions)))
+ (sesman--ask-for-session prompt sessions ask-new ask-all))))))
+
+(defun sesman-linked-session (&optional system cxt-types)
+ "Get the most relevant linked session for SYSTEM.
+CXT-TYPES is as in `sesman-linked-sessions'."
+ (car (sesman-linked-sessions system cxt-types)))
+
+(defun sesman-linked-sessions (&optional system cxt-types)
+ "Return a list of SYSTEM sessions linked in current context.
+CXT-TYPES is a list of context types to considere. Defaults to
+the list returned from `sesman-context-types'."
+ (let* ((system (or system (sesman--system)))
+ (cxt-types (or cxt-types (sesman-context-types system))))
+ ;; just in case some links are lingering due to user errors
+ (sesman--clear-links)
+ (mapcar (lambda (assoc)
+ (gethash (car assoc) sesman-sessions))
+ (sesman--current-links system cxt-types))))
+
+(defun sesman-friendly-sessions (&optional system)
+ "Return a list of friendly (for current context) SYSTEM sessions.
+Session is friendly if `sesman-friendly-session-p' returns non-nil."
+ (let ((system (or system (sesman--system)))
+ sessions)
+ (maphash
+ (lambda (k s)
+ (when (and (eql (car k) system)
+ (sesman-friendly-session-p system s))
+ (push s sessions)))
+ sesman-sessions)
+ (sesman--sort-sessions system sessions)))
+
+(defun sesman-system-sessions (&optional system)
+ "Return a list of sessions registered with SYSTEM."
+ (let ((system (or system (sesman--system)))
+ sessions)
+ (maphash
+ (lambda (k s)
+ (when (eql (car k) system)
+ (push s sessions)))
+ sesman-sessions)
+ (sesman--sort-sessions system sessions)))
+
+(defun sesman-sessions (&optional system)
+ "Return all sessions for SYSTEM.
+Return a list of `sesman-linked-sessions',
+`sesman-friendly-sessions' and all other `sesman-system-sessions'
+in that order."
+ (let* ((system (or system (sesman--system))))
+ (delete-dups
+ (append (sesman-linked-sessions system)
+ (sesman-friendly-sessions system)
+ (sesman-system-sessions system)))))
+
+(defun sesman-register (session &optional system)
+ "Register SESSION into `sesman-sessions' and `sesman-links'.
+SYSTEM defaults to current system. If a session with same name
+is already registered in `sesman-sessions', change the name by
+appending \"<1>\", \"<2>\" ... to the name. This function should
+be called by legacy connection initializers (\"run-xyz\",
+\"xyz-jack-in\" etc.)."
+ (let* ((system (or system (sesman--system)))
+ (ses-name (car session))
+ (i 1))
+ (while (gethash (cons system ses-name) sesman-sessions)
+ (setq ses-name (format "%s<%d>" i)))
+ (setq session (cons ses-name (cdr session)))
+ (puthash (cons system ses-name) session sesman-sessions)
+ (sesman--link-session session system)
+ session))
+
+(defun sesman-unregister (session &optional system)
+ "Unregister SESSION.
+SYSTEM defaults to current system. Remove session from
+`sesman-sessions' and `sesman-links'."
+ (let ((system (or system (sesman--system)))
+ (ses-key (cons system (car session))))
+ (remhash ses-key sesman-sessions)
+ (sesman--clear-links)
+ session))
+
+
+;;; Contexts
+
+(cl-defgeneric sesman-context (cxt-type)
+ "Given context type CXT-TYPE return the context.")
+(cl-defmethod sesman-context ((cxt-type (eql buffer)))
+ "Return current buffer."
+ (current-buffer))
+(cl-defmethod sesman-context ((cxt-type (eql directory)))
+ "Return current directory."
+ default-directory)
+(cl-defmethod sesman-context ((cxt-type (eql project)))
+ "Return current project."
+ (project-current))
+
+(cl-defgeneric sesman-relevant-context-p (cxt-type cxt)
+ "Non-nil if context CXT is relevant to current context of type CXT-TYPE.")
+(cl-defgeneric sesman-relevant-context-p ((cxt-type (eql buffer)) buf)
+ "Non-nil if BUF is `current-buffer'."
+ (eq (current-buffer) buf))
+(cl-defgeneric sesman-relevant-context-p ((cxt-type (eql directory)) dir)
+ "Non-nil if DIR is the parent or equals the `default-directory'."
+ (when (and dir default-directory)
+ (string-match-p (concat "^" dir) default-directory)))
+(cl-defgeneric sesman-relevant-context-p ((cxt-type (eql project)) proj)
+ "Non-nil if PROJ is the parent or equals the `default-directory'."
+ (when (and proj default-directory)
+ (string-match-p (concat "^" (expand-file-name (cdr proj)))
+ default-directory)))
+
+
+;; Internals
+
+(defun sesman--current-links (&optional system cxt-types)
+ (let* ((system (or system (sesman--system)))
+ (cxt-types (or cxt-types (sesman-context-types system))))
+ (mapcan
+ (lambda (cxt-type)
+ (let ((lfn (sesman--lookup-fn system nil cxt-type)))
+ (sesman--sort-links
+ system
+ (seq-filter (lambda (l)
+ (and (funcall lfn l)
+ (sesman-relevant-context-p cxt-type (nth 2 l))))
+ sesman-links))))
+ cxt-types)))
+
+(defun sesman--link-session (session &optional system cxt-type)
+ (let* ((system (or system (sesman--system)))
+ (ses-name (or (car-safe session)
+ (error "SESSION must be a headed list")))
+ (cxt-type (or cxt-type (car (last (sesman-context-types system)))))
+ (cxt-val (sesman-context cxt-type))
+ (key (cons system ses-name))
+ (link (list key cxt-type cxt-val)))
+ (if (member cxt-type sesman-1-to-1-links)
+ (thread-last sesman-links
+ (seq-remove (sesman--lookup-fn system nil cxt-type cxt-val))
+ (cons link)
+ (setq sesman-links))
+ (unless (seq-filter (sesman--lookup-fn system ses-name cxt-type cxt-val)
+ sesman-links)
+ (setq sesman-links (cons link sesman-links))))
+ key))
+
+(defun sesman--abrev-maybe (obj)
+ (if (stringp obj)
+ (abbreviate-file-name obj)
+ obj))
+
+(defmacro sesman--link-session-interactively (cxt-type)
+ (declare (indent 1)
+ (debug (symbolp &rest)))
+ (let ((cxt-name (symbol-name cxt-type)))
+ `(let ((system (sesman--system)))
+ (if (member ',cxt-type (sesman-context-types system))
+ (let ((session (sesman--ask-for-session
+ (format "Link with %s %s: "
+ ,cxt-name (sesman--abrev-maybe
+ (sesman-context ',cxt-type)))
+ (sesman-sessions)
+ 'ask-new)))
+ (sesman--link-session session system ',cxt-type))
+ (error (format "%s association not allowed for this system (%s)"
+ ,(capitalize (symbol-name cxt-type))
+ (sesman--system)))))))
+
+(defun sesman--system ()
+ (if sesman-system
+ (if (functionp sesman-system)
+ (funcall sesman-system)
+ sesman-system)
+ (error "No `sesman-system' in buffer `%s'" (current-buffer))))
+
+(defun sesman--lookup-fn (&optional system ses-name cxt-type cxt-val x)
+ (let ((system (or system (caar x)))
+ (ses-name (or ses-name (cdar x)))
+ (cxt-type (or cxt-type (nth 1 x)))
+ (cxt-val (or cxt-val (nth 2 x))))
+ (lambda (el)
+ (and (or (null system) (eq (caar el) system))
+ (or (null ses-name) (eq (cdar el) ses-name))
+ (or (null cxt-type) (eq (nth 1 el) cxt-type))
+ (or (null cxt-val) (equal (nth 2 el) cxt-val))))))
+
+(defun sesman--unlink (x)
+ (setq sesman-links
+ (seq-remove (sesman--lookup-fn nil nil nil nil x)
+ sesman-links)))
+
+(defun sesman--clear-links ()
+ (setq sesman-links
+ (seq-filter (lambda (x)
+ (gethash (car x) sesman-sessions))
+ sesman-links)))
+
+(defvar sesman--select-session-history nil)
+(defun sesman--ask-for-session (prompt sessions &optional ask-new ask-all)
+ (let* ((name.syms (mapcar (lambda (s)
+ (let ((name (car s)))
+ (cons (if (symbolp name) (symbol-name name)
name)
+ name)))
+ sessions))
+ (nr (length name.syms))
+ (syms (if (and (not ask-new) (= nr 0))
+ (error "No %s sessions found" (sesman--system))
+ (append name.syms
+ (when ask-new '(("*new*")))
+ (when (and ask-all (> nr 1))
+ '(("*all*"))))))
+ (def (caar syms))
+ ;; (def (if (assoc (car sesman--select-session-history) syms)
+ ;; (car sesman--select-session-history)
+ ;; (caar syms)))
+ (sel (completing-read
+ prompt (mapcar #'car syms) nil t nil
'sesman--select-session-history def)))
+ (cond
+ ((string= sel "*new*")
+ (let ((ses (sesman-register)))
+ (message "Started %s" (car ses))
+ (if ask-all (list ses) ses)))
+ ((string= sel "*all*")
+ sessions)
+ (t
+ (let* ((sym (cdr (assoc sel syms)))
+ (ses (assoc sym sessions)))
+ (if ask-all (list ses) ses))))))
+
+(defun sesman--ask-for-link (prompt links &optional ask-all)
+ (let* ((name.keys (mapcar (lambda (x)
+ (let* ((val (nth 2 x))
+ (val (if (listp val) (cdr val) val)))
+ (cons (format "%s:%s:%s" (cdar x) (nth 1 x)
val)
+ x)))
+ links))
+ (name.keys (append name.keys
+ (when (and ask-all (> (length name.keys) 1))
+ '(("*all*")))))
+ (nms (mapcar #'car name.keys))
+ (sel (completing-read "Unlink: " nms nil t nil nil (car nms))))
+ (cond ((string= sel "*all*")
+ links)
+ (ask-all
+ (list (cdr (assoc sel name.keys))))
+ (t
+ (cdr (assoc sel name.keys))))))
+
+(defun sesman--sort-sessions (system sessions)
+ (seq-sort (lambda (x1 x2)
+ (sesman-greater-p system x1 x2))
+ sessions))
+
+(defun sesman--sort-links (system links)
+ (seq-sort (lambda (x1 x2)
+ (sesman-greater-p system
+ (gethash (car x1) sesman-sessions)
+ (gethash (car x2) sesman-sessions)))
+ links))
+
+(provide 'sesman)
- [nongnu] elpa/sesman c81565a88b 071/100: Version 0.3, (continued)
- [nongnu] elpa/sesman c81565a88b 071/100: Version 0.3, ELPA Syncer, 2021/12/28
- [nongnu] elpa/sesman d403a84746 069/100: Put back separator in info display, ELPA Syncer, 2021/12/28
- [nongnu] elpa/sesman cdf0064408 067/100: Use -face in face names, ELPA Syncer, 2021/12/28
- [nongnu] elpa/sesman 563ebeaafb 098/100: Fix broken link in README.md, ELPA Syncer, 2021/12/28
- [nongnu] elpa/sesman 26931e1e64 094/100: Add menu for browser, ELPA Syncer, 2021/12/28
- [nongnu] elpa/sesman b16ba94386 097/100: Make sesman-unlink to take optional LINKS argument (#22), ELPA Syncer, 2021/12/28
- [nongnu] elpa/sesman e0f555f963 100/100: Rename sesman-get-system and defalias sesman--system, ELPA Syncer, 2021/12/28
- [nongnu] elpa/sesman 37b9b50f8f 014/100: Get rid of -get- qualifier, ELPA Syncer, 2021/12/28
- [nongnu] elpa/sesman e06a40589d 010/100: Move back from cider new-connection branch, ELPA Syncer, 2021/12/28
- [nongnu] elpa/sesman ec275e2e10 009/100: Readme, ELPA Syncer, 2021/12/28
- [nongnu] elpa/sesman 8f2784a4ba 001/100: Initial commit,
ELPA Syncer <=
- [nongnu] elpa/sesman 7987deb2c4 011/100: Makefile, tests, travis, checkdoc, .dir-locals, .gitignore etc., ELPA Syncer, 2021/12/28
- [nongnu] elpa/sesman b839a2379a 005/100: More bulk updates, ELPA Syncer, 2021/12/28
- [nongnu] elpa/sesman 27bd3bf457 020/100: Remove disambiguation defcustom and simplify sesman-ensure-linked-session, ELPA Syncer, 2021/12/28
- [nongnu] elpa/sesman df5a081689 029/100: Fix a fixme :-), ELPA Syncer, 2021/12/28
- [nongnu] elpa/sesman 5fc5934b5f 044/100: Extend semantics of 'which' argument in interactive commands, ELPA Syncer, 2021/12/28
- [nongnu] elpa/sesman b8a1cdd20e 056/100: Add lint target for convenience, ELPA Syncer, 2021/12/28
- [nongnu] elpa/sesman 779c034180 082/100: Update doc of the sesman-project generic, ELPA Syncer, 2021/12/28
- [nongnu] elpa/sesman 81a2136489 078/100: New SORT argument to sesman-current-links, ELPA Syncer, 2021/12/28
- [nongnu] elpa/sesman 083cf73f3f 079/100: Version 0.3.2, ELPA Syncer, 2021/12/28
- [nongnu] elpa/sesman 1a6c5448cb 081/100: Don't use deleted sesman-connected-p, ELPA Syncer, 2021/12/28
- Prev by Date:
[nongnu] elpa/sesman ec275e2e10 009/100: Readme
- Next by Date:
[nongnu] elpa/sesman 7987deb2c4 011/100: Makefile, tests, travis, checkdoc, .dir-locals, .gitignore etc.
- Previous by thread:
[nongnu] elpa/sesman ec275e2e10 009/100: Readme
- Next by thread:
[nongnu] elpa/sesman 7987deb2c4 011/100: Makefile, tests, travis, checkdoc, .dir-locals, .gitignore etc.
- Index(es):