Subject: bug#8754: submission of vimvars

From: James Youngman <[email-masked]>
Date: Sun, 29 May 2011 19:05:43 +0100

On Sun, May 29, 2011 at 18:21, Nix <[email-masked]> wrote:
> On 28 May 2011, James Youngman stated:
>
>> +(defun vimvars-enable-feature (var)
>> +  "Emulate VIM's :set VAR for a variables that are just boolean."
>
> Nit-pick of the day: excess 'a'.

Thanks for catching that.   Thanks also to Štěpán for pointing out
that we need to check the bottom of the file too.   Here's an updated
patch fixing both of those problems.

James.
=== modified file 'doc/emacs/ChangeLog'
--- doc/emacs/ChangeLog	2011-05-27 01:00:53 +0000
+++ doc/emacs/ChangeLog	2011-05-29 18:00:23 +0000
@@ -1,3 +1,13 @@
+2011-05-29  James Youngman  <[email-masked]>
+
+	* custom.texi: Vimvars will now find a mode line in the end of the
+	file too.
+
+2011-05-28  James Youngman  <[email-masked]>
+
+	* custom.texi (vi-compatible modeline): New section describing vimvars.
+	* misc.texi (Emulation): Mention vimvars.
+
 2011-05-27  Glenn Morris  <[email-masked]>
 
 	* custom.texi (Specifying File Variables):

=== modified file 'doc/emacs/custom.texi'
--- doc/emacs/custom.texi	2011-05-27 01:00:53 +0000
+++ doc/emacs/custom.texi	2011-05-29 17:59:40 +0000
@@ -1069,6 +1069,7 @@
 @menu
 * Specifying File Variables:: Specifying file local variables.
 * Safe File Variables::       Making sure file local variables are safe.
+* vi-compatible modeline::    Support for vi modelines.
 @end menu
 
 @node Specifying File Variables
@@ -1287,6 +1288,30 @@
 @code{eval} form if that form occurs within the variable
 @code{safe-local-eval-forms}.
 
+@node vi-compatible modeline
+@subsubsection Support for vi modelines
+
+The vi editor also supports a feature like local variables.  Emacs can
+understand these too.  To enable this feature, evaluate this Lisp
+expression (in the @file{.emacs} file, for example):
+
+@example
+(require 'vimvars)
+(add-hook 'find-file-hook 
+          'vimvars-obey-vim-modeline) 
+@end example
+
+Some of the vi modeline settings make no sense for Emacs and so are
+not supported.  Others are partially supported; for example, @samp{vi:
+set shiftwidth=N} is supported only in c-mode.
+
+Only the first and last @code{vimvars-check-lines} lines of a file are
+checked for a vi mode line. If a file contains both Emacs local
+variables and a vi modeline, the vi modeline will be ignored.  You can
+change this by setting
+@code{vimvars-ignore-mode-line-if-local-variables-exist} to
+@code{nil}.
+
 @node Directory Variables
 @subsection Per-Directory Local Variables
 @cindex local variables, for all files in a directory

=== modified file 'doc/emacs/misc.texi'
--- doc/emacs/misc.texi	2011-05-17 02:26:56 +0000
+++ doc/emacs/misc.texi	2011-05-28 21:17:38 +0000
@@ -2556,6 +2556,21 @@
 
 @inforef{Top, VIP, vip}, for full information.
 
+@item vimvars (support for vi mode lines)
+Emacs can understand many of the ``modeline'' configurations that
+vi-compatible editors do.  
+@xref{vi-compatible modeline,,Support for vi modelines}.
+
+
+To enable this feature, evaluate this Lisp
+expression:
+
+@example
+(require 'vimvars)
+(add-hook 'find-file-hook 
+          'vimvars-obey-vim-modeline) 
+@end example
+
 @item WordStar (old wordprocessor)
 @findex wordstar-mode
 @kbd{M-x wordstar-mode} provides a major mode with WordStar-like

=== modified file 'etc/ChangeLog'
--- etc/ChangeLog	2011-05-24 14:22:44 +0000
+++ etc/ChangeLog	2011-05-28 21:17:38 +0000
@@ -1,3 +1,7 @@
+2011-05-28  James Youngman  <[email-masked]>
+
+	* NEWS: Mention addition of vimvars.el.
+
 2011-05-24  Leo Liu  <[email-masked]>
 
 	* NEWS: Mention the new primitive sha1 and the removal of sha1.el.

=== modified file 'etc/NEWS'
--- etc/NEWS	2011-05-27 01:00:53 +0000
+++ etc/NEWS	2011-05-28 21:17:38 +0000
@@ -769,6 +769,11 @@
 
 ** xmodmap-generic-mode for xmodmap files.
 
+** vimvvars.el provides support for vi mode lines
+See "Support for vi modelines" in the Emacs Manual for the
+documentation.
+
+
 
 * Incompatible Lisp Changes in Emacs 24.1

=== modified file 'lisp/ChangeLog'
--- lisp/ChangeLog	2011-05-28 02:10:32 +0000
+++ lisp/ChangeLog	2011-05-29 18:01:37 +0000
@@ -1,3 +1,24 @@
+2011-05-29  James Youngman  <[email-masked]>
+
+	* emulation/vimvars.el (vimvars-check-lines): We also check
+	vimvars-check-lines lines at the bottom of the file.
+	(vimvars-obey-this-vim-modeline): Factor out of
+	vimvars-obey-vim-modeline in order to separate the concerns of
+	finding the modeline and obeying it.
+	(vimvars-obey-top-modeline): New function, locates a modeline at
+	the top of the file.
+	(vimvars-obey-bottom-modeline): New function, locates a modeline
+	at the bottom of the file.
+	(vimvars-obey-vim-modeline): Move some of the body into
+	vimvars-obey-this-vim-modeline.  Call vimvars-obey-top-modeline
+	and (if it fails to find a modeline) vimvars-obey-bottom-modeline.
+	(vimvars-enable-feature): Fix typo in docstring.
+
+2011-05-28  James Youngman  <[email-masked]>
+
+	* emulation/vimvars.el: New file; supports "vi: set foo" mode
+	lines in vi, ex, vim and similar editors.
+
 2011-05-28  Stefan Monnier  <[email-masked]>
 
 	* minibuffer.el (completion--capf-wrapper): Check applicability before

=== added file 'lisp/emulation/vimvars.el'
--- lisp/emulation/vimvars.el	1970-01-01 00:00:00 +0000
+++ lisp/emulation/vimvars.el	2011-05-29 17:56:50 +0000
@@ -0,0 +1,201 @@
+;;; vimvars.el --- Emacs support for VI modelines
+
+;; Copyright (C) 2010 Free Software Foundation, Inc.
+
+;; Author: James Youngman <[email-masked]>
+;; Maintainer: James Youngman <[email-masked]>
+;; Keywords: local-variables, vi, vim, emulations
+
+;; vimvars 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 of the License, or
+;; (at your option) any later version.
+
+;; vimvars 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 vimvars.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Defines a function `vimvars-obey-vim-modeline' which is suitable for
+;; a hook which checks for a VI-style in the current buffer and sets
+;; various Emacs buffer-local variables accordingly.
+
+;;; Code:
+
+(defgroup vimvars nil
+  "Support for VIM mode lines."
+  :group 'find-file)
+
+
+(defcustom vimvars-enabled t
+  "If nil, VIM mode lines will be ignored."
+  :type 'boolean
+  :group 'vimvars)
+(make-variable-buffer-local 'vimvars-enabled)
+
+
+(defcustom vimvars-check-lines 5
+  "The number of lines in the top or bottom of a file that we will search for VIM settings (VIM itself checks 5)."
+  :type 'integer
+  :group 'vimvars)
+
+
+(defcustom vimvars-ignore-mode-line-if-local-variables-exist t
+  "If non-nil, VIM mode lines are ignored in files that have Emacs local variables."
+  :type 'boolean
+  :group 'vimvars)
+
+
+;; It appears that real VIM accepts backslash-escaped characters (for
+;; example \\| inside makeprg).
+;;
+;; Also, VIM accepts vi: and vim: at start-of line (but not ex:)
+;;
+;; Google Code search can be helpful in assessing what options are widely used,
+;; for example see
+;; <http://codesearch.google.com/codesearch?q=(ex|vim%3F):\+(se\+|setlocal)>
+(defconst vimvars-modeline-re
+  "\\(^\\|[ 	]\\)\\(ex\\|vim?\\):[	 ]?\\(set\\|setlocal\\|se\\)? \\([^:]+\\):"
+  "Regex matching a VIM modeline.")
+
+
+(defun vimvars-should-obey-modeline ()
+  "Return non-nil if a VIM modeline should be obeyed in this file."
+  ;; Always return nil if vimvars-enabled is nil.
+  ;; Otherwise, if there are Emacs local variables for this file,
+  ;; return nil unless vimvars-ignore-mode-line-if-local-variables-exist
+  ;; is also nil.
+  (when vimvars-enabled
+    (if file-local-variables-alist
+        (not vimvars-ignore-mode-line-if-local-variables-exist)
+      t)))
+
+  
+(defun vimvars-accept-tag (leader tag)
+  "Return non-nil if LEADER followed by TAG should be accepted as a modeline."
+  (cond
+   ((equal "vim" tag) t)
+   ((equal "vi" tag) t)
+   ;; Accept "ex:" only when it is not at the beginning of a line.
+   ((equal "ex" tag) (not (equal 0 (length leader))))
+   (t nil)))
+
+
+(defun vimvars-obey-this-vim-modeline ()
+  "Obey the mode line in the current regex match string."
+  (message "found a modeline: %s" (match-string 0))
+  (let ((settings-end (match-end 4)))
+    ;; We ignore the local suffix, since for Emacs
+    ;; most settings will be buffer-local anyway.
+    ;;(message "found VIM settings %s" (match-string 4))
+    (goto-char (match-beginning 4))
+    ;; Look for something like this: vi: set sw=4 ts=4:
+    ;; We should look for it in a comment, but for now
+    ;; we won't worry about the syntax of the major mode.
+    (while (re-search-forward
+            " *\\([^= ]+\\)\\(=\\([^ :]+\\)\\)?" settings-end t)
+      (let ((variable (vimvars-expand-option-name (match-string 1))))
+        (if (match-string 3)
+            (vimvars-assign variable (match-string 3))
+          (vimvars-enable-feature variable)))))
+  t)
+
+
+(defun vimvars-obey-top-modeline ()
+  "Check for, and if found, obey a mode line at the top of the file.
+This function moves point."
+  (goto-char (point-min))
+  (if (and
+       (re-search-forward vimvars-modeline-re
+	            (line-end-position vimvars-check-lines) t)
+       (vimvars-accept-tag (match-string 1) (match-string 2)))
+      (vimvars-obey-this-vim-modeline)))
+
+  
+(defun vimvars-obey-bottom-modeline ()
+  "Check for, and if found, obey a mode line at the botom of the file.
+This function moves point."
+  (goto-char (point-max))
+  (if (and
+       (re-search-backward vimvars-modeline-re 
+	             (line-beginning-position 
+		(- 1 vimvars-check-lines)) t)
+       (vimvars-accept-tag (match-string 1) (match-string 2)))
+      (vimvars-obey-this-vim-modeline)))
+  
+
+(defun vimvars-obey-vim-modeline ()
+  "Check the top and bottom of a file for VIM-style settings, and obey them.
+Only the first and last `vimvars-check-lines' lines of the file
+are checked for VIM variables.   You can use this in `find-file-hook'."
+  (when (vimvars-should-obey-modeline)
+    (save-excursion
+      (or (vimvars-obey-top-modeline)
+          (vimvars-obey-bottom-modeline)))))
+
+
+
+(defun vimvars-set-indent (indent)
+  "Set the amount of indentation caused by tab to INDENT in a mode-aware way."
+  (when (equal major-mode 'c-mode) (setq c-basic-offset indent)))
+
+
+(defun vimvars-expand-option-name (option)
+  "Expand the abbreviated VIM :set variable OPTION to its full name."
+  (let ((expansion
+	 (assoc option
+		'(("ro" "readonly")
+		  ("sts" "softtabstop")
+		  ("sw" "shiftwidth")
+		  ("ts" "tabstop")
+		  ("tw" "textwidth")))))
+    (if expansion (cadr expansion) option)))
+   
+
+;;; Not supported:
+;;; comments/com (comment leader), because it's not language-specific in VIM.
+(defun vimvars-assign (var val)
+  "Emulate VIM's :set VAR=VAL."
+  (message "Setting VIM option %s to %s in %s" var val (buffer-name))
+  (cond
+   ((equal var "makeprg") (setq compile-command val))
+   ((equal var "shiftwidth") (vimvars-set-indent (string-to-number val)))
+   ((equal var "softtabstop") t) ; Ignore.
+   ((equal var "tabstop") (setq tab-width (string-to-number val)))
+   ((equal var "textwidth") (set-fill-column (string-to-number val)))
+   (t (message "Don't know how to emulate VIM variable %s" var))))
+
+
+;; FIXME: Also consider supporting ...
+;; fileencoding, encoding could be useful but likely too hairy
+;; fileformat
+;; tags
+;; textmode (but this is obsolete in VIM, replaced by fileformat)
+;; Not supported:
+;; bomb/nobomd (byte order mark control), because I don't expect it is
+;; comonly enough used to justify the maintenance burden.
+(defun vimvars-enable-feature (var)
+  "Emulate VIM's :set VAR for variables that are just boolean."
+  (message "Enabling VIM option %s in %s" var (buffer-name))
+  (cond
+   ((equal var "expandtab") (setq indent-tabs-mode nil))
+   ((equal var "ignorecase") (setq case-fold-search t))
+   ((equal var "readonly") (toggle-read-only 1))
+   ((equal var "wrap") (setq truncate-lines nil))
+   ((equal var "write") (toggle-read-only -1)) ; Similar, not the same.
+   
+   ((equal var "noexpandtab") (setq indent-tabs-mode t))
+   ((equal var "noignorecase") (setq case-fold-search nil))
+   ((equal var "noreadonly") (toggle-read-only -1))
+   ((equal var "nowrap") (setq truncate-lines t))
+   ((equal var "nowrite") (toggle-read-only 1)) ; Similar, not the same
+
+   (t (message "Don't know how to emulate VIM feature %s" var))))
+
+(provide 'vimvars)
+;;; vimvars.el ends here