about summary refs log tree commit diff
path: root/users/wpcarro/emacs/.emacs.d/wpc/ivy-clipmenu.el
blob: 70e68d257cb502d9f884aaecb56045c4b84ae62e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
;;; ivy-clipmenu.el --- Emacs client for clipmenu -*- lexical-binding: t -*-

;; Author: William Carroll <wpcarro@gmail.com>
;; Version: 0.0.1
;; Package-Requires: ((emacs "25.1"))

;;; Commentary:
;; Ivy integration with the clipboard manager, clipmenu.  Essentially, clipmenu
;; turns your system clipboard into a list.
;;
;; To use this module, you must first install clipmenu and ensure that the
;; clipmenud daemon is running.  Refer to the installation instructions at
;; github.com/cdown/clipmenu for those details.
;;
;; This module intentionally does not define any keybindings since I'd prefer
;; not to presume my users' preferences.  Personally, I use EXWM as my window
;; manager, so I call `exwm-input-set-key' and map it to `ivy-clipmenu-copy'.
;;
;; Usually clipmenu integrates with rofi or dmenu.  This Emacs module integrates
;; with ivy.  Launch this when you want to select a clip.
;;
;; Clipmenu itself supports a variety of environment variables that allow you to
;; customize its behavior.  These variables are respected herein.  If you'd
;; prefer to customize clipmenu's behavior from within Emacs, refer to the
;; variables defined in this module.
;;
;; For more information:
;; - See `clipmenu --help`.
;; - Visit github.com/cdown/clipmenu.

;;; Code:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Dependencies
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(require 'f)
(require 's)
(require 'dash)
(require 'ivy)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Variables
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defgroup ivy-clipmenu nil
  "Ivy integration for clipmenu."
  :group 'ivy)

(defcustom ivy-clipmenu-directory
  (or (getenv "XDG_RUNTIME_DIR")
      (getenv "TMPDIR")
      "/tmp")
  "Base directory for clipmenu's data."
  :type 'string
  :group 'ivy-clipmenu)

(defconst ivy-clipmenu-executable-version 5
   "The major version number for the clipmenu executable.")

(defconst ivy-clipmenu-cache-directory
  (f-join ivy-clipmenu-directory
          (format "clipmenu.%s.%s"
                  ivy-clipmenu-executable-version
                  (getenv "USER")))
  "Directory where the clips are stored.")

(defconst ivy-clipmenu-cache-file-pattern
  (f-join ivy-clipmenu-cache-directory "line_cache_*")
  "Glob pattern matching the locations on disk for clipmenu's labels.")

(defcustom ivy-clipmenu-history-length
  (or (getenv "CM_HISTLENGTH") 25)
  "Limit the number of clips in the history.
This value defaults to 25.")

(defvar ivy-clipmenu-history nil
  "History for `ivy-clipmenu-copy'.")

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defun ivy-clipmenu-parse-content (x)
  "Parse the label from the entry, X, in clipmenu's line-cache."
  (->> (s-split " " x)
       (-drop 1)
       (s-join " ")))

(defun ivy-clipmenu-list-clips ()
  "Return a list of the content of all of the clips."
  (->> ivy-clipmenu-cache-file-pattern
       f-glob
       (-map (lambda (path)
               (s-split "\n" (f-read path) t)))
       -flatten
       (-reject #'s-blank?)
       (-sort #'string>)
       (-map #'ivy-clipmenu-parse-content)
       delete-dups
       (-take ivy-clipmenu-history-length)))

(defun ivy-clipmenu-checksum (content)
  "Return the CRC checksum of CONTENT."
  (s-trim-right
   (with-temp-buffer
     (call-process "/bin/bash" nil (current-buffer) nil "-c"
                   (format "cksum <<<'%s'" content))
     (buffer-string))))

(defun ivy-clipmenu-line-to-content (line)
  "Map the chosen LINE from the line cache its content from disk."
  (->> line
       ivy-clipmenu-checksum
       (f-join ivy-clipmenu-cache-directory)
       f-read))

(defun ivy-clipmenu-do-copy (x)
  "Copy string, X, to the system clipboard."
  (kill-new x)
  (message "[ivy-clipmenu.el] Copied!"))

(defun ivy-clipmenu-copy ()
  "Use `ivy-read' to select and copy a clip.
It's recommended to bind this function to a globally available keymap."
  (interactive)
  (let ((ivy-sort-functions-alist nil))
    (ivy-read "Clipmenu: "
              (ivy-clipmenu-list-clips)
              :history 'ivy-clipmenu-history
              :action (lambda (line)
                        (->> line
                             ivy-clipmenu-line-to-content
                             ivy-clipmenu-do-copy)))))

(provide 'ivy-clipmenu)
;;; ivy-clipmenu.el ends here