Emacs: train the cursor

why these jumps?
the
 Bender

the

Introduction


I actually wanted to write a little note "about some peculiarities of work with macros in Clojure". Along the way, but finally decided to more thoroughly acquainted with Emacs.

Of course, I'm not really a contemporary of the Lisp, but I now... dtsat years and the potential of this wonderful language (even more philosophy) it is imagine, both in theory and in practice. It was the case and wrote their own implementation (perhaps for a better understanding of mechanisms of work of the interpreter Lisp than for practical use). However, Emacs hasn't been used since the ancient times quite a dense work with Lisp it cost the built-in editor my version (muLisp, the editor of course was also written for him). Then had to work with "more other" tools, and in recent years altogether in another sphere. Now here come some time "for the soul"...

The actual "dip" in Emacs was quite comfortable — although I'm (still) not unixoid, but to console commands and all the keyboard are understanding. Control settings and gentlemanly set of "plugins" also caused problems. Screwing with SBCL, Clojure and Scala had a little trouble, but the reason was a version mismatch and/or their (versions of) congenital problems.

However, the syndrome of "jumping cursor" (move to end of line when moving to next/previous line if it is shorter the current) causes a slight idiosyncrasy. If it was not about Emacs, it likely would have to accept and search for "conceptuality" in this approach, as is often done when it is impossible to solve problems. But, since we are dealing with the designer editors, the problem was interpreted as a challenge (as it is now fashionable to say).

So leave aside the question of the need for "training" of the cursor — personally for me it's 50/50, convenience and just illegal good exercise to familiarize with the concept of programming Emacs. Maybe someone will also be interesting and useful, at least "on vskidku" ready-made solutions I found (only questions "how to do?..") — I admit that not much was looking for the reason, see above.

the

Target audience


Immediately say, that for those who are familiar with Emacs Lisp is a task for half an hour, so that the article is addressed primarily to those who are starting out with the practical programmability of Emacs. The style of presentation assumes knowledge of the basics of Emacs Lisp and basic concepts of Emacs.

In turn, from the experts I would like to hear comments about alternative solutions which I might not have seen because until fairly superficial acquaintance with the architecture of Emacs.

the

Solution


Actually, my solution is quite obvious:
the
    the
  • since we can move cursor in terms Buterawe need to complement the string "extra" spaces in order to move the cursor to the desired position;
  • the
  • as these spaces are in fact superfluous, it is necessary to remove them at the first opportunity;
  • the
  • to make the functionality easier to minor mode.

The idea was to track the spaces that are added to move the cursor to the desired position, but it considerably would complicate the solution without bringing any particular benefit so let's just remove all spaces at end of lines (strictly speaking from the next non-white character to end of line). With regard to the time of removal of such pobelov, then, to simplify, we restrict the events disable and save buffer.
So, begin by creating a minor mode which will be called say wpers (hereinafter simply mode). The full text of the package with quick installation instructions you can obtain with GitHub. Here I will comment in detail on all the code.

Conceptually, all we need is to intercept team next-line, previous-line right-char move-end-of-line. The first three just have to add spaces, if necessary, the latter will automatically remove the "extra" spaces at the end of the row. The interception is carried out by standard means remap within the key-map-a local a mode.

Possible take everything you can from the code in the constants:
the
;; Functions (commands) which we will intercept
(defconst wpers-overloaded-funs [next-line previous-line right-char move-end-of-line] "Functions overloaded by the mode")

;; The prefix to the names of the intercept () functions 
;; to obtain the names of functions, their intercepts ("new")
(defconst wpers-fun-prefix "wpers-" "Prefix for new functions")

;; Associative list is a mapping from the "old" function names to "new"
(defconst wpers-funs-alist
(mapcar '(lambda (x) (cons x (intern (concat wpers-fun-prefix (symbol-name x)))))
wpers-overloaded-funs)
"alist of (old . new) functions")

;; Key-map mode - the essence of the early interception of the declared functions (wpers-overloaded-funs)
(wpers defconst-mode-map
(reduce '(lambda (s x) (define-key s (vector 'remap (car x)) (cdr x)) s)
wpers-funs-alist :initial-value (make-sparse-keymap))
"Mode map for `wpers'")

;; Associative list is a mapping from variables-hooks (events) in their handlers
(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'll define himself mode:
the
(define-minor-mode wpers-mode
"Toggle the 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)) ; add event handler
(progn
(message "Wpers disabled")
(wpers-kill-final-spaces)
(mapc '(lambda (x) (remove-hook (car x) (cdr x) t)) wreps-hooks-alist)))) ; remove your event handler


Now the actual simple "kitchen" functions of mode:
the
;; Execute the specified form (form) with the restoration of the original position of the cursor in the row (column)
(defmacro wpers-save-vpos (form) "Eval form with saving the current cursor's hotspot 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))))

;;; Move the cursor up/down, preserving the vertical positions

(defun wpers-next-line () "Same as `new-line' but adds the spaces if it's needed
for saving cursor's hotspot 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 hotspot position in the line (column)"
(interactive) (wpers-save-vpos (previous-line)))

;; Move the cursor right beyond the end of the 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) (ca 10 eq))
(insert 32)
(right-char))))

;; Move cursor to end of line and remove insignificant spaces
(defun wpers-move-end-of-line () "the 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)))

;; Remove spaces at the end of all rows in the buffer
(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""))))

;; Turn off the functionality of the mode (return to the previous command handlers) in read-only mode, visual-line, or in mark mode.
(defun wpers--pre-command-hook () "Disabling functionality when the 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))))))


the

Conclusion


The solution of course is not perfect and has several limitations. For example, mode for obvious reasons does not work for read-only buffers. Extra spaces perhaps it would be better to remove "without delay" of, say, post-command-hook. Perhaps at leisure, I will address these and other problems, but at the moment I'm quite happy... maybe not "like a bull", but definitely like the old lisper and beginner emacsen ;)
the

related content


the

UPD: read how to do the same thing without the extra spaces using the overleaf.
Article based on information from habrahabr.ru

Комментарии

Популярные сообщения из этого блога

Integration of PostgreSQL with MS SQL Server for those who want faster and deeper

Custom database queries in MODx Revolution

Parse URL in Zend Framework 2