Search code examples
emacsoverlay

Pin/lock/fix text


With elisp it is possible to add overlays to parts of the buffer to hide them etc. I went through all the possible overlays and couldn't find a way to pin a selection. Is it possible to have a function that, given a selection in a buffer, pins this selection so that when you scroll up or down the selection is always shown? (a bit like what you have with Excel where you can lock some rows or columns so that they always appear on screen).

I wanted to do something like this but with (overlay-put new-overlay 'lock t) but there doesn't appear to be such overlay.

(defun hide-region-hide ()
  "Hides a region by making an invisible overlay over it and save the
overlay on the hide-region-overlays \"ring\""
  (interactive)
  (let ((new-overlay (make-overlay (mark) (point))))
    (push new-overlay hide-region-overlays)
    (overlay-put new-overlay 'invisible t)
    (overlay-put new-overlay 'intangible t)
    (overlay-put new-overlay 'before-string
                 (if hide-region-propertize-markers
                     (propertize hide-region-before-string
                                 'font-lock-face 'hide-region-before-string-face)
                   hide-region-before-string))
    (overlay-put new-overlay 'after-string
                 (if hide-region-propertize-markers
                     (propertize hide-region-after-string
                                 'font-lock-face 'hide-region-after-string-face)
                   hide-region-after-string))))

Solution

  • I came up with this solution that works pretty well:

    (defvar-local pinned-buffer nil
      "Variable to store the buffer that contains the pinned region.")
    
    (defun region-unpin ()
      "Unpin the current region"
      (interactive)
      (when pinned-buffer
        (let ((window (get-buffer-window pinned-buffer 'visible)))
          (setq-local pinned-buffer nil)
          (quit-window t window))))
    
    (defun region-pin ()
      "Pin the current region to the top."
      (interactive)
      (when (use-region-p)
        (let* ((regionp (buffer-substring (mark) (point)))
               (buffer (get-buffer-create "tmp.ml"))
               (mode major-mode))
          (with-current-buffer buffer
            (funcall mode)
            (hide-mode-line-mode)
            (goto-char (window-end))
            (insert regionp)
            (goto-char 0))
          (setq-local window-min-height 1)
          (setq-local pinned-buffer buffer)
          (display-buffer-in-direction buffer '((direction . above)
                                                (inhibit-same-window . t)
                                                (window-height . fit-window-to-buffer)))
          )))
    

    This allows me to have a temporary window with the major mode of my current one, no modeline, the height of the window fits perfectly the selection and the cursor is set to the beginning of it to have the full text displayed in it but whenever I want to pin some more text it goes to the end of the buffer, insert the selected region and goes back up.