Instantly share code, notes, and snippets.

Embed
What would you like to do?

eyeliner

Eyeliner is nougat providing high-level support for spaceline, the package behind Spacemacs’ mode-line. Eyeliner also depends on all-the-icons for providing multiple icon-fonts.

active unmodified buffer https://gist.github.com/dustinlacewell/74d2365600bdecde32aa12179806511d/raw/5295236f5a25fcfb9a0b6433b6f6db6ad9c34a95/unmodified.png

inactive unmodified buffer (dimmed) https://gist.github.com/dustinlacewell/74d2365600bdecde32aa12179806511d/raw/237d860285e57bea09a3226c0710291cbf77e4ba/unmodified-dimmed.png

modified buffer https://gist.githubusercontent.com/dustinlacewell/74d2365600bdecde32aa12179806511d/raw/5295236f5a25fcfb9a0b6433b6f6db6ad9c34a95/modified.png

staged or unstaged changes https://gist.github.com/dustinlacewell/74d2365600bdecde32aa12179806511d/raw/5295236f5a25fcfb9a0b6433b6f6db6ad9c34a95/git-changes.png

user guide

The following sections will explain how Eyeliner works and how to best use it.

how spaceline works

The core concept behind Spaceline are the Segments that make up your mode-line. It depends on Powerline, which colors each of these segments differently so they are visually distinct. Typically, it also features unicode delimeters between the segments such as arrows, waves, bevels or zigzags.

Spaceline separates the mode-line in half into left and right sides. Segments are either configured to appear aligned on the left or the right, with empty space in between.

integrating with spaceline

In order to get your mode-line integrated with Spaceline, use the following snippet after Spaceline has been loaded:

(setq-default mode-line-format
  '("%e" (:eval (spaceline-ml-main))))

specifying what segments you want

Once Spaceline has been loaded you can call (spaceline-install 'main left right) to customize your mode-line. Here is an example showing how to replicate the default segments:

(spaceline-install 'main
  '((eyeliner/buffer-modified)
    (eyeliner/branch-icon :skip-alternate t :tight-right t)
    (eyeliner/branch-name)
    (eyeliner/project-name :skip-alternate t)
    (eyeliner/mode-icon :skip-alternate t :tight-left t :tight-right t)
    (eyeliner/buffer-name))
  '(("%l:%c")))

how spaceline segements are defined

Spaceline provides a macro (spaceline-define-segment NAME BODY) which defines a new Segment. Segments must return some, potentially propertized, text. If Segments return nil, they don’t appear in the mode-line. Additionally, the binding active will be t or nil depending if the Segement is being called for the active buffer or not. This can be used to change colors or hide the Segment.

Here is a simple example showing the buffer-name when the buffer is active. We can choose a different face depending on buffer-state (modified, read-only, etc):

(spaceline-define-segment my/buffer-name-segment
  (when active
    (let* ((buffer-name (buffer-name))
           (buffer-state (format-mode-line "%*"))
           (style (cond
                   ((string= buffer-state "-") 'mode-line)
                   ((string= buffer-state "*") 'mode-line-emphasis)
                   ((string= buffer-state "%") 'mode-line-highlight))))
      (propertize buffer-name 'face style))))

Later, we’ll see how Eyeliner can help you write nice Segments.

how all-the-icons works

all-the-icons.el is a package which provides a high-level API over a number of icon-font packages. It is useful for displaying information icongraphically which is terser and more aesthetically pleasing. It can even provide icons for many major-modes which is nice.

installing the fonts

After all-the-icons has been loaded, you can run (all-the-icons-install-fonts) to install all of the associated fonts. You don’t need to put this into your configuration as it only needs to be run one time.

looking up an icon

Calling (all-the-icons--function-name ICON-NAME) will return the icon as text propertized with a simple face with the appropriate font family. However, you wont need to do this as Eyeliner provides a better interface.

how eyeliner works

Eyeliner is going to help you do the following:

  • define styles which are auto-magically be “dimmed” in inactive buffers
  • define icons which are easy to use with styles
  • defer your segement definitions so you can put them anywhere in your config

defining styles

Styles are functions you can call with some text to have it appropriately propertized based on whether the Style is being displayed in an active buffer or not. When Styles are displayed in inactive buffers they are shown as darkened desaturated versions.

(eyeliner/style greeting "blue")

This will create a function (greeting-style TEXT) which will return the propertized text:

(let ((active t))
  (insert (greeting-style "Hello World")))

defining icons

Icons are functions similar to Styles but they don’t take any text as a parameter and instead always return the propertized font icon.

(eyeliner/icon octoface "octoface" "red")

This creates an Icon function (octoface-icon):

(let ((active t))
  (insert (octoface-icon)))

The dimmed versions of Styles and Icons are returned when active is nil:

(let ((active nil))
  (insert (octoface-icon)))

controlling dimming

Style and Icon dimming is controlled by the settings eyeliner/default-darkness and eyeliner/default-desaturation. They are each a number between 0 and 100 representing what percent the given color quality should be reduced to.

To set the darkness and desaturation for a specific Style or Icon pass the desired values to eyeliner/style or eyeliner/icon:

;; set darkness and desaturation to 30 and 60
(eyeliner/icon octoface "octoface" "red" 30 60)

customizing other face attributes

You can pass a plist of Face Attributes to either eyeliner/style or eyeliner/icon as the sixth parameter:

;; dimming values will use the defaults
(eyeliner/style warning "red" nil nil
  '(:slant italic))

customizing display properties

You can pass Display Properties as well using the seventh parameter:

;; raise the icon slightly to vertically center it
(eyeliner/icon circle "circle-o" "yellow" nil nil nil
  '((raise -0.2)))

deferring segement definitions

Using spaceline-define-segment to define Segments before Spaceline is loaded will resort in an error. If you need the flexibility, Eyeliner provides a simple macro for deferring your Segment definitions:

(eyeliner/defer-segment
 (spaceline-define-segment my/cool-segement
   (when active
     ... )))

customizing built-in segment colors

Eyeliner comes with a number of default Segments to get you started. They all utilize some default colors which you can customize:

  • eyeliner/warm-color for when something might need attending to
  • eyeliner/cool-color for when something is A-OK
  • eyeliner/plain-color a neutral color for other situations

To customize these, simply assign a color to them with setq BEFORE (!) you load this file.

the built in segments

buffer-modified

Buffer modified: circle-o icon in eyeliner/cool-color Buffer unmodified: dot-circle-o icon in eyeliner/warm-color

buffer-name

Buffer modified: the buffer name in eyeliner/plain-color Buffer unmodified: the buffer name in eyeliner/warm-color

branch-icon

No staged or unstaged changes: git-branch icon in eyeliner/cool-color Staged or unstaged changes: diff-added icon in eyeliner/warm-color

branch-name

No staged or unstaged changes: the branch name in eyeliner/cool-color Staged or unstaged changes: the branch name in eyeliner/warm-color

project-name

(projectile-project-name) in eyeliner/plain-color

mode-icon

Major-mode relevant icon in eyeliner/plain-color

all the icons

Need to run (all-the-icons-install-fonts) on first run, then restart.

(use-package all-the-icons)

settings

(setq-default eyeliner/warm-color "indian red")
(setq-default eyeliner/cool-color "deep sky blue")
(setq-default eyeliner/plain-color (face-foreground 'default))
(setq-default eyeliner/default-darkness 35)
(setq-default eyeliner/default-desaturation 65)

boilerplate

(use-package dash)

(defmacro with (name as value &rest body)
  (declare (indent defun))
  `(let ((,name ,value)) ,@body))

(defun transform (expr)
  (if (ignore-errors (fboundp expr)) `(it (apply (quote ,expr) (list it)))
    `(it ,expr)))

(defmacro pipeline (value &rest exprs)
  (declare (indent defun))
  (let* ((transforms (mapcar 'transform exprs))
         (assignments (append `((it ,value)) transforms)))
    `(let* ,assignments it)))

(setq eyeliner/segments '())

(defmacro eyeliner/defer-segment (&rest body)
  `(setq eyeliner/segments (append eyeliner/segments '(,@body))))

(defun eyeliner/define-segments ()
  (mapc 'eval eyeliner/segments))

(defun eyeliner/symbol (name suffix)
  "Return a symbol with the given name and suffix"
  (intern (format "eyeliner/%s-%s" name suffix)))

;; (eyeliner/symbol "foo" "bar")

(defun eyeliner/adjust-color (color &optional darkness desaturation)
  "Return COLOR modified by DARKNESS and DESATURATION"
  (pipeline color
    (color-darken-name it (or darkness eyeliner/default-darkness))
    (color-desaturate-name it (or desaturation eyeliner/default-desaturation))))

;; (eyeliner/adjust-color (badger-color "blue"))

faces

(defun eyeliner/make-face (symbol props)
  "Create a face with the given SYMBOL and PROPS"
  (face-spec-set symbol (list (cons t props))) symbol)

(defun eyeliner/make-active-face (name color &optional props)
  "Create a face with the given COLOR and PROPS and return the
   symbol based on NAME"
  (with symbol as (eyeliner/symbol name "active")
    (with props as (append props `(:foreground ,color))
      (eyeliner/make-face symbol props))))

;; (eyeliner/make-active-face "foo" (badger-color "blue"))

(defun eyeliner/make-inactive-face (name color &optional darkness desaturation props)
  "Create a face with the given COLOR modified by DARKNESS and
   DESATURATION and any other PROPS. Returns the symbol based on
   NAME."
  (with color as (eyeliner/adjust-color color darkness desaturation)
    (with symbol as (eyeliner/symbol name "inactive")
      (with props as `(:foreground ,color)
        (eyeliner/make-face symbol props)))))

;; (eyeliner/make-inactive-face "foo" (badger-color "blue"))

(defun eyeliner/make-face-pair (name color &optional darkness desaturation props)
  "Return the a cons containing symbols, based on NAME, for newly
   created faces. One face's COLOR will be modified by DARKNESS
   and DESATURATION."
  (let ((active-face (eyeliner/make-active-face name color props))
        (inactive-face (eyeliner/make-inactive-face name color darkness desaturation props)))
    (cons active-face inactive-face)))

;; (eyeliner/make-face-pair "foo" (badger-color "blue"))

(defun eyeliner/make-icon-face-pair (icon-name color &optional darkness desaturation props)
  "Create a face suitable for a spaceline icon"
  (let* ((props (append props `(:family ,family))))
    (apply 'eyeliner/make-face-pair icon-name color darkness desaturation props)))

;; (eyeliner/make-icon-face-pair "git-branch" (badger-color "blue"))

icons

(defun eyeliner/get-icon-factory (set-name)
  "Return an icon factory for the given iconset"
  (--when-let (all-the-icons--function-name set-name)
    (when (fboundp it) it)))

;; (eyeliner/get-icon-factory 'octicon)
;; (apply (eyeliner/get-icon-factory 'octicon) '("git-branch"))

(defun eyeliner/get-icon-family (set-name)
  "Return the family-name for a given iconset"
  (--when-let (all-the-icons--family-name set-name)
    (apply it '())))

;; (eyeliner/get-icon-family 'octicon)

(defun eyeliner/find-icon (icon-name)
  "Return a cons containing an icon and its family-name"
  (cl-loop for set-name in '(octicon faicon wicon fileicon material alltheicon)
           for factory = (eyeliner/get-icon-factory set-name)
           for icon = (ignore-errors (apply factory `(,icon-name)))
           for family = (eyeliner/get-icon-family set-name)
           if icon
             return (cons icon family)))

;; (eyeliner/find-icon "lock")

(defmacro eyeliner/with-icon (icon-name &rest body)
  "Execute body while binding icon and family"
  (declare (indent defun))
  `(--when-let (eyeliner/find-icon ,icon-name)
     (cl-destructuring-bind (icon . family) it ,@body)))

;; (propertize (eyeliner/with-icon "git-branch" icon) 'face 'eyeliner/foo-active)

api

style

(defmacro eyeliner/style (name color &optional darkness desaturation props)
  "Define a function which will return propertized text with the
   proper color based on the value of ACTIVE which is bound by
   spaceline."
  (declare (indent defun))
  (cl-destructuring-bind (active-face . inactive-face)
      (let ((color (eval color)))
        (eyeliner/make-face-pair name color darkness desaturation props))
    (with symbol as (eyeliner/symbol name "style")
      `(defun ,symbol (text)
         (propertize text
           'font-lock-face (if active (quote ,active-face)
                             (quote ,inactive-face))
           'face (if active (quote ,active-face)
                   (quote ,inactive-face)))))))

;; (eyeliner/style foo (badger-color "blue"))

icon

(defmacro eyeliner/icon (name icon-name color &optional darkness desaturation props display)
  "Define a function which will return propertized text with the
   proper color based on the value of ACTIVE which is bound by
   spaceline."
  (declare (indent defun))
  (eyeliner/with-icon icon-name
    (cl-destructuring-bind (active-face . inactive-face)
        (let ((color (eval color))
              (face-name (format "%s-icon" name)))
          (eyeliner/make-face-pair face-name color darkness desaturation props))
      (with symbol as (eyeliner/symbol name "icon")
        `(defun ,symbol ()
           (propertize ,icon
             'display (or ,display '((height 1.0) (raise 0.0)))
             'font-lock-face (if active (quote ,active-face)
                               (quote ,inactive-face))
             'face (if active (quote ,active-face)
                     (quote ,inactive-face))))))))

;; (eyeliner/icon branch-new "git-branch" (badger-color "yellow"))

segments

buffer-modified

;; buffer modification icon
(eyeliner/icon unmodified "circle-o" eyeliner/cool-color)
(eyeliner/icon modified "dot-circle-o" eyeliner/warm-color)
(eyeliner/icon locked "diff-added" eyeliner/warm-color)

(eyeliner/defer-segment
 (spaceline-define-segment eyeliner/buffer-modified
  "An `all-the-icons' segment depiciting the current buffers state"
  (let ((buffer-state (format-mode-line "%*")))
    (cond
     ((string= buffer-state "-") (eyeliner/unmodified-icon))
     ((string= buffer-state "*") (eyeliner/modified-icon))
     ((string= buffer-state "%") (eyeliner/locked-icon))))))

buffer-name

;; buffer name
(eyeliner/style buffer-name eyeliner/plain-color)
(eyeliner/style buffer-name-modified eyeliner/warm-color)

(eyeliner/defer-segment
 (spaceline-define-segment eyeliner/buffer-name
  (let* ((buffer-state (format-mode-line "%*"))
         (style (cond
                ((string= buffer-state "-") 'eyeliner/buffer-name-style)
                ((string= buffer-state "*") 'eyeliner/buffer-name-modified-style)
                ((string= buffer-state "%") 'eyeliner/buffer-name-modified-style))))
    (apply style `(,(buffer-name))))))

branch-icon

(eyeliner/icon branch "git-branch" eyeliner/cool-color)
(eyeliner/icon branch-diff "note_add" eyeliner/warm-color nil nil nil
  '((raise -0.2)))

(eyeliner/defer-segment
 (spaceline-define-segment eyeliner/branch-icon
   (when vc-mode
     (if (magit-anything-modified-p) (eyeliner/branch-diff-icon)
       (eyeliner/branch-icon)))))

branch-name

(eyeliner/style branch-clean eyeliner/cool-color)
(eyeliner/style branch-dirty eyeliner/warm-color)

(eyeliner/defer-segment
 (spaceline-define-segment eyeliner/branch-name
   (when vc-mode
     (let ((branch (mapconcat 'concat (cdr (split-string vc-mode "[:-]")) "-")))
       (if (magit-anything-modified-p) (eyeliner/branch-dirty-style branch)
         (eyeliner/branch-clean-style branch))))))

project-name

(eyeliner/style project-name eyeliner/plain-color)

(eyeliner/defer-segment
 (spaceline-define-segment eyeliner/project-name
   (when (projectile-project-p)
     (eyeliner/project-name-style (projectile-project-name)))))

mode-icon

(eyeliner/defer-segment
 (spaceline-define-segment eyeliner/mode-icon
   (let ((icon (all-the-icons-icon-for-mode major-mode)))
     (when icon
       (propertize icon
         'help-echo (format "Major-mode: `%s'" major-mode)
         'display '(raise 0)
         'face `(
                 :foreground eyeliner/plain-color
                 :height 0.9
                 :family ,(all-the-icons-icon-family-for-mode major-mode)
                 :inherit))))))

setup

(use-package spaceline
  :after (magit)
  :config
  (setq-default mode-line-format
    '("%e" (:eval (spaceline-ml-main))))

  (spaceline-helm-mode 1)

  (custom-set-faces
   `(powerline-active2
     ((t (:background ,(badger-color "bg")))))
   `(powerline-inactive2
     ((t (:background ,(badger-color "bg"))))))

  (eyeliner/define-segments)

  (spaceline-install 'main
    '((eyeliner/buffer-modified)
      (eyeliner/branch-icon :skip-alternate t :tight-right t)
      (eyeliner/branch-name)
      (eyeliner/project-name :skip-alternate t)
      (eyeliner/mode-icon :skip-alternate t :tight-left t :tight-right t)
      (eyeliner/buffer-name))
    '(("%l:%c"))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment