From e7657d6ba54ad5714657cff9be86ec416d342a4c Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Sun, 28 Apr 2013 20:54:18 +0100 Subject: Migrate repository from mercurial without history --- emacs/elisp/ap-functions.el | 44 + emacs/elisp/el-get-setup.el | 66 ++ emacs/elisp/eldoc-context.el | 40 + emacs/elisp/eldoc-php.el | 48 + emacs/elisp/electric-return.el | 16 + emacs/elisp/package-install.el | 129 +++ emacs/elisp/php-electric.el | 218 ++++ emacs/elisp/shuffle-lines.el | 20 + emacs/elisp/web-vcs.el | 2342 ++++++++++++++++++++++++++++++++++++++++ emacs/elisp/xrdb-mode.el | 544 ++++++++++ 10 files changed, 3467 insertions(+) create mode 100644 emacs/elisp/ap-functions.el create mode 100644 emacs/elisp/el-get-setup.el create mode 100644 emacs/elisp/eldoc-context.el create mode 100644 emacs/elisp/eldoc-php.el create mode 100644 emacs/elisp/electric-return.el create mode 100644 emacs/elisp/package-install.el create mode 100644 emacs/elisp/php-electric.el create mode 100644 emacs/elisp/shuffle-lines.el create mode 100644 emacs/elisp/web-vcs.el create mode 100644 emacs/elisp/xrdb-mode.el (limited to 'emacs/elisp') diff --git a/emacs/elisp/ap-functions.el b/emacs/elisp/ap-functions.el new file mode 100644 index 00000000..d9278fa0 --- /dev/null +++ b/emacs/elisp/ap-functions.el @@ -0,0 +1,44 @@ +;;;###autoload +(defun ap/remove-extra-cr () + "Remove extraneous CR codes from a file" + (interactive) + (save-excursion + (goto-char (point-min)) + (while (search-forward " +" nil t) + (replace-match "")))) + +;;;###autoload +(defun copy-rectangle (start end) + "Copy the region-rectangle." + (interactive "r") + (setq killed-rectangle (extract-rectangle start end))) + +;;;###autoload +(defun eval-and-replace () + "Replace the preceding sexp with its value." + (interactive) + (backward-kill-sexp) + (condition-case nil + (prin1 (eval (read (current-kill 0))) + (current-buffer)) + (error (message "Invalid expression") + (insert (current-kill 0))))) + +;;;###autoload +(defun ap/byte-compile-get-dest (filename) + (let ((basename (file-name-nondirectory filename)) + (dirname (file-name-directory filename))) + (cond + ((string-equal basename "init.el") + (if (file-exists-p (concat user-emacs-directory "init.el")) + (concat user-emacs-directory "init.elc"))) + (t (let (byte-compile-dest-file-function) + (byte-compile-dest-file filename)))))) + +;;;###autoload +(defun shell-execute () + (interactive) + (let ((file-buffer (or (file-name-nondirectory (buffer-file-name)) "")) + (command (read-shell-command "Shell command: " nil nil nil))) + (shell-command (replace-regexp-in-string "%" file-buffer command)))) diff --git a/emacs/elisp/el-get-setup.el b/emacs/elisp/el-get-setup.el new file mode 100644 index 00000000..2d93e441 --- /dev/null +++ b/emacs/elisp/el-get-setup.el @@ -0,0 +1,66 @@ +(package-initialize) + +(setq + el-get-sources '( + (:name use-package + :type github + :pkgname "jwiegley/use-package") + (:name packed + :type github + :pkgname "tarsius/packed") + (:name auto-compile + :type github + :depends packed + :pkgname "tarsius/auto-compile") + (:name project-persist + :type github + :pkgname "rdallasgray/project-persist")) + + eg:basic-packages + '( + auto-compile + auto-indent-mode + autopair + ace-jump-mode + dired+ + diminish + expand-region + helm + help+ + help-fns+ + help-macro+ + help-mode+ + mic-paren + misc-cmds + multiple-cursors + projectile + project-persist + paredit + solarized-theme + smart-tab + smarttabs + smex + undo-tree + use-package + ) + + eg:windows-packages + '(ntcmd + w32-browser + ;; powershell + ;; powershell-mode + ) + + eg:cygwin-packages + '(windows-path)) + +(el-get-elpa-build-local-recipes) + +(el-get 'sync eg:basic-packages) + +(cond ((eq system-type 'cygwin) + (el-get 'sync eg:cygwin-packages) + (el-get 'sync eg:windows-packages)) + + ((eq system-type 'windows-nt) + (el-get 'sync eg:windows-packages))) diff --git a/emacs/elisp/eldoc-context.el b/emacs/elisp/eldoc-context.el new file mode 100644 index 00000000..df8797f2 --- /dev/null +++ b/emacs/elisp/eldoc-context.el @@ -0,0 +1,40 @@ +(provide 'eldoc-context) + +(defun rgr/toggle-context-help () + "Turn on or off the context help. +Note that if ON and you hide the help buffer then you need to +manually reshow it. A double toggle will make it reappear" + (interactive) + (with-current-buffer (help-buffer) + (unless (local-variable-p 'context-help) + (set (make-local-variable 'context-help) t)) + (if (setq context-help (not context-help)) + (progn + (if (not (get-buffer-window (help-buffer))) + (display-buffer (help-buffer))))) + (message "Context help %s" (if context-help "ON" "OFF")))) + +(defun rgr/context-help () + "Display function or variable at point in *Help* buffer if visible. +Default behaviour can be turned off by setting the buffer local +context-help to false" + (interactive) + (let ((rgr-symbol (symbol-at-point))) ; symbol-at-point http://www.emacswiki.org/cgi-bin/wiki/thingatpt%2B.el + (with-current-buffer (help-buffer) + (unless (local-variable-p 'context-help) + (set (make-local-variable 'context-help) t)) + (if (and context-help (get-buffer-window (help-buffer)) + rgr-symbol) + (if (fboundp rgr-symbol) + (describe-function rgr-symbol) + (if (boundp rgr-symbol) (describe-variable rgr-symbol))))))) + +(defadvice eldoc-print-current-symbol-info + (around eldoc-show-c-tag activate) + (cond + ((eq major-mode 'emacs-lisp-mode) (rgr/context-help) ad-do-it) + ((eq major-mode 'lisp-interaction-mode) (rgr/context-help) ad-do-it) + ((eq major-mode 'apropos-mode) (rgr/context-help) ad-do-it) + (t ad-do-it))) + +(global-set-key (kbd "C-c h") 'rgr/toggle-context-help) \ No newline at end of file diff --git a/emacs/elisp/eldoc-php.el b/emacs/elisp/eldoc-php.el new file mode 100644 index 00000000..8101fea0 --- /dev/null +++ b/emacs/elisp/eldoc-php.el @@ -0,0 +1,48 @@ +(require 'xml) +(provide 'eldoc-php) + +(setq my-php-function-doc-hash (make-hash-table :test 'equal)) + + +(defun my-php-fetch-function-doc (function) + (let ((doc (gethash function my-php-function-doc-hash 'nope))) + (when (eq doc 'nope) + (setq doc nil) + + (let ((buf (url-retrieve-synchronously (concat "http://uk3.php.net/manual-lookup.php?pattern=" function)))) + (with-current-buffer buf + (goto-char (point-min)) + (let (desc) + (when (re-search-forward "
\\(\\(.\\|\n\\)*?\\)
" nil t) + (setq desc + (replace-regexp-in-string + " +" " " + (replace-regexp-in-string + "\n" "" + (replace-regexp-in-string "<.*?>" "" (match-string-no-properties 1))))) + ;; Don't show the function description + ;; (when (re-search-forward "

\\(\\(.\\|\n\\)*?\\)

" nil t) + ;; (setq desc + ;; (concat desc "\n\n" + ;; (replace-regexp-in-string + ;; " +" " " + ;; (replace-regexp-in-string + ;; "\n" "" + ;; (replace-regexp-in-string "<.*?>" "" (match-string-no-properties 1))))))) + ) + + (if desc + (setq doc (xml-substitute-special desc))))) + + (kill-buffer buf)) + + (puthash function doc my-php-function-doc-hash)) + + doc)) + + +(defun my-php-eldoc-function () + (let ((symbol (thing-at-point 'symbol))) + (if (and symbol + (not (eq (elt symbol 0) ?$))) + (my-php-fetch-function-doc symbol)))) diff --git a/emacs/elisp/electric-return.el b/emacs/elisp/electric-return.el new file mode 100644 index 00000000..fcbebb3c --- /dev/null +++ b/emacs/elisp/electric-return.el @@ -0,0 +1,16 @@ +(defvar electrify-return-match + "[\]}\)\"]" + "If this regexp matches the text after the cursor, do an \"electric\" + return.") +(defun electrify-return-if-match (arg) + "If the text after the cursor matches `electrify-return-match' then + open and indent an empty line between the cursor and the text. Move the + cursor to the new line." + (interactive "P") + (let ((case-fold-search nil)) + (if (looking-at electrify-return-match) + (save-excursion (newline-and-indent))) + (newline arg) + (indent-according-to-mode))) +;; Using local-set-key in a mode-hook is a better idea. +;(global-set-key (kbd "RET") 'electrify-return-if-match) \ No newline at end of file diff --git a/emacs/elisp/package-install.el b/emacs/elisp/package-install.el new file mode 100644 index 00000000..f4ac9768 --- /dev/null +++ b/emacs/elisp/package-install.el @@ -0,0 +1,129 @@ +;;; package-install.el --- auto-installer for package.el + +;; Copyright (C) 2007, 2008 Tom Tromey + +;; This file is not (yet) part of GNU Emacs. +;; However, it is distributed under the same license. + +;; 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 2, 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. + +;; Code: + +;;; We don't want to define anything global here, so no defuns or +;;; defvars. + +;; Some values we need, copied from package.el, but with different +;; names. +(let ((my-archive-base "http://tromey.com/elpa/") + (my-user-dir (expand-file-name "~/.emacs.d/elpa"))) + + (require 'pp) + (let ((download + (lambda (url) + (if (fboundp 'url-retrieve-synchronously) + ;; Use URL to download. + (let ((buffer (url-retrieve-synchronously url))) + (save-excursion + (set-buffer buffer) + (goto-char (point-min)) + (re-search-forward "^$" nil 'move) + (forward-char) + (delete-region (point-min) (point)) + buffer)) + ;; Use wget to download. + (save-excursion + (with-current-buffer + (get-buffer-create + (generate-new-buffer-name " *Download*")) + (shell-command (concat "wget -q -O- " url) + (current-buffer)) + (goto-char (point-min)) + (current-buffer))))))) + + ;; Make the ELPA directory. + (make-directory my-user-dir t) + + ;; Download package.el and put it in the user dir. + (let ((pkg-buffer (funcall download (concat my-archive-base "package.el")))) + (save-excursion + (set-buffer pkg-buffer) + (setq buffer-file-name + (concat (file-name-as-directory my-user-dir) + "package.el")) + (save-buffer) + (kill-buffer pkg-buffer))) + + ;; Load package.el. + (load (expand-file-name "~/.emacs.d/elpa/package.el")) + + ;; Download URL package if we need it. + (unless (fboundp 'url-retrieve-synchronously) + ;; Note that we don't name the symbol "url-version", as that + ;; will cause us not to define the real url-version when + ;; url-vars is loaded, which in turn will cause errors later. + ;; Thanks to Tom Breton for this subtlety. + (let* ((the-version "1.15") + (pkg-buffer (funcall download (concat my-archive-base + "url-" the-version ".tar")))) + (save-excursion + (set-buffer pkg-buffer) + (package-unpack 'url the-version) + (kill-buffer pkg-buffer)))) + + ;; Arrange to load package.el at startup. + ;; Partly copied from custom-save-all. + + + (let ((filename (or user-init-file + (and (yes-or-no-p "You have no user-init-file, probably because Emacs was started with -q. Use ~/.emacs? ") + (convert-standard-filename "~/.emacs")))) + (magic (pp-to-string + '(when (load (expand-file-name "~/.emacs.d/elpa/package.el")) + (package-initialize))))) + (if (not filename) + (warn (concat "Cannot automatically activate package.el after reboot.\n" + "Please append the following code to your .emacs manually:\n" + "%s") magic) + (let ((old-buffer (find-buffer-visiting filename))) + (with-current-buffer (let ((find-file-visit-truename t)) + (or old-buffer (find-file-noselect filename))) + (unless (eq major-mode 'emacs-lisp-mode) + (emacs-lisp-mode)) + (let ((inhibit-read-only t)) + (save-excursion + (goto-char (point-max)) + (newline (if (bolp) 2 1)) + (insert ";;; This was installed by package-install.el.\n") + (insert ";;; This provides support for the package system and\n") + (insert ";;; interfacing with ELPA, the package archive.\n") + (insert ";;; Move this code earlier if you want to reference\n") + (insert ";;; packages in your .emacs.\n") + (insert magic))) + (let ((file-precious-flag t)) + (save-buffer)) + (unless old-buffer + (kill-buffer (current-buffer))))))) + + ;; Start the package manager. + (package-initialize) + + ;; Read package archive to give the user a nice initial + ;; experience. FIXME: this doesn't work, at least squid gave a + ;; weird error when I tried it :( + ;; (package-refresh-contents) + )) + +;;; package-install.el ends here diff --git a/emacs/elisp/php-electric.el b/emacs/elisp/php-electric.el new file mode 100644 index 00000000..599b2b1d --- /dev/null +++ b/emacs/elisp/php-electric.el @@ -0,0 +1,218 @@ +;; -*- Emacs-Lisp -*- +;; +;; php-electric.el --- electric submode for the php-mode +;; +;; Version: 1.0 +;; Release-Date: Sunday 04 March 2007 +;; +;; Copyright (C) 2007 +;; by Nikolay V. Nemshilov aka St. +;; +;; +;; License +;; +;; This program 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 2 +;; of the License, or (at your option) any later version. +;; +;; This program 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 this program; if not, write to the Free Software +;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +;; +;; +;; Features: +;; * autocompletion of the language contructions +;; such as if, for, foreach, etc blocks, +;; +;; * autocompletion of classes, interfaces and functions +;; definitions +;; +;; * autocompletion of the paired symbols, like [], (), "",'' +;; +;; +;; Usage: +;; Nothing magical, just place the file in a directory where +;; Emacs can find it, and write +;; +;; (require 'php-electric) +;; +;; in your configuration files ~/.emacs or wherever you keep it. +;; Then you can switch on/off the mode by the following command +;; +;; M-x php-electric-mode +;; +;; If you like to have it switched on automatically, you should +;; put the command in your php-mode hook or create new one, +;; like that +;; +;; (add-hook 'php-mode-hook '(lambda () (php-electric-mode))) +;; +;; That's it. +;; +;; +;; Changelog: +;; Sunday 04 March 2007 +;; The first version 1.0 has been came out. +;; + +(defgroup php-electric nil + "Minor php-electric mode" + :group 'php) + +;; list of keywords which expandible by the {} pair +(defconst php-electric-expandible-simple-re + "\\(try\\|else\\|do\\)") + +;; list of keywords which expandible with the (){} construction +(defconst php-electric-expandible-as-struct-re + "\\(while\\|for\\|foreach\\|if\\|elseif\\|catch\\)") + +;; list of keywords which expandible with the name(){} construction +(defconst php-electric-expandible-as-func-re + "\\(function\\)") + +;; list of keywords which expandible with the name{} construction +(defconst php-electric-expandible-as-class-re + "\\(class\\|interface\\)") + +;; list of the paired chars +(defvar php-electric-matching-delimeter-alist + '((?\[ . ?\]) + (?\( . ?\)) + (?\" . ?\") + (?\' . ?\'))) + +;; the minor-mode definition +(define-minor-mode php-electric-mode + "Minor electric-mode for the php-mode" + nil + "-El" + php-mode-map + (php-electric-keymap)) + +;; list of accessible keys +(defun php-electric-keymap() + (define-key php-mode-map " " 'php-electric-space) + (define-key php-mode-map "{" 'php-electric-curlies) + (define-key php-mode-map "(" 'php-electric-brackets) + (define-key php-mode-map "[" 'php-electric-matching-char) + (define-key php-mode-map "\"" 'php-electric-matching-char) + (define-key php-mode-map "\'" 'php-electric-matching-char)) + +;; handler for the spaces insertions +(defun php-electric-space(arg) + (interactive "P") + (self-insert-command (prefix-numeric-value arg)) + (if (php-electric-is-code-at-point-p) + (if (php-electric-line-is-simple-expandible) + ;; inserting just a pair of curleis + (progn + (insert "{")(php-electric-insert-new-line-and-statement-end)) + (if (php-electric-line-is-expandible-as-struct) + ;; inserting a structure definition + (progn + (if (not (char-equal ?\( (preceding-char))) + ;; cmd () { - style construction + (progn + (insert "(")(set-register 98 (point-marker))(insert ") {")) + + ;; cmd( ){ - style construction + (progn + (insert " ")(set-register 98 (point-marker))(insert " ){"))) + (php-electric-insert-new-line-and-statement-end) + (jump-to-register 98)(set-register 98 nil)) + (if (php-electric-line-is-expandible-as-func) + ;; inserting the function expanding + (save-excursion + (insert "(){")(php-electric-insert-new-line-and-statement-end)) + (if (php-electric-line-is-expandible-as-class) + ;; inserting the class expanding + (save-excursion + (insert "{")(php-electric-insert-new-line-and-statement-end)))))))) + +;; handler for the { chars +(defun php-electric-curlies(arg) + (interactive "P") + (self-insert-command (prefix-numeric-value arg)) + (if (php-electric-is-code-at-point-p) + (progn + (php-electric-insert-new-line-and-statement-end)))) + +;; handler for the ( chars +(defun php-electric-brackets(arg) + (interactive "P") + + (if (php-electric-is-code-at-point-p) + ;; checking if it's a statement + (if (php-electric-line-is-expandible-as-struct) + (progn (php-electric-space arg)) + (progn (php-electric-matching-char arg))) + (self-insert-command (prefix-numeric-value arg)))) + +;; handler for the paired chars, [], (), "", '' +(defun php-electric-matching-char(arg) + (interactive "P") + (self-insert-command (prefix-numeric-value arg)) + (if (php-electric-is-code-at-point-p) + (save-excursion + (insert (cdr (assoc last-command-char + php-electric-matching-delimeter-alist)))))) + +;; checks if the current pointer situated in a piece of code +(defun php-electric-is-code-at-point-p() + (and php-electric-mode + (let* ((properties (text-properties-at (point)))) + (and (null (memq 'font-lock-string-face properties)) + (null (memq 'font-lock-comment-face properties)))))) + +;; checks if the current line expandible with a simple {} construction +(defun php-electric-line-is-simple-expandible() + (let* ((php-electric-expandible-simple-real-re + (concat php-electric-expandible-simple-re "\\s-$"))) + (save-excursion + (backward-word 1) + (looking-at php-electric-expandible-simple-real-re)))) + +;; checks if the current line expandible with the (){} construction +(defun php-electric-line-is-expandible-as-struct() + (let* ((php-electric-expandible-as-struct-real-re + (concat php-electric-expandible-as-struct-re "[ ]*$")) + (php-electric-expandible-as-struct-with-bracket-re + (concat php-electric-expandible-as-struct-re "($"))) + (save-excursion + (backward-word 1) + (or (looking-at php-electric-expandible-as-struct-real-re) + (looking-at php-electric-expandible-as-struct-with-bracket-re))))) + +;; checks if the current line expandible with the name(){} construction +(defun php-electric-line-is-expandible-as-func() + (let* ((php-electric-expandible-as-func-real-re + (concat php-electric-expandible-as-func-re "\\s-$"))) + (save-excursion + (backward-word 1) + (looking-at php-electric-expandible-as-func-real-re)))) + +;; checks if the current line expandible with the name{} construction +(defun php-electric-line-is-expandible-as-class() + (let* ((php-electric-expandible-as-class-real-re + (concat php-electric-expandible-as-class-re "\\s-$"))) + (save-excursion + (backward-word 1) + (looking-at php-electric-expandible-as-class-real-re)))) + +;; "shortcut" to insert \n} construction +(defun php-electric-insert-new-line-and-statement-end() + (newline-and-indent)(set-register 99 (point-marker)) + (insert "\n}")(indent-according-to-mode) + (jump-to-register 99)(set-register 99 nil)) + + +(provide 'php-electric) + +;; end of the file \ No newline at end of file diff --git a/emacs/elisp/shuffle-lines.el b/emacs/elisp/shuffle-lines.el new file mode 100644 index 00000000..be0a98f9 --- /dev/null +++ b/emacs/elisp/shuffle-lines.el @@ -0,0 +1,20 @@ +;;;###autoload +(defun move-line-down () + (interactive) + (let ((col (current-column))) + (save-excursion + (forward-line) + (transpose-lines 1)) + (forward-line) + (move-to-column col))) + +;;;###autoload +(defun move-line-up () + (interactive) + (let ((col (current-column))) + (save-excursion + (forward-line) + (transpose-lines -1)) + (move-to-column col))) + +(provide 'shuffle-lines) diff --git a/emacs/elisp/web-vcs.el b/emacs/elisp/web-vcs.el new file mode 100644 index 00000000..1e2c3226 --- /dev/null +++ b/emacs/elisp/web-vcs.el @@ -0,0 +1,2342 @@ +;;; web-vcs.el --- Download file trees from VCS web pages +;; +;; Author: Lennart Borgman (lennart O borgman A gmail O com) +;; Created: 2009-11-26 Thu +(defconst web-vcs:version "0.62") ;; Version: +;; Last-Updated: 2011-03-12 Sat +;; URL: +;; Keywords: +;; Compatibility: +;; +;; Features that might be required by this library: +;; +;; `advice', `advice-preload', `backquote', `bytecomp', `cus-edit', +;; `cus-face', `cus-load', `cus-start', `help-fns', `ietf-drums', +;; `mail-parse', `mail-prsvr', `mm-bodies', `mm-decode', +;; `mm-encode', `mm-util', `rfc2045', `rfc2047', `rfc2231', +;; `timer', `web-autoload', `wid-edit'. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;;; Commentary: +;; +;; Update file trees within Emacs from VCS systems using information +;; on their web pages. +;; +;; Available download commands are currently: +;; +;; `web-vcs-nxhtml' +;; +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;;; Change log: +;; +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; This program 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. +;; +;; This program 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 this program; see the file COPYING. If not, write to +;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth +;; Floor, Boston, MA 02110-1301, USA. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;;; Code: + +(eval-when-compile (require 'cl)) +(eval-when-compile (require 'compile)) +(eval-and-compile (require 'cus-edit)) +(eval-and-compile (require 'mm-decode)) +(eval-when-compile (require 'url-http)) + +(require 'advice) +(require 'web-autoload nil t) +;; (require 'url-util) +;; (require 'url) +;;(require 'url-parse) + +(defvar web-vcs-comp-dir nil) + +(defgroup web-vcs nil + "Customization group for web-vcs." + :group 'nxhtml) + +(defcustom web-vcs-links-regexp + `( + (lp-1-10 ;; Id + ;; Comment: + "http://www.launchpad.com/ uses this 2009-11-29 with Loggerhead 1.10 (generic?)" + ;; Files URL regexp: + ;; + ;; Extend this format to catch date/time too. + ;; + ;; ((patt (rx ...)) + ;; ;; use subexp numbers + ;; (url 1) + ;; (time 2) + ;; (rev 3)) + + ((time 1) + (url 2) + (patt ,(rx-to-string '(and "" + (submatch (regexp "[^<]*")) + "" + (0+ space) + "" + (regexp ".+") + "" + (*? (regexp ".\\|\n")) + "href=\"" + (submatch (regexp ".*/download/[^\"]*")) + "\"")))) + ;; Dirs URL regexp: + ,(rx-to-string '(and "href=\"" + (group (regexp ".*%3A/[^\"]*/")) + "\"")) + ;; File name URL part regexp: + "\\([^\/]*\\)$" + ;; Page revision regexp: + ,(rx-to-string '(and "for revision" + (+ whitespace) + "" + (submatch (+ digit)) + "")) + ;; Release revision regexp: + ,(rx-to-string '(and "/" + (submatch (+ digit)) + "\"" (+ (not (any ">"))) ">" + (optional "Release ") + (+ digit) "." (+ digit) "<")) + ) + (lp ;; Id + ;; Comment: + "http://www.launchpad.com/ uses this 2010-06-26 with Loggerhead 1.17 (generic?)" + ;; Files URL regexp: + ;; + ;; Extend this format to catch date/time too. + ;; + ;; ((patt (rx ...)) + ;; ;; use subexp numbers + ;; (url 1) + ;; (time 2) + ;; (rev 3)) + ((time 1) + (url 2) + (patt ,(rx-to-string '(and "" + (submatch + (*\? + (not (any "<")))) + "" + (*? anything) + "") + ;;(and "href=\"" (group (regexp ".*%3A/[^\"]*/")) "\"") + ) + ;; File name URL part regexp: + "\\([^\/]*\\)$" + ;; Page revision regexp: + ,(rx-to-string '(and "for revision" + (+ whitespace) + "" + (submatch (+ digit)) + "")) + ;; Release revision regexp: + ,(rx-to-string '(and "/" + (submatch (+ digit)) + "\"" (+ (not (any ">"))) ">" + (optional "Release ") + (+ digit) "." (+ digit) "<")) + ) + ) + "Regexp patterns for matching links on a VCS web page. +The patterns are grouped by VCS web system type. + +\\ +To make a new pattern you can do like this: + +- Open the file in Firefox. +- View the buffer source in Emacs in some way. +- Turn on `mozadd-mirror-mode'. +- Use the command `mozadd-init-href-patten'. +- Then start `re-builder' to refine the pattern. + (Or, use isearch if you prefer that.) +- Use `ourcomments-copy-target-region-to-reb' for + easy copying from target buffer to re-builder. +- To see what you patterns matches in the web page + use `M-x mozadd-update-mozilla'. +- If the page looks terrible then add a + tag by doing `M-x mozadd-add-href-base'. + +*Note: It is always sub match 1 from these patterns that are + used." + :type '(repeat + (list + (symbol :tag "VCS web system type specifier") + (string :tag "Description") + (set (list (const time) integer) + (list (const url) integer) + (list (const patt) regexp)) + ;;(regexp :tag "Files URL regexp") + (regexp :tag "Dirs URL regexp") + (regexp :tag "File name URL part regexp") + (regexp :tag "Page revision regexp") + (regexp :tag "Release revision regexp") + )) + :group 'web-vcs) + +(defface web-vcs-mode-line + '((t (:foreground "black" :background "OrangeRed"))) + "Mode line face during download." + :group 'web-vcs) + +(defface web-vcs-mode-line-inactive + '((t (:foreground "black" :background "Orange"))) + "Mode line face during download." + :group 'web-vcs) + +(defface web-vcs-gold + '((t (:foreground "black" :background "gold"))) + "Face for web-vcs messages." + :group 'web-vcs) + +(defface web-vcs-red + '((t (:foreground "black" :background "#f86"))) + "Face for web-vcs messages." + :group 'web-vcs) + +(defface web-vcs-green + '((t (:foreground "black" :background "#8f6"))) + "Face for web-vcs messages." + :group 'web-vcs) + +(defface web-vcs-yellow + '((t (:foreground "black" :background "yellow"))) + "Face for web-vcs messages." + :group 'web-vcs) + +(defface web-vcs-pink + '((t (:foreground "black" :background "pink"))) + "Face for web-vcs messages." + :group 'web-vcs) + +(defcustom web-vcs-default-download-directory + "~/.emacs.d/" + "Default download directory." + :type '(choice (const :tag "~/.emacs.d/" "~/.emacs.d/") + (const :tag "Fist site-lisp in `load-path'" site-lisp-dir) + (const :tag "Directory where `site-run-file' lives" site-run-dir) + (string :tag "Specify directory")) + :group 'web-vcs) + +;;(web-vcs-default-download-directory) +;;;###autoload +(defun web-vcs-default-download-directory () + "Try to find a suitable place. +Use the choice in `web-vcs-default-download-directory'. +If this does not fit fall back to \"~/.emacs.d/\"." + (let* ((site-run-dir (when site-run-file + (let ((lib (locate-library site-run-file))) + (when lib + (file-name-directory lib))))) + (site-lisp-dir (catch 'first-site-lisp + (dolist (d load-path) + (let ((dir (file-name-nondirectory (directory-file-name d)))) + (when (string= dir "site-lisp") + (throw 'first-site-lisp (file-name-as-directory d))))))) + (dummy (message "site-run-dir=%S site-lisp-dir=%S" site-run-dir site-lisp-dir)) + (dir (or (case web-vcs-default-download-directory + ;;('~/.emacs.d/ "~/.emacs.d/") + ('site-lisp-dir site-lisp-dir) + ('site-run-dir site-run-dir)) + web-vcs-default-download-directory))) + (or dir "~/.emacs.d/"))) + + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Logging + +(defcustom web-vcs-log-file "~/.emacs.d/web-vcs-log.org" + "Log file for web-vcs." + :type 'file + :group 'web-vcs) + +;;;###autoload +(defun web-vcs-log-edit () + "Open log file." + (interactive) + (find-file web-vcs-log-file)) + +(defvar web-vcs-log-save-timer nil) + +(defun web-vcs-log-save-when-idle () + (when (timerp web-vcs-log-save-timer) (cancel-timer web-vcs-log-save-timer)) + (run-with-idle-timer 0 nil 'web-vcs-log-save)) + +(defun web-vcs-log-save () + (let ((log-buf (find-buffer-visiting web-vcs-log-file))) + (when (and log-buf (buffer-modified-p log-buf)) + (with-current-buffer log-buf + (basic-save-buffer))) + log-buf)) + +(defun web-vcs-log-close () + (let ((log-buf (web-vcs-log-save))) + (when log-buf + (kill-buffer log-buf)))) + +;; Fix-me: Add some package descriptor to log +(defun web-vcs-log (url dl-file msg) + (unless (file-exists-p web-vcs-log-file) + (let ((dir (file-name-directory web-vcs-log-file))) + (unless (file-directory-p dir) + (make-directory dir)))) + (with-current-buffer (find-file-noselect web-vcs-log-file) + (setq buffer-save-without-query t) + (web-vcs-log-save-when-idle) + (save-restriction + (widen) + (let ((today-entries (format-time-string "* %Y-%m-%d")) + (now (format-time-string "%H:%M:%S GMT" nil t))) + (goto-char (point-max)) + (unless (re-search-backward (concat "^" today-entries) nil t) + (goto-char (point-max)) + (insert "\n" today-entries "\n")) + (goto-char (point-max)) + (when url + (insert "** Downloading file " now "\n" + (format " file [[file:%s][%s]]\n from %s\n" dl-file dl-file url) + )) + (cond + ((stringp msg) + (goto-char (point-max)) + (insert msg "\n")) + (msg (basic-save-buffer))))))) + + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Finding and downloading files + +;;;###autoload +(defun web-vcs-get-files-from-root (web-vcs full-url dl-dir) + "Download a file tree from VCS system using the web interface. +Use WEB-VCS entry in variable `web-vcs-links-regexp' to download +files via http from FULL-URL to directory DL-DIR. + +Show FULL-URL first and offer to visit the page. That page will +give you information about version control system \(VCS) system +used etc." + (unless (web-vcs-contains-moved-files dl-dir) + (let ((resize-mini-windows (or resize-mini-windows t))) + (when (if (not (y-or-n-p (concat "Download files from \"" full-url "\".\n" + "You can see on that page which files will be downloaded.\n\n" + "Visit that page before downloading? "))) + t + (browse-url full-url) + (if (y-or-n-p "Start downloading? ") + t + (message "Aborted") + nil)) + (message "") + (web-vcs-get-files-on-page web-vcs full-url t (file-name-as-directory dl-dir) nil) + t)))) + +(defun web-vcs-get-files-on-page (web-vcs page-url recursive dl-dir test) + "Download files listed by WEB-VCS on web page PAGE-URL. +WEB-VCS is a specifier in `web-vcs-links-regexp'. + +If RECURSIVE go into sub folders on the web page and download +files from them too. + +Place the files under DL-DIR. + +Before downloading check if the downloaded revision already is +the same as the one on the web page. This is stored in the file +web-vcs-revision.txt. After downloading update this file. + +If TEST is non-nil then do not download, just list the files." + (unless (string= dl-dir (file-name-as-directory (expand-file-name dl-dir))) + (error "Download dir dl-dir=%S must be a full directory path" dl-dir)) + (catch 'command-level + (when (web-vcs-contains-moved-files dl-dir) + (throw 'command-level nil)) + (let ((vcs-rec (or (assq web-vcs web-vcs-links-regexp) + (error "Does not know web-cvs %S" web-vcs))) + (start-time (current-time))) + (unless (file-directory-p dl-dir) + (if (yes-or-no-p (format "Directory %S does not exist, create it? " + (file-name-as-directory + (expand-file-name dl-dir)))) + (make-directory dl-dir t) + (message "Can't download then") + (throw 'command-level nil))) + ;; (let ((old-win (selected-window))) + ;; (unless (eq (get-buffer "*Messages*") (window-buffer old-win)) + ;; (switch-to-buffer-other-window "*Messages*")) + ;; (goto-char (point-max)) + ;; (insert "\n") + ;; (insert (propertize (format "\n\nWeb-Vcs Download: %S\n" page-url) 'face 'web-vcs-gold)) + ;; (insert "\n") + ;; (redisplay t) + ;; (set-window-point (selected-window) (point-max)) + ;; (select-window old-win)) + (web-vcs-message-with-face 'web-vcs-gold "\n\nWeb-Vcs Download: %S\n" page-url) + (web-vcs-display-messages nil) + (let* ((rev-file (expand-file-name "web-vcs-revision.txt" dl-dir)) + (rev-buf (find-file-noselect rev-file)) + ;; Fix-me: Per web vcs speficier. + (old-rev-range (with-current-buffer rev-buf + (widen) + (goto-char (point-min)) + (when (re-search-forward (format "%s:\\(.*\\)\n" web-vcs) nil t) + ;;(buffer-substring-no-properties (point-min) (line-end-position)) + ;;(match-string 1) + (cons (match-beginning 1) (match-end 1)) + ))) + (old-revision (when old-rev-range + (with-current-buffer rev-buf + (buffer-substring-no-properties (car old-rev-range) + (cdr old-rev-range))))) + (dl-revision (web-vcs-get-revision-on-page vcs-rec page-url)) + ret + moved) + (when (and old-revision (string= old-revision dl-revision)) + (when (y-or-n-p (format "You already have revision %s. Quit? " dl-revision)) + (message "Aborted") + (kill-buffer rev-buf) + (throw 'command-level nil))) + ;; We do not have a revision number once we start download. + (with-current-buffer rev-buf + (when old-rev-range + (delete-region (car old-rev-range) (cdr old-rev-range)) + (basic-save-buffer))) + (setq ret (web-vcs-get-files-on-page-1 + vcs-rec page-url + dl-dir + "" + nil + (if recursive 0 nil) + dl-revision + test + 0)) + (setq moved (nth 1 ret)) + ;; Now we have a revision number again. + (with-current-buffer rev-buf + (when (= 0 (buffer-size)) + (insert "WEB VCS Revisions\n\n")) + (goto-char (point-max)) + (unless (eolp) (insert "\n")) + (insert (format "%s:%s\n" web-vcs dl-revision)) + (basic-save-buffer) + (kill-buffer)) + (message "-----------------") + (web-vcs-message-with-face 'web-vcs-gold "Web-Vcs Download Ready: %S" page-url) + (web-vcs-message-with-face 'web-vcs-gold " Time elapsed: %S" + (web-vcs-nice-elapsed start-time (current-time))) + (when (> moved 0) + (web-vcs-message-with-face 'web-vcs-yellow + " %i files updated (old versions renamed to *.moved)" + moved)))))) + +(defun web-vcs-get-files-on-page-1 (vcs-rec page-url dl-root dl-relative file-mask recursive dl-revision test num-files) + "Download files listed by VCS-REC on web page page-URL. +VCS-REC should be an entry like the entries in the list +`web-vcs-links-regexp'. + +If FILE-MASK is non-nil then it is used to match a file relative +path. Only matching files will be downloaded. FILE-MASK can +have two forms, a regular expression or a function. + +If FILE-MASK is a regular expression then each part of the path +may be a regular expresion \(not containing /). + +If FILE-MASK is a function then this function is called in each +directory under DL-ROOT. The function is called with the +directory as a parameter and should return a cons. The first +element of the cons should be a regular expression matching file +names in that directory that should be downloaded. The cdr +should be t if subdirectories should be visited. + +If RECURSIVE go into sub folders on the web page and download +files from them too. + +Place the files under DL-DIR. + +The revision on the page page-URL should match DL-REVISION if this is non-nil. + +If TEST is non-nil then do not download, just list the files" + ;;(web-vcs-message-with-face 'font-lock-comment-face "web-vcs-get-files-on-page-1 %S %S %S %S" page-url dl-root dl-relative file-mask) + (let* ((files-matcher (nth 2 vcs-rec)) + (dirs-href-regexp (nth 3 vcs-rec)) + (revision-regexp (nth 5 vcs-rec)) + ;; (setq x (url-generic-parse-url "http://somewhere.com/file/path.el")) + ;; (setq x (url-generic-parse-url "http://somewhere.com")) + ;; (setq x (url-generic-parse-url "/somewhere.com")) + ;; (url-type x) + ;; (url-host x) + ;; (url-filename x) + ;; (url-fullness x) + ;; (url-port x) + ;; (setq y (url-expand-file-name "/suburl/other.el" x)) + ;; (setq y (url-expand-file-name "http://other.com/suburl/other.el" x)) + ;;(page-urlobj (url-generic-parse-url page-url)) + ;;(page-url-fullness (or (url-fullness page-urlobj) (error "Incomplete URL: %S" page-url))) + ;;(page-url-host (url-host page-urlobj)) + ;;(page-url-type (url-type page-urlobj)) + ;;(page-url-file (url-filename page-urlobj)) + ;;(page-host-url (concat page-url-type "://" page-url-host)) + (dl-dir (file-name-as-directory (expand-file-name dl-relative dl-root))) + (lst-dl-relative (web-vcs-file-name-as-list dl-relative)) + (lst-file-mask (when (stringp file-mask) (web-vcs-file-name-as-list file-mask))) + ;;(url-buf (url-retrieve-synchronously page-url)) + this-page-revision + files + suburls + (moved 0) + (temp-file-base (expand-file-name "web-vcs-temp-list.tmp" dl-dir)) + temp-list-file + temp-list-buf + folder-res + http-sts) + ;; Fix-me: It looks like there is maybe a bug in url-copy-file so + ;; that it runs synchronously. Try to workaround the problem by + ;; making a new file temp file name. + (web-vcs-display-messages nil) + (unless (file-directory-p dl-dir) (make-directory dl-dir t)) + ;;(message "TRACE: dl-dir=%S" dl-dir) + (setq temp-list-file (make-temp-name temp-file-base)) + (setq temp-list-buf (web-vcs-ass-folder-cache page-url)) + (unless temp-list-buf + ;;(setq temp-list-buf (generate-new-buffer "web-wcs-folder")) + ;;(web-vcs-url-copy-file-and-check page-url temp-list-file nil) + (let ((ready nil)) + (while (not ready) + (setq folder-res (web-vcs-url-retrieve-synch page-url)) + ;; (with-current-buffer temp-list-buf + ;; (insert-file-contents temp-list-file)) + (if (memq (cdr folder-res) '(200 201)) + (setq ready t) + (web-vcs-message-with-face 'web-vcs-red "Could not get %S" page-url) + (web-vcs-display-messages t) + (when (y-or-n-p (format "Could not get %S, visit page to see what is wrong? " page-url)) + (browse-url page-url)) + (unless (y-or-n-p "Try again? (It is safe to break here and try again later.) ") + (throw 'command-level nil)))))) + ;;(with-current-buffer temp-list-buf + (with-current-buffer (car folder-res) + ;;(delete-file temp-list-file) + ;;(find-file-noselect temp-list-file) + (when dl-revision + (setq this-page-revision (web-vcs-get-revision-from-url-buf vcs-rec (current-buffer) page-url))) + (when dl-revision + (unless (string= dl-revision this-page-revision) + (web-vcs-message-with-face 'web-vcs-red "Revision on %S is %S, but should be %S" + page-url this-page-revision dl-revision) + (web-vcs-display-messages t) + (throw 'command-level nil))) + ;; Find files + (goto-char (point-min)) + (let ((files-href-regexp (nth 1 (assq 'patt files-matcher))) + (url-num (nth 1 (assq 'url files-matcher))) + (time-num (nth 1 (assq 'time files-matcher)))) + (while (re-search-forward files-href-regexp nil t) + ;; Fix-me: What happened to full url??? + (let* ((file (match-string url-num)) + (time (match-string time-num)) + (full-file (url-expand-file-name file page-url))) + (add-to-list 'files (list full-file time))))) + (when (< (length files) num-files) + (message "files-on-page-1: found %d files, expected %d" (length files) num-files)) + ;; Find subdirs + (when recursive + (goto-char (point-min)) + (while (re-search-forward dirs-href-regexp nil t) + (let* ((suburl (match-string 1)) + (lenurl (length page-url)) + (full-suburl (url-expand-file-name suburl page-url))) + ;;(message "suburl=%S" suburl) + (when (and (> (length full-suburl) lenurl) + (string= (substring full-suburl 0 lenurl) page-url)) + ;;(message "...added") + (add-to-list 'suburls full-suburl))))) + (kill-buffer)) + ;; Download files + ;;(message "TRACE: files=%S" files) + (web-vcs-download-files vcs-rec files dl-dir dl-root) + ;; Download subdirs + (when suburls + (dolist (suburl (reverse suburls)) + (let* ((dl-sub-dir (substring suburl (length page-url))) + (full-dl-sub-dir (file-name-as-directory + (expand-file-name dl-sub-dir dl-dir))) + (rel-dl-sub-dir (file-relative-name full-dl-sub-dir dl-root))) + ;;(message "web-vcs-get-revision-from-url-buf dir: %S %S" file-mask rel-dl-sub-dir) + (when (or (not file-mask) + (not (stringp file-mask)) + (= 1 (length (web-vcs-file-name-as-list file-mask))) + (web-vcs-match-folderwise file-mask rel-dl-sub-dir)) + ;;(message "matched dir %S" rel-dl-sub-dir) + (unless (web-vcs-contains-file dl-dir full-dl-sub-dir) + (error "Subdir %S not in %S" dl-sub-dir dl-dir)) + (let* ((ret (web-vcs-get-files-on-page-1 vcs-rec + suburl + dl-root + rel-dl-sub-dir + file-mask + (1+ recursive) + this-page-revision + test + 0))) + (setq moved (+ moved (nth 1 ret)))))))) + (list this-page-revision moved))) + +(defun web-vcs-get-missing-matching-files (web-vcs url dl-dir file-mask num-files) + "Download missing files from VCS system using the web interface. +Use WEB-VCS entry in variable `web-vcs-links-regexp' to download +files via http from URL to directory DL-DIR. + +FILE-MASK is used to match files that should be downloaded. See +`web-vcs-get-files-on-page-1' for more information. + +Before downloading offer to visit the page from which the +downloading will be made. +" + (unless file-mask (error "file-mask is nil")) + (let ((vcs-rec (or (assq web-vcs web-vcs-links-regexp) + (error "Does not know web-cvs %S" web-vcs)))) + (web-vcs-get-files-on-page-1 vcs-rec url dl-dir "" file-mask 0 nil nil num-files))) + + +;; (web-vcs-get-files-on-page 'lp "http://bazaar.launchpad.net/%7Enxhtml/nxhtml/main/files/head%3A/" t "c:/test/temp13/" t) + +(defvar web-vcs-folder-cache nil) ;; dyn var +(defun web-vcs-add-folder-cache (url buf) + (add-to-list 'web-vcs-folder-cache (list url buf))) +(defun web-vcs-ass-folder-cache (url) + (assoc url web-vcs-folder-cache)) +(defun web-vcs-clear-folder-cache () + (while web-vcs-folder-cache + (let ((ub (car web-vcs-folder-cache))) + (setq web-vcs-folder-cache (cdr web-vcs-folder-cache)) + (kill-buffer (nth 1 ub))))) + +(defun web-vcs-url-copy-file-and-check (file-url dl-file dest-file) + "Copy FILE-URL to DL-FILE. +Log what happened. Use DEST-FILE in the log, not DL-FILE which is +a temporary file." + (let ((http-sts nil) + (file-nonempty nil) + (fail-reason nil)) + (when dest-file (web-vcs-log file-url dest-file nil)) + (web-vcs-display-messages nil) + ;;(message "before url-copy-file %S" dl-file) + (setq http-sts (web-vcs-url-copy-file file-url dl-file nil t)) ;; don't overwrite, keep time + ;;(message "after url-copy-file %S" dl-file) + (if (and (file-exists-p dl-file) + (setq file-nonempty (< 0 (nth 7 (file-attributes dl-file)))) ;; file size 0 + (memq http-sts '(200 201))) + (when dest-file + (web-vcs-log nil nil " Done.\n")) + (setq fail-reason + (cond + (http-sts (format "HTTP %s" http-sts)) + (file-nonempty "File looks bad") + (t "Server did not respond"))) + (unless dest-file (web-vcs-log file-url dl-file "TEMP FILE")) + (web-vcs-log nil nil (format " *Failed:* %s\n" fail-reason)) + ;; Requires user attention and intervention + (web-vcs-message-with-face 'web-vcs-red "Download failed: %s, %S" fail-reason file-url) + (web-vcs-display-messages t) + (when (y-or-n-p (format "Vist page %S to see what is wrong? " file-url)) + (browse-url file-url)) + (message "\n") + (web-vcs-message-with-face 'web-vcs-yellow "Please retry what you did before!\n") + (throw 'command-level nil)))) + +(defvar web-autoload-temp-file-prefix "TEMPORARY-WEB-AUTO-LOAD-") +(defvar web-autoload-active-file-sub-url) ;; Dyn var, active during file download check +(defun web-autoload-acvtive () + (and (boundp 'web-autoload-active-file-sub-url) + web-autoload-active-file-sub-url)) + +(defun web-vcs-download-files (vcs-rec files dl-dir dl-root) + (dolist (file (reverse files)) + (let* ((url-file (nth 0 file)) + (url-file-time-str (nth 1 file)) + ;; date-to-time assumes GMT so this is ok: + (url-file-time (when url-file-time-str (date-to-time url-file-time-str))) + (url-file-name-regexp (nth 4 vcs-rec)) + (url-file-rel-name (progn + (when (string-match url-file-name-regexp url-file) + (match-string 1 url-file)))) + (dl-file-name (expand-file-name url-file-rel-name dl-dir)) + (dl-file-time (nth 5 (file-attributes dl-file-name))) + (file-rel-name (file-relative-name dl-file-name dl-root)) + (file-name (file-name-nondirectory dl-file-name)) + (temp-file (expand-file-name (concat web-autoload-temp-file-prefix file-name) dl-dir)) + temp-buf) + (cond + ;;((and file-mask (not (web-vcs-match-folderwise file-mask file-rel-name)))) + ((and dl-file-time + url-file-time + (progn + ;;(message "dl-file-time =%s" (when dl-file-time (current-time-string dl-file-time))) + ;;(message "url-file-time=%s" (when url-file-time (current-time-string url-file-time))) + ;;(message "url-file-tstr=%s" (when url-file-time url-file-time-str)) + t) + (time-less-p url-file-time + (time-add dl-file-time (seconds-to-time 1)))) + (web-vcs-message-with-face 'web-vcs-green "Local file %s is newer or same age" file-rel-name)) + ;;(test (progn (message "TEST url-file=%S" url-file) (message "TEST url-file-rel-name=%S" url-file-rel-name) (message "TEST dl-file-name=%S" dl-file-name) )) + (t + ;; Avoid trouble with temp file + (while (setq temp-buf (find-buffer-visiting temp-file)) + (set-buffer-modified-p nil) (kill-buffer temp-buf)) + (when (file-exists-p temp-file) (delete-file temp-file)) + ;;(web-vcs-message-with-face 'font-lock-comment-face "Starting url-copy-file %S %S t t" url-file temp-file) + (web-vcs-url-copy-file-and-check url-file temp-file dl-file-name) + ;;(web-vcs-message-with-face 'font-lock-comment-face "Finished url-copy-file %S %S t t" url-file temp-file) + (let* ((time-after-url-copy (current-time)) + (old-buf-open (find-buffer-visiting dl-file-name))) + (when (and old-buf-open (buffer-modified-p old-buf-open)) + (save-excursion + (switch-to-buffer old-buf-open) + (when (y-or-n-p (format "Buffer %S is modified, save to make a backup? " dl-file-name)) + (save-buffer)))) + (if (and dl-file-time (web-vcs-equal-files dl-file-name temp-file)) + (progn + (delete-file temp-file) + (when url-file-time (set-file-times dl-file-name url-file-time)) + (web-vcs-message-with-face 'web-vcs-green "File %S was ok" dl-file-name)) + (when dl-file-time + (let ((backup (concat dl-file-name ".moved"))) + (rename-file dl-file-name backup t))) + ;; Be paranoid and let user check here. I actually + ;; believe that is a very good thing here. + (web-vcs-be-paranoid temp-file dl-file-name file-rel-name) + (rename-file temp-file dl-file-name) + (when url-file-time (set-file-times dl-file-name url-file-time)) + ;; (let ((buf (find-buffer-visiting dl-file-name))) + ;; (when buf + ;; (with-current-buffer buf + ;; (message "before revert-buffer") + ;; (revert-buffer nil t t) + ;; (message "after revert-buffer") + ;; ))) + (if dl-file-time + (web-vcs-message-with-face 'web-vcs-yellow "Updated %S" dl-file-name) + (web-vcs-message-with-face 'web-vcs-green "Downloaded %S" dl-file-name)) + (when old-buf-open + (with-current-buffer old-buf-open + (set-buffer-modified-p nil) + (revert-buffer nil t t))) + (with-current-buffer (find-file-noselect dl-file-name) + (setq header-line-format + (propertize (format-time-string "This file was downloaded %Y-%m-%d %H:%M") + 'face 'web-vcs-green)))) + (web-vcs-display-messages nil) + ;; This is both for user and remote server load. Do not remove this. + (redisplay t) (sit-for (- 1.0 (float-time (time-subtract (current-time) time-after-url-copy)))) + ;; (unless old-buf-open + ;; (when old-buf + ;; (kill-buffer old-buf))) + ))) + (redisplay t)))) + +(defun web-vcs-get-revision-on-page (vcs-rec url) + "Get revision number using VCS-REC on page URL. +VCS-REC should be an entry like the entries in the list +`web-vcs-links-regexp'." + ;; url-insert-file-contents + (let ((url-buf (url-retrieve-synchronously url))) + (web-vcs-get-revision-from-url-buf vcs-rec url-buf url))) + +(defun web-vcs-get-revision-from-url-buf (vcs-rec url-buf rev-page-url) + "Get revision number using VCS-REC. +VCS-REC should be an entry in the list `web-vcs-links-regexp'. +The buffer URL-BUF should contain the content on page +REV-PAGE-URL." + (let ((revision-regexp (nth 5 vcs-rec))) + ;; Get revision number + (with-current-buffer url-buf + (goto-char (point-min)) + (if (not (re-search-forward revision-regexp nil t)) + (progn + (web-vcs-message-with-face 'web-vcs-red "Can't find revision number on %S" rev-page-url) + (web-vcs-display-messages t) + (when (y-or-n-p (format "Coult not find rev no on %S, visit page to see what is wrong? " rev-page-url)) + (browse-url rev-page-url)) + (throw 'command-level nil)) + (match-string 1))))) + + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Auto Download + + +;; fix-me: To emulation-mode-map +;; Fix-me: put this on better keys +(defvar web-vcs-paranoid-state-mode-map + (let ((map (make-sparse-keymap))) + (define-key map [(control ?c)(control ?c)] 'exit-recursive-edit) + (define-key map [(control ?c)(control ?n)] 'web-autoload-continue-no-stop) + (define-key map [(control ?c)(control ?r)] 'web-vcs-investigate-elisp-file) + (define-key map [(control ?c)(control ?q)] 'web-vcs-quit-auto-download) + map)) + +(defun web-vcs-quit-auto-download () + "Quit download process. +This stops the current web autoload processing." + (interactive) + ;; Fix-me. + (when (y-or-n-p "Stop web autoload processing? You can resume it later. ") + (web-vcs-message-with-face 'web-vcs-red + "Stopped autoloading in process. It will be resumed when necessary again.") + (web-vcs-log nil nil "User stopped autoloading") + (throw 'top-level 'web-autoload-stop))) + +(define-minor-mode web-vcs-paranoid-state-mode + "Mode used temporarily during user check of downloaded file. +Do not turn on this yourself." + :lighter (concat " " (propertize "Download file check" 'face 'font-lock-warning-face)) + :global t + :group 'web-vcs + (or (not web-vcs-paranoid-state-mode) + (web-autoload-acvtive) + (error "This mode can't be used when not downloading"))) + +(defcustom web-autoload-paranoid t + "Be paranoid and break to check each file after download." + :type 'boolean + :group 'web-vcs) + +(defun web-autoload-continue-no-stop () + "Continue web auto download. +This is used after inspecting downloaded elisp files. Set +`web-autoload-paranoid' to nil before contiuning to avoid further +breaks to check downloaded files." + (interactive) + (setq web-autoload-paranoid nil) + (web-autoload-continue)) + +(defun web-autoload-continue () + "Continue web auto download. +This is used after inspecting downloaded elisp files." + (interactive) + (if (< 0 (recursion-depth)) + (exit-recursive-edit) + (web-autoload-byte-compile-queue))) + +(defun web-vcs-be-paranoid (temp-file file-dl-name file-sub-url) + "Be paranoid and check FILE-DL-NAME." + (when (or (not (boundp 'web-autoload-paranoid)) + web-autoload-paranoid) + (save-window-excursion + (let* ((comp-buf (get-buffer "*Compilation*")) + (comp-win (and comp-buf + (get-buffer-window comp-buf))) + (msg-win (web-vcs-display-messages nil)) + temp-buf + (kf-desc (lambda (fun) + (let* ((key (where-is-internal fun nil t)) + (k-desc (when key (key-description key))) + (fmt-kf "\n %s (or %s)") + (fmt-f "\n %s")) + (if key + (format fmt-kf k-desc fun) + (format fmt-f fun) + ))))) + (if comp-win + (progn + (select-window comp-win) + (find-file file-dl-name)) + (select-window msg-win) + (find-file-other-window temp-file)) + (setq temp-buf (current-buffer)) + (web-vcs-log-save) + (message "-") + (message "") + (with-selected-window msg-win + (goto-char (point-max))) + (let ((proceed nil) + (web-autoload-active-file-sub-url file-sub-url) ;; Dyn var, active during file download check + (ws (with-current-buffer "*Messages*" (point-marker)))) + (web-vcs-paranoid-state-mode 1) + (web-vcs-message-with-face + 'secondary-selection + (concat "Please check the downloaded file and then continue by doing" + (funcall kf-desc 'exit-recursive-edit) + (if (fboundp 'web-autoload-continue-no-stop) + (concat + "\n\nOr, for no more breaks to check files do" + (funcall kf-desc 'web-autoload-continue-no-stop)) + "") + "\n\nTo stop the web autoloading process for now do" + (funcall kf-desc 'web-autoload-quit-download) + "\n\nTo see the log file you can do" + (funcall kf-desc 'web-vcs-log-edit) + "\n")) + (message "") + (let ((msg-win (car (get-buffer-window-list "*Messages*" nil nil)))) + (when msg-win (set-window-start msg-win ws))) + (while (not proceed) + (condition-case err + (when (eq 'web-autoload-stop + (catch 'top-level + ;; Fix-me: review file before rename! + (setq header-line-format + (propertize + (format "Review for downloading. Continue: C-c C-c%s. Destination: %S" + (if (string= "el" (file-name-extension file-dl-name)) + ", Check: C-c C-r" + "") + file-dl-name) + 'face 'web-vcs-red)) + (unwind-protect + (progn + (recursive-edit)) + (web-vcs-paranoid-state-mode -1)) + (with-current-buffer temp-buf + (set-buffer-modified-p nil) + (kill-buffer temp-buf)) + (setq proceed t))) + (throw 'top-level t)) + (error (message "%s" (error-message-string err)))))) + (web-vcs-display-messages t) + )))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Auto Download Compile Queue +;; +;; Downloaded elisp files are placed in a compile queue. They are not +;; compiled until all required elisp files are downloaded (and +;; optionally compiled). +;; +;; This mechanism works through +;; - reading (eval-when-compile ...) etc in the files +;; - a defadviced require that is the driver of the process + +(defvar web-autoload-compile-queue nil) + +(defvar web-autoload-byte-compile-queue-active nil) ;; Dyn var + +(defun web-autoload-byte-compile-file (file load comp-fun) + (if nil ;;(file-exists-p file) + (byte-compile-file file load) + (let ((added-entry (list file load comp-fun nil))) + (if (member added-entry web-autoload-compile-queue) + (setq added-entry nil) + (web-vcs-message-with-face 'web-vcs-gold "Add to compile queue (%S %s)" file load) + (setq web-autoload-compile-queue (cons added-entry + web-autoload-compile-queue))) + (when added-entry + (if web-autoload-byte-compile-queue-active + (throw 'web-autoload-comp-restart t) + (web-autoload-byte-compile-queue)))))) + +;;(web-autoload-byte-compile-queue) +(defun web-autoload-byte-compile-queue () + (let ((top-entry) + (web-autoload-byte-compile-queue-active t)) + (while (and web-autoload-compile-queue + (not (equal top-entry + (car web-autoload-compile-queue)))) + (setq top-entry (car web-autoload-compile-queue)) + (catch 'web-autoload-comp-restart + (web-autoload-byte-compile-first) + (setq web-autoload-compile-queue (cdr web-autoload-compile-queue)))))) + +(defun web-autoload-byte-compile-first () + "Compile first file on compile queue and maybe load it. +Compile the car of `web-autoload-compile-queue' and load if this +entry says so." + (let* ((compiled-it nil) + (first-entry (car web-autoload-compile-queue)) + (el-file (nth 0 first-entry)) + (load (nth 1 first-entry)) + (comp-fun (nth 2 first-entry)) + (req-done (nth 3 first-entry)) + (elc-file (byte-compile-dest-file el-file)) + (need-compile (or (not (file-exists-p elc-file)) + (file-newer-than-file-p el-file elc-file)))) + (if (not need-compile) + nil ;;(when load (load elc-file)) + (unless req-done + (web-autoload-do-eval-requires el-file) + (setcar (nthcdr 3 first-entry) t)) + (when (catch 'web-autoload-comp-restart + (condition-case err + (progn + (web-vcs-message-with-face 'font-lock-comment-face "Start byte compiling %S" el-file) + (web-vcs-message-with-face 'web-vcs-pink "Compiling QUEUE: %S" web-autoload-compile-queue) + (let ((web-autoload-skip-require-advice t)) (funcall comp-fun el-file load)) + (web-vcs-message-with-face 'font-lock-comment-face "Ready byte compiling %S" el-file) + ;; Return nil to tell there are no known problems + (if (file-exists-p elc-file) + nil + (web-vcs-message-with-face + 'web-vcs-red "Error: byte compiling did not produce %S" elc-file) + (web-vcs-display-messages nil) + ;; Clean up before restart + (web-autoload-try-cleanup-after-failed-compile first-entry) + t)) + (error + (web-vcs-message-with-face + 'web-vcs-red "Error in byte compiling %S: %s" el-file (error-message-string err)) + (web-vcs-display-messages nil) + ;; Clean up before restart + (web-autoload-try-cleanup-after-failed-compile first-entry) + t ;; error + ))) + (throw 'web-autoload-comp-restart t) + )))) + +(defun web-autoload-do-eval-requires (el-file) + "Do eval-when-compile and eval-and-compile." + ;;(message "web-autoload-do-eval-requires %S" el-file) + (let ((old-buf (find-buffer-visiting el-file))) + (with-current-buffer (or old-buf (find-file-noselect el-file)) + (let ((here (point)) + (web-autoload-require-skip-noerror-entries t)) + (save-restriction + (widen) + (goto-char (point-min)) + ;;(message "web-autoload-do-eval-requires cb=%s" (current-buffer)) + (while (progn + (while (progn (skip-chars-forward " \t\n\^l") + (looking-at ";")) + (forward-line 1)) + (not (eobp))) + (let ((form (read (current-buffer)))) + (when (memq (car form) '(eval-when-compile eval-and-compile)) + (web-vcs-message-with-face 'web-vcs-gold " eval %S" form) + (eval form)) + ))) + (if old-buf (kill-buffer) (goto-char here)))))) + + +;; Fix-me: protect against deep nesting +(defun web-autoload-do-require (feature filename noerror) + (let* ((feat-name (symbol-name feature)) + (lib (or filename feat-name))) + (if (load lib noerror t) + (progn + (unless (featurep feature) + (error "web-autoload: Required feature `%s' was not provided" feature)) + feature) + nil + ))) + +(defvar web-autoload-require-skip-noerror-entries nil) + +(defadvice require (around + web-autoload-ad-require) + (let ((feature (ad-get-arg 0)) + (filename (ad-get-arg 1)) + (noerror (ad-get-arg 2))) + (if (featurep feature) + feature + (if (or filename + (and noerror + (or (not (boundp 'web-autoload-skip-require-advice)) + web-autoload-skip-require-advice))) + (progn + (message "Doing nearly original require %s, because skipping" (ad-get-arg 0)) + ;; Can't ad-do-it because defadviced functions in load + ;;(web-autoload-do-require feature filename noerror) + ;; + ;; Fix-me: Implement lazy loading here? Could it be done with while-no-input? + ;; + ;;(when (assq feature web-autoload-require-list) ) + ad-do-it) + (unless (and noerror + web-autoload-require-skip-noerror-entries) + (let* ((auto-rec (assq feature web-autoload-require-list)) + (web-vcs (nth 1 auto-rec)) + (base-url (nth 2 auto-rec)) + (relative-url (nth 3 auto-rec)) + (base-dir (nth 4 auto-rec)) + (comp-fun (nth 5 auto-rec))) + (if (not auto-rec) + ad-do-it + (let* ((full-el (concat (expand-file-name relative-url base-dir) ".el")) + (full-elc (byte-compile-dest-file full-el)) + (our-buffer (current-buffer)) ;; Need to come back here + (our-wcfg (current-window-configuration)) + (mode-line-old (web-vcs-redefine-face 'mode-line 'web-vcs-mode-line)) + (mode-line-inactive-old (web-vcs-redefine-face 'mode-line-inactive 'web-vcs-mode-line-inactive)) + (header-line-format-old (with-current-buffer "*Messages*" + (prog1 + header-line-format + (setq header-line-format + (propertize "Downloading needed files..." + 'face 'web-vcs-mode-line + ;;'face '(:height 1.5) ;; does not work + )))))) + ;; Fix-me: can't update while accessing the menus + ;;(message "trying (redisplay t) ;; mode line") + ;;(sit-for 1) (redisplay t) ;; mode line + (unwind-protect + (progn + (web-vcs-message-with-face 'web-vcs-gold "Doing the really adviced require for %s" feature) + ;; Check if already downloaded first + (unless (file-exists-p full-el) + (setq base-url (eval base-url)) + ;; Download and try again + (setq relative-url (concat relative-url ".el")) + (web-vcs-message-with-face 'web-vcs-green "Need to download feature '%s" feature) + (catch 'web-autoload-comp-restart + (web-vcs-get-missing-matching-files web-vcs base-url base-dir relative-url))) + (set-buffer our-buffer) ;; Before we load.. + (when web-autoload-autocompile + (unless (file-exists-p full-elc) + ;; Byte compile the downloaded file + (web-autoload-byte-compile-file full-el t comp-fun))) + (web-vcs-message-with-face 'web-vcs-gold "Doing finally require for %s" feature) + (set-buffer our-buffer) ;; ... and after we load + (set-window-configuration our-wcfg)) + (with-current-buffer "*Messages*" (setq header-line-format header-line-format-old)) + (web-vcs-redefine-face 'mode-line mode-line-old) + (web-vcs-redefine-face 'mode-line-inactive mode-line-inactive-old))) + ad-do-it))))))) + +;; (setq x (web-vcs-redefine-face 'mode-line (setq z (face-all-attributes 'web-vcs-mode-line (selected-frame))))) +;; (setq x (web-vcs-redefine-face 'mode-line 'web-vcs-mode-line)) +;; (setq y (web-vcs-redefine-face 'mode-line x)) +;; (describe-face 'web-vcs-mode-line) +(defun web-vcs-redefine-face (face as-new) + "Redefine FACE to use the attributes in AS-NEW. +AS-NEW may be either a face or a list returned by `face-all-attributes'. +Return an alist with old attributes." + (let ((ret (face-all-attributes face (selected-frame))) + (new-face-att (if (facep as-new) + (face-all-attributes as-new (selected-frame)) + as-new)) + new-at-prop-list + ) + (dolist (at new-face-att) + (let ((sym (car at)) + (val (cdr at))) + (unless (eq val 'unspecified) + (setq new-at-prop-list (cons sym + (cons val + new-at-prop-list))) + ;;(message "new=%S" new-at-prop-list) + ))) + (apply 'set-face-attribute face (selected-frame) new-at-prop-list) + ret + )) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Web Autoload Define + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Helpers + +;;(web-vcs-file-name-as-list "/a/b/c.el") +;;(web-vcs-file-name-as-list "a/b/c.el") +;;(web-vcs-file-name-as-list "c:/a/b/c.el") +;;(web-vcs-file-name-as-list ".*/a/c/") +;;(web-vcs-file-name-as-list "[^/]*/a/c/") ;; Just avoid this. +;;(web-vcs-file-name-as-list "\\(?:\\.\\.?\\|README\\.txt\\(?:\\.moved\\)?\\|a\\(?:lts\\|utostart\\(?:\\.elc?\\|22\\.elc?\\)\\)\\|e\\(?:macs22\\.cmd\\|tc\\)\\|nxhtml\\(?:-\\(?:base\\.elc?\\|loaddefs\\.el\\(?:\\.moved\\|c\\)?\\|web-vcs\\.el\\(?:\\.moved\\|c\\)?\\)\\|maint\\.elc?\\)?\\|related\\|tests\\|util\\|web-\\(?:autoload\\.elc?\\|vcs\\.el\\(?:\\.moved\\|c\\)?\\)\\)") + +(defun web-vcs-file-name-as-list (filename) + "Split file name FILENAME into a list with file names." + ;; We can't use the primitives since they converts \ to / and + ;; therefore damages the reg exps. Just use our knowledge of the + ;; internal file name representation instead. + (split-string filename "/")) +;; (let ((lst-name nil) +;; (head filename) +;; (old-head "")) +;; (while (and (not (string= old-head head)) +;; (> (length head) 0)) +;; (let* ((file-head (directory-file-name head)) +;; (tail (file-name-nondirectory (directory-file-name head)))) +;; (setq old-head head) +;; (setq head (file-name-directory file-head)) +;; ;; For an abs path the final tail is "", use root instead: +;; (when (= 0 (length tail)) +;; (setq tail head)) +;; (setq lst-name (cons tail lst-name)))) +;; lst-name)) + +;;(web-vcs-match-folderwise ".*/util/mum.el" "top/util/mum.el") +;;(web-vcs-match-folderwise ".*/util/mu.el" "top/util/mum.el") +;;(web-vcs-match-folderwise ".*/ut/mum.el" "top/util/mum.el") +;;(web-vcs-match-folderwise ".*/ut../mum.el" "top/util/mum.el") +;;(web-vcs-match-folderwise ".*/ut../mum.el" "top/util") +;;(web-vcs-match-folderwise ".*/ut../mum.el" "top") +;;(web-vcs-match-folderwise "top/ut../mum.el" "top") +(defun web-vcs-match-folderwise (regex file) + "Split REGEXP as a file path and match against FILE parts." + ;;(message "folderwise %S %S" regex file) + (let ((lst-regex (web-vcs-file-name-as-list regex)) + (lst-file (web-vcs-file-name-as-list file))) + ;; Called from web-vcs-download-files for tree? + (when (= 1 (length lst-regex)) + (setq lst-file (last lst-file)) + (message "lst-file => %S" lst-file) + ) + (when (>= (length lst-regex) (length lst-file)) + (catch 'match + (while lst-file + (let ((head-file (car lst-file)) + (head-regex (car lst-regex))) + (unless (or (= 0 (length head-file)) ;; Last /, if present, gives "" + (string-match-p (concat "^" head-regex "$") head-file)) + (throw 'match nil))) + (setq lst-file (cdr lst-file)) + (setq lst-regex (cdr lst-regex))) + t)))) + +(defun web-vcs-contains-file (dir file) + "Return t if DIR contain FILE." + (assert (string= dir (file-name-as-directory (expand-file-name dir))) t) + (assert (or (string= file (file-name-as-directory (expand-file-name file))) + (string= file (expand-file-name file))) t) + (let ((dir-len (length dir))) + (assert (string= "/" (substring dir (1- dir-len)))) + (when (> (length file) dir-len) + (string= dir (substring file 0 dir-len))))) + +(defun web-vcs-nice-elapsed (start-time end-time) + "Format elapsed time between START-TIME and END-TIME nicely. +Those times should have the same format as time returned by +`current-time'." + (format-seconds "%h h %m m %z%s s" (float-time (time-subtract end-time start-time)))) + +;; (web-vcs-equal-files "web-vcs.el" "temp.tmp") +;; (web-vcs-equal-files "../.nosearch" "temp.tmp") +(defun web-vcs-equal-files (file-a file-b) + "Return t if files FILE-A and FILE-B are equal." + (let* ((cmd (if (eq system-type 'windows-nt) + (list "fc" nil nil nil + "/B" "/OFF" + (convert-standard-filename file-a) + (convert-standard-filename file-b)) + (list diff-command nil nil nil + "--binary" "-q" file-a file-b))) + (ret (apply 'call-process cmd))) + ;;(message "ret=%s, cmd=%S" ret cmd) (sit-for 2) + (cond + ((= 1 ret) + nil) + ((= 0 ret) + t) + (t + (error "%S returned %d" cmd ret))))) + +(defun web-vcs-display-messages (select) + "Display *Messages* buffer. Select its window if SELECT." + (let ((msg-win (display-buffer "*Messages*"))) + (with-selected-window msg-win (goto-char (point-max))) + (when select (select-window msg-win)) + msg-win)) + +;; (web-vcs-message-with-face 'secondary-selection "I am saying: %s and %s" "Hi" "Farwell!") +;;;###autoload +(defun web-vcs-message-with-face (face format-string &rest args) + "Display a colored message at the bottom of the string. +FACE is the face to use for the message. +FORMAT-STRING and ARGS are the same as for `message'. + +Also put FACE on the message in *Messages* buffer." + (with-current-buffer "*Messages*" + (save-restriction + (widen) + (let* ((start (let ((here (point))) + (goto-char (point-max)) + (prog1 + (copy-marker + (if (bolp) (point-max) + (1+ (point-max)))) + (goto-char here)))) + (msg-with-face (propertize (apply 'format format-string args) + 'face face))) + ;; This is for the echo area: + (message "%s" msg-with-face) + ;; This is for the buffer: + (when (< 0 (length msg-with-face)) + (goto-char (1- (point-max))) + ;;(backward-char) + ;;(unless (eolp) (goto-char (line-end-position))) + (put-text-property start (point) + 'face face)))))) + +(defun web-vcs-num-moved (root) + "Return nof files matching *.moved inside directory ROOT." + (let* ((file-regexp ".*\\.moved$") + (files (directory-files root t file-regexp)) + (subdirs (directory-files root t))) + (dolist (subdir subdirs) + (when (and (file-directory-p subdir) + (not (or (string= "/." (substring subdir -2)) + (string= "/.." (substring subdir -3))))) + (setq files (append files (web-vcs-rdir-get-files subdir file-regexp) nil)))) + (length files))) + +;; Copy of rdir-get-files in ourcomment-util.el +(defun web-vcs-rdir-get-files (root file-regexp) + (let ((files (directory-files root t file-regexp)) + (subdirs (directory-files root t))) + (dolist (subdir subdirs) + (when (and (file-directory-p subdir) + (not (or (string= "/." (substring subdir -2)) + (string= "/.." (substring subdir -3))))) + (setq files (append files (web-vcs-rdir-get-files subdir file-regexp) nil)))) + files)) + +(defun web-vcs-contains-moved-files (dl-dir) + "Return t if there are *.moved files in DL-DIR." + (let ((num-moved (web-vcs-num-moved dl-dir))) + (when (> num-moved 0) + (web-vcs-message-with-face 'font-lock-warning-face + (concat "There are %d *.moved files (probably from prev download)\n" + "in %S.\nPlease delete them first.") + num-moved dl-dir) + t))) + + +(defun web-vcs-set&save-option (symbol value) + (customize-set-variable symbol value) + (customize-set-value symbol value) + (when (condition-case nil (custom-file) (error nil)) + (customize-mark-to-save symbol) + (custom-save-all) + (message "web-vcs: Saved option %s with value %s" symbol value))) + +(defvar web-vcs-el-this (or load-file-name + (when (boundp 'bytecomp-filename) bytecomp-filename) + buffer-file-name)) + + +(require 'bytecomp) +(defun web-vcs-byte-compile-newer-file (el-file load) + (let ((elc-file (byte-compile-dest-file el-file))) + (when (or (not (file-exists-p elc-file)) + (file-newer-than-file-p el-file elc-file)) + (byte-compile-file el-file load)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Compiling + +;;;###autoload +(defun web-vcs-byte-compile-file (file &optional load extra-load-path comp-dir) + "Byte compile FILE in a new Emacs sub process. +EXTRA-LOAD-PATH is added to the front of `load-path' during +compilation. + +FILE is set to `buffer-file-name' when called interactively. +If LOAD" + (interactive (list (buffer-file-name) + t)) + (when (with-no-warnings (called-interactively-p)) + (unless (eq major-mode 'emacs-lisp-mode) + (error "Must be in emacs-lisp-mode"))) + (let* ((old-env-load-path (getenv "EMACSLOADPATH")) + (sub-env-load-path (or old-env-load-path + ;;(mapconcat 'identity load-path ";"))) + (mapconcat 'identity load-path path-separator))) + ;; Fix-me: name of compile log buffer. When should it be + ;; deleted? How do I bind it to byte-compile-file? Or do I? + (file-buf (find-buffer-visiting file)) + (old-out-buf (get-buffer "*Compile-Log*")) + (default-directory (or (when old-out-buf + (with-current-buffer old-out-buf + default-directory)) + comp-dir + (and (boundp 'nxhtml-install-dir) nxhtml-install-dir) + default-directory)) + (out-buf (or old-out-buf (get-buffer-create "*Compile-Log*"))) + (elc-file (byte-compile-dest-file file)) + (this-emacs-exe (locate-file invocation-name + (list invocation-directory) + exec-suffixes)) + (debug-on-error t) + start) + ;; (when (and file-buf + ;; (buffer-modified-p file-buf)) + ;; (switch-to-buffer file-buf) + ;; (error "Buffer must be saved first: %S" file-buf)) + (dolist (full-p extra-load-path) + ;;(setq sub-env-load-path (concat full-p ";" sub-env-load-path))) + (setq sub-env-load-path (concat full-p path-separator sub-env-load-path))) + (unless (get-buffer-window out-buf (selected-frame)) + (if (string= file (buffer-file-name)) + (display-buffer out-buf) + (unless (eq (current-buffer) out-buf) + (switch-to-buffer out-buf)))) + (with-selected-window (get-buffer-window out-buf) + (with-current-buffer out-buf + (unless (local-variable-p 'web-vcs-comp-dir) + (set (make-local-variable 'web-vcs-comp-dir) (or comp-dir default-directory))) + (setq default-directory web-vcs-comp-dir) + (widen) + (goto-char (point-max)) + (when (or (= 0 (buffer-size)) + (not (derived-mode-p 'compilation-mode))) + (insert (propertize "\nWeb VCS compilation output" 'font-lock-face 'font-lock-comment-face)) + (compilation-mode) + (setq font-lock-verbose nil) + (font-lock-add-keywords nil + '(("\\" . 'compilation-info)))) + (let ((inhibit-read-only t) + (rel-file (file-relative-name file))) + (insert "\n\n") + (insert "** Compile " rel-file "\n")) + (setq start (point)) + (when (file-exists-p elc-file) (delete-file elc-file)) + (if (or (not window-system) + (< emacs-major-version 23)) + (byte-compile-file file) + ;;(message "web-vcs-byte-compile-file:sub-env-load-path=%s" sub-env-load-path) + (unless (file-exists-p this-emacs-exe) + (error "Can't find this-emacs-exe=%s" this-emacs-exe)) + (unless (stringp sub-env-load-path) (error "I did it again, sub-env-load-path=%S" sub-env-load-path)) + (setenv "EMACSLOADPATH" sub-env-load-path) + ;; Fix-me: status + (let* ((inhibit-read-only t) + (ret (apply 'call-process this-emacs-exe nil out-buf t + "-Q" "--batch" + "--eval" "(setq debug-on-error t)" + "--eval" "(remove-hook 'find-file-hook 'vc-find-file-hook)" + "--file" file + "-f" "emacs-lisp-byte-compile" + nil))) + ;;(insert (format "call-process returned: %s\n" ret)) + ) + (setenv "EMACSLOADPATH" old-env-load-path)) + (goto-char start) + (while (re-search-forward "^\\([a-zA-Z0-9/\._-]+\\):[0-9]+:[0-9]+:" nil t) + (let ((rel-file (file-relative-name file)) + (inhibit-read-only t)) + (replace-match rel-file nil nil nil 1))) + (goto-char (point-max)))) + (when (file-exists-p elc-file) + (when (and load window-system) (load elc-file)) + t))) + + +;;;;;;;;;;;;;;;;;;;;;;;; +;;; Temporary helpers, possibly included in Emacs + +;; Fix-me: Doing (require 'url-http) in the functions below led to +;; that url-show-status is void. So I require it here instead. +;;(require 'url-http) + +;; (setq x (web-vcs-url-retrieve-synch "http://emacswiki.org/")) +;;;###autoload +(defun web-vcs-url-retrieve-synch (url) + "Retrieve URL, return cons with buffer and http status." + (require 'url-http) + (let* ((url-show-status nil) ;; just annoying showing status here + (buffer (url-retrieve-synchronously url)) + (handle nil) + (http-status nil)) + (if (not buffer) + (error "Retrieving url %s gave no buffer" url)) + (with-current-buffer buffer + (if (= 0 (buffer-size)) + (progn + (kill-buffer) + nil) + (setq http-status (url-http-parse-response)) + (if (memq http-status '(200 201)) + (progn + (goto-char (point-min)) + (unless (search-forward "\n\n" nil t) + (error "Could not find header end in buffer for %s" url)) + (delete-region (point-min) (point)) + (set-buffer-modified-p nil) + (goto-char (point-min))) + (kill-buffer buffer) + (setq buffer nil)))) + (cons buffer http-status))) + +;; Modified just to return http status +;;;###autoload +(defun web-vcs-url-copy-file (url newname &optional ok-if-already-exists + keep-time preserve-uid-gid) + "Copy URL to NEWNAME. Both args must be strings. +Signals a `file-already-exists' error if file NEWNAME already exists, +unless a third argument OK-IF-ALREADY-EXISTS is supplied and non-nil. +A number as third arg means request confirmation if NEWNAME already exists. +This is what happens in interactive use with M-x. +Fourth arg KEEP-TIME non-nil means give the new file the same +last-modified time as the old one. (This works on only some systems.) +Fifth arg PRESERVE-UID-GID is ignored. +A prefix arg makes KEEP-TIME non-nil." + (if (and (file-exists-p newname) + (not ok-if-already-exists)) + (error "Opening output file: File already exists, %s" newname)) + (require 'url-http) + (let ((buffer (url-retrieve-synchronously url)) + (handle nil) + (ret nil)) + (if (not buffer) + (error "Retrieving url %s gave no buffer" url)) + (with-current-buffer buffer + (if (= 0 (buffer-size)) + (progn + (kill-buffer) + nil) + (setq ret (url-http-parse-response)) + (setq handle (mm-dissect-buffer t)) + (mm-save-part-to-file handle newname) + (kill-buffer buffer) + (mm-destroy-parts handle))) + ret)) + +(defun web-vcs-read-and-accept-key (prompt accepted &optional reject-message help-function) + (let ((key nil) + rejected + (resize-mini-windows (or resize-mini-windows t))) + (while (not (member key accepted)) + (if (and help-function + (or (member key help-event-list) + (eq key ??))) + (funcall help-function) + (unless rejected + (setq rejected t) + (setq prompt (concat (or reject-message "Please answer with one of the alternatives.") + "\n\n" + prompt)) + (setq key (web-vcs-read-key prompt))))) + key)) + +(defconst web-vcs-read-key-empty-map (make-sparse-keymap)) + +(defvar web-vcs-read-key-delay 0.01) ;Fast enough for 100Hz repeat rate, hopefully. + +(defun web-vcs-read-key (&optional prompt) + "Read a key from the keyboard. +Contrary to `read-event' this will not return a raw event but instead will +obey the input decoding and translations usually done by `read-key-sequence'. +So escape sequences and keyboard encoding are taken into account. +When there's an ambiguity because the key looks like the prefix of +some sort of escape sequence, the ambiguity is resolved via `web-vcs-read-key-delay'." + (let ((overriding-terminal-local-map web-vcs-read-key-empty-map) + (overriding-local-map nil) + (old-global-map (current-global-map)) + (timer (run-with-idle-timer + ;; Wait long enough that Emacs has the time to receive and + ;; process all the raw events associated with the single-key. + ;; But don't wait too long, or the user may find the delay + ;; annoying (or keep hitting more keys which may then get + ;; lost or misinterpreted). + ;; This is only relevant for keys which Emacs perceives as + ;; "prefixes", such as C-x (because of the C-x 8 map in + ;; key-translate-table and the C-x @ map in function-key-map) + ;; or ESC (because of terminal escape sequences in + ;; input-decode-map). + web-vcs-read-key-delay t + (lambda () + (let ((keys (this-command-keys-vector))) + (unless (zerop (length keys)) + ;; `keys' is non-empty, so the user has hit at least + ;; one key; there's no point waiting any longer, even + ;; though read-key-sequence thinks we should wait + ;; for more input to decide how to interpret the + ;; current input. + (throw 'read-key keys))))))) + (unwind-protect + (progn + (use-global-map web-vcs-read-key-empty-map) + (message (concat (apply 'propertize prompt (member 'face minibuffer-prompt-properties)) + (propertize " " 'face 'cursor))) + (aref (catch 'read-key (read-key-sequence-vector nil nil t)) 0)) + (cancel-timer timer) + (use-global-map old-global-map)))) + +;; End temp helpers +;;;;;;;;;;;;;;;;;;;;;;;; + +;;(web-vcs-existing-files-matcher default-directory) +(defun web-vcs-existing-files-matcher (dir) + (let ((files-and-dirs (directory-files dir nil "[^#~]$")) + files + (default-directory dir)) + (dolist (df files-and-dirs) + (unless (file-directory-p df) + (setq files (cons df files)))) + (cons (regexp-opt files) t))) + +(defun web-vcs-update-existing-files (vcs base-url dl-dir this-dir) + (let ((files-and-dirs (directory-files this-dir nil "\\(?:\\.elc\\|\\.moved\\|[^#~]\\)$")) + files + dirs + (this-rel (file-relative-name this-dir dl-dir)) + file-mask) + (when (string= "./" this-rel) (setq this-rel "")) + (dolist (df files-and-dirs) + (if (and (file-directory-p df) + (not (member df '("." "..")))) + (setq dirs (cons df dirs)) + (setq files (cons df files)))) + ;;(web-vcs-message-with-face 'hi-blue "this-rel=%S %S %S" this-rel dl-dir this-dir) + (setq file-mask (concat this-rel (regexp-opt files))) + ;;(web-vcs-message-with-face 'hi-blue "r=%S" file-mask) + (web-vcs-get-missing-matching-files vcs base-url dl-dir file-mask (length files)) + (dolist (d dirs) + (web-vcs-update-existing-files vcs base-url dl-dir + (file-name-as-directory + (expand-file-name d this-dir)))))) + + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Some small bits for security and just overview. + +(defun web-vcs-fontify-as-ps-print() + (save-restriction + (widen) + (let ((start (point-min)) + (end (point-max))) + (cond ((and (boundp 'jit-lock-mode) (symbol-value 'jit-lock-mode)) + (jit-lock-fontify-now start end)) + ;; ((and (boundp 'lazy-lock-mode) (symbol-value 'lazy-lock-mode)) + ;; (lazy-lock-fontify-region start end)) + )))) + + +;;(web-vcs-get-fun-details 'describe-function) +;;(web-vcs-get-fun-details 'require) +;;(describe-function 'describe-function) +(defun web-vcs-get-fun-details (function) + (unless (symbolp function) (error "Not a symbol: %s" function)) + (unless (functionp function) (error "Not a function: %s" function)) + ;; Do as in `describe-function': + (let* ((advised (and (symbolp function) (featurep 'advice) + (ad-get-advice-info function))) + ;; If the function is advised, use the symbol that has the + ;; real definition, if that symbol is already set up. + (real-function + (or (and advised + (let ((origname (cdr (assq 'origname advised)))) + (and (fboundp origname) origname))) + function)) + ;; Get the real definition. + (def (if (symbolp real-function) + (symbol-function real-function) + function)) + errtype file-name (beg "") string) + ;; Just keep this as it is to more easily compare with `describe-function-1'. + (setq string + (cond ((or (stringp def) + (vectorp def)) + "a keyboard macro") + ((subrp def) + (if (eq 'unevalled (cdr (subr-arity def))) + (concat beg "special form") + (concat beg "built-in function"))) + ((byte-code-function-p def) + (concat beg "compiled Lisp function")) + ((symbolp def) + (while (and (fboundp def) + (symbolp (symbol-function def))) + (setq def (symbol-function def))) + ;; Handle (defalias 'foo 'bar), where bar is undefined. + (or (fboundp def) (setq errtype 'alias)) + (format "an alias for `%s'" def)) + ((eq (car-safe def) 'lambda) + (concat beg "Lisp function")) + ((eq (car-safe def) 'macro) + "a Lisp macro") + ((eq (car-safe def) 'autoload) + ;;(setq file-name-auto (nth 1 def)) + ;;(setq file-name-auto (find-lisp-object-file-name function def)) + ;;(setq file-auto-noext (file-name-sans-extension file-name-auto)) + (format "%s autoloaded %s" + (if (commandp def) "an interactive" "an") + (if (eq (nth 4 def) 'keymap) "keymap" + (if (nth 4 def) "Lisp macro" "Lisp function")))) + ((keymapp def) + (let ((is-full nil) + (elts (cdr-safe def))) + (while elts + (if (char-table-p (car-safe elts)) + (setq is-full t + elts nil)) + (setq elts (cdr-safe elts))) + (if is-full + "a full keymap" + "a sparse keymap"))) + (t ""))) + (setq file-name (find-lisp-object-file-name function def)) + (list errtype advised file-name string) + )) + +;; (setq next-error-function 'web-vcs-investigate-next-error) +;; fix-me: +;; (defvar web-vcs-investigate-header-str "Found these possible problems when reading the file:\n") +;; (defun web-vcs-investigate-next-error (argp reset) +;; (interactive "p") +;; ;; Search from within the investigate output buffer +;; (with-current-buffer +;; ;; Choose the buffer and make it current. +;; (if (next-error-buffer-p (current-buffer)) +;; (current-buffer) +;; (next-error-find-buffer nil nil +;; (lambda () +;; (let ((here (point))) +;; (save-restriction +;; (widen) +;; (goto-char (point-min)) +;; (string= (buffer-substring-no-properties +;; 0 (length web-vcs-investigate-header-str)) +;; web-vcs-investigate-header-str)))))) + +;; (goto-char (cond (reset (point-min)) +;; ((< argp 0) (line-beginning-position)) +;; ((> argp 0) (line-end-position)) +;; ((point)))) +;; (occur-find-match +;; (abs argp) +;; (if (> 0 argp) +;; #'previous-single-property-change +;; #'next-single-property-change) +;; "No more matches") +;; ;; In case the *Occur* buffer is visible in a nonselected window. +;; (let ((win (get-buffer-window (current-buffer) t))) +;; (if win (set-window-point win (point)))) +;; (occur-mode-goto-occurrence))) + +;;(web-vcs-investigate-read "c:/emacsw32/nxhtml/nxhtml/nxhtml-autoload.el" "*Messages*") +(defun web-vcs-investigate-read (elisp out-buf) + "Check forms in buffer by reading it." + (let* ((here (point)) + unsafe-eval re-fun re-var + elisp-el-file + (is-same-file (lambda (file) + (when file + (setq file (concat (file-name-sans-extension file) ".el")) + (string= (file-truename file) elisp-el-file))))) + (with-current-buffer elisp + (setq elisp-el-file (when (buffer-file-name) + (file-truename (buffer-file-name)))) + (save-restriction + (widen) + (web-vcs-fontify-as-ps-print) + (goto-char (point-min)) + (while (progn + (while (progn (skip-chars-forward " \t\n\^l") + (looking-at ";")) + (forward-line 1)) + (not (eobp))) + (let* ((pos (point)) + (form (read (current-buffer))) + (def (nth 0 form)) + (sym (and (listp form) + (symbolp (nth 1 form)) + (nth 1 form))) + (form-fun (and sym + (functionp sym) + (symbol-function sym))) + (form-var (boundp sym)) + (safe-forms '( declare-function + defun defmacro defsubst + define-minor-mode define-globalized-minor-mode + defvar defconst + defcustom + defface defgroup + ;; fix-me: check if these do re-fun too: + define-derived-mode + define-global-minor-mode + define-globalized-minor-mode + + make-local-variable make-variable-buffer-local + provide + require + message)) + (safe-eval (or (memq def safe-forms) + (and (memq def '( eval-when-compile eval-and-compile)) + (or (not (consp (nth 1 form))) + (memq (car (nth 1 form)) safe-forms))))) + ) + (cond + ((not safe-eval) + (setq unsafe-eval + (cons (list form (copy-marker pos) (buffer-substring pos (point))) + unsafe-eval))) + ((and form-fun + (memq def '( defun defmacro define-minor-mode define-globalized-minor-mode))) + (setq re-fun (cons (cons sym pos) re-fun))) + ((and form-var + (memq def '( defvar defconst defcustom)) + (or (not (eq sym 'defvar)) + (< 2 (length form)))) + (setq re-var (cons sym re-var))))))) + (goto-char here)) + (with-current-buffer out-buf + (save-restriction + (widen) + (goto-char (point-max)) + (unless (bobp) (insert "\n\n")) + (insert (propertize "Found these possible problems when reading the file:\n" + 'font-lock-face '(:height 1.5))) + (or unsafe-eval + re-fun + (insert "\n" + "Found no problems (but there may still be)" + "\n")) + + ;; Fix-me: Link + (when unsafe-eval + (insert "\n" + (propertize + (format "* Forms that are executed when loading the file (found %s):" + (length unsafe-eval)) + 'font-lock-face '(:background "yellow" :height 1.2)) + "\n\n") + (dolist (u (reverse unsafe-eval)) + (insert (format "Line %s\n" + (with-current-buffer elisp + (line-number-at-pos (marker-position (nth 1 u)))))) + ;; (insert-text-button (format "Go to form below, line %s" (marker-position (nth 1 u))) + ;; 'font-lock-face '(compilation-info underline) + ;; 'action + ;; `(lambda (button) + ;; (let* ((marker ,(nth 1 u)) + ;; (buf (marker-buffer marker))) + ;; (switch-to-buffer-other-window buf) + ;; (unless (and (< marker (point-max)) + ;; (> marker (point-min))) + ;; (widen)) + ;; (goto-char marker)))) + (insert (nth 2 u) "\n\n")) + (insert "\n")) + (when re-fun + (insert (propertize + (format "\n* The file perhaps redefines these functions that are defined now (%s):\n" + (length re-fun)) + 'font-lock-face '(:background "yellow" :height 1.2))) + (setq re-fun (sort re-fun (lambda (a b) (string< (symbol-name (car a)) (symbol-name (car b)))))) + (let ((row 0) + (re-fun-with-info (mapcar (lambda (fun) + (cons fun (web-vcs-get-fun-details (car fun)))) + re-fun)) + re-fun-other-files + (n-same 0) + (n-web-auto 0)) + ;; Check same file + (dolist (info re-fun-with-info) + (let* ((file-name (nth 3 info)) + (fun (car (nth 0 info))) + (web-auto (get fun 'web-autoload))) + (cond ((funcall is-same-file file-name) + (setq n-same (1+ n-same))) + (web-auto + (setq n-web-auto (1+ n-web-auto)) + (setq re-fun-other-files (cons info re-fun-other-files))) + (t + (setq re-fun-other-files (cons info re-fun-other-files)))))) + + (when (< 0 n-same) + (insert "\n " + (propertize (format "%s functions alreay defined by this file (which seems ok)" n-same) + 'font-lock-face 'web-vcs-green) + "\n")) + + (dolist (info re-fun-other-files) + (let* ((fun-rec (nth 0 info)) + (errtype (nth 1 info)) + (advised (nth 2 info)) + (file-name (nth 3 info)) + (string (nth 4 info)) + (fun (car fun-rec)) + (fun-pos (cdr fun-rec)) + (fun-web-auto (get fun 'web-autoload)) + ) + (when (= 0 (% row 5)) (insert "\n")) + (setq row (1+ row)) + (insert " `") + (insert-text-button (format "%s" fun) + 'action + `(lambda (button) + (describe-function ',fun))) + (insert "'") + (insert " (" string) + (when fun-web-auto + (insert " autoloaded from web, ") + (insert-text-button "info" + 'action + `(lambda (button) + ;; Fix-me: maybe a bit more informative ... ;-) + (message "%S" ',fun-web-auto)))) + (insert ")") + (when advised (insert ", " (propertize "adviced" 'font-lock-face 'font-lock-warning-face))) + (insert ", " + (cond + ((funcall is-same-file file-name) + (propertize "defined in this file" 'font-lock-face 'web-vcs-green) + ) + (fun-web-auto + (if (not (web-autoload-acvtive)) + (propertize "web download not active" 'font-lock-face 'web-vcs-yellow) + ;; See if file matches + (let ((active-sub-url web-autoload-active-file-sub-url) + (fun-sub-url (nth 2 fun-web-auto))) + (setq active-sub-url (file-name-sans-extension active-sub-url)) + (if (string-match-p fun-sub-url active-sub-url) + (propertize "web download, matches" 'font-lock-face 'web-vcs-yellow) + (propertize "web download, doesn't matches" 'font-lock-face 'web-vcs-red) + )))) + (t + (propertize "defined in other file" 'font-lock-face 'web-vcs-red)))) + (unless (funcall is-same-file file-name) + (insert " (") + (insert-text-button "go to new definition" + 'action + `(lambda (button) + (interactive) + (let ((m-pos ,(with-current-buffer elisp + (copy-marker fun-pos)))) + (switch-to-buffer-other-window (marker-buffer m-pos)) + (goto-char m-pos)))) + (insert ")")) + (insert "\n") + ))))) + (web-vcs-investigate-output-mode) + ))) + +(defvar web-vcs-investigate-current-file nil) +(make-variable-buffer-local 'web-vcs-investigate-current-file) +(put 'web-vcs-investigate-current-file 'permanent-local t) + +(defun web-vcs-investigate-current-file () + `(,web-vcs-investigate-current-file)) + +;; (defun web-vcs-investigate-fontification-fun (bound) +;; ;;(compilation-error-properties (file line end-line col end-col type fmt) +;; (while (re-search-forward "^Line \\([0-9]+\\)$" bound t) +;; (put-text-property (match-beginning 1) (match-end 1) +;; 'face 'highlight) +;; (let ((line (string-to-number (match-string-no-properties 1)))) +;; (compilation-error-properties 'web-vcs-investigate-current-file line line nil nil nil nil)) +;; ) +;; nil) + + +;; (defvar web-vcs-investigate-output-font-lock-keywords +;; ;; '(("^\\*\\*\\* \\(.+\\.el\\): \\([^ \n]+\\)" +;; ;; '(("^\\*\\*\\* \\(.+\\.el\\): \\([^ \n]+\\)" +;; ;; (1 font-lock-function-name-face) +;; ;; (2 font-lock-comment-face))) +;; ;; "Keywords used to highlight a checkdoc diagnostic buffer.") +;; nil) +;; ;;'(("^\\(Line\\) \\([0-9]+\\)$" 1 2))) +;; ;;'(web-vcs-investigate-fontification-fun)) + +(defvar web-vcs-investigate-output-error-regex-alist + '( + ("^Line \\([0-9]+\\)$" web-vcs-investigate-current-file 1 + ;; column type + nil 1) + ;; Fix-me: This is just a terrible hack making the hit into a + ;; compilation error point with # as the link and the rest as the + ;; action for that line. And it even does not work... - Only the + ;; first line becomes an error line. No idea why at the moment. + ("\\(#\\)Eval the file with all" web-vcs-investigate-current-file nil nil nil 1) + ("\\(#\\)Eval the file with just" web-vcs-investigate-current-file nil nil nil 1) + ("\\(#\\)Eval the file with no" web-vcs-investigate-current-file nil nil nil 1) + )) + +;; (defvar checkdoc-pending-errors nil +;; "Non-nil when there are errors that have not been displayed yet.") + +(define-compilation-mode web-vcs-investigate-output-mode "Investigate Elisp" + "Set up the major mode for the buffer containing the list of errors." + (set (make-local-variable 'compilation-error-regexp-alist) + web-vcs-investigate-output-error-regex-alist) + ;;(set (make-local-variable 'compilation-error-face) grep-hit-face) + ;; (set (make-local-variable 'compilation-mode-font-lock-keywords) + ;; web-vcs-investigate-output-font-lock-keywords) + ) + +;; I am quite tired of doing this over and over again. Why is this not +;; in Emacs? +(defvar web-vcs-button-mode-map + (let ((map (make-sparse-keymap))) + (define-key map [tab] 'forward-button) + (define-key map [(shift tab)] 'backward-button) + map)) +(define-minor-mode web-vcs-button-mode + "Just to bind `forward-button' etc" + :lighter nil) + +(defvar web-vcs-eval-output-start nil) +(make-variable-buffer-local 'web-vcs-eval-output-start) +(defvar web-vcs-eval-output-end nil) +(make-variable-buffer-local 'web-vcs-eval-output-end) + +;;(web-vcs-investigate-elisp-file) +;;;###autoload +(defun web-vcs-investigate-elisp-file (file-or-buffer) + (interactive (list + (if (derived-mode-p 'emacs-lisp-mode) + (current-buffer) + (read-file-name "Elisp file to check: ")))) + (let* ((elisp (if (bufferp file-or-buffer) + file-or-buffer + (find-file-noselect file-or-buffer))) + (elisp-file (with-current-buffer elisp (buffer-file-name))) + (out-buf-name "Web VCS Sec Inv") + (out-buf (let ((old-buf (get-buffer out-buf-name))) + (when old-buf (kill-buffer old-buf)) + (get-buffer-create out-buf-name)))) + (if (not (with-current-buffer elisp (derived-mode-p 'emacs-lisp-mode))) + (progn + (unless (eq (current-buffer) elisp) + (display-buffer elisp)) + (message "Buffer %s is not in emacs-lisp-mode" (buffer-name elisp))) + (switch-to-buffer-other-window out-buf) + (setq web-vcs-investigate-current-file elisp-file) + (let ((inhibit-read-only t)) + (erase-buffer) + (setq buffer-read-only t) + (web-vcs-button-mode 1) + (insert (propertize "A quick look for problems" 'font-lock-face '(:height 1.5))) + (let ((here (point))) + (insert + "\n" + (propertize + (concat "Note that this is just a quick look at the file." + " You have to investigate the file more carefully yourself" + " (or be sure someone else has done it for you)." + " The following are checked for here:" + "\n") + 'font-lock-face font-lock-comment-face)) + (fill-region here (point))) + (insert + (propertize + (concat + "- Top level forms that might be executed when loading the file.\n" + "- Redefinition of functions.\n") + 'font-lock-face font-lock-comment-face)) + + (insert "\n") + (if elisp-file + (progn + (insert "File ") + (insert-text-button elisp-file + 'action + `(lambda (button) + (interactive) + (find-file-other-window ,elisp-file)))) + (insert "Buffer ") + (insert-text-button (buffer-name elisp) + 'action + `(lambda (button) + (interactive) + (switch-to-buffer-other-window ,elisp)))) + + (web-vcs-investigate-read elisp out-buf) + (when elisp-file + (insert "\n\n\n") + (insert (propertize "* Investigate what the file loads and redefines\n" + 'font-lock-face '(:background "yellow" :height 1.2))) + (let ((here (point))) + (insert "\nIf you want to see what will actually be added to `load-history'" + " and which functions will be defined you can" + " load the file in a batch Emacs session" + " and show the result here." + " (`load-path' will be set to your current value for the loading.)" + "\n" + ) + (fill-region here (point)) + + (setq here (point)) + (insert "\nYour current Emacs will not be affected by the loading," + " but please be aware that this does not mean your computer can not be." + "\n" + ) + (fill-region here (point)) + + (insert (propertize "\n Note: Click the part after #.\n" 'font-lock-face 'italic)) + (when t ;init-file-user + (insert " ") + (insert-text-button "#Load the file with all your current init files" + 'action `(lambda (button) (interactive) + (web-vcs-investigate-eval ,elisp-file ,out-buf "--debug-init"))) + (insert "\n")) + (when t ;(and site-run-file (not init-file-user)) + (insert " ") + (insert-text-button "#Load the file with just your site init file (i.e. -q)" + 'action `(lambda (button) (interactive) + (web-vcs-investigate-eval ,elisp-file ,out-buf "-q"))) + (insert "\n")) + (when t ;(not site-run-file) + (insert " ") + (insert-text-button "#Load the file with no init file (i.e. -Q)" + 'action `(lambda (button) (interactive) + (web-vcs-investigate-eval ,elisp-file ,out-buf "-Q"))) + (insert "\n")) + + (setq web-vcs-eval-output-start (point)) + (setq web-vcs-eval-output-end (point-max)) + )) + (set-buffer-modified-p nil) + (goto-char (point-min)))))) + + +;;(web-vcs-investigate-eval "c:/emacsw32/nxhtml/nxhtml/nxhtml-autoload.el" "*Messages*") +;;(web-vcs-investigate-eval "c:/emacsw32/nxhtml/autostart.el" "*Messages*") +(defun web-vcs-investigate-eval (elisp-file out-buf init) + "Get compile loads when evaling buffer. +Eval the buffer in a fresh Emacs and return the resulting +load-history entries with comments about what is new etc." + (let* ((emacs-exe (locate-file invocation-name + (list invocation-directory) + exec-suffixes)) + ;; see custom-load-symbol + (get-lhe '(let ((lhe (or (assoc buffer-file-name load-history) + (assoc (concat (file-name-sans-extension buffer-file-name) ".elc") + load-history)))) + (prin1 "STARTHERE\n") + (prin1 lhe))) + (elisp-file-name (file-name-sans-extension (file-name-nondirectory elisp-file))) + (elisp-el-file (file-truename (concat (file-name-sans-extension elisp-file) ".el"))) + (temp-prefix web-autoload-temp-file-prefix) + (temp-prefix-len (length temp-prefix)) + (is-downloading (and (boundp 'web-autoload-paranoid) + web-autoload-paranoid)) + (is-temp-file (and is-downloading + (< (length temp-prefix) (length elisp-file-name)) + (string= temp-prefix + (substring elisp-file-name 0 temp-prefix-len)))) + (elisp-feature-name (if is-temp-file + (substring elisp-file-name temp-prefix-len) + elisp-file-name)) + (is-same-file (lambda (file) + (when file ;; self protecting + (setq file (concat (file-name-sans-extension file) ".el")) + (string= (file-truename file) elisp-el-file)))) + (active-sub-url (when (web-autoload-acvtive) + (file-name-sans-extension web-autoload-active-file-sub-url))) + whole-result + batch-error + result) + (with-current-buffer out-buf + (when web-vcs-eval-output-start + (let ((here (point)) + (inhibit-read-only t)) + (save-restriction + (widen) + ;;(goto-char web-vcs-eval-output-start) + (delete-region web-vcs-eval-output-start web-vcs-eval-output-end)) + (goto-char here)))) + ;; Fix-me: do not use temp buffer so we can check errors + (with-temp-buffer + (let ((old-loadpath (getenv "EMACSLOADPATH")) + ;;(new-loadpath (mapconcat 'identity load-path ";")) + (new-loadpath (mapconcat 'identity load-path path-separator)) + ret-val) + (setenv new-loadpath) + (message "Loading file in batch Emacs...") + (setq ret-val + (call-process emacs-exe nil + (current-buffer) + t "--batch" + ;; fix-me: "-Q" - should be run in the users current environment. + ;; init-file-user nil => -Q + ;; site-run-file nil => -q + + ;; (cond + ;; ((not init-file-user) "-Q") + ;; ((not site-run-file) "-q") + ;; (t "--debug-init")) ;; have to have something here... + init + + "-eval" (format "(setq load-path '%S)" load-path) + "-l" elisp-file + elisp-file + "-eval" (format "%S" get-lhe))) + (message "Loading file in batch Emacs... done, returned %S" ret-val) + (setenv old-loadpath)) + ;; Fix-me: how do you check the exit status on different platforms? + (setq whole-result (buffer-substring-no-properties (point-min) (point-max))) + (condition-case err + (progn + (goto-char (point-min)) + (search-forward "STARTHERE") + (search-forward "(") + (backward-char) + (setq result (read (current-buffer)))) + (error (message "") + ;; Process should probably have failed if we are here, + ;; but anyway... ;-) + (setq batch-error + (concat "Sorry, batch Emacs failed. It returned this message:\n\n" + whole-result + (if is-downloading + (concat + "\n--------\n" + "The error may depend on that not all needed files are yet downloaded.\n") + "\n"))) + ))) + (with-current-buffer out-buf + (let ((here (point)) + (inhibit-read-only t)) + (save-restriction + (widen) + ;;(goto-char (point-max)) + (goto-char web-vcs-eval-output-start) + (if batch-error + (progn + (insert "\n\n") + (insert (propertize batch-error 'font-lock-face 'web-vcs-red))) + (insert (propertize (format "\n\nLoading file (%s) added to `load-history':\n\n" init) + 'font-lock-face '(:height 1.5))) + (insert " (\"" (car result) "\"\n") + (dolist (e (cdr result)) + (insert (format " %S" e)) + (cond ((stringp e)) ;; Should not happen... + ;; Variables + ((symbolp e) + (insert " - ") + (insert (if (not (boundp e)) + (propertize "New" 'font-lock-face 'web-vcs-yellow) + (let ((e-file (symbol-file e))) + (if (funcall is-same-file e-file) + (propertize "Same file now" 'font-lock-face 'web-vcs-green) + (let* ((fun-web-auto (get e 'web-autoload)) + (fun-sub-url (nth 2 fun-web-auto))) + (if (and fun-sub-url + (string= fun-sub-url active-sub-url)) + (propertize "Web download, matches current download" + 'font-lock-face 'web-vcs-yellow) + (propertize (format "Loaded from %S now" e-file) + 'font-lock-face 'web-vcs-red)))))))) + ;; provide + ((eq (car e) 'provide) + (insert " - ") + (let* ((feat (car e)) + (feat-name (symbol-name feat))) + (insert (cond + ((not (featurep feat)) + (if (or (string= elisp-feature-name + (symbol-name (cdr e)))) + (propertize "Web download, matches file name" 'font-lock-face 'web-vcs-green) + (propertize "Does not match file name" 'font-lock-face 'web-vcs-red))) + (t + ;; symbol-file will be where it is loaded + ;; so check load-path instead. + (let ((file (locate-library feat-name))) + (if (funcall is-same-file file) + (propertize "Probably loaded from same file now" 'font-lock-face 'web-vcs-yellow) + (propertize (format "Probably loaded from %S now" file) + 'font-lock-face 'web-vcs-yellow)))))))) + ;; require + ((eq (car e) 'require) + (if (featurep (cdr e)) + (insert " - " (propertize "Loaded now" 'font-lock-face 'web-vcs-green)) + (insert " - " (propertize "Not loaded now" 'font-lock-face 'web-vcs-yellow)))) + ;; Functions + ((memq (car e) '( defun macro)) + (insert " - ") + (let ((fun (cdr e))) + (insert (if (functionp fun) + (let ((e-file (symbol-file e))) + (if (funcall is-same-file e-file) + (propertize "Same file now" 'font-lock-face 'web-vcs-green) + (let* ((fun-web-auto (get fun 'web-autoload)) + (fun-sub-url (nth 2 fun-web-auto))) + ;; Fix-me: check for temp download file. + (if (string= fun-sub-url active-sub-url) + (propertize "Web download, matches current download" + 'font-lock-face 'web-vcs-yellow) + (propertize (format "Loaded from %S now" e-file) + 'font-lock-face 'web-vcs-yellow))))) + ;; Note that web autoloaded functions are already defined. + (propertize "New" 'font-lock-face 'web-vcs-yellow)))))) + (insert "\n")) + (insert " )\n") + (setq web-vcs-eval-output-end (point-max)) + (goto-char here)))) + (set-buffer-modified-p nil)))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Specific for nXhtml + +;;(defvar nxhtml-web-vcs-base-url "http://bazaar.launchpad.net/%7Enxhtml/nxhtml/main/") +(defvar nxhtml-web-vcs-base-url "http://bazaar.launchpad.net/~nxhtml/nxhtml/main/") + +;; Fix-me: make gen for 'lp etc +(defun nxhtml-download-root-url (revision) + (let* ((base-url nxhtml-web-vcs-base-url) + (files-url (concat base-url "files/")) + (rev-part (if revision (number-to-string revision) + ;; "head%3A/" + "head:/" + ))) + (concat files-url rev-part))) + +(defun web-vcs-nxhtml () + "Install nXhtml. +Download and install nXhtml." + (interactive) + (catch 'command-level + (setq debug-on-error t) + (let* ((this-dir (file-name-directory web-vcs-el-this)) + (root-url (nxhtml-download-root-url nil)) + ;;(files '("nxhtml-web-vcs.el" "nxhtml-base.el")) + (files '("nxhtml-web-vcs.el")) + (files2 (mapcar (lambda (file) + (cons file (expand-file-name file this-dir))) + files)) + need-dl) + (dolist (file files2) + (unless (file-exists-p (cdr file)) + (setq need-dl t))) + (when need-dl + (let ((prompt + (concat "Welcome to install nXhtml." + "\nFirst the nXhtml specific web install file must be downloaded." + "\nYou will get a chance to review it before it is used." + "\n\nDo you want to continue? ")) + (resize-mini-windows (or resize-mini-windows t))) + (unless (y-or-n-p prompt) + (message "Aborted") + (throw 'command-level nil)))) + (message nil) + (unless (get-buffer-window "*Messages*") + (web-vcs-display-messages t) + (delete-other-windows)) + (dolist (file files2) + (unless (file-exists-p (cdr file)) + (web-vcs-get-missing-matching-files 'lp root-url this-dir (car file) 0))) + (load (cdr (car files2)))) + (call-interactively 'nxhtml-setup-install))) + + +(provide 'web-vcs) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; web-vcs.el ends here diff --git a/emacs/elisp/xrdb-mode.el b/emacs/elisp/xrdb-mode.el new file mode 100644 index 00000000..712f0cbf --- /dev/null +++ b/emacs/elisp/xrdb-mode.el @@ -0,0 +1,544 @@ +;;; xrdb-mode.el --- mode for editing X resource database files + +;; Copyright (C) 1998,1999,2000 Free Software Foundation, Inc. + +;; Author: 1994-2003 Barry A. Warsaw +;; Maintainer: barry@python.org +;; Created: May 1994 +;; Keywords: data languages + +(defconst xrdb-version "2.31" + "`xrdb-mode' version number.") + +;; This program 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 2 +;; of the License, or (at your option) any later version. +;; +;; This program 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 this program; if not, write to the Free Software +;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +;;; Commentary: +;; +;; This file provides a major mode for editing X resource database +;; files. It includes font-lock definitions and commands for +;; controlling indentation, re-indenting by subdivisions, and loading +;; and merging into the the resource database. +;; +;; To use, put the following in your .emacs: +;; +;; (autoload 'xrdb-mode "xrdb-mode" "Mode for editing X resource files" t) +;; +;; You may also want something like: +;; +;; (setq auto-mode-alist +;; (append '(("\\.Xdefaults$" . xrdb-mode) +;; ("\\.Xenvironment$" . xrdb-mode) +;; ("\\.Xresources$" . xrdb-mode) +;; ("*.\\.ad$" . xrdb-mode) +;; ) +;; auto-mode-alist)) + +;;; Credits: +;; +;; The database merge feature was inspired by Joel N. Weber II. +;; +;; The canonical Web site for xrdb-mode is +;; + +;;; Code: +(require 'custom) + + + +(defgroup xrdb nil + "Support for editing X resource database files" + :group 'languages) + +(defcustom xrdb-mode-hook nil + "*Hook to be run when `xrdb-mode' is entered." + :type 'hook + :group 'xrdb) + +(defcustom xrdb-subdivide-by 'paragraph + "*Default alignment subdivision when re-indenting a region or buffer. +This variable controls how much of the buffer is searched to find a +goal column on which to align. Every non-comment line in the region +defined by this variable is scanned for the first `:' character on the +line, and this character's column is the line's goal column. The +rightmost line goal column in the region is taken as the region's goal +column. + +This variable can take one of the following symbol values: + + `buffer' - All lines in the buffer are scanned. This is the + slowest option. + + `paragraph' - All lines in the paragraph are scanned. Paragraphs + are delimited by blank lines, comment lines, and page + delimiters. + + `page' - All lines in the page are scanned. Pages are delimited + with `page-delimiter', usually ^L (control-L). + + `line' - Only the previous non-comment line is scanned. This is + the fastest method. + +This variable is used by the various indentation commands, and can be +overridden in those commands by using \\[universal-argument]." + :type '(radio (const :tag "Do not subdivide buffer" buffer) + (const :tag "Subdivide by paragraphs" paragraph) + (const :tag "Subdivide by pages" page) + (const :tag "Each line is independent" line)) + :group 'xrdb) + +(defcustom xrdb-compress-whitespace nil + "*Collapse all whitespace to a single space after insertion of `:'." + :type 'boolean + :group 'xrdb) + +(defcustom xrdb-program "xrdb" + "*Program to run to load or merge resources in the X resource database." + :type 'string + :group 'xrdb) + +(defcustom xrdb-program-args '("-merge") + "*List of string arguments to pass to `xrdb-program'." + :type '(repeat string) + :group 'xrdb) + +(defvar xrdb-master-file nil + "If non-nil, merge in the named file instead of the buffer's file. +The intent is to allow you to set this variable in the file's local +variable section, e.g.: + + ! Local Variables: + ! xrdb-master-file: \"Xdefaults\" + ! End: + +so that typing \\[xrdb-database-merge-buffer-or-region] in that buffer +merges the named master file instead of the buffer's file. Note that +if the file name has a relative path, the `default-directory' for the +buffer is prepended to come up with a file name. + +You may also want to set `xrdb-program-args' in the local variables +section as well.") +(make-variable-buffer-local 'xrdb-master-file) + + +;; Non-user customizable +(defconst xrdb-comment-re "^[ \t]*[!]" + "Regular expression describing the beginning of a comment line.") + + + +;; utilities +(defun xrdb-point (position) + ;; Returns the value of point at certain commonly referenced POSITIONs. + ;; POSITION can be one of the following symbols: + ;; + ;; bol -- beginning of line + ;; eol -- end of line + ;; bod -- beginning of defun + ;; boi -- back to indentation + ;; ionl -- indentation of next line + ;; iopl -- indentation of previous line + ;; bonl -- beginning of next line + ;; bopl -- beginning of previous line + ;; bop -- beginning of paragraph + ;; eop -- end of paragraph + ;; bopg -- beginning of page + ;; eopg -- end of page + ;; + ;; This function does not modify point or mark. + (let ((here (point))) + (cond + ((eq position 'bod) (beginning-of-defun)) + ((eq position 'bol) (beginning-of-line)) + ((eq position 'eol) (end-of-line)) + ((eq position 'boi) (back-to-indentation)) + ((eq position 'bonl) (forward-line 1)) + ((eq position 'bopl) (forward-line -1)) + ((eq position 'bop) (forward-paragraph -1)) + ((eq position 'eop) (forward-paragraph 1)) + ((eq position 'bopg) (forward-page -1)) + ((eq position 'eopg) (forward-page 1)) + (t + (error "unknown buffer position requested: %s" position))) + (prog1 + (point) + (goto-char here)) + )) + +(defmacro xrdb-safe (&rest body) + ;; safely execute BODY, return nil if an error occurred + `( (condition-case nil + (progn (,@ body)) + (error nil)))) + +(defsubst xrdb-skip-to-separator () + ;; skip forward from the beginning of the line to the separator + ;; character as given by xrdb-separator-char. Returns t if the + ;; char was found, otherwise, nil. + (beginning-of-line) + (skip-chars-forward "^:" (xrdb-point 'eol)) + (and (eq (char-after) ?:) + (current-column))) + +(defsubst xrdb-in-comment-p (&optional lim) + (let* ((lim (or lim (xrdb-point 'bod))) + (state (parse-partial-sexp lim (point)))) + (nth 4 state))) + +(defsubst xrdb-boi-col () + (let ((here (point))) + (goto-char (xrdb-point 'boi)) + (prog1 + (current-column) + (goto-char here)))) + +(defvar xrdb-prompt-history nil) + +(defun xrdb-prompt-for-subdivision () + (let ((options '(("buffer" . buffer) + ("paragraphs" . paragraph) + ("pages" . page) + ("lines" . line))) + (completion-ignore-case t)) + (cdr (assoc + (completing-read "Subdivide alignment by? " options nil t + (cons (format "%s" xrdb-subdivide-by) 0) + 'xrdb-prompt-history) + options)))) + + +;; commands +(defun xrdb-electric-separator (arg) + "Insert a colon, and possibly indent line. +Numeric argument inserts that many separators. If the numeric +argument is not given, or is 1, and the separator is not inserted in a +comment, then the line is indented according to `xrdb-subdivide-by'." + (interactive "P") + (self-insert-command (prefix-numeric-value arg)) + ;; only do electric behavior if arg is not given + (or arg + (xrdb-in-comment-p) + (xrdb-indent-line)) + ;; compress whitespace + (and xrdb-compress-whitespace + (just-one-space))) + +(defun xrdb-electric-bang (arg) + "Insert an exclamation point to start a comment. +Numeric argument inserts that many exclamation characters. If the +numeric argument is not given, or is 1, and the bang character is the +first character on a line, the line is indented to column zero." + (interactive "P") + (let ((how-many (prefix-numeric-value arg))) + (self-insert-command how-many) + (save-excursion + (if (and (= how-many 1) + (xrdb-in-comment-p) + (memq (char-before (xrdb-point 'boi)) '(?\n nil))) + (indent-line-to 0))) + )) + + +(defun xrdb-indent-line (&optional arg) + "Align the current line according to `xrdb-subdivide-by'. +With optional \\[universal-argument], prompt for subdivision." + (interactive "P") + (xrdb-align-to-column + (xrdb-guess-goal-column (if arg + (xrdb-prompt-for-subdivision) + xrdb-subdivide-by)) + (xrdb-point 'bol) + (xrdb-point 'bonl))) + +(defun xrdb-indent-region (start end &optional arg) + "Indent all lines in the region according to `xrdb-subdivide-by'. +With optional \\[universal-argument], prompt for subdivision." + (interactive "r\nP") + (xrdb-align-to-column + (xrdb-guess-goal-column (if arg + (xrdb-prompt-for-subdivision) + xrdb-subdivide-by)) + start end)) + +(defun xrdb-indent-page (&optional arg) + "Indent all lines in the page according to `xrdb-subdivide-by'. +With optional \\[universal-argument], prompt for subdivision." + (interactive "P") + (xrdb-align-to-column + (xrdb-guess-goal-column (if arg + (xrdb-prompt-for-subdivision) + xrdb-subdivide-by)) + (xrdb-point 'bopg) + (xrdb-point 'eopg))) + +(defun xrdb-indent-paragraph (&optional arg) + "Indent all lines in the paragraph according to `xrdb-subdivide-by'. +With optional \\[universal-argument], prompt for subdivision." + (interactive "P") + (xrdb-align-to-column + (xrdb-guess-goal-column (if arg + (xrdb-prompt-for-subdivision) + xrdb-subdivide-by)) + (xrdb-point 'bop) + (xrdb-point 'eop))) + +(defun xrdb-indent-buffer (&optional arg) + "Indent all lines in the buffer according to `xrdb-subdivide-by'. +With optional \\[universal-argument], prompt for subdivision." + (interactive "P") + (let ((subdivide-by (if arg + (xrdb-prompt-for-subdivision) + xrdb-subdivide-by))) + (save-excursion + (beginning-of-buffer) + (if (eq subdivide-by 'buffer) + (xrdb-align-to-column (xrdb-guess-goal-column 'buffer) + (point-min) (point-max)) + (let (mvfwdfunc indentfunc) + (cond + ((eq subdivide-by 'paragraph) + (setq mvfwdfunc 'forward-paragraph + indentfunc 'xrdb-indent-paragraph)) + ((eq subdivide-by 'page) + (setq mvfwdfunc 'forward-page + indentfunc 'xrdb-indent-page)) + ((eq subdivide-by 'line) + (setq mvfwdfunc 'forward-line + indentfunc 'xrdb-indent-page)) + (t (error "Illegal alignment subdivision: %s" subdivide-by)) + ) + (while (< (point) (point-max)) + (funcall indentfunc) + (funcall mvfwdfunc 1)) + ))))) + + +;; internal alignment functions +(defun xrdb-align-to-column (goalcol &optional start end) + (let ((start (or start (xrdb-point 'bol))) + (end (or end (xrdb-point 'bonl)))) + (save-excursion + (save-restriction + (narrow-to-region start end) + (beginning-of-buffer) + (while (< (point) (point-max)) + (if (and (not (looking-at xrdb-comment-re)) + (xrdb-skip-to-separator)) + (indent-line-to (max 0 (+ goalcol + (- (current-column)) + (xrdb-boi-col)) + ))) + (forward-line 1)) + )))) + +(defun xrdb-guess-goal-column (subdivide-by) + ;; Returns the goal column of the current line based on SUBDIVIDE-BY, + ;; which can be any value allowed by `xrdb-subdivide-by'. + (let ((here (point)) + (goalcol 0)) + (save-restriction + (cond + ((eq subdivide-by 'line) + (while (and (zerop (forward-line -1)) + (or (looking-at xrdb-comment-re) + (not (xrdb-skip-to-separator))))) + ;; maybe we didn't find one + (if (not (xrdb-skip-to-separator)) + (goto-char here)) + (narrow-to-region (xrdb-point 'bol) (xrdb-point 'bonl))) + ((eq subdivide-by 'page) + (narrow-to-page)) + ((eq subdivide-by 'paragraph) + (narrow-to-region (xrdb-point 'bop) (xrdb-point 'eop))) + ((eq subdivide-by 'buffer)) + (t (error "Illegal alignment subdivision: %s" subdivide-by))) + (goto-char (point-min)) + (while (< (point) (point-max)) + (if (and (not (looking-at xrdb-comment-re)) + (xrdb-skip-to-separator)) + (setq goalcol (max goalcol (- (current-column) (xrdb-boi-col))))) + (forward-line 1))) + (goto-char here) + goalcol)) + + + +;; major-mode stuff +(defvar xrdb-mode-abbrev-table nil + "Abbreviation table used in `xrdb-mode' buffers.") +(define-abbrev-table 'xrdb-mode-abbrev-table ()) + + +(defvar xrdb-mode-syntax-table nil + "Syntax table used in `xrdb-mode' buffers.") +(if xrdb-mode-syntax-table + nil + (setq xrdb-mode-syntax-table (make-syntax-table)) + (modify-syntax-entry ?! "<" xrdb-mode-syntax-table) + (modify-syntax-entry ?\\ "\\" xrdb-mode-syntax-table) + (modify-syntax-entry ?\n ">" xrdb-mode-syntax-table) + (modify-syntax-entry ?/ ". 14" xrdb-mode-syntax-table) + (modify-syntax-entry ?* "_ 23" xrdb-mode-syntax-table) + (modify-syntax-entry ?. "_" xrdb-mode-syntax-table) + (modify-syntax-entry ?# "_" xrdb-mode-syntax-table) + (modify-syntax-entry ?? "_" xrdb-mode-syntax-table) + (modify-syntax-entry ?< "(" xrdb-mode-syntax-table) + (modify-syntax-entry ?> ")" xrdb-mode-syntax-table) + ) + + +(defvar xrdb-mode-map () + "Keymap used in `xrdb-mode' buffers.") +(if xrdb-mode-map + () + (setq xrdb-mode-map (make-sparse-keymap)) + ;; make the separator key electric + (define-key xrdb-mode-map ":" 'xrdb-electric-separator) + (define-key xrdb-mode-map "!" 'xrdb-electric-bang) + (define-key xrdb-mode-map "\t" 'xrdb-indent-line) + (define-key xrdb-mode-map "\C-c\C-a" 'xrdb-indent-buffer) + (define-key xrdb-mode-map "\C-c\C-b" 'xrdb-submit-bug-report) + (define-key xrdb-mode-map "\C-c\C-c" 'xrdb-database-merge-buffer-or-region) + (define-key xrdb-mode-map "\C-c\C-p" 'xrdb-indent-paragraph) + (define-key xrdb-mode-map "\C-c\[" 'xrdb-indent-page) + (define-key xrdb-mode-map "\C-c\C-r" 'xrdb-indent-region) + ) + +;;;###autoload +(defun xrdb-mode () + "Major mode for editing xrdb config files" + (interactive) + (kill-all-local-variables) + (set-syntax-table xrdb-mode-syntax-table) + (setq major-mode 'xrdb-mode + mode-name "xrdb" + local-abbrev-table xrdb-mode-abbrev-table) + (use-local-map xrdb-mode-map) + (setq font-lock-defaults '(xrdb-font-lock-keywords)) + ;; local variables + (make-local-variable 'parse-sexp-ignore-comments) + (make-local-variable 'comment-start-skip) + (make-local-variable 'comment-start) + (make-local-variable 'comment-end) + (make-local-variable 'paragraph-start) + (make-local-variable 'paragraph-separate) + (make-local-variable 'paragraph-ignore-fill-prefix) + (make-local-variable 'indent-region-function) + ;; now set their values + (setq parse-sexp-ignore-comments t + comment-start-skip "![ \t]*" + comment-start "! " + comment-end "") + (setq indent-region-function 'xrdb-indent-region + paragraph-ignore-fill-prefix t + paragraph-start (concat "^[ \t]*$\\|^[ \t]*[!]\\|" page-delimiter) + paragraph-separate paragraph-start) + (run-hooks 'xrdb-mode-hook)) + + + +;; faces and font-locking +(defvar xrdb-option-name-face 'xrdb-option-name-face + "Face for option name on a line in an X resource db file") + +(defvar xrdb-option-value-face 'xrdb-option-value-face + "Face for option value on a line in an X resource db file") + +(make-face 'xrdb-option-name-face) +(make-face 'xrdb-option-value-face) + +(defun xrdb-font-lock-mode-hook () + (or (face-differs-from-default-p 'xrdb-option-name-face) + (copy-face 'font-lock-keyword-face 'xrdb-option-name-face)) + (or (face-differs-from-default-p 'xrdb-option-value-face) + (copy-face 'font-lock-string-face 'xrdb-option-value-face)) + (remove-hook 'font-lock-mode-hook 'xrdb-font-lock-mode-hook)) +(add-hook 'font-lock-mode-hook 'xrdb-font-lock-mode-hook) + +(defvar xrdb-font-lock-keywords + (list '("^[ \t]*\\([^\n:]*:\\)[ \t]*\\(.*\\)$" + (1 xrdb-option-name-face) + (2 xrdb-option-value-face))) + "Additional expressions to highlight in X resource db mode.") +(put 'xrdb-mode 'font-lock-defaults '(xrdb-font-lock-keywords)) + + + +;; merging and manipulating the X resource database +(defun xrdb-database-merge-buffer-or-region (start end) + "Merge the current buffer's resources into the X resource database. + +`xrdb-program' is the program to actually call, with the arguments +specified in `xrdb-program-args'. This latter can be set to do either +a merge or a load, etc. Also, if the file local variable +`xrdb-master-file' is non-nil, then it is merged instead of the +buffer's file. + +If the current region is active, it is merged instead of the buffer, +and this overrides any use of `xrdb-master-file'." + (interactive + ;; the idea here is that if the region is inactive, start and end + ;; will be nil, if not passed in programmatically + (list (xrdb-safe (and (mark) (region-beginning))) + (xrdb-safe (and (mark) (region-end))))) + (message "Merging with args: %s..." xrdb-program-args) + (let ((outbuf (get-buffer-create "*Shell Command Output*"))) + ;; I prefer the XEmacs way of doing this, but this is the easiest + ;; way to work in both XEmacs and Emacs. + (with-current-buffer outbuf (erase-buffer)) + (cond + ((and start end) + (apply 'call-process-region start end xrdb-program nil outbuf t + xrdb-program-args)) + (xrdb-master-file + (apply 'call-process xrdb-program xrdb-master-file outbuf t + xrdb-program-args)) + (t + (apply 'call-process-region (point-min) (point-max) xrdb-program + nil outbuf t xrdb-program-args))) + (if (not (zerop (with-current-buffer outbuf (buffer-size)))) + (pop-to-buffer outbuf))) + (message "Merging... done")) + + + +;; submitting bug reports + +(defconst xrdb-mode-help-address "tools-help@python.org" + "Address for xrdb-mode bug reports.") + +(defun xrdb-submit-bug-report () + "Submit via mail a bug report on xrdb-mode." + (interactive) + ;; load in reporter + (require 'reporter) + (let ((reporter-prompt-for-summary-p t) + (varlist '(xrdb-subdivide-by + xrdb-mode-hook + xrdb-compress-whitespace + ))) + (and (if (y-or-n-p "Do you want to submit a report on xrdb-mode? ") + t + (message "") + nil) + (require 'reporter) + (reporter-submit-bug-report + xrdb-mode-help-address + (format "xrdb-mode %s" xrdb-version) + varlist nil nil "Dear Barry,") + ))) + + +(provide 'xrdb-mode) +;;; xrdb-mode.el ends here -- cgit 1.4.1