├── README.org ├── org-table-sticky-header.el └── screenshots └── demo.gif /README.org: -------------------------------------------------------------------------------- 1 | #+TITLE: org-table-sticky-header-mode 2 | [[https://melpa.org/#/org-table-sticky-header][file:https://melpa.org/packages/org-table-sticky-header-badge.svg]] 3 | 4 | A minor mode to show the sticky header for org-mode tables. 5 | 6 | * Overview 7 | Similar to =semantic-stickyfunc-mode=, this package uses the header line to 8 | show the table header when it is out of sight. 9 | 10 | * Usage 11 | Recommend: install from melpa. 12 | 13 | To install manually: 14 | : (add-to-list 'load-path "/path/to/org-table-sticky-header.el") 15 | 16 | =M-x org-table-sticky-header-mode= to enable the minor mode in an org-mode 17 | buffer. 18 | 19 | To automatically enable the minor mode in all org-mode buffers, use 20 | : (add-hook 'org-mode-hook 'org-table-sticky-header-mode) 21 | 22 | * Demo 23 | [[./screenshots/demo.gif]] 24 | -------------------------------------------------------------------------------- /org-table-sticky-header.el: -------------------------------------------------------------------------------- 1 | ;;; org-table-sticky-header.el --- Sticky header for org-mode tables -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2017 Junpeng Qiu 4 | 5 | ;; Author: Junpeng Qiu 6 | ;; Keywords: extensions 7 | ;; Version: 0.1.0 8 | ;; Package-Requires: ((org "8.2.10") (emacs "24.4")) 9 | 10 | ;; This program is free software; you can redistribute it and/or modify 11 | ;; it under the terms of the GNU General Public License as published by 12 | ;; the Free Software Foundation, either version 3 of the License, or 13 | ;; (at your option) any later version. 14 | 15 | ;; This program is distributed in the hope that it will be useful, 16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | ;; GNU General Public License for more details. 19 | 20 | ;; You should have received a copy of the GNU General Public License 21 | ;; along with this program. If not, see . 22 | 23 | ;;; Commentary: 24 | 25 | ;; ______________________________ 26 | 27 | ;; ORG-TABLE-STIKCY-HEADER-MODE 28 | 29 | ;; Junpeng Qiu 30 | ;; ______________________________ 31 | 32 | 33 | ;; Table of Contents 34 | ;; _________________ 35 | 36 | ;; 1 Overview 37 | ;; 2 Usage 38 | ;; 3 Demo 39 | 40 | 41 | ;; [[file:https://melpa.org/packages/org-table-sticky-header-badge.svg]] 42 | 43 | ;; A minor mode to show the sticky header for org-mode tables. 44 | 45 | 46 | ;; [[file:https://melpa.org/packages/org-table-sticky-header-badge.svg]] 47 | ;; https://melpa.org/#/org-table-sticky-header 48 | 49 | 50 | ;; 1 Overview 51 | ;; ========== 52 | 53 | ;; Similar to `semantic-stickyfunc-mode', this package uses the header 54 | ;; line to show the table header when it is out of sight. 55 | 56 | 57 | ;; 2 Usage 58 | ;; ======= 59 | 60 | ;; To install manually: 61 | ;; ,---- 62 | ;; | (add-to-list 'load-path "/path/to/org-table-sticky-header.el") 63 | ;; `---- 64 | 65 | ;; `M-x org-table-sticky-header-mode' to enable the minor mode in an 66 | ;; org-mode buffer. 67 | 68 | ;; To automatically enable the minor mode in all org-mode buffers, use 69 | ;; ,---- 70 | ;; | (add-hook 'org-mode-hook 'org-table-sticky-header-mode) 71 | ;; `---- 72 | 73 | 74 | ;; 3 Demo 75 | ;; ====== 76 | 77 | ;; [./screenshots/demo.gif] 78 | 79 | ;;; Code: 80 | 81 | (require 'org) 82 | (require 'org-table) 83 | 84 | (defface org-table-sticky-header-face 85 | '((t :inherit 'default)) 86 | "Face for org-table-sticky-header." 87 | :group 'org-faces) 88 | 89 | (defvar org-table-sticky-header--last-win-start -1) 90 | (defvar org-table-sticky-header--old-header-line-format nil) 91 | 92 | (defun org-table-sticky-header--is-header-p (line) 93 | (not 94 | (or (string-match "^ *|-" line) 95 | (let ((cells (split-string line "|")) 96 | (ret t)) 97 | (catch 'break 98 | (dolist (c cells ret) 99 | (unless (or (string-match "^ *$" c) 100 | (string-match "^ *<[0-9]+> *$" c) 101 | (string-match "^ *<[rcl][0-9]*> *$" c)) 102 | (throw 'break nil)))))))) 103 | 104 | (defun org-table-sticky-header--table-real-begin () 105 | (save-excursion 106 | (goto-char (org-table-begin)) 107 | (while (and (not (eobp)) 108 | (not (org-table-sticky-header--is-header-p 109 | (buffer-substring-no-properties 110 | (point-at-bol) 111 | (point-at-eol))))) 112 | (forward-line)) 113 | (point))) 114 | 115 | (defun org-table-sticky-header-org-table-header-visible-p () 116 | (save-excursion 117 | (goto-char org-table-sticky-header--last-win-start) 118 | (>= (org-table-sticky-header--table-real-begin) (point)))) 119 | 120 | (defun org-table-sticky-header--get-display-line-number-width () 121 | (if (bound-and-true-p display-line-numbers-mode) 122 | ;; 2 extra columns for padding 123 | (+ 2 (line-number-display-width)) 124 | 0)) 125 | 126 | (defun org-table-sticky-header--get-line-prefix-width (line) 127 | (let (prefix) 128 | (or (and (bound-and-true-p org-indent-mode) 129 | (setq prefix (get-text-property 0 'line-prefix line)) 130 | (string-width prefix)) 131 | 0))) 132 | 133 | (defun org-table-sticky-header--get-visual-header (text visual-col) 134 | (if (= visual-col 0) 135 | text 136 | (with-temp-buffer 137 | (insert text) 138 | (goto-char (point-min)) 139 | (while (> visual-col 0) 140 | (when (string= (get-text-property (point) 'display) "=>") 141 | (setq visual-col (1- visual-col))) 142 | (move-point-visually 1) 143 | (setq visual-col (1- visual-col))) 144 | (buffer-substring (point) (point-at-eol))))) 145 | 146 | (defun org-table-sticky-header-get-org-table-header () 147 | (let ((col (window-hscroll)) 148 | visual-header) 149 | (save-excursion 150 | (goto-char org-table-sticky-header--last-win-start) 151 | (if (bobp) 152 | "" 153 | (if (org-at-table-p 'any) 154 | (goto-char (org-table-sticky-header--table-real-begin)) 155 | (forward-line -1)) 156 | (setq visual-header 157 | (org-table-sticky-header--get-visual-header 158 | (buffer-substring (point-at-bol) (point-at-eol)) 159 | col)) 160 | (remove-text-properties 0 161 | (length visual-header) 162 | '(face nil) 163 | visual-header) 164 | visual-header)))) 165 | 166 | (defun org-table-sticky-header--fetch-header () 167 | (if (org-table-sticky-header-org-table-header-visible-p) 168 | (setq header-line-format org-table-sticky-header--old-header-line-format) 169 | ;; stole from `semantic-stickyfunc-mode' 170 | (let ((line (org-table-sticky-header-get-org-table-header))) 171 | (setq header-line-format 172 | `(:eval (list 173 | (propertize 174 | " " 175 | 'display 176 | '((space :align-to 177 | ,(+ (org-table-sticky-header--get-display-line-number-width) 178 | (org-table-sticky-header--get-line-prefix-width line))))) 179 | (propertize 180 | ,line 181 | 'face 'org-table-sticky-header-face))))))) 182 | 183 | (defun org-table-sticky-header--scroll-function (win start-pos) 184 | (unless (= org-table-sticky-header--last-win-start start-pos) 185 | (setq org-table-sticky-header--last-win-start start-pos) 186 | (save-match-data 187 | (org-table-sticky-header--fetch-header)))) 188 | 189 | (defun org-table-sticky-header--insert-delete-column () 190 | (if org-table-sticky-header-mode 191 | (save-match-data 192 | (org-table-sticky-header--fetch-header)))) 193 | 194 | (defun org-table-sticky-header--table-move-column (&optional left) 195 | (if org-table-sticky-header-mode 196 | (save-match-data 197 | (org-table-sticky-header--fetch-header)))) 198 | 199 | ;;;###autoload 200 | (define-minor-mode org-table-sticky-header-mode 201 | "Sticky header for org-mode tables." 202 | nil " OTSH" nil 203 | (if org-table-sticky-header-mode 204 | (if (derived-mode-p 'org-mode) 205 | (progn 206 | (setq org-table-sticky-header--old-header-line-format header-line-format) 207 | (add-hook 'window-scroll-functions 208 | 'org-table-sticky-header--scroll-function 'append 'local) 209 | (advice-add 'org-table-delete-column :after #'org-table-sticky-header--insert-delete-column) 210 | (advice-add 'org-table-insert-column :after #'org-table-sticky-header--insert-delete-column) 211 | (advice-add 'org-table-move-column :after #'org-table-sticky-header--table-move-column) 212 | (setq org-table-sticky-header--last-win-start (window-start)) 213 | (org-table-sticky-header--fetch-header)) 214 | (setq org-table-sticky-header-mode nil) 215 | (error "Not in `org-mode'")) 216 | (advice-remove 'org-table-delete-column #'org-table-sticky-header--insert-delete-column) 217 | (advice-remove 'org-table-insert-column #'org-table-sticky-header--insert-delete-column) 218 | (advice-remove 'org-table-move-column #'org-table-sticky-header--table-move-column) 219 | (remove-hook 'window-scroll-functions 'org-table-sticky-header--scroll-function 'local) 220 | (setq header-line-format org-table-sticky-header--old-header-line-format))) 221 | 222 | (provide 'org-table-sticky-header) 223 | ;;; org-table-sticky-header.el ends here 224 | -------------------------------------------------------------------------------- /screenshots/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cute-jumper/org-table-sticky-header/b65442857128ab04724aaa301e60aa874a31a798/screenshots/demo.gif --------------------------------------------------------------------------------