From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: David Reitter Newsgroups: gmane.emacs.devel Subject: visual-line-mode Date: Sun, 29 Jun 2008 07:30:27 +0100 Message-ID: NNTP-Posting-Host: lo.gmane.org Mime-Version: 1.0 (Apple Message framework v924) Content-Type: multipart/mixed; boundary=Apple-Mail-6-190625925 X-Trace: ger.gmane.org 1214721055 28125 80.91.229.12 (29 Jun 2008 06:30:55 GMT) X-Complaints-To: usenet@ger.gmane.org NNTP-Posting-Date: Sun, 29 Jun 2008 06:30:55 +0000 (UTC) Cc: Chong Yidong , "Lennart Borgman \(gmail\)" To: Emacs-Devel devel Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Sun Jun 29 08:31:40 2008 Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([199.232.76.165]) by lo.gmane.org with esmtp (Exim 4.50) id 1KCqRl-00013q-AM for ged-emacs-devel@m.gmane.org; Sun, 29 Jun 2008 08:31:38 +0200 Original-Received: from localhost ([127.0.0.1]:46680 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1KCqQu-0002X8-Pk for ged-emacs-devel@m.gmane.org; Sun, 29 Jun 2008 02:30:44 -0400 Original-Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1KCqQo-0002X2-1J for emacs-devel@gnu.org; Sun, 29 Jun 2008 02:30:38 -0400 Original-Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1KCqQl-0002Wq-3v for emacs-devel@gnu.org; Sun, 29 Jun 2008 02:30:36 -0400 Original-Received: from [199.232.76.173] (port=42655 helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1KCqQk-0002Wn-V4 for emacs-devel@gnu.org; Sun, 29 Jun 2008 02:30:35 -0400 Original-Received: from mx20.gnu.org ([199.232.41.8]:5277) by monty-python.gnu.org with esmtps (TLS-1.0:RSA_AES_256_CBC_SHA1:32) (Exim 4.60) (envelope-from ) id 1KCqQk-0002jX-2l for emacs-devel@gnu.org; Sun, 29 Jun 2008 02:30:34 -0400 Original-Received: from ug-out-1314.google.com ([66.249.92.171]) by mx20.gnu.org with esmtp (Exim 4.60) (envelope-from ) id 1KCqQi-0000iR-8I for emacs-devel@gnu.org; Sun, 29 Jun 2008 02:30:32 -0400 Original-Received: by ug-out-1314.google.com with SMTP id l31so131001ugc.48 for ; Sat, 28 Jun 2008 23:30:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=domainkey-signature:received:received:message-id:from:to :content-type:mime-version:subject:date:cc:x-mailer; bh=MYBLxhvSt2PlMud+U2jDPcLSaRQ9EILVPpfAV2TXRII=; b=vLuZ0xxiSvM1CPMkcMOv6J0tZKyS90N3qHHY3XVTI+EEktEAUfd5FSsvQq8HqxFUfe rq6580ZTBgxGpuAwm6RxojfrTW6Kc4Sq8r6jZ7R8+cB7YWcBZd/11SseXLB528Va/fSA b2+NvDl1iahQ2N7VaxXbU+RryXCzVHODTRSEo= DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma; h=message-id:from:to:content-type:mime-version:subject:date:cc :x-mailer; b=RXMjFHdZPSCdOlNjl39I2373niVArsSJwa5fhiYb0PkWEVvuBsu2kYyoMyOXUc9PMf LR4f4y22mN5kHXxe+e46H7rd1m2TeLuziOPhi4gLEe7Qvg9EnqQORePWA0ifKAwZKmuf qv52c2206EzQPa4WBRw2RzZcLQmC5i0mdjBgI= Original-Received: by 10.66.236.16 with SMTP id j16mr3094304ugh.31.1214721029576; Sat, 28 Jun 2008 23:30:29 -0700 (PDT) Original-Received: from ?192.168.1.72? ( [93.96.127.15]) by mx.google.com with ESMTPS id x37sm764170ugc.74.2008.06.28.23.30.28 (version=TLSv1/SSLv3 cipher=RC4-MD5); Sat, 28 Jun 2008 23:30:28 -0700 (PDT) X-Mailer: Apple Mail (2.924) X-detected-kernel: by mx20.gnu.org: Linux 2.6 (newer, 2) X-detected-kernel: by monty-python.gnu.org: Linux 2.6, seldom 2.4 (older, 4) X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Xref: news.gmane.org gmane.emacs.devel:100124 Archived-At: --Apple-Mail-6-190625925 Content-Type: text/plain; charset=US-ASCII; format=flowed; delsp=yes Content-Transfer-Encoding: 7bit Here is the visual-line navigation mode that I promised. It has only been tested minimally, and I haven't tried it with the trunk (because they don't compile or work on my machine). Let me know how it works with the new DTWW. --Apple-Mail-6-190625925 Content-Disposition: attachment; filename=visual-line.el Content-Type: application/octet-stream; x-mac-creator=454D4178; x-unix-mode=0644; x-mac-type=54455854; name="visual-line.el" Content-Transfer-Encoding: 7bit ;;; visual-line.el ;; Copyright (C) 2008 Free Software Foundation ;; Maintainer: David Reitter ;; Authors: David Reitter ;; Keywords: mail ;; This file is part of GNU Emacs. ;; GNU Emacs is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 3, or (at your option) ;; any later version. ;; GNU Emacs is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs; see the file COPYING. If not, write to the ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, ;; Boston, MA 02110-1301, USA. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Overview: ;; ;; `visual-line-mode' and `global-visual-line-mode' enable ;; navigation by visual lines. Vertical movement commands such as ;; `next-line' and `previous-line' (normally bound to up/down arrow ;; keys) will move the point to the next line as shown on the ;; screen, even if that is the same line in the underlying buffer. ;; The point is moved to a position that is located (on the screen) ;; horizontally close (pixel-wise), rather than to an equivalent ;; by-character column. ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Notable changes: ;; ;; Initial version: ;; This file was adapted from Aquamacs Emacs. ;; Lennart Borgmann contributed the code that creates a minor mode ;; for this. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Code Comments: ;; Note that `visual-line-up' and friends use two different methods to ;; figure out the best position to move to because of a slowness with ;; outline-(minor-)mode. One of the methods (basically binary search) is ;; much faster when a lot of hidden text is present, but a bit slower in ;; all other cases. (defun visual-col-at-point () "Returns the visual column at point. The visual column is relative to the left window edge, not to the beginning of the (unwrapped) line." (- (point) (save-excursion (vertical-motion 0) (point)))) ;; seems slower (in situations with very long lines) ;;(or (car (nth 6 (posn-at-point))) 0)) (defun visual-pixel-col-at-point () "Returns the pixel column at point. This is the distance from the left edge of the window to the character at point." (or (car-safe (pos-visible-in-window-p (point) nil 'partial)) 0)) (defvar visual-movement-temporary-goal-column nil) (make-variable-buffer-local 'visual-movement-temporary-goal-column) (defvar visual-previous-scroll-margin 'none) (defun visual-restore-scroll-margin () "Restore the scroll margin." (if (integerp visual-previous-scroll-margin) (setq scroll-margin visual-previous-scroll-margin)) (remove-hook 'pre-command-hook 'visual-restore-scroll-margin)) (defcustom visual-scroll-margin nil "Number of lines of margin at top and bottom of a window. For visual scrolling with up and down keys, this value applies instead of `scroll-margin' if it is non-nil. The reason this variable exists is that clicks in the first and last line of a window will set the cursor within the standard scroll-margin, causing the buffer to scroll immediately. This is usually undesired. In this case, set `scroll-margin' to zero and `visual-scroll-margin' to the desired margin." :group 'Windows) (defun visual-line-up (num-lines) "Move cursor vertically up NUM-LINES lines. Interactively, vscroll tall lines if `auto-window-vscroll' is enabled. If there is no character in the target line exactly over the current horizontal pixel position, the cursor is positioned close to the character in that line at the same position, or at the end of the line if it is not long enough. The command C-x C-n can be used to create a semipermanent goal column for this command. Then instead of trying to move exactly vertically (or as close as possible), this command moves to the specified goal column (or as close as possible). The goal column is stored in the variable `goal-column', which is nil when there is no goal column. This function differs from `previous-line' as it moves vertically in the visual sense. The result differs when variable-width font is used or when characters of non-standard width (e.g. TABs) are used. If you are thinking of using this in a Lisp program, consider using `forward-line' with a negative argument instead. It is usually easier to use and more reliable (no dependence on goal column, etc.)." (interactive "p") (if (bobp) (signal 'beginning-of-buffer nil)) (let ((pixel-col (visual-pixel-col-at-point)) (visual-col (visual-col-at-point)) (old-point (point)) (end-of-old-line)) ;; temporary binding of scroll-margin ;; cannot do this with a temporary let binding (setq visual-previous-scroll-margin scroll-margin) (if visual-scroll-margin (setq scroll-margin visual-scroll-margin)) (add-hook 'pre-command-hook 'visual-restore-scroll-margin) (save-excursion (vertical-motion 1) ;; trying going one down, to left (setq end-of-old-line (point))) (vertical-motion 0) (let* ((beg-of-old-line ;; move right, but not further than to end of line (prog1 (point) (vertical-motion (- num-lines)))) ;; one up again (beg-of-new-line (point)) (rel-beg-of-old-line (- beg-of-old-line (point) 1))) ;; handle track-eol... (if (and track-eol (= old-point (1- end-of-old-line)) ;; Don't count beg of empty line as end of line ;; unless we just did explicit end-of-line. (or (not (= old-point beg-of-old-line)) (eq last-command 'end-of-line))) (setq visual-movement-temporary-goal-column 9999)) ;; approximate positioning (if (and (or goal-column visual-movement-temporary-goal-column) (memq last-command '(visual-line-up visual-line-down visual-line-up-in-buffers visual-line-down-in-buffers)) (= old-point (1- end-of-old-line))) ;; jumping from end of line (forward-char (min (or goal-column visual-movement-temporary-goal-column) rel-beg-of-old-line)) ;; else, do complete positioning ;; save original position (setq visual-movement-temporary-goal-column visual-col) ; (forward-char (min visual-col rel-beg-of-old-line)) ;; this won't work because we don't have the ;; absolute position, just the position within window ;; (let ((p (pos-visible-in-window-p old-point nil 'p)) ;; (p2 (pos-visible-in-window-p beg-of-new-line nil 'p) )) ;; (print (cons (car p) (cdr p2))) ;; (posn-set-point (cons (car p) (cdr p2))) ;; ) (if (> (abs (- (point) beg-of-old-line)) 400) ;; find-position-at-pixel-col is much faster when ;; large portions of hidden text are to be crossed. ;; this can happen in outline-(minor-)mode for instance. (goto-char (find-position-at-pixel-col pixel-col)) ;; approximate positioning (forward-char (min visual-col rel-beg-of-old-line)) (if (>= (visual-pixel-col-at-point) pixel-col) (progn (while (and (> (visual-pixel-col-at-point) pixel-col) (> (point) beg-of-new-line)) ;; do not cross line (forward-char -1))) (progn (while (and (< (visual-pixel-col-at-point) pixel-col) (< (point) (1- beg-of-old-line))) ;; do not cross line (forward-char +1))))) )))) (defun visual-line-down (num-lines) "Move cursor vertically down NUM-LINES lines. Interactively, vscroll tall lines if `auto-window-vscroll' is enabled. If there is no character in the target line exactly under the current column, the cursor is positioned after the character in that line which spans this column, or at the end of the line if it is not long enough. If there is no line in the buffer after this one, behavior depends on the value of `next-line-add-newlines'. If non-nil, it inserts a newline character to create a line, and moves the cursor to that line. Otherwise it moves the cursor to the end of the buffer. The command C-x C-n can be used to create a semipermanent goal column for this command. Then instead of trying to move exactly vertically (or as close as possible), this command moves to the specified goal column (or as close as possible). The goal column is stored in the variable `goal-column', which is nil when there is no goal column. This function differs from `next-line' as it moves vertically in the visual sense. The result differs when variable-width font is used or when characters of non-standard width (e.g. TABs) are used. If you are thinking of using this in a Lisp program, consider using `forward-line' instead. It is usually easier to use and more reliable (no dependence on goal column, etc.)." (interactive "p") (if (and next-line-add-newlines (= num-lines 1)) (if (save-excursion (end-of-line) (eobp)) ;; When adding a newline, don't expand an abbrev. (let ((abbrev-mode nil)) (end-of-line) (insert hard-newline))) (if (eobp) (signal 'end-of-buffer nil))) (let ((pixel-col (visual-pixel-col-at-point)) (visual-col (visual-col-at-point)) (old-point (point)) (beg-of-line) (next-line-start) (rel-next-line-start)) ;; temporary binding of scroll-margin ;; cannot do this with a temporary let binding (setq visual-previous-scroll-margin scroll-margin) (if visual-scroll-margin (setq scroll-margin visual-scroll-margin)) (add-hook 'pre-command-hook 'visual-restore-scroll-margin) (vertical-motion num-lines) ;; down (save-excursion (setq beg-of-line (point)) (vertical-motion +1) ;; down (setq next-line-start (point)) (setq rel-next-line-start (- (point) beg-of-line 1))) (unless (= beg-of-line (point-max)) ;; handle track-eol... (if (and track-eol (= old-point (1- next-line-start)) ;; Don't count beg of empty line as end of line ;; unless we just did explicit end-of-line. (or (not (= 0 visual-col)) (eq last-command 'end-of-line))) (setq visual-movement-temporary-goal-column 9999)) ;; approximate positioning (if (and (or goal-column visual-movement-temporary-goal-column) (memq last-command '(visual-line-up visual-line-down visual-line-up-in-buffers visual-line-down-in-buffers)) (= old-point (- beg-of-line 1))) ;; jumping from end of line (forward-char (min (or goal-column visual-movement-temporary-goal-column) rel-next-line-start)) ;; else, do complete positioning ;; save original position (setq visual-movement-temporary-goal-column visual-col) ;; find-position-at-pixel-col is much faster when ;; large portions of hidden text are to be crossed. ;; this can happen in outline-(minor-)mode for instance. (if (> (abs (- old-point next-line-start)) 400) (goto-char (find-position-at-pixel-col pixel-col)) (forward-char (min visual-col rel-next-line-start)) (if (> (visual-pixel-col-at-point) pixel-col) (progn (while (and (> (visual-pixel-col-at-point) pixel-col) (> (point) beg-of-line)) ;; do not cross line (forward-char -1))) (progn (while (and (< (visual-pixel-col-at-point) pixel-col) (< (point) (1- next-line-start))) ;; do not cross line (forward-char +1))))))))) (defun find-position-at-pixel-col (pixel-col) (let ((beg-of-line) (end-of-line)) (vertical-motion 1) ;; trying going one down, to left (setq end-of-line (point)) (if (eq (point) (point-max)) (vertical-motion 0) (vertical-motion -1)) (setq beg-of-line (point)) (let ((op (point))) ;; move to beg of line (vertical-motion 0) ;; trying going one down, to left (forward-char (/ pixel-col (frame-char-width))) (find-position-at-pixel-col-recursive beg-of-line end-of-line pixel-col) (let* ((nearest-pos (point)) (smallest-distance (abs (- pixel-col (visual-pixel-col-at-point))))) (let ((pdif (abs (- pixel-col (progn (forward-char -1) (visual-pixel-col-at-point)))))) (when (< pdif smallest-distance) (setq nearest-pos (point)) (setq smallest-distance pdif))) (let ((pdif (abs (- pixel-col (progn (forward-char 2) (visual-pixel-col-at-point)))))) (when (< pdif smallest-distance) (setq nearest-pos (point)) (setq smallest-distance pdif))) (goto-char nearest-pos)) (point)))) (defun find-position-at-pixel-col-recursive (beg-of-line end-of-line pixel-col) ;; set it in the middle (if (eq beg-of-line end-of-line) (point) (let ((middle (+ beg-of-line (round (/ (- end-of-line beg-of-line) 2))))) (if (or (eq middle (point)) ;; wouldn't change point any more (eq (visual-pixel-col-at-point) pixel-col)) (point) (goto-char middle) (if (> (visual-pixel-col-at-point) pixel-col) (find-position-at-pixel-col-recursive beg-of-line (point) pixel-col) (find-position-at-pixel-col-recursive (point) end-of-line pixel-col)))))) (defun beginning-of-visual-line () "Move point to the beginning of the current visual line." (interactive) (if (bobp) (signal 'beginning-of-buffer nil)) (vertical-motion 0)) (defun end-of-visual-line () "Move point to the end of the current visual line." (interactive) (if (eobp) (signal 'end-of-buffer nil)) (let ((end-of-line (line-end-position))) (vertical-motion 1) (unless (eobp) ;;or: (< (point) end-of-line) ;; jumping over wrapped text (backward-char 1)))) ;; this code based on simple.el (defun kill-visual-line (&optional arg) "Kill the rest of the visual line; if no nonblanks there, kill thru newline. With prefix argument, kill that many lines from point. Negative arguments kill lines backward. With zero argument, kills the text before point on hthe current line. When calling from a program, nil means \"no arg\", a number counts as a prefix arg. To kill a whole line, when point is not at the beginning, type \ \\[beginning-of-line] \\[kill-line] \\[kill-line]. If `kill-whole-line' is non-nil, then this command kills the whole line including its terminating newline, when used at the beginning of a line with no argument. As a consequence, you can always kill a whole line by typing \\[beginning-of-line] \\[kill-line]. If you want to append the killed line to the last killed text, use \\[append-next-kill] before \\[kill-line]. If the buffer is read-only, Emacs will beep and refrain from deleting the line, but put the line in the kill ring anyway. This means that you can use this command to copy text from a read-only buffer. \(If the variable `kill-read-only-ok' is non-nil, then this won't even beep.)" (interactive "P") (kill-region (point) ;; It is better to move point to the other end of the ;; kill before killing. That way, in a read-only ;; buffer, point moves across the text that is copied ;; to the kill ring. The choice has no effect on undo ;; now that undo records the value of point from before ;; the command was run. (progn (if arg (vertical-motion (prefix-numeric-value arg)) (if (eobp) (signal 'end-of-buffer nil)) (let ((end (save-excursion (end-of-visual-line) (point)))) (if (or (save-excursion ;; If trailing whitespace is visible, ;; don't treat it as nothing. (unless show-trailing-whitespace (skip-chars-forward " \t" end)) (= (point) end)) (and kill-whole-line (bolp))) (visual-line-down 1) (goto-char end)))) (point)))) (defun kill-whole-visual-line (&optional arg) "Kill current visual line. With prefix arg, kill that many lines starting from the current line. If arg is negative, kill backward. Also kill the preceding newline. \(This is meant to make \\[repeat] work well with negative arguments.\) If arg is zero, kill current line but exclude the trailing newline." (interactive "p") (if (and (> arg 0) (eobp) (save-excursion (vertical-motion 0) (eobp))) (signal 'end-of-buffer nil)) (if (and (< arg 0) (bobp) (save-excursion (vertical-motion 1) (bobp))) (signal 'beginning-of-buffer nil)) (unless (eq last-command 'kill-region) (kill-new "") (setq last-command 'kill-region)) (cond ((zerop arg) ;; We need to kill in two steps, because the previous command ;; could have been a kill command, in which case the text ;; before point needs to be prepended to the current kill ;; ring entry and the text after point appended. Also, we ;; need to use save-excursion to avoid copying the same text ;; twice to the kill ring in read-only buffers. (save-excursion ;; delete in one go (kill-region (progn (vertical-motion 0) (point)) (progn (vertical-motion 1) (point))) )) ((< arg 0) (save-excursion (kill-region (point) (progn (end-of-visual-line) (point)))) (kill-region (point) (progn (vertical-motion (1+ arg)) (unless (bobp) (backward-char)) (point)))) (t (save-excursion (kill-region (progn (vertical-motion 0) (point)) (progn (vertical-motion arg) (point))))))) (defun visual-line-up-in-buffers () "Moves the cursor up one (visual) line. If the `up' key would normally be bound to something else than `previous-line' (as it is the case in minibuffers), the other binding is called." (interactive) (let* (visual-line-mode ;; turn off mode temporarily (binding (key-binding [up]))) (if (eq binding 'previous-line) (call-interactively (function visual-line-up)) (call-interactively binding)))) (defun visual-line-down-in-buffers () "Moves the cursor down one (visual) line. If the `down' key would normally be bound to something else than `next-line' (as it is the case in minibuffers), the other binding is called." (interactive) (let* (visual-line-mode ;; turn off mode temporarily (binding (key-binding [down]))) (if (eq binding 'next-line) (call-interactively (function visual-line-down)) (call-interactively binding)))) ;; mark functions for CUA (dolist (cmd '( beginning-of-visual-line end-of-visual-line visual-line-down visual-line-up visual-line-up-in-buffers visual-line-down-in-buffers)) (put cmd 'CUA 'move)) (defalias 'original-kill-line 'kill-line) (defalias 'original-next-line 'next-line) (defalias 'original-previous-line 'previous-line) (defun line-wrapped-p () "Return non-nil if the current line is wrapped." (let ((here (point)) result) (vertical-motion 0) (setq result (/= (line-beginning-position) (point))) (unless result (let ((line-end-pos (line-end-position))) (vertical-motion 1) (setq result (/= line-end-pos (- (point) 1))))) (goto-char here) result)) (defvar visual-line-map (let ((map (make-sparse-keymap))) (define-key map [remap next-line] 'visual-line-down) (define-key map [remap previous-line] 'visual-line-up) (define-key map [remap kill-line] 'kill-visual-line) (define-key map [(control shift ?k)] 'original-kill-line) (define-key map [remap move-beginning-of-line] 'beginning-of-visual-line) (define-key map [remap move-end-of-line] 'end-of-visual-line) map)) (define-minor-mode visual-line-mode "Define key binding for visual line moves." :keymap visual-line-map :group 'convenience) (defun turn-on-visual-line-mode () (visual-line-mode 1)) (define-globalized-minor-mode global-visual-line-mode visual-line-mode turn-on-visual-line-mode :lighter " vl") (provide 'visual-line) --Apple-Mail-6-190625925 Content-Type: text/plain; charset=US-ASCII; format=flowed Content-Transfer-Encoding: 7bit --Apple-Mail-6-190625925--