git.fiddlerwoaroof.com
Raw Blame History
(cl:in-package #:cl-user)

(defpackage #:mfa-tool.editor-color-theme
  (:use #:cl)
  (:export #:all-color-themes
           #:color-theme-args
           #:color-theme
           #:define-color-theme
           #:remove-color-theme
           #:zenburn-paren-colors))

(in-package #:mfa-tool.editor-color-theme)

;;; Configuration

(defvar *foreground-color* nil)

(defvar *background-color* nil)

(defconstant +default-parenthesis-font-face-colours+ '(:red :black :darkgreen :darkorange3 :blue :purple))

;;; Implementation

(defvar *all-color-themes* (make-hash-table :test 'string=))

(defun all-color-themes ()
  (loop for key being the hash-keys in *all-color-themes*
        collect key))

(defun color-theme-data (theme-name)
  (multiple-value-bind (color-theme-data found?)
      (gethash theme-name *all-color-themes*)
    (if found?
        color-theme-data
        (error "No color theme named ~s found." theme-name))))

(defun color-theme-super-theme-names (theme-name)
  (first (color-theme-data theme-name)))

(defun color-theme-args (theme-name)
  (rest (color-theme-data theme-name)))

(defvar *all-editor-panes* (make-hash-table :test 'eq
                                            :weak-kind :key))

(defun update-editor-pane (pane)
  (setf (capi:simple-pane-foreground pane) (or *foreground-color* :color_windowtext))
  (setf (capi:simple-pane-background pane) (or *background-color* :color_window))

  (let ((recolorize-p (editor::buffer-font-lock-mode-p (capi:editor-pane-buffer pane))))
    (when recolorize-p
      (gp:invalidate-rectangle pane)))
  (values))

(defun update-editor-panes ()
  (maphash #'(lambda (pane value)
               (declare (ignore value))
               (update-editor-pane pane))
           *all-editor-panes*)
  (values))

(defvar *editor-face-names*
  '(:region
    :show-point-face
    :interactive-input-face
    :highlight
    :non-focus-complete-face
    :font-lock-function-name-face
    :font-lock-comment-face
    :font-lock-type-face
    :font-lock-variable-name-face
    :font-lock-string-face
    :font-lock-keyword-face
    :font-lock-builtin-face
    :compiler-note-highlight
    :compiler-warning-highlight
    :compiler-error-highlight
    ))

(defun set-color-theme (theme-name)
  (destructuring-bind (&rest color-theme-args
                       &key foreground background &allow-other-keys)
      (color-theme-args theme-name)

    (setf *foreground-color* (or foreground :color_windowtext))
    (setf *background-color* (or background :color_window))

    (lw:when-let (parenthesis-colors
                  (getf color-theme-args :parenthesis-font-face-colours
                        +default-parenthesis-font-face-colours+))
      (editor::set-parenthesis-colours parenthesis-colors))

    (dolist (name *editor-face-names*)
      (let* ((color-theme-args-for-face (getf color-theme-args name))
             (face-name (intern (string name) '#:editor))
             (face (editor:make-face face-name :if-exists t)))
        (apply 'editor:make-face face-name :if-exists :overwrite
                                           :documentation (or (getf color-theme-args-for-face :documentation)
                                                              (slot-value face 'documentation))
                                           color-theme-args-for-face))))

  theme-name)

(defun color-theme (theme-name)
  (mapc 'set-color-theme (color-theme-super-theme-names theme-name))
  (set-color-theme theme-name)

  (update-editor-panes)

  theme-name)

(defun define-color-theme (theme-name super-theme-names
                           &rest color-theme-args &key &allow-other-keys)
  (dolist (super-theme-name super-theme-names)
    (multiple-value-bind (color-theme-data found?)
        (gethash super-theme-name *all-color-themes*)
      (declare (ignore color-theme-data))
      (unless found?
        (warn "Inherited color theme ~s not defined." super-theme-name))))

  (setf (gethash theme-name *all-color-themes*) (list* super-theme-names color-theme-args))

  theme-name)

(defun remove-color-theme (theme-name)
  (remhash theme-name *all-color-themes*))

(sys::without-warning-on-redefinition
  (defmethod initialize-instance :around ((pane capi:editor-pane) &key &allow-other-keys)
    (multiple-value-prog1
        (call-next-method)

      (setf (gethash pane *all-editor-panes*) pane)

      (when *foreground-color*
        (setf (capi:simple-pane-foreground pane) *foreground-color*))
      (when *background-color*
        (setf (capi:simple-pane-background pane) *background-color*))))
  )

;; This makes it "work" after the podium is launched
(defun is-editor-pane-p (obj)
  (and (typep obj 'capi:editor-pane)
       (not (eq obj (hcl:class-prototype (class-of obj))))))

(defun cache-existing-pane (pane)
  (setf (gethash pane *all-editor-panes*) pane))

(defun cache-if-pane (obj)
  (when (is-editor-pane-p obj)
    (cache-existing-pane obj)))

#+:lispworks-personal-edition
(hcl:sweep-all-objects #'cache-if-pane)


;;; Initial color themes

(define-color-theme "default" ()
  :foreground nil :background nil
  :region '(:foreground :color_highlighttext
            :background :color_highlight)
  :show-point-face '(:background :green)
  :interactive-input-face '(:foreground :red3)
  :highlight '(:bold-p t)
  :non-focus-complete-face '(:background :tweak_background)
  :font-lock-function-name-face '(:foreground :blue)
  :font-lock-comment-face '(:foreground :firebrick)
  :font-lock-type-face '(:foreground :forestgreen)
  :font-lock-variable-name-face '(:foreground :darkgoldenrod)
  :font-lock-string-face '(:foreground :rosybrown)
  :font-lock-keyword-face '(:foreground :purple)
  :font-lock-builtin-face '(:foreground :orchid)
  :compiler-note-highlight '(:foreground :magenta)
  :compiler-warning-highlight '(:foreground :orange3)
  :compiler-error-highlight '(:foreground :red))

(define-color-theme "plain" ()
  :foreground nil :background nil
  :region '(:foreground :color_highlighttext
            :background :color_highlight)
  :show-point-face '()
  :interactive-input-face '()
  :highlight '(:bold-p t)
  :non-focus-complete-face '(:background :tweak_background)
  :font-lock-function-name-face '()
  :font-lock-comment-face '()
  :font-lock-type-face '()
  :font-lock-variable-name-face '()
  :font-lock-string-face '()
  :font-lock-keyword-face '()
  :font-lock-builtin-face '()
  :compiler-note-highlight '()
  :compiler-warning-highlight '()
  :compiler-error-highlight '())

(define-color-theme "emacs" ()
  :foreground nil :background nil
  :region '(:foreground :color_highlighttext
            :background :color_highlight)
  :show-point-face '(:background :green)
  :interactive-input-face '(:foreground :red3)
  :highlight '(:bold-p t)
  :non-focus-complete-face '(:background :tweak_background)
  :font-lock-function-name-face '(:foreground :blue)
  :font-lock-comment-face '(:foreground :gray40)
  :font-lock-type-face '(:foreground :forestgreen)
  :font-lock-variable-name-face '(:foreground :darkgoldenrod)
  :font-lock-string-face '(:foreground :rosybrown)
  :font-lock-keyword-face '(:foreground :purple)
  :font-lock-builtin-face '(:foreground :orchid)
  :compiler-note-highlight '(:foreground :magenta)
  :compiler-warning-highlight '(:foreground :orange3)
  :compiler-error-highlight '(:foreground :red))

(define-color-theme "torte" ()
  :foreground (color:make-rgb 0.8s0 0.8s0 0.8s0)
  :background (color:make-rgb 0.0s0 0.0s0 0.0s0)
  :region '(:foreground :color_highlighttext
            :background :color_highlight)
  :show-point-face `(:background ,(color:make-rgb 0.6275s0 0.1255s0 0.9412s0))
  :interactive-input-face '(:foreground :pink)
  :highlight '(:bold-p t)
  :non-focus-complete-face '(:background :tweak_background)
  :font-lock-function-name-face `(:foreground ,(color:make-rgb 0.0s0 1.0s0 1.0s0))
  :font-lock-comment-face `(:foreground ,(color:make-rgb 0.5s0 0.6275s0 1.0s0))
  :font-lock-type-face `(:foreground ,(color:make-rgb 0.5s0 1.0s0 0.5s0))
  :font-lock-variable-name-face `(:foreground ,(color:make-rgb 1.0s0 1.0s0 1.0s0))
  :font-lock-string-face `(:foreground ,(color:make-rgb 1.0s0 0.6275s0 0.6275s0))
  :font-lock-keyword-face `(:foreground ,(color:make-rgb 1.0s0 1.0s0 0.0s0))
  :font-lock-builtin-face `(:foreground ,(color:make-rgb 1.0s0 1.0s0 0.0s0))
  :compiler-note-highlight '(:foreground :magenta)
  :compiler-warning-highlight '(:foreground :orange)
  :compiler-error-highlight '(:foreground :red))


(defun make-rgb (red green blue &optional alpha)
  (color:make-rgb (/ red 255s0)
                  (/ green 255s0)
                  (/ blue 255s0)
                  (and alpha (/ alpha 255s0))))

(defvar *solarized-color-table*
  '(:solarized-base03  (#x00 #x2b #x36)
    :solarized-base02  (#x07 #x36 #x42)
    :solarized-base01  (#x58 #x6e #x75)
    :solarized-base00  (#x65 #x7b #x83)
    :solarized-base0   (#x83 #x94 #x96)
    :solarized-base1   (#x93 #xa1 #xa1)
    :solarized-base2   (#xee #xe8 #xd5)
    :solarized-base3   (#xfd #xf6 #xe3)
    :solarized-yellow  (#xb5 #x89 #x00)
    :solarized-orange  (#xcb #x4b #x16)
    :solarized-red     (#xdc #x32 #x2f)
    :solarized-magenta (#xd3 #x36 #x82)
    :solarized-violet  (#x6c #x71 #xc4)
    :solarized-blue    (#x26 #x8b #xd2)
    :solarized-cyan    (#x2a #xa1 #x98)
    :solarized-green   (#x85 #x99 #x00)))

(loop for list on *solarized-color-table* by #'cddr
      for name = (first list)
      for rgb = (second list)
      do
         (color:define-color-alias
             name
             (apply #'make-rgb rgb)))

(define-color-theme "solarized-light" ()
  :foreground :solarized-base00
  :background :solarized-base3
  :region '(:foreground :solarized-base1
            :background :solarized-base3
            :inverse-p t)
  :highlight '(:background :solarized-base2)
  :font-lock-function-name-face '(:foreground :solarized-blue)
  :font-lock-comment-face '(:foreground :solarized-base1 :italic-p t)
  :font-lock-type-face '(:foreground :solarized-yellow)
  :font-lock-variable-name-face '(:foreground :solarized-blue)
  :font-lock-string-face '(:foreground :solarized-cyan)
  :font-lock-keyword-face '(:foreground :solarized-green)
  :font-lock-builtin-face '(:foreground :solarized-green)
  :compiler-note-highlight '(:foreground :solarized-green
                             :bold-p t)
  :compiler-warning-highlight '(:foreground :solarized-orange
                                :bold-p t)
  :compiler-error-highlight '(:foreground :solarized-red
                              :inverse-p t)
  :show-point-face '(:foreground :solarized-cyan
                     :bold-p t :inverse-p t)
  :interactive-input-face '(:foreground :solarized-red)
  :non-focus-complete-face '(:background :solarized-base3)
  :parenthesis-font-face-colours '(:solarized-red
                                   :solarized-base01
                                   :solarized-green
                                   :solarized-orange
                                   :solarized-blue
                                   :solarized-magenta))

(define-color-theme "solarized-dark" ()
  :foreground :solarized-base0
  :background :solarized-base03
  :region '(:foreground :solarized-base01
            :background :solarized-base03
            :inverse-p t)
  :highlight '(:background :solarized-base02)
  :font-lock-function-name-face '(:foreground :solarized-blue)
  :font-lock-comment-face '(:foreground :solarized-base01 :italic-p t)
  :font-lock-type-face '(:foreground :solarized-yellow)
  :font-lock-variable-name-face '(:foreground :solarized-blue)
  :font-lock-string-face '(:foreground :solarized-cyan)
  :font-lock-keyword-face '(:foreground :solarized-green)
  :font-lock-builtin-face '(:foreground :solarized-green)
  :compiler-note-highlight '(:foreground :solarized-green
                             :bold-p t)
  :compiler-warning-highlight '(:foreground :solarized-orange
                                :bold-p t)
  :compiler-error-highlight '(:foreground :solarized-red
                              :inverse-p t)
  :show-point-face '(:foreground :solarized-cyan
                     :bold-p t :inverse-p t)
  :interactive-input-face '(:foreground :solarized-red)
  :non-focus-complete-face '(:background :solarized-base03)
  :parenthesis-font-face-colours '(:solarized-red
                                   :solarized-base1
                                   :solarized-green
                                   :solarized-orange
                                   :solarized-blue
                                   :solarized-magenta))

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defun hex->color (hex)
    (declare (optimize (speed 3) (safety 1) (debug 1)))
    (check-type hex (string 7))
    (flet ((extract-digits (string start end)
             (check-type string (simple-string 7))
             (parse-integer string
                            :start start
                            :end end
                            :radix 16)))
      (let* ((hex (coerce hex 'simple-string))
             (r (extract-digits hex 1 3))
             (g (extract-digits hex 3 5))
             (b (extract-digits hex 5 7)))
        (color:make-rgb (/ r 255.0)
                        (/ g 255.0)
                        (/ b 255.0))))))

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defparameter +zenburn-colors+
    `((zenburn-fg+2 ,(hex->color "#FFFFEF"))
      (zenburn-fg+1 ,(hex->color "#F5F5D6"))
      (zenburn-fg ,(hex->color "#DCDCCC"))
      (zenburn-fg-1 ,(hex->color "#A6A689"))
      (zenburn-fg-2 ,(hex->color "#656555"))
      (zenburn-black ,(hex->color "#000000"))
      (zenburn-bg-2 ,(hex->color "#000000"))
      (zenburn-bg-1 ,(hex->color "#111112"))
      (zenburn-bg-05 ,(hex->color "#383838"))
      (zenburn-bg ,(hex->color "#2A2B2E"))
      (zenburn-bg+05 ,(hex->color "#494949"))
      (zenburn-bg+1 ,(hex->color "#4F4F4F"))
      (zenburn-bg+2 ,(hex->color "#5F5F5F"))
      (zenburn-bg+3 ,(hex->color "#6F6F6F"))
      (zenburn-red+2 ,(hex->color "#ECB3B3"))
      (zenburn-red+1 ,(hex->color "#DCA3A3"))
      (zenburn-red ,(hex->color "#CC9393"))
      (zenburn-red-1 ,(hex->color "#BC8383"))
      (zenburn-red-2 ,(hex->color "#AC7373"))
      (zenburn-red-3 ,(hex->color "#9C6363"))
      (zenburn-red-4 ,(hex->color "#8C5353"))
      (zenburn-red-5 ,(hex->color "#7C4343"))
      (zenburn-red-6 ,(hex->color "#6C3333"))
      (zenburn-orange ,(hex->color "#DFAF8F"))
      (zenburn-yellow ,(hex->color "#F0DFAF"))
      (zenburn-yellow-1 ,(hex->color "#E0CF9F"))
      (zenburn-yellow-2 ,(hex->color "#D0BF8F"))
      (zenburn-green-5 ,(hex->color "#2F4F2F"))
      (zenburn-green-4 ,(hex->color "#3F5F3F"))
      (zenburn-green-3 ,(hex->color "#4F6F4F"))
      (zenburn-green-2 ,(hex->color "#5F7F5F"))
      (zenburn-green-1 ,(hex->color "#6F8F6F"))
      (zenburn-green ,(hex->color "#7F9F7F"))
      (zenburn-green+1 ,(hex->color "#8FB28F"))
      (zenburn-green+2 ,(hex->color "#9FC59F"))
      (zenburn-green+3 ,(hex->color "#AFD8AF"))
      (zenburn-green+4 ,(hex->color "#BFEBBF"))
      (zenburn-cyan ,(hex->color "#93E0E3"))
      (zenburn-blue+3 ,(hex->color "#BDE0F3"))
      (zenburn-blue+2 ,(hex->color "#ACE0E3"))
      (zenburn-blue+1 ,(hex->color "#94BFF3"))
      (zenburn-blue ,(hex->color "#8CD0D3"))
      (zenburn-blue-1 ,(hex->color "#7CB8BB"))
      (zenburn-blue-2 ,(hex->color "#6CA0A3"))
      (zenburn-blue-3 ,(hex->color "#5C888B"))
      (zenburn-blue-4 ,(hex->color "#4C7073"))
      (zenburn-blue-5 ,(hex->color "#366060"))
      (zenburn-magenta ,(hex->color "#DC8CC3")))))

(defmacro with-zenburn-colors (&body body)
  `(let ,+zenburn-colors+
     (declare (ignorable ,@(mapcar 'car +zenburn-colors+)))
     ,@body))

(with-zenburn-colors
  (define-color-theme "zenburn" ()
    :foreground zenburn-fg
    :background zenburn-bg
    :region `(:foreground ,zenburn-fg+1
              :background ,zenburn-bg+1)
    :show-point-face `(:background ,zenburn-bg+2)
    :interactive-input-face `(:foreground ,zenburn-red)
    :highlight '(:bold-p t)
    :non-focus-complete-face `(:background :tweak_background)
    :font-lock-function-name-face `(:foreground ,zenburn-blue)
    :font-lock-comment-face `(:foreground ,zenburn-fg-1)
    :font-lock-type-face `(:foreground ,zenburn-green)
    :font-lock-variable-name-face `(:foreground ,zenburn-yellow)
    :font-lock-string-face `(:foreground ,zenburn-orange)
    :font-lock-keyword-face `(:foreground ,zenburn-cyan)
    :font-lock-builtin-face `(:foreground ,zenburn-blue+1)
    :compiler-note-highlight `(:foreground ,zenburn-fg+1)
    :compiler-warning-highlight `(:foreground ,zenburn-orange)
    :compiler-error-highlight `(:foreground ,zenburn-red+1)))

(defun zenburn-paren-colors ()
  (with-zenburn-colors
    (capi:set-editor-parenthesis-colors
     (list zenburn-red
           zenburn-green
           zenburn-blue-1
           zenburn-green+1
           zenburn-blue+1
           zenburn-green+2
           zenburn-orange
           zenburn-cyan
           zenburn-magenta
           zenburn-yellow))))

;;; Show presence when loaded
(pushnew :editor-color-theme *features*)