tvl-depot/configs/shared/emacs/.emacs.d/elpa/sesman-20180903.1826/sesman-browser.el
William Carroll 9da3ffee41 Update Emacs packages
This is a massive diff that I had to do in a hurry - when leaving
Urbint. I'm pretty sure that most of these are updating Emacs packages,
but I'm not positive.
2018-10-02 09:54:39 -04:00

461 lines
18 KiB
EmacsLisp
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;; sesman-browser.el --- Interactive Browser for Sesman -*- lexical-binding: t -*-
;;
;; Copyright (C) 2018, Vitalie Spinu
;; Author: Vitalie Spinu
;; URL: https://github.com/vspinu/sesman
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; 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:
;;
;; Interactive session browser.
;;
;;; Code:
(require 'seq)
(require 'sesman)
(defgroup sesman-browser nil
"Browser for Sesman."
:prefix "sesman-browser-"
:group 'sesman
:link '(url-link :tag "GitHub" "https://github.com/vspinu/sesman"))
(defface sesman-browser-highligh-face
'((default (:inherit highlight :weight bold)))
"Face used to highlight currently selected button."
:group 'sesman-browser)
(defface sesman-browser-button-face
'((default (:inherit button :slant italic)))
"Face used to highlight currently selected object."
:group 'sesman-browser)
(defvar-local sesman-browser--sort-types '(name relevance))
(defcustom sesman-browser-sort-type 'name
"Default sorting type in sesman browser buffers.
Currently can be either 'name or 'relevance."
:type '(choice (const name) (const relevance))
:group 'sesman-browser)
(defvar sesman-browser-map
(let (sesman-browser-map)
(define-prefix-command 'sesman-browser-map)
(define-key sesman-browser-map (kbd "r") #'sesman-browser-restart-session)
(define-key sesman-browser-map (kbd "q") #'sesman-browser-quit-session)
(define-key sesman-browser-map (kbd "b") #'sesman-browser-link-with-buffer)
(define-key sesman-browser-map (kbd "d") #'sesman-browser-link-with-directory)
(define-key sesman-browser-map (kbd "p") #'sesman-browser-link-with-project)
(define-key sesman-browser-map (kbd "u") #'sesman-browser-unlink)
sesman-browser-map)
"Prefix keymap for sesman commands from sesman browser.")
(defvar sesman-browser-mode-map
(let ((sesman-browser-mode-map (make-sparse-keymap)))
(define-key sesman-browser-mode-map (kbd "n") #'sesman-browser-vertical-next)
(define-key sesman-browser-mode-map (kbd "p") #'sesman-browser-vertical-prev)
(define-key sesman-browser-mode-map (kbd "f") #'sesman-browser-forward)
(define-key sesman-browser-mode-map (kbd "b") #'sesman-browser-backward)
(define-key sesman-browser-mode-map [remap forward-paragraph] #'sesman-browser-session-next)
(define-key sesman-browser-mode-map [remap backward-paragraph] #'sesman-browser-session-prev)
(define-key sesman-browser-mode-map (kbd "C-M-n") #'sesman-browser-session-next)
(define-key sesman-browser-mode-map (kbd "C-M-p") #'sesman-browser-session-prev)
(define-key sesman-browser-mode-map (kbd "<tab>") #'sesman-browser-forward)
(define-key sesman-browser-mode-map (kbd "<backtab>") #'sesman-browser-backward)
(define-key sesman-browser-mode-map (kbd "<RET>") #'sesman-goto)
(define-key sesman-browser-mode-map (kbd "o") #'sesman-show)
(define-key sesman-browser-mode-map (kbd "t") #'sesman-browser-toggle-sort)
(define-key sesman-browser-mode-map (kbd "S") #'sesman-browser-toggle-sort)
(define-key sesman-browser-mode-map (kbd "l b") #'sesman-browser-link-with-buffer)
(define-key sesman-browser-mode-map (kbd "l d") #'sesman-browser-link-with-directory)
(define-key sesman-browser-mode-map (kbd "l p") #'sesman-browser-link-with-project)
(define-key sesman-browser-mode-map (kbd "u") #'sesman-browser-unlink)
(define-key sesman-browser-mode-map (kbd "s") 'sesman-browser-map)
(define-key sesman-browser-mode-map (kbd "C-c C-s") 'sesman-browser-map)
sesman-browser-mode-map)
"Local keymap in `sesman-browser-mode'.")
;;; Utilities
(defun sesman-browser--closeby-pos (prop lax)
(or (when (get-text-property (point) prop)
(point))
(when (and (not (bobp))
(get-text-property (1- (point)) prop))
(1- (point)))
(when lax
(let ((next (save-excursion
(and
(goto-char (next-single-char-property-change (point) prop))
(get-text-property (point) prop)
(point))))
(prev (save-excursion
(and
(goto-char (previous-single-char-property-change (point) prop))
(not (bobp))
(get-text-property (1- (point)) prop)
(1- (point))))))
(if next
(if prev
(if (< (- (point) prev) (- next (point)))
prev
next)
next)
prev)))))
(defun sesman-browser--closeby-value (prop lax)
(when-let ((pos (sesman-browser--closeby-pos prop lax)))
(get-text-property pos prop)))
(defun sesman-browser-get (what &optional no-error lax)
"Get value of the property WHAT at point.
If NO-ERROR is non-nil, don't throw an error if no value has been found and
return nil. If LAX is non-nil, search nearby and return the closest value."
(when (derived-mode-p 'sesman-browser-mode)
(or (let ((prop (pcase what
('session :sesman-session)
('link :sesman-link)
('object :sesman-object)
(_ what))))
(sesman-browser--closeby-value prop 'lax))
(unless no-error
(user-error "No %s %s" what (if lax "nearby" "at point"))))))
;;; Navigation
(defvar-local sesman-browser--section-overlay nil)
(defvar-local sesman-browser--stop-overlay nil)
(when (fboundp 'define-fringe-bitmap)
(define-fringe-bitmap 'sesman-left-bar
[#b00001100] nil nil '(top t)))
(defun sesman-browser--next (prop)
(let ((pos (point)))
(goto-char (previous-single-char-property-change (point) prop))
(unless (get-text-property (point) prop)
(goto-char (previous-single-char-property-change (point) prop)))
(when (bobp)
(goto-char pos))))
(defun sesman-browser--prev (prop)
(let ((pos (point)))
(goto-char (next-single-char-property-change (point) prop))
(unless (get-text-property (point) prop)
(goto-char (next-single-char-property-change (point) prop)))
(when (eobp)
(goto-char pos))))
(defun sesman-browser-forward ()
"Go to next button."
(interactive)
(sesman-browser--prev :sesman-stop))
(defun sesman-browser-backward ()
"Go to previous button."
(interactive)
(sesman-browser--next :sesman-stop))
(defun sesman-browser-vertical-next ()
"Go to next button section or row."
(interactive)
(sesman-browser--prev :sesman-vertical-stop))
(defun sesman-browser-vertical-prev ()
"Go to previous button section or row."
(interactive)
(sesman-browser--next :sesman-vertical-stop))
(defun sesman-browser-session-next ()
"Go to next session."
(interactive)
(sesman-browser--prev :sesman-session-stop))
(defun sesman-browser-session-prev ()
"Go to previous session."
(interactive)
(sesman-browser--next :sesman-session-stop))
;;; Display
(defun sesman-goto (&optional no-switch)
"Go to most relevant buffer for session at point.
If NO-SWITCH is non-nil, only display the buffer."
(interactive "P")
(let ((object (get-text-property (point) :sesman-object)))
(if (and object (bufferp object))
(if no-switch
(display-buffer object)
(pop-to-buffer object))
(let* ((session (sesman-browser-get 'session))
(info (sesman-session-info (sesman--system) session))
(buffers (or (plist-get info :buffers)
(let ((objects (plist-get info :objects)))
(seq-filter #'bufferp objects)))))
(if buffers
(let ((most-recent-buf (seq-find (lambda (b)
(member b buffers))
(buffer-list))))
(if no-switch
(display-buffer most-recent-buf)
(pop-to-buffer most-recent-buf)))
(user-error "Cannot jump to session %s; it doesn't contain any buffers" (car session)))))))
(defun sesman-show ()
"Show the most relevant buffer for the session at point."
(interactive)
(sesman-goto 'no-switch))
(defun sesman-browser--sensor-function (&rest ignore)
(let ((beg (or (when (get-text-property (point) :sesman-stop)
(if (get-text-property (1- (point)) :sesman-stop)
(previous-single-char-property-change (point) :sesman-stop)
(point)))
(next-single-char-property-change (point) :sesman-stop)))
(end (next-single-char-property-change (point) :sesman-stop)))
(move-overlay sesman-browser--stop-overlay beg end)
(when window-system
(when-let* ((beg (get-text-property (point) :sesman-fragment-beg))
(end (get-text-property (point) :sesman-fragment-end)))
(move-overlay sesman-browser--section-overlay beg end)))))
;;; Sesman UI
(defun sesman-browser-quit-session ()
"Quite session at point."
(interactive)
(sesman-quit (sesman-browser-get 'session)))
(defun sesman-browser-restart-session ()
"Restart session at point."
(interactive)
(sesman-restart (sesman-browser-get 'session)))
(defun sesman-browser-link-with-buffer ()
"Ask for buffer to link session at point to."
(interactive)
(let ((session (sesman-browser-get 'session)))
(sesman-link-with-buffer 'ask session)))
(defun sesman-browser-link-with-directory ()
"Ask for directory to link session at point to."
(interactive)
(let ((session (sesman-browser-get 'session)))
(sesman-link-with-directory 'ask session)))
(defun sesman-browser-link-with-project ()
"Ask for project to link session at point to."
(interactive)
(let ((session (sesman-browser-get 'session)))
(sesman-link-with-project 'ask session)))
(defun sesman-browser-unlink ()
"Unlink the link at point or ask for link to unlink."
(interactive)
(if-let ((link (sesman-browser-get 'link 'no-error)))
(sesman--unlink link)
(if-let ((links (sesman-links (sesman--system)
(sesman-browser-get 'session))))
(mapc #'sesman--unlink
(sesman--ask-for-link "Unlink: " links 'ask-all))
(user-error "No links for session %s" (car (sesman-browser-get 'session)))))
(run-hooks 'sesman-post-command-hook))
;;; Major Mode
(defun sesman-browser-revert (&rest _ignore)
"Refresh current browser buffer."
(let ((pos (point)))
(sesman-browser)
;; simple but not particularly reliable or useful
(goto-char (min pos (point-max)))))
(defun sesman-browser-revert-all (system)
"Refresh all Sesman SYSTEM browsers."
(mapc (lambda (b)
(with-current-buffer b
(when (and (derived-mode-p 'sesman-browser-mode)
(eq system (sesman--system)))
(sesman-browser-revert))))
(buffer-list)))
(defun sesman-browser--goto-stop (stop-value)
(let ((search t))
(goto-char (point-min))
(while search
(goto-char (next-single-char-property-change (point) :sesman-stop))
(if (eobp)
(progn (setq search nil)
(goto-char (next-single-char-property-change (point-min) :sesman-stop)))
(when (equal (get-text-property (point) :sesman-stop) stop-value)
(setq search nil))))))
(defun sesman-browser-toggle-sort ()
"Toggle sorting of sessions.
See `sesman-browser-sort-type' for the default sorting type."
(interactive)
(when (eq sesman-browser-sort-type
(car sesman-browser--sort-types))
(pop sesman-browser--sort-types))
(unless sesman-browser--sort-types
(setq-local sesman-browser--sort-types (default-value 'sesman-browser--sort-types)))
(setq sesman-browser-sort-type (pop sesman-browser--sort-types))
(let ((stop (sesman-browser-get :sesman-stop nil 'lax)))
(sesman-browser)
(sesman-browser--goto-stop stop)
(sesman-browser--sensor-function))
(message "Sorted by %s"
(propertize (symbol-name sesman-browser-sort-type) 'face 'bold)))
(define-derived-mode sesman-browser-mode special-mode "SesmanBrowser"
"Interactive view of Sesman sessions.
When applicable, system specific commands are locally bound to j when point is
on a session object."
;; ensure there is a sesman-system here
(sesman--system)
(delete-all-overlays)
(setq-local sesman-browser--stop-overlay (make-overlay (point) (point)))
(overlay-put sesman-browser--stop-overlay 'face 'sesman-browser-highligh-face)
(setq-local sesman-browser--section-overlay (make-overlay (point) (point)))
(when window-system
(let* ((fringe-spec '(left-fringe sesman-left-bar sesman-browser-highligh-face))
(dummy-string (propertize "|" 'display fringe-spec)))
(overlay-put sesman-browser--section-overlay 'line-prefix dummy-string)))
(add-hook 'sesman-post-command-hook 'sesman-browser-revert nil t)
(setq-local display-buffer-base-action '(nil . ((inhibit-same-window . t))))
(setq-local sesman-browser--sort-types (default-value 'sesman-browser--sort-types))
(setq-local revert-buffer-function #'sesman-browser-revert))
(defun sesman-browser--insert-session (system ses i)
(let ((ses-name (car ses))
(head-template "%17s")
beg end)
(setq beg (point))
;; session header
(insert (format "%3d: " i))
(insert (propertize (car ses)
:sesman-stop ses-name
:sesman-vertical-stop t
:sesman-session-stop t
'face 'bold
'cursor-sensor-functions (list #'sesman-browser--sensor-function)
'mouse-face 'highlight)
"\n")
;; links
(insert (format head-template "linked-to: "))
(let ((link-groups (sesman-grouped-links system ses))
(vert-stop))
(dolist (grp link-groups)
(let* ((type (car grp)))
(dolist (link (cdr grp))
(when (> (current-column) fill-column)
(insert "\n" (format head-template " "))
(setq vert-stop nil))
(let ((val (sesman--abbrev-path-maybe (sesman--lnk-value link))))
(insert (propertize (sesman--format-context type val 'sesman-browser-button-face)
:sesman-stop (car link)
:sesman-vertical-stop (unless vert-stop (setq vert-stop t))
:sesman-link link
'cursor-sensor-functions (list #'sesman-browser--sensor-function)
'mouse-face 'highlight)))
(insert " ")))))
(insert "\n")
;; objects
(insert (format head-template "objects: "))
(let* ((info (sesman-session-info system ses))
(map (plist-get info :map))
(objects (plist-get info :objects))
(strings (or (plist-get info :strings)
(mapcar (lambda (x) (format "%s" x)) objects)))
(kvals (seq-mapn #'cons objects strings))
(kvals (seq-sort (lambda (a b) (string-lessp (cdr a) (cdr b)))
kvals))
(vert-stop))
(dolist (kv kvals)
(when (> (current-column) fill-column)
(insert "\n" (format head-template " "))
(setq vert-stop nil))
(let ((str (replace-regexp-in-string ses-name "%s" (cdr kv) nil t)))
(insert (propertize str
:sesman-stop str
:sesman-vertical-stop (unless vert-stop (setq vert-stop t))
:sesman-object (car kv)
'cursor-sensor-functions (list #'sesman-browser--sensor-function)
'face 'sesman-browser-button-face
'mouse-face 'highlight
'help-echo "mouse-2: visit in other window"
'keymap map)
" "))))
;; session properties
(setq end (point))
(put-text-property beg end :sesman-session ses)
(put-text-property beg end :sesman-session-name ses-name)
(put-text-property beg end :sesman-fragment-beg beg)
(put-text-property beg end :sesman-fragment-end end)
(insert "\n\n")))
;;;###autoload
(defun sesman-browser ()
"Display an interactive session browser.
See `sesman-browser-mode' for more details."
(interactive)
(let* ((system (sesman--system))
(pop-to (called-interactively-p 'any))
(sessions (sesman-sessions system))
(cur-session (when pop-to
(sesman-current-session 'CIDER)))
(buff (get-buffer-create (format "*sesman %s browser*" system))))
(with-current-buffer buff
(setq-local sesman-system system)
(sesman-browser-mode)
(cursor-sensor-mode 1)
(let ((inhibit-read-only t)
(sessions (pcase sesman-browser-sort-type
('name (seq-sort (lambda (a b) (string-greaterp (car b) (car a)))
sessions))
('relevance (sesman--sort-sessions system sessions))
(_ (error "Invalid `sesman-browser-sort-type'"))))
(i 0))
(erase-buffer)
(insert "\n ")
(insert (propertize (format "%s Sessions:" system)
'face '(bold font-lock-keyword-face)))
(insert "\n\n")
(dolist (ses sessions)
(setq i (1+ i))
(sesman-browser--insert-session system ses i))
(when pop-to
(pop-to-buffer buff)
(sesman-browser--goto-stop (car cur-session)))
(sesman-browser--sensor-function)))))
(provide 'sesman-browser)
;;; sesman-browser.el ends here