Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request: Line number 1 in front of first line of text #17

Open
rappie opened this issue Apr 26, 2022 · 10 comments
Open

Feature request: Line number 1 in front of first line of text #17

rappie opened this issue Apr 26, 2022 · 10 comments
Labels
help wanted Extra attention is needed

Comments

@rappie
Copy link

rappie commented Apr 26, 2022

Is your feature request related to a problem? Please describe.
With line numbers enabled, line number 1 is currently shown at the top of the buffer. This is perfectly workable but it would be nice if it was in front of the actual first line of text.

Describe the solution you'd like
Have line number 1 be in front of the first line of text.

image

@trevorpogue
Copy link
Owner

trevorpogue commented Apr 27, 2022

Thanks for submitting this request. Unfortunately it looks like making this happen would require modifying the Emacs C source code, which isn't something that could be covered in a patch to the topspace package elisp code. Based on this, I may need to close this issue without providing a solution, sorry about that!

More details: The line numbers are displayed by setting the display-line-numbers variable, which interfaces with the Emacs C source code. There is an elisp-based minor mode called display-line-numbers-mode, but it just modifies the display-line-numbers* variables which are interpreted in the C code.

@rappie
Copy link
Author

rappie commented Apr 27, 2022

Thanks for looking into it :)

@trevorpogue trevorpogue added the wontfix This will not be worked on label Apr 27, 2022
@trevorpogue trevorpogue added help wanted Extra attention is needed and removed wontfix This will not be worked on labels May 13, 2022
@trevorpogue
Copy link
Owner

I'm going to re-open this issue because it would be nice to have it implemented eventually as a longer-term issue, either by myself or someone else willing to help. It will likely require additional functionality to be added to the Emacs C core and then could be available in a future Emacs version. I will eventually either open an issue with the Emacs devel mailing list for this or try to contribute the change myself but I will have to wait till I have more time to spend understanding the Emacs C core first which I've only briefly looked at before.

@trevorpogue trevorpogue reopened this May 13, 2022
@jarcode-foss
Copy link

@trevorpogue a hacky workaround for this issue in my case has been to use (run-with-idle-timer 0 nil #'sit-for 0), which causes Emacs to schedule an immediate redisplay and correct the problem. This needs to be called whenever the first line is moved to, or moved from.

(let ((ln-pos (line-number-at-pos)))
  (if (eq ln-pos 1)
      (run-with-idle-timer 0 nil #'sit-for 0)))

in my case, I have a lot of custom keybindings for buffer navigation with functions that constantly call (recenter) (among other commands) to keep my cursor squarely in the center of my screen at all times, so this pretty much solves the issue for me.

I've tried to find some sort of universal way of calling this, like using:

 (defadvice linum-update (after fix-topspace-linum-update activate)
   ...)
 (ad-activate 'linum-update)

but even though it executes accordingly, it doesn't seem to work at all. Even adding a ton of delay to run-with-idle-timer doesn't work, which is awfully confusing when I have it working fine elsewhere in interactive functions.

just some food for thought if anyone is interested in exploring further.

@trevorpogue
Copy link
Owner

trevorpogue commented Jul 29, 2022

Thanks for the tip! And I also use many hooks calling recenter frequently and have my own custom cursor-centering minor mode that has some improvements over centered-cursor-mode (maybe I will put it on GitHub at some point).

Anyways, I tried your code snippet, calling (run-with-idle-timer 0 nil #'sit-for 0), but I didn't see any effect, the "1" was still displayed too high up in the left margin afterwards like in rappie's screenshot above. What exactly do you see happening for you?

@jarcode-foss
Copy link

@trevorpogue I was getting 1 displayed at the start of the file, but only immediately after jumping to or from the first line. Various actions would update the line number position, getting it to re-draw correctly. I just added some code to force emacs to redraw immediately instead.

This may have something to do with the fact I also change the face of the line numbers when highlighted, to complement hl-line. Here's my full configuration for it:

(require 'hl-line)

(defface custom-linum-hl
  `((t :inherit linum :background ,(face-background 'hl-line nil t)))
  "Face for the current line number."
  :group 'linum)

(defvar custom-linum-format-string "%3d ")

(add-hook 'linum-before-numbering-hook 'custom-linum-get-format-string)

(defun custom-linum-get-format-string ()
  (let* ((width (1+ (length (number-to-string
                             (count-lines (point-min) (point-max))))))
         (format (concat "%" (number-to-string width) "d ")))
    (setq custom-linum-format-string format)))

(defvar custom-linum-current-line-number 0)

(setq linum-format 'custom-linum-format)

(defun custom-linum-format (line-number)
  (propertize (format custom-linum-format-string line-number) 'face
              (if (eq line-number custom-linum-current-line-number)
                  'custom-linum-hl
                'linum)))

(defadvice linum-update (around custom-linum-update)
  (let ((custom-linum-current-line-number (line-number-at-pos)))
    ad-do-it))
(ad-activate 'linum-update)

example of one of my binds (I am using sit-for directly here due to the preceding interactive calls, but scheduling it with run-with-idle-timer works regardless of where I call it in the function):

(defun custom-up-bind()
  (interactive)
  (call-interactively 'previous-line)
  (call-interactively 'recenter)
  (call-interactively 'move-end-of-line)
  (let ((ln-pos (line-number-at-pos)))
    (if (eq ln-pos 1)
        (sit-for 0))))

image:

with my configuration, I can now only reproduce the line numbering bug with specific mouse actions.

perhaps the real solution might be changing/updating the face?

@trevorpogue
Copy link
Owner

trevorpogue commented Jul 30, 2022

This is encouraging because I didn't actually think it was possible to ever have the 1 show below the very top of the window using elisp. I have yet to get it to do this on my end still but I will keep looking into it next week.

@jarcode-foss
Copy link

@trevorpogue to clarify, this is with linum-mode. If I run both modes at the same time I get the original bug only with display-line-numbers-mode:

my configuration predates display-line-numbers-mode, which is why I encountered this bug differently. Perhaps the workaround here is to use linum-mode with an improved hack for redrawing.

@trevorpogue
Copy link
Owner

trevorpogue commented Aug 1, 2022

@jarcode-foss okay that makes more sense and is good to know. I was able to reproduce your bug now and I seem to have a fix for it. There is a function topspace--after-scroll that's called when no top space is present before scrolling but it is present after scrolling, and at the end of this function I made it call linum-update-window. If you like, you could try the most recent topspace version (from melpa or by cloning this repo) and let me know if it fixed your bug.

So, anyone who wants this feature can use linum-mode for now instead of the native display-line-numbers-mode (which is supposed to be faster than linum-mode). Edit: Thanks to @jarcode-foss for pointing this out.

@TheRobotFox
Copy link

For display-line-numbers-mode there seems to be another although very hacky solution. You can use the overlay property display-line-numbers-disable to stop the line numbering from being drawn. Then you can manually insert it with another overlay.
It's not perfect and quite ugly, but works surprisingly well.
My Conf:

;; Zen mode
(defun zen-mode-bodge ()
  (let ((ov (make-overlay 1 1 nil t t)))
    (overlay-put ov 'priority 9999999)
    (overlay-put ov 'before-string (propertize "   1 " 'face 'line-number))
    (overlay-put ov 'zen--remove-from-buffer-tag t))
  (let ((ov (make-overlay 1 2)))
    (overlay-put ov 'display-line-numbers-disable t)
    (overlay-put ov 'zen--remove-from-buffer-tag t)))

(defun zen-mode-clear ()
  (remove-overlays 1 2 'zen--remove-from-buffer-tag t))

;; install hooks
(advice-add 'topspace--enable :after #'zen-mode-bodge)
(advice-add 'topspace--disable :after #'zen-mode-clear)

There don't seem to be any hooks defined so I am using add-advice instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

4 participants