📜 ⬆️ ⬇️

Emacs: we train the cursor

What are these jumps for?
  Ostap Bender 

Introduction


Actually, I wanted to write a short note about some of the features of working with Clojure macros. But at the same time I decided to finally get acquainted with Emacs more thoroughly.

Of course, I’m not exactly the same as Lisp, but we’ve already known ... for twenty years, and the potential of this wonderful language (even more likely philosophy) is quite possible in theory and in practice. It was a matter of writing its own implementations (rather, for a better understanding of the mechanisms of operation of the Lisp interpreter than for practical use). However, Emacs was hardly ever used. in the old days of fairly dense work with lisp, it was quite costly with the built-in editor of my version (muLisp, the editor, of course, was also written on it). Then we had to work with “more other” tools, and in recent years, a completely different field. Now here is a little time "for the soul" ...

Actually, the “immersion” in Emacs was quite comfortable - although I (for some reason still) are not a Unixoid, but I understand the console commands and the whole keyboard experience. Setting up the control and gentlemanly set of "plug-ins" also did not cause problems. Screwing SBCL, Clojure and Scala had to tinker a bit, but it was the inadequacy of the versions and / or their (versions) inherent problems.
')
However, the “jumping cursor” syndrome (moving to the end of the line when moving to the next / previous line if it is shorter than the current one) causes a slight idiosyncrasy. If it were not for Emacs, it would most likely have to be reconciled and look for “conceptuality” in this approach, as is often done when it is impossible to solve problems. But, since we are dealing with the designer of editors, the problem was interpreted as a challenge (as it is now fashionable to say).

So let us leave aside the question of the necessity of “training” the cursor - for me personally this “50 to 50” convenience and just good physics is a good exercise to familiarize yourself with the concept of programming Emacs. Perhaps someone will also be interesting and useful, at least “on a vskidku” I did not find ready-made solutions (only the questions “how to do? ..”) - I admit that I didn’t search much because of the reasons above.

The target audience


I’ll just say that for those who are familiar with Emacs Lisp this is a half an hour problem, so the article is addressed primarily to those who begin to get acquainted with the practical possibilities of programming Emacs. The writing style implies knowledge of the basics of Emacs Lisp and the basic concepts of Emacs.

In turn, from the experts I would like to hear comments on alternative solutions that I probably did not see because of a superficial familiarity with the Emacs architecture for the time being.

Decision


Actually, my solution is quite obvious:

The idea was to track exactly those spaces that are added to move the cursor to the desired position, but this would significantly complicate the decision without bringing any special benefit, so we will simply remove all spaces at the end of lines (strictly speaking, following the non-blank character to the end of the line) . As for the moment of removal of such whales, then, for simplicity, we restrict ourselves to the events of turning off the mode and saving the buffer .

So, let's start creating the minor mode which will be called, say, wpers (hereinafter referred to simply mode ). You can get the full package with brief installation instructions from GitHub . Here I will comment on all the code in detail.

Conceptually, all we need is to intercept the commands next-line, previous-line, right-char and move-end-of-line. The first three just have to add spaces, if necessary, the last one will automatically remove the "extra" spaces at the end of the line. Interception is carried out using standard remap means within the framework of a key-map local for the mode .

If possible, we can take all that is possible from the code into constants:
;;  ()     (defconst wpers-overloaded-funs [next-line previous-line right-char move-end-of-line] "Functions overloaded by the mode") ;;     ("")  ;;       ("") (defconst wpers-fun-prefix "wpers-" "Prefix for new functions") ;;   -   ""    "" (defconst wpers-funs-alist (mapcar '(lambda (x) (cons x (intern (concat wpers-fun-prefix (symbol-name x))))) wpers-overloaded-funs) "alist (old . new) functions") ;; Key-map  -       (wpers-overloaded-funs) (defconst wpers-mode-map (reduce '(lambda (sx) (define-key s (vector 'remap (car x)) (cdr x)) s) wpers-funs-alist :initial-value (make-sparse-keymap)) "Mode map for `wpers'") ;;   -   - ()    (defconst wreps-hooks-alist '((pre-command-hook . wpers--pre-command-hook) (auto-save-hook . wpers-kill-final-spaces) (before-save-hook . wpers-kill-final-spaces)) "alist (hook-var . hook-function)") 


Next, we define the mode itself:
 (define-minor-mode wpers-mode "Toggle persistent cursor mode." :init-value nil :lighter " wpers" :group 'wpers :keymap wpers-mode-map (if wpers-mode (progn (message "Wpers enabled") (mapc '(lambda (x) (add-hook (car x) (cdr x) nil t)) wreps-hooks-alist)) ;     (progn (message "Wpers disabled") (wpers-kill-final-spaces) (mapc '(lambda (x) (remove-hook (car x) (cdr x) t)) wreps-hooks-alist)))) ;     


Now the simple "kitchen" of the functional mode :
 ;;    (form)        () (defmacro wpers-save-vpos (form) "Eval form with saving current cursor's position in the line (column)" (let ((old-col (make-symbol "old-col"))) `(let ((,old-col (current-column)) last-col) ,form (move-to-column ,old-col t)))) ;;;   /      (defun wpers-next-line () "Same as `new-line' but adds the spaces if it's needed for saving cursor's position in the line (column)" (interactive) (wpers-save-vpos (next-line))) (defun wpers-previous-line () "Same as `previous-line' but adds the spaces if it's needed for saving cursor's position in the line (column)" (interactive) (wpers-save-vpos (previous-line))) ;;   , " "   (defun wpers-right-char () "Same as `right-char' but adds the spaces if cursor at end of line (column)" (interactive) (let ((ca (char-after))) (if (or (null ca) (eq ca 10)) (insert 32) (right-char)))) ;;          (defun wpers-move-end-of-line () "Function `move-end-of-line' is called and then removes all trailing spaces" (interactive) (move-end-of-line nil) (while (eq (char-before) 32) (delete-char -1))) ;;        (defun wpers-kill-final-spaces () "Deleting all trailing spaces for all lines in the buffer" (save-excursion (goto-char (point-min)) (while (search-forward-regexp " +$" nil t) (replace-match "")))) ;;    (    )   read-only, visual-line    . (defun wpers--pre-command-hook () "Disabling functionality when buffer is read only, visual-line-mode is non-nil or marking is active" (if (or buffer-read-only this-command-keys-shift-translated mark-active visual-line-mode) (let ((fn-pair (rassoc this-command wpers-funs-alist))) (when fn-pair (setq this-command (car fn-pair)))))) 


Conclusion


This solution is certainly not ideal and has a number of limitations. For example, the mode for obvious reasons does not work for read-only buffers. Unnecessary spaces might have been better removed “by hot pursuit”, say in post-command-hook . Perhaps at leisure I will deal with these and other problems, but at the moment I’m quite happy ... maybe not "like an elephant", but definitely like an old lisper and novice emacser;)

Materials on the topic




UPD: read in the sequel how to do the same without extra spaces using overlays .

Source: https://habr.com/ru/post/265531/


All Articles