Skip to content

Latest commit

 

History

History
782 lines (686 loc) · 31.1 KB

clave.src.org

File metadata and controls

782 lines (686 loc) · 31.1 KB

clave engine

 ;; define hooks
(defvar clave-on-hook nil
  "Hook for `clave-on'")
(defvar clave-off-hook nil
  "Hook for `clave-off'")

;; define keymaps
(defvar clave-minor-mode-map (make-sparse-keymap)
  "Keybinding for `clave' minor mode.")

(defvar clave-map (make-sparse-keymap)
  "Keybinding for `clave' command mode.")

;; (setq clave-map (make-sparse-keymap))

;; define variables
(defvar clave--off-func '(lambda ())
  "Function to remove map.")

(defvar clave-on-p nil "Clave state.")

(defvar clave-lighter " λ"  ;; ξѣѢѮ£₽⦾λ
  "Default mode line lighter for minor mode `clave'")

(defvar clave-lighter-off nil
  "Directly disable lighter for minor mode `clave'. Other hacks (e.g., blackout.el) might not work.")

(defvar clave-input-method nil "Stores current-input-method in clave-off state.")

(defvar clave-map-init-standard-keys
  '(
    ;; Numbers are Workman's difficulty of keys. The higher number the worse.
    ;; https://web.archive.org/web/20170311215135/http://www.workmanlayout.com/blog/wp-content/uploads/2010/10/keyboard_graded1.png
    ;; Numbers row 
    "`" "~"
    "1" "!"
    "2" "@"
    "3" "#"
    "4" "$"
    "5" "%"
    "6" "^"
    "7" "&"
    "8" "*"
    "9" "("
    "0" ")"
    "-" "_"
    "=" "+"
    ;; LEFT HAND 
    ;; - Top row
    "q" "Q"  ; 4
    "w" "W"  ; 2
    "e" "E"  ; 2
    "r" "R"  ; 3
    "t" "T"  ; 4
    ;; - Home row
    "a" "A"  ; 1.5
    "s" "S"  ; 1
    "d" "D"  ; 1
    "f" "F"  ; 1
    "g" "G"  ; 3
    ;; - Bottom row
    "z" "Z"  ; 4
    "x" "X"  ; 4
    "c" "C"  ; 3
    "v" "V"  ; 2
    "b" "B"  ; 5
    ;; RIGHT HAND
    ;; - Top row
    "y" "Y"  ; 5
    "u" "U"  ; 3
    "i" "I"  ; 2
    "o" "O"  ; 2
    "p" "P"  ; 4
    "[" "{"  
    "]" "}"  
    "\\" "|" 
    ;; - Home row
    "h" "H"  ; 3
    "j" "J"  ; 1
    "k" "K"  ; 1
    "l" "L"  ; 1
    ";" ":"  ; 1.5
    "'" "\""
    ;; - Bottom row
    "n" "N"  ; 3
    "m" "M"  ; 2
    "," "<"  ; 3
    "." ">"  ; 4
    "/" "?"  ; 4
    ))

(defvar clave-map-init-standard-extra-keys
  '("TAB"
    "RET"
    "SPC"
    "DEL"))

(defvar clave-keys nil
  "List of keybindings description in form of (ACTIVE-MAP CLAVE-MAP KEY COMMAND TYPE LABEL) defined with `clave-remap-key'.")

(defmacro clave-map-init (map-name &optional extra-keys keys)
  "Creates MAP-NAME keymap if is does not exist and binds dummy commands to KEYS. If KEYS is nil use `clave-map-init-standard-keys' list of keys instead. If EXTRA-KEYS are set it creates extra dummy functions and binds it to the end of MAP-NAME keymap. If EXTRA-KEYS is set to t it uses `clave-map-init-standard-keys' list as extra keys. Both KEYS and EXTRA-KEYS should be list of valid `kbd' arguments.
       Examples:
           ;; binds `clave-map-init-standard-keys' to my-map
           (clave-map-init 'my-map)
           ;; binds `clave-map-init-standard-keys' and `clave-map-init-standard-extra-keys'
           (clave-map-init 'my-map t)
           ;; add one extra key
           (clave-map-init 'my-map '(\"ESC\"))
           ;; binds one key
           (clave-map-init 'my-map nil '(\"ESC\"))"
  `(progn
     ;; check keymap
     (unless (boundp ,map-name)
       (defvar ,(eval map-name) (make-sparse-keymap)))
     ;; define dummy functions
     ,@(mapcar
        (lambda (key)
          (let ((func-name
                 (make-symbol (concat (symbol-name (eval map-name)) "-" key))))
            `(progn
               (defun ,func-name ()
                 "This is clave dummy command meant for rebinding."
                 (interactive))
               (define-key ,(eval map-name) (kbd ,key) (quote ,func-name)))))
        (append (or (eval keys)
                    clave-map-init-standard-keys)
                (when extra-keys
                  (if (equal extra-keys t)
                      clave-map-init-standard-extra-keys
                    (eval extra-keys)))))))

;; populate clave-map with dummy commands
(clave-map-init 'clave-map)

(defmacro clave-remap-key (active-map clave-map key command &optional type label)
  "Remaps dummy CLAVE-MAP-KEY function to COMMAND in ACTIVE-MAP and appends (ACTIVE-MAP CLAVE-MAP KEY COMMAND TYPE LABEL) to `clave-keys' list.

If CLAVE-MAP does not exist at evaluation then it is initialized by `clave-init-map' with  `clave-map-init-standard-extra-keys'. If command is unquoted symbol then it is assumed to be a keymap which is bind directly to key (without remapping) as there is no known mechanism to remap command to keymap."
  (let* ((clave-map-name (if clave-map (symbol-name clave-map) "clave-map"))
         (clave-func (make-symbol (concat clave-map-name "-" key)))
         ;; the below I learned from xah-fly-keys and bind-key.el 
         ;; it is meant to pass keymap symbol to define-key and not the map itself
         (clave-map-var (make-symbol "clave-map-name"))
         (active-map-var (make-symbol "active-map-name"))
         (command-var (make-symbol "command-name"))
         type-keymap)
    `(let ((,active-map-var ,active-map))
       (unless (boundp (quote ,clave-map)) 
         (clave-map-init (quote ,clave-map) t))
       ,(if (symbolp command)
            (if active-map
                (error "Clave: Cannot bind `%s' prefix map to non clave map `%s'! If it is command and not prefix map then quote it." (symbol-name command) (symbol-name active-map))
              `(progn 
                 (unless (keymapp (quote ,command))
                   (clave-map-init (quote ,command) t))
                 ;; check if it is autoloaded map
                 (if (autoloadp (symbol-function (quote ,command)))
                     (let ((,clave-map-var ,clave-map))
                       (define-key ,clave-map-var ,key (quote ,command)))
                   (let ((,command-var ,command)
                         (,clave-map-var ,clave-map))
                     (define-key ,clave-map-var ,key ,command-var)))))
          (if active-map
              `(define-key ,active-map-var [remap ,clave-func] ,command)
            `(global-set-key [remap ,clave-func] ,command)))
       (add-to-list 'clave-keys '(,(if active-map
                                       (symbol-name active-map)
                                     "global-map")
                                  ,clave-map-name
                                  ,key
                                  ;; get command as string
                                  ,(if (symbolp command)
                                       ;; if not 'command then it must be keymap
                                       (progn (setq type-keymap "keymap")
                                              (symbol-name command))
                                     ;; if 'command it is command
                                     (symbol-name (eval command)))
                                  ,(or type type-keymap)
                                  ,label)))))

;; test
;; (clave-remap-key (package-map . package) nil "a" 'a-func)
;; (clave-remap-key package-map nil "a" 'a-func)
;; (clave-remap-key nil clave-other-map "a" 'a-func)
;; (clave-remap-key nil clave-other-map "a" a-func)
;; (clave-remap-key package-map clave-other-map "a" a-func)
;; (clave-remap-key org-map nil "RET" 'a-func "edit" "✖")

(defun clave-remap-normalize-args (args &optional for-use-package)
  "Checks if the ARGS are fine and normalize them into list of bindings descriptions for `clave-remap-key' macro as follows (ACTIVE-MAP CLAVE-MAP KEY COMMAND TYPE LABEL)."
  ;; harmonize between (("a" b)) and ("a" b) args
  (unless (cdr args) (setq args (car args)))
  (let (param-active-map
        param-clave-map
        param-bind-after
        return-args)
    (while args
      (let ((x (car args)))
        (pcase x
          ((or 
            ;; (KEY BINDING)
            `(,(pred stringp) ,_)
            ;; (KEY BINDING TYPE)
            `(,(pred stringp) ,_ ,_)
            ;; (KEY BINDING TYPE LABEL)
            `(,(pred stringp) ,_ ,_ ,_))
           ;; return list of (MAP CLAVE-MAP KEY BINDING &optional TYPE LABEL)
           ;; for use-package return list of (BIND-AFTER MAP CLAVE-MAP KEY BINDING &optional TYPE LABEL)
           (setq return-args
                 (append return-args
                         (list 
                          (append
                           (when for-use-package
                             (list param-bind-after))
                           (list param-active-map)
                           (list param-clave-map)
                           x))))
           (setq args (cdr args)))
          ;; keywords
          (':active-map
           (setq param-active-map (cadr args))
           ;; reset param-clave-map to default map
           (setq param-clave-map nil)
           (setq args (cddr args)))
          (':bind-after
           (setq param-bind-after (cadr args))
           (setq args (cddr args)))
          (':clave-map
           (setq param-clave-map (cadr args))
           (setq args (cddr args)))
          ;; skip value
          (_ 
           (warn "clave-remap-normalize-args: Do not know how to process '%s' keyword." x)
           (setq args (cdr args))))))
    ;; return list
    return-args))

;; (clave-remap-normalize-args
;;  '(("a" 'a-func) ;; remaps clave-map-a to a-func in global-map
;;    ("b" 'b-func) ;; remaps clave-map-b to b-func in global-map
;;    :active-map my-map
;;    ("c" 'c-func) ;; remaps clave-map-b to b-func in global-map
;;    :clave-map clave-a-map
;;    ("b" 'b-func)
;;    :bind-after c
;;    :active-map c-map
;;    ("c" 'c-func)) t)



(defmacro clave-remap (&rest args)
  "Remaps clave keys (clave dummy functions) to commands. The ARGS should be a list of following elements:
  - binding description (KEY COMMAND &optional TYPE LABEL)
  - :active-map keyword followed by symbol (unquoted)
  - :clave-map keyword followed by symbol (unquoted)
  - :bind-after keyword followed by symbol (unquoted) - (similar to eval-after-load for bindings to keymaps that are not nessesary loaded)

  Order matters: First it remaps dummy-functions clave-map-KEY from default `clave-map' to `global-map'. Everything after :active-map specification binds to that map until next :active-map specification. Similar for :clave-map specification albeit it tells from which clave map which clave dummy function to bind (see `clave-map-init' for details).

  Example:
  (clave-remap
    (\"a\" 'a-func) ;; remaps clave-map-a to a-func in global-map
    (\"b\" 'b-func) ;; remaps clave-map-b to b-func in global-map
    :active-map my-map
    (\"c\" 'c-func) ;; remaps clave-map-b to b-func in global-map
    :clave-map clave-a-map
    (\"b\" 'b-func)
    :active-map c-map
    (\"c\" 'c-func))

  The each remap specification when processed passed to `clave-remap-key' macro."
  (macroexp-progn
   (mapcar
    (lambda (arg) `(clave-remap-key ,@arg))
    (clave-remap-normalize-args args))))

;; (clave-remap
;; ( ("a" 'a-func) ;; remaps clave-map-a to a-func in global-map
;;  ("b" 'b-func) ;; remaps clave-map-b to b-func in global-map
;;  :active-map my-map
;;  ("c" 'c-func) ;; remaps clave-map-b to b-func in global-map
;;  :clave-map clave-a-map
;;  ("b" 'b-func)
;;  :active-map c-map
;;  ("c" 'c-func)))




;; functions

(defun clave-change-terminal-cursor-to-box ()
  "Change terminal cursor to box. Same as typing echo -e '\e[2 q' in the terminal

https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h4-Functions-using-CSI-_-ordered-by-the-final-character-lparen-s-rparen:CSI-Ps-SP-q.1D81"
  (send-string-to-terminal "\033[2 q"))

(defun clave-change-terminal-cursor-to-bar ()
  "Change terminal cursor to bar. Same as typing echo -e '\e[6 q' in the terminal

https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h4-Functions-using-CSI-_-ordered-by-the-final-character-lparen-s-rparen:CSI-Ps-SP-q.1D81"
  (send-string-to-terminal "\033[6 q"))

(defun clave-change-terminal-cursor-to-hbar ()
  "Change terminal cursor to bar. Same as typing echo -e '\e[4 q' in the terminal

https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h4-Functions-using-CSI-_-ordered-by-the-final-character-lparen-s-rparen:CSI-Ps-SP-q.1D81"
  (send-string-to-terminal "\033[4 q"))


(defvar clave-indicate-cursor-on 'box)
(defvar clave-indicate-cursor-off 'hbar)
(defvar clave-indicate-cursor-off-input-method-active 'bar)

(defun clave-on-indicate ()
  "Indicate clave on state."
  (if (display-graphic-p)
      (modify-all-frames-parameters
       (list (cons 'cursor-type clave-indicate-cursor-on)))
     (clave-change-terminal-cursor-to-box))
  (global-hl-line-mode 1))

(defun clave-off-indicate (input-method)
  "Indicate clave off state."
  (if (display-graphic-p)
      (modify-all-frames-parameters
       (list (cons 'cursor-type
                   (if input-method
                       clave-indicate-cursor-off-input-method-active
                     clave-indicate-cursor-off))))
       (if input-method
           (clave-change-terminal-cursor-to-bar)
         (clave-change-terminal-cursor-to-hbar)))
    (global-hl-line-mode 0))

(defun clave-on ()
  "Activate `clave' command mode."
  (interactive)
  ;; preserve input method
  (setq clave-input-method current-input-method)
  (deactivate-input-method)
  ;; activate clave-map
  (setq clave--off-func
        (set-transient-map clave-map (lambda () t)))
  (setq clave-on-p t)
  (clave-on-indicate)
  (run-hooks 'clave-on-hook))

(defvar clave-toggle-input-method-if-on-off t
  "Whether to toggle input method if clave was turned off right after it was turned on")

(defun clave-off ()
  "Activate `clave' insertion mode."
  (interactive)
  (funcall clave--off-func)
  (setq clave-on-p nil)
  ;; restore input method
  (when clave-input-method
    (activate-input-method clave-input-method))
  ;; switch input method on clave on-off
  (when (and
         clave-toggle-input-method-if-on-off
         (functionp 'sv-toggle-input-method)
         (equal last-command 'clave-on))
    ;; (toggle-input-method)
    (sv-toggle-input-method))
  (clave-off-indicate current-input-method)
  (run-hooks 'clave-off-hook))  

;; we need an escape from clave-on
(define-key clave-map (kbd "SPC") 'clave-off)

;; clave minor mode
(defun clave-set-hooks ()
  "Sets hooks for `clave' minor mode states"
  (add-hook 'minibuffer-setup-hook 'clave-off)
  (add-hook 'shell-mode-hook 'clave-off)
  (add-hook 'minibuffer-exit-hook 'clave-on)
  (add-hook 'isearch-mode-end-hook 'clave-on))

(defun clave-unset-hooks ()
  "Unets hooks for `clave' minor mode states. Used for turning `clave' minor mode of."
  (remove-hook 'minibuffer-setup-hook 'clave-off)
  (remove-hook 'shell-mode-hook 'clave-off)
  (remove-hook 'minibuffer-exit-hook 'clave-on)
  (remove-hook 'isearch-mode-end-hook 'clave-on))

       ;;;###autoload
(define-minor-mode clave
  "A personalized modal keybinding set, like vim, but based on ergonomic principles, like Dvorak layout and personal preferences. Inspired by xah-fly-keys (URL `http://ergoemacs.org/misc/ergoemacs_vi_mode.html')"
  :init-value nil
  :global t
  :lighter (:eval (unless clave-lighter-off clave-lighter))
  :keymap clave-minor-mode-map
  (if clave
      (progn (clave-off-indicate current-input-method)
             (clave-set-hooks))
    (progn (clave-unset-hooks)
           (clave-off))))

clave use-package integration

;; clave use-package integration

;;add :remap keyword
(require 'seq)
(setq use-package-keywords 
      (append
       (seq-take-while (lambda (el) (not (equal el :bind))) use-package-keywords)
       '(:remap)
       (seq-drop-while (lambda (el) (not (equal el :bind))) use-package-keywords)))

;; (add-to-list 'use-package-keywords :remap)
;; (setq use-package-keywords (remove ':remap use-package-keywords))


(defun use-package-normalize/:remap (name keyword args)
  "Checks if the argumets are fine. See `clave-remap' for expected ARGS and how it is processed."
  (clave-remap-normalize-args args 'for-use-package))

    ;;;; test

;; (use-package-normalize/:remap nil nil '(("a" 'sdf "asdf")
;;                                         :active-map aaa
;;                                         :clave-map clave-org
;;                                         ("a" 'sdf "asdf" "sadf")
;;                                         ("a" 'sdf "asdf")
;;                                         :bind-after bbb
;;                                         :active-map bbb-map
;;                                         ("a" 'sdf "asdf" "sadf")))


;; (use-package-normalize/:remap nil nil '((("a" 'sdf "asdf")
;; 					 :clave-map clave-org-zero
;; 					("a" 'sdf "asdf" "saf")
;;                                         ("a" 'sdf "asdf")
;;                                          :active-map aaa
;;                                         :clave-map clave-org
;;                                         ("a" 'sdf "asdf" "sadf")
;;                                         ("a" 'sdf "asdf")
;;                                         :active-map bbb
;;                                         ("a" 'sdf "asdf" "sadf"))))

(defun use-package-handler/:remap (name _keyword args rest state)
  (use-package-concat
   (use-package-process-keywords name rest state)
   `(,@(mapcar #'(lambda (clave-remap-args)
                   (pcase-let
                       ((`(,bind-after ,active-map ,clave-map ,key ,command ,type ,label)
                         clave-remap-args))
                     (let ((autoload-and-remap
                            `(progn
                               ;; autoload if not bound
                               ;; treat unquoted command as keymap
                               (if (and (symbolp (quote ,command)) (not (boundp (quote ,command))))
                                   (autoload (quote ,command) ,(symbol-name name) nil nil 'keymap)
                                 (unless (or (keymapp ,command) (fboundp ,command))
                                   (autoload ,command ,(symbol-name name) nil t)))
                               (clave-remap-key ,@(cdr clave-remap-args)))))
                       (if active-map
                           (if bind-after
                               `(eval-after-load (quote ,bind-after) (quote ,autoload-and-remap))
                             `(eval-after-load (quote ,name)
                                '(clave-remap-key ,@(cdr clave-remap-args))))
                         autoload-and-remap))))
               args))))

;; (use-package pack
;;   :remap
;;   (:clave-map clave-map
;;    ("a" 'a-func)
;;    ("b" 'b-func)
;;    ("f" clave-files-map)
;;    :active-map a-map
;;    ("c" 'c-func)
;;    :clave-map clave-a-map
;;    ("d" 'd-func)
;;    ("d" d-map)
;;    :bind-after b-mode
;;    :active-map b-map
;;    ("c" 'c-func)
;;    :clave-map clave-a-map
;;    ("d" 'd-func)
;;    ("d" d-map)
;;    ))

clave visualize

This is raw data for keyboard visualization at http://www.keyboard-layout-editor.com

[{f:1,a:3},"ESC","F1","F2","F3","F4","F5","F6","F7","F8","F9","F10","F11","F12","NmLk","ScrLk","Insert"],
["`","1","2","3","4","5","6","7","8","9","0","-","=",{w:2},"DEL","Home"],
[{w:1.5},"TAB","q","w","e","r","t","y","u","i","o","p","[","]",{w:1.5},"\\","Page Up"],
[{w:1.75},"Caps Lock","a","s","d","f","g","h","j","k","l",";","'",{w:2.25},"RET","Page Down"],
[{w:2.25},"Shift","z","x","c","v","b","n","m",",",".","/",{w:1.75},"Shift","↑","End"],
[{w:1.25},"Ctrl",{w:1.25},"Win",{w:1.25},"Alt",{w:6.25},"SPC","Alt","Fn","Ctrl","←","↓","→"]

[{f:1,a:3},"ESC","F1","F2","F3","F4","F5","F6","F7","F8","F9","F10","F11","F12","NmLk","ScrLk","Insert"],
["`","1","2","3","4","5","6","7","8","9","0","-","=",{w:2},"DEL","Home"],
[{w:1.5},"TAB","Q","W","E","R","T","Y","U","I","O","P","[","]",{w:1.5},"\\","Page Up"],
[{w:1.75},"Caps Lock","A","S","D","F","G","H","J","K","L",";","'",{w:2.25},"RET","Page Down"],
[{w:2.25},"Shift","Z","X","C","V","B","N","M",",",".","/",{W:1.75},"Shift","↑","End"],
[{w:1.25},"Ctrl",{w:1.25},"Win",{w:1.25},"Alt",{w:6.25},"SPC","Alt","Fn","Ctrl","←","↓","→"]



// with colors
[{f:1,a:3,c:"#b0b0b0"},"ESC",{c:"#b0b0b0"},"F1",{c:"#b0b0b0"},"F2",{c:"#b0b0b0"},"F3",{c:"#b0b0b0"},"F4",{c:"#b0b0b0"},"F5",{c:"#b0b0b0"},"F6",{c:"#b0b0b0"},"F7",{c:"#b0b0b0"},"F8",{c:"#b0b0b0"},"F9",{c:"#b0b0b0"},"F10",{c:"#b0b0b0"},"F11",{c:"#b0b0b0"},"F12",{c:"#b0b0b0"},"NmLk",{c:"#b0b0b0"},"ScrLk",{c:"#b0b0b0"},"Insert"],
["`",{c:"#b0b0b0"},"1",{c:"#b0b0b0"},"2",{c:"#b0b0b0"},"3",{c:"#b0b0b0"},"4",{c:"#b0b0b0"},"5",{c:"#b0b0b0"},"6",{c:"#b0b0b0"},"7",{c:"#b0b0b0"},"8",{c:"#b0b0b0"},"9",{c:"#b0b0b0"},"0",{c:"#b0b0b0"},"-",{c:"#b0b0b0"},"=",{w:2,c:"#b0b0b0"},"DEL",{c:"#b0b0b0"},"Home"],
[{w:1.5,c:"#b0b0b0"},"TAB",{c:"#b0b0b0"},"q",{c:"#b0b0b0"},"w",{c:"#b0b0b0"},"e",{c:"#b0b0b0"},"r",{c:"#b0b0b0"},"t",{c:"#b0b0b0"},"y",{c:"#b0b0b0"},"u",{c:"#b0b0b0"},"i",{c:"#b0b0b0"},"o",{c:"#b0b0b0"},"p",{c:"#b0b0b0"},"[",{c:"#b0b0b0"},"]",{w:1.5,c:"#b0b0b0"},"\\",{c:"#b0b0b0"},"Page Up"],
[{w:1.75,c:"#b0b0b0"},"Caps Lock",{c:"#b0b0b0"},"a",{c:"#b0b0b0"},"s",{c:"#b0b0b0"},"d",{c:"#b0b0b0"},"f",{c:"#b0b0b0"},"g",{c:"#b0b0b0"},"h",{c:"#b0b0b0"},"j",{c:"#b0b0b0"},"k",{c:"#b0b0b0"},"l",{c:"#b0b0b0"},";",{c:"#b0b0b0"},"\"",{w:2.25,c:"#b0b0b0"},"RET",{c:"#b0b0b0"},"Page Down"],
[{w:2.25,c:"#b0b0b0"},"Shift",{c:"#b0b0b0"},"z",{c:"#b0b0b0"},"x",{c:"#b0b0b0"},"c",{c:"#b0b0b0"},"v",{c:"#b0b0b0"},"b",{c:"#b0b0b0"},"n",{c:"#b0b0b0"},"m",{c:"#b0b0b0"},",",{c:"#b0b0b0"},".",{c:"#b0b0b0"},"/",{w:1.75,c:"#b0b0b0"},"Shift",{c:"#b0b0b0"},"↑",{c:"#b0b0b0"},"End"],
[{w:1.25,c:"#b0b0b0"},"Ctrl",{w:1.25,c:"#b0b0b0"},"Win",{w:1.25,c:"#b0b0b0"},"Alt",{w:6.25,c:"#b0b0b0"},"SPC",{c:"#b0b0b0"},"Alt",{c:"#b0b0b0"},"Fn",{c:"#b0b0b0"},"Ctrl",{c:"#b0b0b0"},"←",{c:"#b0b0b0"},"↓",{c:"#b0b0b0"},"→"]
;; clave visualizatoin with KLE

(defvar clave-keys-colors
      (seq-reverse '(
"#B55762"
"#BB5D5B"
"#C16E60"
"#C77F64"
"#CD9168"
"#D2A36C"
"#D8B570"
"#DEC875"
"#E3DB79"
"#E4E97E"
"#DBEE82"
"#CCEC8B"
"#C0EA94"
"#B7E89D"
"#B2E7A5"
"#AFE6AD"
"#B5E5BA"
"#BCE5C7"
"#C3E5D1"
"#C9E5DA"
"#D0E6E0"
)))

(defun clave-clm-count-commands (logs-regex commands)
  ;; here I can get a dates diapason from user
  (when-let* ((sv-clm/logging-dir-p (boundp 'sv-clm/logging-dir))
              ;; get all files in form YYYY-MM-DD
              (files (directory-files-recursively sv-clm/logging-dir logs-regex)))
    (defun count-command (command)
      (beginning-of-buffer)
      (setq count 0)
      (while (search-forward (concat " " command "\n") nil t)
        (setq count (1+ count)))
      count)
    (with-temp-buffer
      ;; insert all files
      (mapcar 'insert-file-contents files)
      (mapcar 'count-command commands))))

;; test
;; (clave-clm-count-commands "2020-[0-9\\\\-]+" '("next-line" "org-clock-goto"))

(defun clave-kle-make-key-labels (clave-keys
                                  logs-regex
                                  clave-map-filter
                                  active-map-filter
                                  &optional
                                  log-counts)
  (defun maps-match-p (key-description)
    (pcase-let ((`(,active-map ,clave-map) key-description))
      (and (string= clave-map clave-map-filter)
           (string= active-map active-map-filter))))
  (defun get-key-color (count)
    (let*  ((step (/ (- (seq-max keys-counts) (seq-min keys-counts))
                     (- (length clave-keys-colors) 1)))
            (color-index (round (/ count step))))
      (nth color-index clave-keys-colors)))
  (defun log+ (count) (log (1+ count)))
  ;; set defaults
  (let* (;; filter keymaps
         (keys (seq-filter 'maps-match-p clave-keys))
         (keys-commands
          (mapcar (lambda (x) (nth 3 x)) keys))
         (keys-counts
          (clave-clm-count-commands logs-regex keys-commands))
         (keys-counts
          (if log-counts (mapcar 'log+ keys-counts) keys-counts))
         (keys-colors
          (mapcar 'get-key-color keys-counts)))
    (defun make-key-label (key-description key-color)
      (pcase-let
          ((`(,active-map ,clave-map ,key ,command ,type ,label)
            key-description))
        (list key
              (concat
               (when key-color
                 ;; # is %23
                 (concat "&_c=%23" (substring key-color 1) "%3B"))
               "&="
               (url-encode-url
                (concat
                 (clave-kle-encode-url (if label label command))
                 "\n\n\n\n\n\n\n\n\n\n\n"))))))
    (seq-mapn 'make-key-label
              keys
              keys-colors)))

;; test
;; (clave-kle-make-key-labels clave-keys "2020-08")

(defvar clave-kle-encode-url-chars
      '(("/"   "%2F%2F")
        ("="   "%2F=")
        (";"   "%2F%3B")
        ("`"   "%60" )
        ("#"   "%23")
        ("\""  "%22" )
        ("["   "%5B" )
        ("]"   "%5D" )
        ("\\"  "%5C" )))

(defun clave-kle-encode-url (str &optional chars)
  (let ((chars (if chars chars
                 (when (boundp 'clave-kle-encode-url-chars)
                   clave-kle-encode-url-chars))))
    (while (setq char (pop chars))
      (setq str
            (replace-regexp-in-string
             (regexp-quote (car char)) (cadr char) str nil 'literal)))
    str))

;; test
;; (clave-kle-encode-url  "/asdf=")



(defun clave-kle-decode-url (str &optional chars)
  (let ((chars (if chars chars
                 (when (boundp 'clave-kle-encode-url-chars)
                   clave-kle-encode-url-chars))))
    (while (setq char (pop chars))
      (setq str
            (replace-regexp-in-string
             (regexp-quote (cadr char)) (car char) str nil 'literal)))
    str))


;; (clave-kle-decode-url "%22")

(defvar clave-kle-url
      "http://www.keyboard-layout-editor.com/##@@_f:1&a:3%3B&=ESC&=F1&=F2&=F3&=F4&=F5&=F6&=F7&=F8&=F9&=F10&=F11&=F12&=NmLk&=ScrLk&=Insert%3B&@=%60&=1&=2&=3&=4&=5&=6&=7&=8&=9&=0&=-&=%2F=&_w:2%3B&=DEL&=Home%3B&@_w:1.5%3B&=TAB&=q&=w&=e&=r&=t&=y&=u&=i&=o&=p&=%5B&=%5D&_w:1.5%3B&=%5C&=Page%20Up%3B&@_w:1.75%3B&=Caps%20Lock&=a&=s&=d&=f&=g&=h&=j&=k&=l&=%2F%3B&='&_w:2.25%3B&=RET&=Page%20Down%3B&@_w:2.25%3B&=Shift&=z&=x&=c&=v&=b&=n&=m&=,&=.&=%2F%2F&_w:1.75%3B&=Shift&=%E2%86%91&=End%3B&@_w:1.25%3B&=Ctrl&_w:1.25%3B&=Win&_w:1.25%3B&=Alt&_w:6.25%3B&=SPC&=Alt&=Fn&=Ctrl&=%E2%86%90&=%E2%86%93&=%E2%86%92")


;; http://www.keyboard-layout-editor.com
;; @_w:1.5 - properties and ends with ; (%3B)
;; &@=%60 - new line
;; search for keys between "&=" and "%3B"
;; For each matched KEY I need to insert my commad and add "\n\n\n\n\nKEY"

(defun clave-kle-make-url (&optional clave-map-filter active-map-filter)
  (let ((clave-map-filter (if clave-map-filter
                              clave-map-filter
                            "clave-map"))
        (active-map-filter (if active-map-filter
                               active-map-filter
                             "global-map")))
    (with-temp-buffer
      (insert clave-kle-url)
      (beginning-of-buffer)
      (while (search-forward "&=" nil t)
        (mapcar
         (lambda (key-description)
           (pcase-let
               ((`(,active-map ,clave-map ,key ,command ,type ,label)
                 key-description))
             (when (and (string= active-map active-map-filter)
                        (string= clave-map clave-map-filter)
                        (or (looking-at
                             (regexp-quote
                              (concat key "&=")))
                            (looking-at
                             (regexp-quote
                              (concat key "%3B")))))
               (insert (url-encode-url
                        (concat
                         (if label label command)
                         "\n\n\n\n\n"))))))
         clave-keys))
      (buffer-string))))

(defvar clave-kle-default-key-color "#cccccc")

;; new
(defun clave-kle-make-url
    (&optional logs-regex clave-map-filter active-map-filter log-counts)
  (let ((colorful-commands
         (clave-kle-make-key-labels
          clave-keys logs-regex clave-map-filter active-map-filter log-counts))
        (clave-kle-default-key-color
         (clave-kle-encode-url clave-kle-default-key-color))
        ;; toggle case sensitive search
        (case-fold-search nil)
        colorful-command)
    (with-temp-buffer
      (insert clave-kle-url)
      ;; TODO: insert title (maps names)
      (beginning-of-buffer)
      (search-forward "/##@")
      (insert (url-encode-url (concat "_name=" clave-map-filter
                      " from " active-map-filter
                      "%3B&")))
      (while (setq colorful-command (pop colorful-commands))
        (beginning-of-buffer)
        (when (re-search-forward
               (concat "&=\\("
                       (regexp-quote (clave-kle-encode-url (car colorful-command)))
                       "\\)\\(&=\\|%3B\\|&_\\)")
               ;; "&=" starts key label
               ;; "%3B" starts new line
               ;; "&_" starts property (w is width)
               nil t)
          ;; the easiest is to wrap into colors!
          ;; first insert default color as next
          (goto-char (match-beginning 2))
          (insert (concat "&_c=" clave-kle-default-key-color "%3B"))
          ;; then go to begginning
          (goto-char (match-beginning 0))
          ;; remove &= as it will be in colorful-commands
          (delete-char 2)
          ;; insert key description and color
          (insert (cadr colorful-command))))
      (buffer-string))))


(defun clave-kle-show (&optional logs-regex clave-map-filter active-map-filter log-counts)
  (interactive
   (list (read-regexp "Filter log files by regex:" "2020-")
         (completing-read
          "Chose clave keymap to visualize:"
          (delete-dups (mapcar 'cadr clave-keys)))
         (completing-read
          "Chose context keymap to visualize:"
          (delete-dups (mapcar 'car clave-keys)))
         (y-or-n-p "Log the counts")))
  (browse-url
   (clave-kle-make-url logs-regex clave-map-filter active-map-filter log-counts)))

;; (clave-kle-make-url)

provide clave

(provide 'clave)

;; clave.el ends here