;;; hybrid-linum-mode.el --- Hybrid line numbers in Emacs -*- lexical-binding: t -*- ;; Author: Jakub Nowak ;; Maintainer: Jakub Nowak ;; Version: 1.0 ;; Package-Requires: none ;; Homepage: git.cyan.sh/birdt_/hybrid-linum-mode ;; 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 of the License, 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. If not, see . ;;; Commentary: ;; This is not likely particularly fast, but it should not be particularly slow either. ;;; Code: (defface relative-line-number-face '((t :inherit shadow)) "Face used for showing surrounding (relative) line numbers." :group 'basic-faces :group 'hybrid-linum-mode) (defface current-line-number-face '((t :inherit font-lock-keyword-face)) "Face used for showing the current (absolute) line number." :group 'basic-faces :group 'hybrid-linum-mode) (defcustom current-line-number-display-function (lambda () (number-to-string (line-number-at-pos last-post-command-position))) "Function that returns the string to display at the current line." :type 'function :group 'hybrid-linum-mode) (defun relative-line-number (&optional pos) "Return relative line number for POS (defaults to point). The current line returns 0." (let* ((pos-line (line-number-at-pos (or pos (point)))) (current-line (line-number-at-pos (point)))) (abs (- pos-line current-line)))) (defun build-hybrid-line-number () "Generate line number string for current line." (let* ((line (number-to-string (relative-line-number last-post-command-position)))) (propertize (if (equal "0" line) (funcall current-line-number-display-function) (concat " " (when (= 1 (length line)) " ") ;; Pad with an extra space if the number is a single digit. line " ")) 'face (if (equal "0" line) 'current-line-number-face 'relative-line-number-face)))) (defun display-hybrid-line-numbers (start limit) "Display line numbers in the margin between START and LIMIT." (goto-char start) (while (< (point) limit) (let ((line-str (build-hybrid-line-number)) (ov (make-overlay (line-beginning-position) (line-beginning-position)))) (overlay-put ov 'hybrid-linum-margin t) (overlay-put ov 'before-string (propertize " " 'display `((margin left-margin) ,line-str)))) (forward-line 1))) (defvar last-post-command-position 0 "Holds the cursor position from the last run of post-command-hooks.") (make-variable-buffer-local 'last-post-command-position) (defvar hybrid-linum-mode-enabled ) (defvar previous-margin-width 0 "Holds the previous margin width before hybrid-linum-mode was activated.") (make-variable-buffer-local 'previous-margin-width) (defvar previous-line-number-mode 0 "Holds the previous value of display-line-numbers-mode before hybrid-linum-mode was activated.") (make-variable-buffer-local 'previous-line-number-mode) (defun update-linenum-on-cursor-move () "Update hybrid line number if the cursor position changed." (unless (equal (point) last-post-command-position) (setq last-post-command-position (point)) (save-excursion (remove-overlays (point-min) (point-max) 'hybrid-linum-margin t) (display-hybrid-line-numbers (window-start) (window-end))))) (define-minor-mode hybrid-linum-mode "Show left-aligned line numbers in the left margin." :init-value nil (if hybrid-linum-mode (progn (setq previous-line-number-mode display-line-numbers) (setq previous-margin-width left-margin-width) (setq display-line-numbers nil) (setq left-margin-width 5) (setq last-post-command-position (point)) (set-window-buffer nil (current-buffer)) (remove-overlays (point-min) (point-max) 'hybrid-linum-margin t) (save-excursion (display-hybrid-line-numbers (window-start) (window-end))) (add-hook 'post-command-hook #'update-linenum-on-cursor-move 0 t)) (remove-overlays (point-min) (point-max) 'hybrid-linum-margin t) (remove-hook 'post-command-hook #'update-linenum-on-cursor-move) (setq left-margin-width previous-margin-width) (setq display-line-numbers previous-line-number-mode) (set-window-buffer nil (current-buffer)))) (provide 'hybrid-linum-mode) ;;; hybrid-linum-mode.el ends here