--- /dev/null
+;;; ac-helm-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+
+\f
+;;;### (autoloads (ac-complete-with-helm) "ac-helm" "ac-helm.el"
+;;;;;; (21793 7937 0 0))
+;;; Generated autoloads from ac-helm.el
+
+(autoload 'ac-complete-with-helm "ac-helm" "\
+Select `auto-complete' candidates by `helm'.
+It is useful to narrow candidates.
+
+\(fn)" t nil)
+
+;;;***
+\f
+;;;### (autoloads nil nil ("ac-helm-pkg.el") (21793 7937 70647 0))
+
+;;;***
+\f
+(provide 'ac-helm-autoloads)
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; coding: utf-8
+;; End:
+;;; ac-helm-autoloads.el ends here
--- /dev/null
+(define-package "ac-helm" "20140919.917" "Helm interface for auto-complete" (quote ((helm "1.6.3") (auto-complete "1.4.0") (popup "0.5.0") (cl-lib "0.5"))))
--- /dev/null
+;;; ac-helm.el --- Helm interface for auto-complete
+
+;; Copyright (C) 2009 rubikitch
+;; Copyright (C) 2013 Yasuyuki Oka <yasuykNOSPAMgmail.com>
+
+;; Author: rubikitch <rubikitchNOSPAMruby-lang.org>
+;; Yasuyuki Oka <yasuykNOSPAMgmail.com>
+;; Maintainer: Yasuyuki Oka <yasuykNOSPAMgmail.com>
+;; Version: 2.2
+;; Package-Version: 20140919.917
+;; Package-Requires: ((helm "1.6.3")(auto-complete "1.4.0")(popup "0.5.0") (cl-lib "0.5"))
+;; Keywords: completion, convenience, helm
+
+;; This file 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.
+
+;; This file 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.
+
+;;; Commentary:
+
+;; Auto Complete with Helm. It enables us to narrow candidates
+;; with helm interface. If you have helm-match-plugin.el,
+;; candidates can be narrowed many times.
+
+;; Commands:
+;;
+;; Below are complete command list:
+;;
+;; `ac-complete-with-helm'
+;; Select auto-complete candidates by `helm'.
+;;
+;; Customizable Options:
+;;
+;; Below are customizable option list:
+;;
+
+;; Installation:
+
+;; Add the following to your emacs init file:
+;;
+;; (require 'ac-helm) ;; Not necessary if using ELPA package
+;; (global-set-key (kbd "C-:") 'ac-complete-with-helm)
+;; (define-key ac-complete-mode-map (kbd "C-:") 'ac-complete-with-helm)
+
+;; That's all.
+
+;;; Code:
+
+(require 'helm)
+(require 'helm-match-plugin nil t)
+(require 'helm-elisp)
+(require 'auto-complete)
+(require 'cl-lib)
+(require 'popup)
+
+;;;###autoload
+(defun ac-complete-with-helm ()
+ "Select `auto-complete' candidates by `helm'.
+It is useful to narrow candidates."
+ (interactive)
+ (unless ac-completing
+ (call-interactively 'auto-complete))
+ (with-helm-show-completion ac-point (point)
+ (helm :sources 'helm-source-auto-complete-candidates
+ :buffer "*helm auto-complete*")))
+
+(defun helm-auto-complete-init ()
+ (helm-attrset 'ac-candidates ac-candidates)
+ (helm-attrset 'menu-width
+ (popup-preferred-width ac-candidates))
+ (helm-attrset 'ac-prefix ac-prefix)
+ (when (<= (length ac-candidates) 1)
+ (helm-exit-minibuffer))
+ (ac-abort))
+
+(defun helm-auto-complete-action (string)
+ (delete-char (- (length (helm-attr 'ac-prefix))))
+ (insert string)
+ (prog1 (let ((action (get-text-property 0 'action string)))
+ (if action (funcall action)))
+ ;; for GC
+ (helm-attrset 'ac-candidates nil)))
+
+(defun helm-auto-complete-candidates ()
+ (cl-loop for x in (helm-attr 'ac-candidates) collect
+ (cons
+ (helm-aif (get-text-property 0 'action x)
+ (format "%s%s <%s>"
+ x
+ ;; padding
+ (make-string (- (helm-attr 'menu-width) (length x)) ? )
+ ;; action function name
+ it)
+ x)
+ x)))
+
+(defvar helm-source-auto-complete-candidates
+ '((name . "Auto Complete")
+ (init . helm-auto-complete-init)
+ (candidates . helm-auto-complete-candidates)
+ (action . helm-auto-complete-action)
+ (persistent-action . popup-item-show-help)
+ (ac-candidates)
+ (menu-width)))
+
+(provide 'ac-helm)
+
+;;; ac-helm.el ends here
--- /dev/null
+Auto Complete with Helm. It enables us to narrow candidates
+with helm interface. If you have helm-match-plugin.el,
+candidates can be narrowed many times.
+
+Commands:
+
+Below are complete command list:
+
+ `ac-complete-with-helm'
+ Select auto-complete candidates by `helm'.
+
+Customizable Options:
+
+Below are customizable option list:
+
+
+Installation:
+
+Add the following to your emacs init file:
+
+(require 'ac-helm) ;; Not necessary if using ELPA package
+(global-set-key (kbd "C-:") 'ac-complete-with-helm)
+(define-key ac-complete-mode-map (kbd "C-:") 'ac-complete-with-helm)
+
+That's all.
--- /dev/null
+;;; epl-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+(add-to-list 'load-path (or (file-name-directory #$) (car load-path)))
+\f
+;;;### (autoloads nil nil ("epl.el") (21908 14282 24908 0))
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+;;; epl-autoloads.el ends here
--- /dev/null
+(define-package "epl" "20150517.433" "Emacs Package Library" '((cl-lib "0.3")) :url "http://github.com/cask/epl" :keywords '("convenience"))
--- /dev/null
+;;; epl.el --- Emacs Package Library -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2013-2015 Sebastian Wiesner
+;; Copyright (C) 1985-1986, 1992, 1994-1995, 1999-2015 Free Software
+
+;; Author: Sebastian Wiesner <swiesnerNOSPAMlunaryorn.com>
+;; Maintainer: Johan Andersson <johan.rejeepNOSPAMgmail.com>
+;; Sebastian Wiesner <swiesnerNOSPAMlunaryorn.com>
+;; Version: 0.9-cvs
+;; Package-Version: 20150517.433
+;; Package-Requires: ((cl-lib "0.3"))
+;; Keywords: convenience
+;; URL: http://github.com/cask/epl
+
+;; This file is NOT part of GNU Emacs.
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; A package management library for Emacs, based on package.el.
+
+;; The purpose of this library is to wrap all the quirks and hassle of
+;; package.el into a sane API.
+
+;; The following functions comprise the public interface of this library:
+
+;;; Package directory selection
+
+;; `epl-package-dir' gets the directory of packages.
+
+;; `epl-default-package-dir' gets the default package directory.
+
+;; `epl-change-package-dir' changes the directory of packages.
+
+;;; Package system management
+
+;; `epl-initialize' initializes the package system and activates all
+;; packages.
+
+;; `epl-reset' resets the package system.
+
+;; `epl-refresh' refreshes all package archives.
+
+;; `epl-add-archive' adds a new package archive.
+
+;;; Package objects
+
+;; Struct `epl-requirement' describes a requirement of a package with `name' and
+;; `version' slots.
+
+;; `epl-requirement-version-string' gets a requirement version as string.
+
+;; Struct `epl-package' describes an installed or installable package with a
+;; `name' and some internal `description'.
+
+;; `epl-package-version' gets the version of a package.
+
+;; `epl-package-version-string' gets the version of a package as string.
+
+;; `epl-package-summary' gets the summary of a package.
+
+;; `epl-package-requirements' gets the requirements of a package.
+
+;; `epl-package-directory' gets the installation directory of a package.
+
+;; `epl-package-from-buffer' creates a package object for the package contained
+;; in the current buffer.
+
+;; `epl-package-from-file' creates a package object for a package file, either
+;; plain lisp or tarball.
+
+;; `epl-package-from-descriptor-file' creates a package object for a package
+;; description (i.e. *-pkg.el) file.
+
+;;; Package database access
+
+;; `epl-package-installed-p' determines whether a package is installed, either
+;; built-in or explicitly installed.
+
+;; `epl-package-outdated-p' determines whether a package is outdated, that is,
+;; whether a package with a higher version number is available.
+
+;; `epl-built-in-packages', `epl-installed-packages', `epl-outdated-packages'
+;; and `epl-available-packages' get all packages built-in, installed, outdated,
+;; or available for installation respectively.
+
+;; `epl-find-built-in-package', `epl-find-installed-packages' and
+;; `epl-find-available-packages' find built-in, installed and available packages
+;; by name.
+
+;; `epl-find-upgrades' finds all upgradable packages.
+
+;; `epl-built-in-p' return true if package is built-in to Emacs.
+
+;;; Package operations
+
+;; `epl-install-file' installs a package file.
+
+;; `epl-package-install' installs a package.
+
+;; `epl-package-delete' deletes a package.
+
+;; `epl-upgrade' upgrades packages.
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'package)
+
+\f
+(unless (fboundp #'define-error)
+ ;; `define-error' for 24.3 and earlier, copied from subr.el
+ (defun define-error (name message &optional parent)
+ "Define NAME as a new error signal.
+MESSAGE is a string that will be output to the echo area if such an error
+is signaled without being caught by a `condition-case'.
+PARENT is either a signal or a list of signals from which it inherits.
+Defaults to `error'."
+ (unless parent (setq parent 'error))
+ (let ((conditions
+ (if (consp parent)
+ (apply #'append
+ (mapcar (lambda (parent)
+ (cons parent
+ (or (get parent 'error-conditions)
+ (error "Unknown signal `%s'" parent))))
+ parent))
+ (cons parent (get parent 'error-conditions)))))
+ (put name 'error-conditions
+ (delete-dups (copy-sequence (cons name conditions))))
+ (when message (put name 'error-message message)))))
+
+(defsubst epl--package-desc-p (package)
+ "Whether PACKAGE is a `package-desc' object.
+
+Like `package-desc-p', but return nil, if `package-desc-p' is not
+defined as function."
+ (and (fboundp 'package-desc-p) (package-desc-p package)))
+
+\f
+;;; EPL errors
+(define-error 'epl-error "EPL error")
+
+(define-error 'epl-invalid-package "Invalid EPL package" 'epl-error)
+
+(define-error 'epl-invalid-package-file "Invalid EPL package file"
+ 'epl-invalid-package)
+
+\f
+;;; Package directory
+(defun epl-package-dir ()
+ "Get the directory of packages."
+ package-user-dir)
+
+(defun epl-default-package-dir ()
+ "Get the default directory of packages."
+ (eval (car (get 'package-user-dir 'standard-value))))
+
+(defun epl-change-package-dir (directory)
+ "Change the directory of packages to DIRECTORY."
+ (setq package-user-dir directory)
+ (epl-initialize))
+
+\f
+;;; Package system management
+(defvar epl--load-path-before-initialize nil
+ "Remember the load path for `epl-reset'.")
+
+(defun epl-initialize (&optional no-activate)
+ "Load Emacs Lisp packages and activate them.
+
+With NO-ACTIVATE non-nil, do not activate packages."
+ (setq epl--load-path-before-initialize load-path)
+ (package-initialize no-activate))
+
+(defalias 'epl-refresh 'package-refresh-contents)
+
+(defun epl-add-archive (name url)
+ "Add a package archive with NAME and URL."
+ (add-to-list 'package-archives (cons name url)))
+
+(defun epl-reset ()
+ "Reset the package system.
+
+Clear the list of installed and available packages, the list of
+package archives and reset the package directory."
+ (setq package-alist nil
+ package-archives nil
+ package-archive-contents nil
+ load-path epl--load-path-before-initialize)
+ (when (boundp 'package-obsolete-alist) ; Legacy package.el
+ (setq package-obsolete-alist nil))
+ (epl-change-package-dir (epl-default-package-dir)))
+
+\f
+;;; Package structures
+(cl-defstruct (epl-requirement
+ (:constructor epl-requirement-create))
+ "Structure describing a requirement.
+
+Slots:
+
+`name' The name of the required package, as symbol.
+
+`version' The version of the required package, as version list."
+ name
+ version)
+
+(defun epl-requirement-version-string (requirement)
+ "The version of a REQUIREMENT, as string."
+ (package-version-join (epl-requirement-version requirement)))
+
+(cl-defstruct (epl-package (:constructor epl-package-create))
+ "Structure representing a package.
+
+Slots:
+
+`name' The package name, as symbol.
+
+`description' The package description.
+
+The format package description varies between package.el
+variants. For `package-desc' variants, it is simply the
+corresponding `package-desc' object. For legacy variants, it is
+a vector `[VERSION REQS DOCSTRING]'.
+
+Do not access `description' directly, but instead use the
+`epl-package' accessors."
+ name
+ description)
+
+(defmacro epl-package-as-description (var &rest body)
+ "Cast VAR to a package description in BODY.
+
+VAR is a symbol, bound to an `epl-package' object. This macro
+casts this object to the `description' object, and binds the
+description to VAR in BODY."
+ (declare (indent 1))
+ (unless (symbolp var)
+ (signal 'wrong-type-argument (list #'symbolp var)))
+ `(if (epl-package-p ,var)
+ (let ((,var (epl-package-description ,var)))
+ ,@body)
+ (signal 'wrong-type-argument (list #'epl-package-p ,var))))
+
+(defsubst epl-package--package-desc-p (package)
+ "Whether the description of PACKAGE is a `package-desc'."
+ (epl--package-desc-p (epl-package-description package)))
+
+(defun epl-package-version (package)
+ "Get the version of PACKAGE, as version list."
+ (epl-package-as-description package
+ (cond
+ ((fboundp 'package-desc-version) (package-desc-version package))
+ ;; Legacy
+ ((fboundp 'package-desc-vers)
+ (let ((version (package-desc-vers package)))
+ (if (listp version) version (version-to-list version))))
+ (:else (error "Cannot get version from %S" package)))))
+
+(defun epl-package-version-string (package)
+ "Get the version from a PACKAGE, as string."
+ (package-version-join (epl-package-version package)))
+
+(defun epl-package-summary (package)
+ "Get the summary of PACKAGE, as string."
+ (epl-package-as-description package
+ (cond
+ ((fboundp 'package-desc-summary) (package-desc-summary package))
+ ((fboundp 'package-desc-doc) (package-desc-doc package)) ; Legacy
+ (:else (error "Cannot get summary from %S" package)))))
+
+(defsubst epl-requirement--from-req (req)
+ "Create a `epl-requirement' from a `package-desc' REQ."
+ (let ((version (cadr req)))
+ (epl-requirement-create :name (car req)
+ :version (if (listp version) version
+ (version-to-list version)))))
+
+(defun epl-package-requirements (package)
+ "Get the requirements of PACKAGE.
+
+The requirements are a list of `epl-requirement' objects."
+ (epl-package-as-description package
+ (mapcar #'epl-requirement--from-req (package-desc-reqs package))))
+
+(defun epl-package-directory (package)
+ "Get the directory PACKAGE is installed to.
+
+Return the absolute path of the installation directory of
+PACKAGE, or nil, if PACKAGE is not installed."
+ (cond
+ ((fboundp 'package-desc-dir)
+ (package-desc-dir (epl-package-description package)))
+ ((fboundp 'package--dir)
+ (package--dir (symbol-name (epl-package-name package))
+ (epl-package-version-string package)))
+ (:else (error "Cannot get package directory from %S" package))))
+
+(defun epl-package-->= (pkg1 pkg2)
+ "Determine whether PKG1 is before PKG2 by version."
+ (not (version-list-< (epl-package-version pkg1)
+ (epl-package-version pkg2))))
+
+(defun epl-package--from-package-desc (package-desc)
+ "Create an `epl-package' from a PACKAGE-DESC.
+
+PACKAGE-DESC is a `package-desc' object, from recent package.el
+variants."
+ (if (and (fboundp 'package-desc-name)
+ (epl--package-desc-p package-desc))
+ (epl-package-create :name (package-desc-name package-desc)
+ :description package-desc)
+ (signal 'wrong-type-argument (list 'epl--package-desc-p package-desc))))
+
+(defun epl-package--parse-info (info)
+ "Parse a package.el INFO."
+ (if (epl--package-desc-p info)
+ (epl-package--from-package-desc info)
+ ;; For legacy package.el, info is a vector [NAME REQUIRES DESCRIPTION
+ ;; VERSION COMMENTARY]. We need to re-shape this vector into the
+ ;; `package-alist' format [VERSION REQUIRES DESCRIPTION] to attach it to the
+ ;; new `epl-package'.
+ (let ((name (intern (aref info 0)))
+ (info (vector (aref info 3) (aref info 1) (aref info 2))))
+ (epl-package-create :name name :description info))))
+
+(defun epl-package-from-buffer (&optional buffer)
+ "Create an `epl-package' object from BUFFER.
+
+BUFFER defaults to the current buffer.
+
+Signal `epl-invalid-package' if the buffer does not contain a
+valid package file."
+ (let ((info (with-current-buffer (or buffer (current-buffer))
+ (condition-case err
+ (package-buffer-info)
+ (error (signal 'epl-invalid-package (cdr err)))))))
+ (epl-package--parse-info info)))
+
+(defun epl-package-from-lisp-file (file-name)
+ "Parse the package headers the file at FILE-NAME.
+
+Return an `epl-package' object with the header metadata."
+ (with-temp-buffer
+ (insert-file-contents file-name)
+ (condition-case err
+ (epl-package-from-buffer (current-buffer))
+ ;; Attach file names to invalid package errors
+ (epl-invalid-package
+ (signal 'epl-invalid-package-file (cons file-name (cdr err))))
+ ;; Forward other errors
+ (error (signal (car err) (cdr err))))))
+
+(defun epl-package-from-tar-file (file-name)
+ "Parse the package tarball at FILE-NAME.
+
+Return a `epl-package' object with the meta data of the tarball
+package in FILE-NAME."
+ (condition-case nil
+ ;; In legacy package.el, `package-tar-file-info' takes the name of the tar
+ ;; file to parse as argument. In modern package.el, it has no arguments
+ ;; and works on the current buffer. Hence, we just try to call the legacy
+ ;; version, and if that fails because of a mismatch between formal and
+ ;; actual arguments, we use the modern approach. To avoid spurious
+ ;; signature warnings by the byte compiler, we suppress warnings when
+ ;; calling the function.
+ (epl-package--parse-info (with-no-warnings
+ (package-tar-file-info file-name)))
+ (wrong-number-of-arguments
+ (with-temp-buffer
+ (insert-file-contents-literally file-name)
+ ;; Switch to `tar-mode' to enable extraction of the file. Modern
+ ;; `package-tar-file-info' relies on `tar-mode', and signals an error if
+ ;; called in a buffer with a different mode.
+ (tar-mode)
+ (epl-package--parse-info (with-no-warnings
+ (package-tar-file-info)))))))
+
+(defun epl-package-from-file (file-name)
+ "Parse the package at FILE-NAME.
+
+Return an `epl-package' object with the meta data of the package
+at FILE-NAME."
+ (if (string-match-p (rx ".tar" string-end) file-name)
+ (epl-package-from-tar-file file-name)
+ (epl-package-from-lisp-file file-name)))
+
+(defun epl-package--parse-descriptor-requirement (requirement)
+ "Parse a REQUIREMENT in a package descriptor."
+ ;; This function is only called on legacy package.el. On package-desc
+ ;; package.el, we just let package.el do the work.
+ (cl-destructuring-bind (name version-string) requirement
+ (list name (version-to-list version-string))))
+
+(defun epl-package-from-descriptor-file (descriptor-file)
+ "Load a `epl-package' from a package DESCRIPTOR-FILE.
+
+A package descriptor is a file defining a new package. Its name
+typically ends with -pkg.el."
+ (with-temp-buffer
+ (insert-file-contents descriptor-file)
+ (goto-char (point-min))
+ (let ((sexp (read (current-buffer))))
+ (unless (eq (car sexp) 'define-package)
+ (error "%S is no valid package descriptor" descriptor-file))
+ (if (and (fboundp 'package-desc-from-define)
+ (fboundp 'package-desc-name))
+ ;; In Emacs snapshot, we can conveniently call a function to parse the
+ ;; descriptor
+ (let ((desc (apply #'package-desc-from-define (cdr sexp))))
+ (epl-package-create :name (package-desc-name desc)
+ :description desc))
+ ;; In legacy package.el, we must manually deconstruct the descriptor,
+ ;; because the load function has eval's the descriptor and has a lot of
+ ;; global side-effects.
+ (cl-destructuring-bind
+ (name version-string summary requirements) (cdr sexp)
+ (epl-package-create
+ :name (intern name)
+ :description
+ (vector (version-to-list version-string)
+ (mapcar #'epl-package--parse-descriptor-requirement
+ ;; Strip the leading `quote' from the package list
+ (cadr requirements))
+ summary)))))))
+
+\f
+;;; Package database access
+(defun epl-package-installed-p (package)
+ "Determine whether a PACKAGE is installed.
+
+PACKAGE is either a package name as symbol, or a package object."
+ (let ((name (if (epl-package-p package)
+ (epl-package-name package)
+ package))
+ (version (when (epl-package-p package)
+ (epl-package-version package))))
+ (package-installed-p name version)))
+
+(defun epl--parse-built-in-entry (entry)
+ "Parse an ENTRY from the list of built-in packages.
+
+Return the corresponding `epl-package' object."
+ (if (fboundp 'package--from-builtin)
+ ;; In package-desc package.el, convert the built-in package to a
+ ;; `package-desc' and convert that to an `epl-package'
+ (epl-package--from-package-desc (package--from-builtin entry))
+ (epl-package-create :name (car entry) :description (cdr entry))))
+
+(defun epl-built-in-packages ()
+ "Get all built-in packages.
+
+Return a list of `epl-package' objects."
+ ;; This looks mighty strange, but it's the only way to force package.el to
+ ;; build the list of built-in packages. Without this, `package--builtins'
+ ;; might be empty.
+ (package-built-in-p 'foo)
+ (mapcar #'epl--parse-built-in-entry package--builtins))
+
+(defun epl-find-built-in-package (name)
+ "Find a built-in package with NAME.
+
+NAME is a package name, as symbol.
+
+Return the built-in package as `epl-package' object, or nil if
+there is no built-in package with NAME."
+ (when (package-built-in-p name)
+ ;; We must call `package-built-in-p' *before* inspecting
+ ;; `package--builtins', because otherwise `package--builtins' might be
+ ;; empty.
+ (epl--parse-built-in-entry (assq name package--builtins))))
+
+(defun epl-package-outdated-p (package)
+ "Determine whether a PACKAGE is outdated.
+
+A package is outdated, if there is an available package with a
+higher version.
+
+PACKAGE is either a package name as symbol, or a package object.
+In the former case, test the installed or built-in package with
+the highest version number, in the later case, test the package
+object itself.
+
+Return t, if the package is outdated, or nil otherwise."
+ (let* ((package (if (epl-package-p package)
+ package
+ (or (car (epl-find-installed-packages package))
+ (epl-find-built-in-package package))))
+ (available (car (epl-find-available-packages
+ (epl-package-name package)))))
+ (and package available (version-list-< (epl-package-version package)
+ (epl-package-version available)))))
+
+(defun epl--parse-package-list-entry (entry)
+ "Parse a list of packages from ENTRY.
+
+ENTRY is a single entry in a package list, e.g. `package-alist',
+`package-archive-contents', etc. Typically it is a cons cell,
+but the exact format varies between package.el versions. This
+function tries to parse all known variants.
+
+Return a list of `epl-package' objects parsed from ENTRY."
+ (let ((descriptions (cdr entry)))
+ (cond
+ ((listp descriptions)
+ (sort (mapcar #'epl-package--from-package-desc descriptions)
+ #'epl-package-->=))
+ ;; Legacy package.el has just a single package in an entry, which is a
+ ;; standard description vector
+ ((vectorp descriptions)
+ (list (epl-package-create :name (car entry)
+ :description descriptions)))
+ (:else (error "Cannot parse entry %S" entry)))))
+
+(defun epl-installed-packages ()
+ "Get all installed packages.
+
+Return a list of package objects."
+ (apply #'append (mapcar #'epl--parse-package-list-entry package-alist)))
+
+(defsubst epl--filter-outdated-packages (packages)
+ "Filter outdated packages from PACKAGES."
+ (let (res)
+ (dolist (package packages)
+ (when (epl-package-outdated-p package)
+ (push package res)))
+ (nreverse res)))
+
+(defun epl-outdated-packages ()
+ "Get all outdated packages, as in `epl-package-outdated-p'.
+
+Return a list of package objects."
+ (epl--filter-outdated-packages (epl-installed-packages)))
+
+(defsubst epl--find-package-in-list (name list)
+ "Find a package by NAME in a package LIST.
+
+Return a list of corresponding `epl-package' objects."
+ (let ((entry (assq name list)))
+ (when entry
+ (epl--parse-package-list-entry entry))))
+
+(defun epl-find-installed-package (name)
+ "Find the latest installed package by NAME.
+
+NAME is a package name, as symbol.
+
+Return the installed package with the highest version number as
+`epl-package' object, or nil, if no package with NAME is
+installed."
+ (car (epl-find-installed-packages name)))
+(make-obsolete 'epl-find-installed-package 'epl-find-installed-packages "0.7")
+
+(defun epl-find-installed-packages (name)
+ "Find all installed packages by NAME.
+
+NAME is a package name, as symbol.
+
+Return a list of all installed packages with NAME, sorted by
+version number in descending order. Return nil, if there are no
+packages with NAME."
+ (epl--find-package-in-list name package-alist))
+
+(defun epl-available-packages ()
+ "Get all packages available for installation.
+
+Return a list of package objects."
+ (apply #'append (mapcar #'epl--parse-package-list-entry
+ package-archive-contents)))
+
+(defun epl-find-available-packages (name)
+ "Find available packages for NAME.
+
+NAME is a package name, as symbol.
+
+Return a list of available packages for NAME, sorted by version
+number in descending order. Return nil, if there are no packages
+for NAME."
+ (epl--find-package-in-list name package-archive-contents))
+
+(cl-defstruct (epl-upgrade
+ (:constructor epl-upgrade-create))
+ "Structure describing an upgradable package.
+Slots:
+
+`installed' The installed package
+
+`available' The package available for installation."
+ installed
+ available)
+
+(defun epl-find-upgrades (&optional packages)
+ "Find all upgradable PACKAGES.
+
+PACKAGES is a list of package objects to upgrade, defaulting to
+all installed packages.
+
+Return a list of `epl-upgrade' objects describing all upgradable
+packages."
+ (let ((packages (or packages (epl-installed-packages)))
+ upgrades)
+ (dolist (pkg packages)
+ (let* ((version (epl-package-version pkg))
+ (name (epl-package-name pkg))
+ ;; Find the latest available package for NAME
+ (available-pkg (car (epl-find-available-packages name)))
+ (available-version (when available-pkg
+ (epl-package-version available-pkg))))
+ (when (and available-version (version-list-< version available-version))
+ (push (epl-upgrade-create :installed pkg
+ :available available-pkg)
+ upgrades))))
+ (nreverse upgrades)))
+
+(defalias 'epl-built-in-p 'package-built-in-p)
+
+\f
+;;; Package operations
+
+(defalias 'epl-install-file 'package-install-file)
+
+(defun epl-package-install (package &optional force)
+ "Install a PACKAGE.
+
+PACKAGE is a `epl-package' object. If FORCE is given and
+non-nil, install PACKAGE, even if it is already installed."
+ (when (or force (not (epl-package-installed-p package)))
+ (if (epl-package--package-desc-p package)
+ (package-install (epl-package-description package))
+ ;; The legacy API installs by name. We have no control over versioning,
+ ;; etc.
+ (package-install (epl-package-name package)))))
+
+(defun epl-package-delete (package)
+ "Delete a PACKAGE.
+
+PACKAGE is a `epl-package' object to delete."
+ ;; package-delete allows for packages being trashed instead of fully deleted.
+ ;; Let's prevent his silly behavior
+ (let ((delete-by-moving-to-trash nil))
+ ;; The byte compiler will warn us that we are calling `package-delete' with
+ ;; the wrong number of arguments, since it can't infer that we guarantee to
+ ;; always call the correct version. Thus we suppress all warnings when
+ ;; calling `package-delete'. I wish there was a more granular way to
+ ;; disable just that specific warning, but it is what it is.
+ (if (epl-package--package-desc-p package)
+ (with-no-warnings
+ (package-delete (epl-package-description package)))
+ ;; The legacy API deletes by name (as string!) and version instead by
+ ;; descriptor. Hence `package-delete' takes two arguments. For some
+ ;; insane reason, the arguments are strings here!
+ (let ((name (symbol-name (epl-package-name package)))
+ (version (epl-package-version-string package)))
+ (with-no-warnings
+ (package-delete name version))
+ ;; Legacy package.el does not remove the deleted package
+ ;; from the `package-alist', so we do it manually here.
+ (let ((pkg (assq (epl-package-name package) package-alist)))
+ (when pkg
+ (setq package-alist (delq pkg package-alist))))))))
+
+(defun epl-upgrade (&optional packages preserve-obsolete)
+ "Upgrade PACKAGES.
+
+PACKAGES is a list of package objects to upgrade, defaulting to
+all installed packages.
+
+The old versions of the updated packages are deleted, unless
+PRESERVE-OBSOLETE is non-nil.
+
+Return a list of all performed upgrades, as a list of
+`epl-upgrade' objects."
+ (let ((upgrades (epl-find-upgrades packages)))
+ (dolist (upgrade upgrades)
+ (epl-package-install (epl-upgrade-available upgrade) 'force)
+ (unless preserve-obsolete
+ (epl-package-delete (epl-upgrade-installed upgrade))))
+ upgrades))
+
+(provide 'epl)
+
+;;; epl.el ends here
--- /dev/null
+A package management library for Emacs, based on package.el.
+
+The purpose of this library is to wrap all the quirks and hassle of
+package.el into a sane API.
+
+The following functions comprise the public interface of this library:
+
+Package directory selection
+
+`epl-package-dir' gets the directory of packages.
+
+`epl-default-package-dir' gets the default package directory.
+
+`epl-change-package-dir' changes the directory of packages.
+
+Package system management
+
+`epl-initialize' initializes the package system and activates all
+packages.
+
+`epl-reset' resets the package system.
+
+`epl-refresh' refreshes all package archives.
+
+`epl-add-archive' adds a new package archive.
+
+Package objects
+
+Struct `epl-requirement' describes a requirement of a package with `name' and
+`version' slots.
+
+`epl-requirement-version-string' gets a requirement version as string.
+
+Struct `epl-package' describes an installed or installable package with a
+`name' and some internal `description'.
+
+`epl-package-version' gets the version of a package.
+
+`epl-package-version-string' gets the version of a package as string.
+
+`epl-package-summary' gets the summary of a package.
+
+`epl-package-requirements' gets the requirements of a package.
+
+`epl-package-directory' gets the installation directory of a package.
+
+`epl-package-from-buffer' creates a package object for the package contained
+in the current buffer.
+
+`epl-package-from-file' creates a package object for a package file, either
+plain lisp or tarball.
+
+`epl-package-from-descriptor-file' creates a package object for a package
+description (i.e. *-pkg.el) file.
+
+Package database access
+
+`epl-package-installed-p' determines whether a package is installed, either
+built-in or explicitly installed.
+
+`epl-package-outdated-p' determines whether a package is outdated, that is,
+whether a package with a higher version number is available.
+
+`epl-built-in-packages', `epl-installed-packages', `epl-outdated-packages'
+and `epl-available-packages' get all packages built-in, installed, outdated,
+or available for installation respectively.
+
+`epl-find-built-in-package', `epl-find-installed-packages' and
+`epl-find-available-packages' find built-in, installed and available packages
+by name.
+
+`epl-find-upgrades' finds all upgradable packages.
+
+`epl-built-in-p' return true if package is built-in to Emacs.
+
+Package operations
+
+`epl-install-file' installs a package file.
+
+`epl-package-install' installs a package.
+
+`epl-package-delete' deletes a package.
+
+`epl-upgrade' upgrades packages.
--- /dev/null
+;;; fullscreen-mode-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+
+\f
+;;;### (autoloads (fullscreen-mode fullscreen-mode-fullscreen-toggle
+;;;;;; fullscreen-mode-windowed fullscreen-mode-fullscreen) "fullscreen-mode"
+;;;;;; "fullscreen-mode.el" (21697 19209 0 0))
+;;; Generated autoloads from fullscreen-mode.el
+
+(autoload 'fullscreen-mode-fullscreen "fullscreen-mode" "\
+Sets frame's fullscreen parameter to fullboth
+
+\(fn)" t nil)
+
+(autoload 'fullscreen-mode-windowed "fullscreen-mode" "\
+Set frame's fullscreen parameter back to it's previous windowed state
+
+\(fn)" t nil)
+
+(autoload 'fullscreen-mode-fullscreen-toggle "fullscreen-mode" "\
+Toggles the frame's fullscreen state
+
+\(fn)" t nil)
+
+(defvar fullscreen-mode nil "\
+Non-nil if Fullscreen mode is enabled.
+See the command `fullscreen-mode' for a description of this minor mode.")
+
+(custom-autoload 'fullscreen-mode "fullscreen-mode" nil)
+
+(autoload 'fullscreen-mode "fullscreen-mode" "\
+Provides fullscreen-mode-toggle, bound to F11 that toggles the frame between fullscreen and windowed.
+
+\(fn &optional ARG)" t nil)
+
+;;;***
+\f
+;;;### (autoloads nil nil ("fullscreen-mode-pkg.el") (21697 19209
+;;;;;; 854137 0))
+
+;;;***
+\f
+(provide 'fullscreen-mode-autoloads)
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; coding: utf-8
+;; End:
+;;; fullscreen-mode-autoloads.el ends here
--- /dev/null
+(define-package "fullscreen-mode" "20130524.956" "fullscreen window support for Emacs" (quote nil))
--- /dev/null
+;;; fullscreen-mode.el --- fullscreen window support for Emacs
+
+;; Author: Ryan Mulligan <ryanNOSPAMryantm.com>
+;; Version: 20130524.956
+;; X-Original-Version: 0.0.1
+;; URL: https://github.com/ryantm/fullscreen-mode
+;; Keywords: fullscreen, fullscreen-mode
+
+;; This file is not part of GNU Emacs.
+
+;;; Install:
+
+;; Install package
+;; (package-install 'fullscreen-mode)
+;;
+;; Enable minor mode
+;; (fullscreen-mode 1)
+
+;;; Commentary:
+
+;; Global minor mode that provides
+;; `fullscreen-mode-fullscreen-toggle',
+;; which toggles the frame between fullscreen and windowed.
+;; `fullscreen-mode-fullscreen-toggle' is bound to F11.
+
+;;; 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 3
+;; 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.
+
+;;; Code:
+(defvar fullscreen-mode-windowed-frame-state (make-hash-table :weakness 'key)
+ "Hash keyed by frames with the value of the fullscreen frame parameter before going to fullscreen.
+ Stored so fullscreen-mode-toggle can go back to it.")
+
+(defun fullscreen-mode-windowed-frame-state-update ()
+ "Save fullscreen-windowed-frame-state with the current frame-parameter state"
+ (let ((fullscreen-frame-parameter (frame-parameter nil 'fullscreen)))
+ (if (not (equal fullscreen-frame-parameter 'fullboth))
+ (puthash
+ (selected-frame)
+ fullscreen-frame-parameter
+ fullscreen-mode-windowed-frame-state))))
+
+(defun fullscreen-mode-windowed-frame-state-restore ()
+ "Restore the frame-parameter stored in fullscreen-windowed-frame-state"
+ (let ((fullscreen-frame-parameter (gethash (selected-frame) fullscreen-mode-windowed-frame-state)))
+ (set-frame-parameter nil 'fullscreen fullscreen-frame-parameter)))
+
+(defun fullscreen-mode-fullscreen-p ()
+ "Predicate for fullscreen frame parameter being set to 'fullboth"
+ (equal (frame-parameter nil 'fullscreen) 'fullboth))
+
+;;;###autoload
+(defun fullscreen-mode-fullscreen ()
+ "Sets frame's fullscreen parameter to fullboth"
+ (interactive)
+ (fullscreen-mode-windowed-frame-state-update)
+ (set-frame-parameter nil 'fullscreen 'fullboth))
+
+;;;###autoload
+(defun fullscreen-mode-windowed ()
+ "Set frame's fullscreen parameter back to it's previous windowed state"
+ (interactive)
+ (fullscreen-mode-windowed-frame-state-restore))
+
+;;;###autoload
+(defun fullscreen-mode-fullscreen-toggle ()
+ "Toggles the frame's fullscreen state"
+ (interactive)
+ (if (fullscreen-mode-fullscreen-p)
+ (fullscreen-mode-windowed)
+ (fullscreen-mode-fullscreen)))
+
+(defvar fullscreen-mode-map
+ (let ((m (make-sparse-keymap)))
+ (define-key m (kbd "<f11>") 'fullscreen-mode-fullscreen-toggle)
+ m)
+ "Keymap for `fullscreen-mode'.")
+
+;;;###autoload
+(define-minor-mode fullscreen-mode
+ "Provides fullscreen-mode-toggle, bound to F11 that toggles the frame between fullscreen and windowed."
+ :global t
+ :keymap fullscreen-mode-map)
+
+(provide 'fullscreen-mode)
+;;; fullscreen-mode.el ends here
--- /dev/null
+Global minor mode that provides
+`fullscreen-mode-fullscreen-toggle',
+which toggles the frame between fullscreen and windowed.
+`fullscreen-mode-fullscreen-toggle' is bound to F11.
--- /dev/null
+;;; git-commit-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+(add-to-list 'load-path (or (file-name-directory #$) (car load-path)))
+\f
+;;;### (autoloads nil "git-commit" "git-commit.el" (21910 48889 0
+;;;;;; 0))
+;;; Generated autoloads from git-commit.el
+
+(defvar global-git-commit-mode t "\
+Non-nil if Global-Git-Commit mode is enabled.
+See the command `global-git-commit-mode' for a description of this minor mode.
+Setting this variable directly does not take effect;
+either customize it (see the info node `Easy Customization')
+or call the function `global-git-commit-mode'.")
+
+(custom-autoload 'global-git-commit-mode "git-commit" nil)
+
+(autoload 'global-git-commit-mode "git-commit" "\
+Edit Git commit messages.
+This global mode arranges for `git-commit-setup' to be called
+when a Git commit message file is opened. That usually happens
+when Git uses the Emacsclient as $GIT_EDITOR to have the user
+provide such a commit message.
+
+\(fn &optional ARG)" t nil)
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+;;; git-commit-autoloads.el ends here
--- /dev/null
+(define-package "git-commit" "20150701.1326" "Edit Git commit messages" '((emacs "24.4") (dash "2.10.0") (with-editor "2.1.0")) :url "https://github.com/magit/magit" :keywords '("git" "tools" "vc"))
--- /dev/null
+;;; git-commit.el --- Edit Git commit messages -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2010-2015 The Magit Project Contributors
+;;
+;; You should have received a copy of the AUTHORS.md file which
+;; lists all contributors. If not, see http://magit.vc/authors.
+
+;; Authors: Jonas Bernoulli <jonasNOSPAMbernoul.li>
+;; Sebastian Wiesner <lunaryornNOSPAMgmail.com>
+;; Florian Ragwitz <raflNOSPAMdebian.org>
+;; Marius Vollmer <marius.vollmerNOSPAMgmail.com>
+;; Maintainer: Jonas Bernoulli <jonasNOSPAMbernoul.li>
+
+;; Package-Requires: ((emacs "24.4") (dash "2.10.0") (with-editor "2.1.0"))
+;; Keywords: git tools vc
+;; Package-Version: 20150701.1326
+;; Homepage: https://github.com/magit/magit
+
+;; This file is not part of GNU Emacs.
+
+;; This file 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 file 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 file. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This package assists the user in writing good Git commit messages.
+
+;; While Git allows for the message to be provided on the command
+;; line, it is preferable to tell Git to create the commit without
+;; actually passing it a message. Git then invokes the `$GIT_EDITOR'
+;; (or if that is undefined `$EDITOR') asking the user to provide the
+;; message by editing the file ".git/COMMIT_EDITMSG" (or another file
+;; in that directory, e.g. ".git/MERGE_MSG" for merge commits).
+
+;; When `global-git-commit-mode' is enabled, which it is by default,
+;; then opening such a file causes the features described below, to
+;; be enabled in that buffer. Normally this would be done using a
+;; major-mode but to allow the use of any major-mode, as the user sees
+;; fit, it is done here by running a setup function, which among other
+;; things turns on the preferred major-mode, by default `text-mode'.
+
+;; Git waits for the `$EDITOR' to finish and then either creates the
+;; commit using the contents of the file as commit message, or, if the
+;; editor process exited with a non-zero exit status, aborts without
+;; creating a commit. Unfortunately Emacsclient (which is what Emacs
+;; users should be using as `$EDITOR' or at least as `$GIT_EDITOR')
+;; does not differentiate between "successfully" editing a file and
+;; aborting; not out of the box that is.
+
+;; By making use of the `with-editor' package this package provides
+;; both ways of finish an editing session. In either case the file
+;; is saved, but Emacseditor's exit code differs.
+;;
+;; C-c C-c Finish the editing session successfully by returning
+;; with exit code 0. Git then creates the commit using
+;; the message it finds in the file.
+;;
+;; C-c C-k Aborts the edit editing session by returning with exit
+;; code 1. Git then aborts the commit.
+
+;; Aborting the commit does not cause the message to be lost, but
+;; relying solely on the file not being tampered with is risky. This
+;; package additionally stores all aborted messages for the duration
+;; of the current session (i.e. until you close Emacs). To get back
+;; an aborted message use M-p and M-n while editing a message.
+;;
+;; M-p Replace the buffer contents with the previous message
+;; from the message ring. Of course only after storing
+;; the current content there too.
+;;
+;; M-n Replace the buffer contents with the next message from
+;; the message ring, after storing the current content.
+
+;; Some support for pseudo headers as used in some projects is
+;; provided by these commands:
+;;
+;; C-c C-s Insert a Signed-off-by header.
+;; C-C C-a Insert a Acked-by header.
+;; C-c C-t Insert a Tested-by header.
+;; C-c C-r Insert a Reviewed-by header.
+;; C-c C-o Insert a Cc header.
+;; C-c C-p Insert a Reported-by header.
+;; C-c M-s Insert a Suggested-by header.
+
+;; When Git requests a commit message from the user, it does so by
+;; having her edit a file which initially contains some comments,
+;; instructing her what to do, and providing useful information, such
+;; as which files were modified. These comments, even when left
+;; intact by the user, do not become part of the commit message. This
+;; package ensures these comments are propertizes as such and further
+;; prettifies them by using different faces for various parts, such as
+;; files.
+
+;; Finally this package highlights style errors, like lines that are
+;; to long, or when the second line is not empty. It may even nag you
+;; when you attempt to finish the commit without having fixed these
+;; issues. Some people like that nagging, I don't, so you'll have to
+;; enable it. Which brings me to the last point. Like any
+;; respectable Emacs package, this one too is highly customizable:
+;;
+;; M-x customize-group RET git-commit RET
+
+;;; Code:
+;;;; Dependencies
+
+(require 'dash)
+(require 'log-edit)
+(require 'ring)
+(require 'server)
+(require 'with-editor)
+
+(eval-when-compile (require 'recentf))
+
+;;;; Declarations
+
+(defvar flyspell-generic-check-word-predicate)
+
+;;; Options
+;;;; Variables
+
+(defgroup git-commit nil
+ "Edit Git commit messages."
+ :prefix "git-commit-"
+ :group 'tools)
+
+;;;###autoload
+(define-minor-mode global-git-commit-mode
+ "Edit Git commit messages.
+This global mode arranges for `git-commit-setup' to be called
+when a Git commit message file is opened. That usually happens
+when Git uses the Emacsclient as $GIT_EDITOR to have the user
+provide such a commit message."
+ :group 'git-commit
+ :type 'boolean
+ :global t
+ :init-value t
+ :initialize (lambda (symbol exp)
+ (custom-initialize-default symbol exp)
+ (when global-git-commit-mode
+ (add-hook 'find-file-hook 'git-commit-setup-check-buffer)))
+ (if global-git-commit-mode
+ (add-hook 'find-file-hook 'git-commit-setup-check-buffer)
+ (remove-hook 'find-file-hook 'git-commit-setup-check-buffer)))
+
+(defcustom git-commit-major-mode 'text-mode
+ "Major mode used to edit Git commit messages.
+The major mode configured here is turned on by the minor mode
+`git-commit-mode'."
+ :group 'git-commit
+ :type '(choice (function-item text-mode)
+ (const :tag "No major mode")))
+
+(defcustom git-commit-setup-hook
+ '(git-commit-save-message
+ git-commit-setup-changelog-support
+ git-commit-turn-on-auto-fill
+ git-commit-propertize-diff
+ with-editor-usage-message)
+ "Hook run at the end of `git-commit-setup'."
+ :group 'git-commit
+ :type 'hook
+ :options '(magit-revert-buffers
+ git-commit-save-message
+ git-commit-setup-changelog-support
+ git-commit-turn-on-auto-fill
+ git-commit-turn-on-flyspell
+ git-commit-propertize-diff
+ with-editor-usage-message))
+
+(defcustom git-commit-finish-query-functions
+ '(git-commit-check-style-conventions)
+ "List of functions called to query before performing commit.
+
+The commit message buffer is current while the functions are
+called. If any of them returns nil, then the commit is not
+performed and the buffer is not killed. The user should then
+fix the issue and try again.
+
+The functions are called with one argument. If it is non-nil
+then that indicates that the user used a prefix argument to
+force finishing the session despite issues. Functions should
+usually honor this wish and return non-nil."
+ :options '(git-commit-check-style-conventions)
+ :type 'hook
+ :group 'git-commit)
+
+(defcustom git-commit-summary-max-length 50
+ "Fontify characters beyond this column in summary lines as errors."
+ :group 'git-commit
+ :safe 'numberp
+ :type 'number)
+
+(defcustom git-commit-fill-column 72
+ "Automatically wrap commit message lines beyond this column."
+ :group 'git-commit
+ :safe 'numberp
+ :type 'number)
+
+(defcustom git-commit-known-pseudo-headers
+ '("Signed-off-by" "Acked-by" "Cc"
+ "Suggested-by" "Reported-by" "Tested-by" "Reviewed-by")
+ "A list of Git pseudo headers to be highlighted."
+ :group 'git-commit
+ :safe (lambda (val) (and (listp val) (-all-p 'stringp val)))
+ :type '(repeat string))
+
+;;;; Faces
+
+(defgroup git-commit-faces nil
+ "Faces for highlighting Git commit messages."
+ :prefix "git-commit-"
+ :group 'git-commit
+ :group 'faces)
+
+(defface git-commit-summary
+ '((t :inherit font-lock-type-face))
+ "Face used for the summary in commit messages."
+ :group 'git-commit-faces)
+
+(defface git-commit-overlong-summary
+ '((t :inherit font-lock-warning-face))
+ "Face used for the tail of overlong commit message summaries."
+ :group 'git-commit-faces)
+
+(defface git-commit-nonempty-second-line
+ '((t :inherit font-lock-warning-face))
+ "Face used for non-whitespace on the second line of commit messages."
+ :group 'git-commit-faces)
+
+(defface git-commit-note
+ '((t :inherit font-lock-string-face))
+ "Face used for notes in commit messages."
+ :group 'git-commit-faces)
+
+(defface git-commit-pseudo-header
+ '((t :inherit font-lock-string-face))
+ "Font used for pseudo headers in commit messages."
+ :group 'git-commit-faces)
+
+(defface git-commit-known-pseudo-header
+ '((t :inherit font-lock-keyword-face))
+ "Face used for the keywords of known pseudo headers in commit messages."
+ :group 'git-commit-faces)
+
+(defface git-commit-comment-branch
+ '((t :inherit font-lock-variable-name-face))
+ "Face used for branch names in commit message comments."
+ :group 'git-commit-faces)
+
+(defface git-commit-comment-detached
+ '((t :inherit git-commit-comment-branch))
+ "Face used for detached `HEAD' in commit message comments."
+ :group 'git-commit-faces)
+
+(defface git-commit-comment-heading
+ '((t :inherit git-commit-known-pseudo-header))
+ "Face used for headings in commit message comments."
+ :group 'git-commit-faces)
+
+(defface git-commit-comment-file
+ '((t :inherit git-commit-pseudo-header))
+ "Face used for file names in commit message comments."
+ :group 'git-commit-faces)
+
+(defface git-commit-comment-action
+ '((t :inherit git-commit-comment-branch))
+ "Face used for actions in commit message comments."
+ :group 'git-commit-faces)
+
+;;; Keymap
+
+(defvar git-commit-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd "C-c C-s") 'git-commit-signoff)
+ (define-key map (kbd "C-c C-a") 'git-commit-ack)
+ (define-key map (kbd "C-c C-t") 'git-commit-test)
+ (define-key map (kbd "C-c C-r") 'git-commit-review)
+ (define-key map (kbd "C-c C-o") 'git-commit-cc)
+ (define-key map (kbd "C-c C-p") 'git-commit-reported)
+ (define-key map (kbd "C-c C-i") 'git-commit-suggested)
+ (define-key map (kbd "C-c M-s") 'git-commit-save-message)
+ (define-key map (kbd "M-p") 'git-commit-prev-message)
+ (define-key map (kbd "M-n") 'git-commit-next-message)
+ ;; Old bindings to avoid confusion
+ (define-key map (kbd "C-c C-x s") 'git-commit-signoff)
+ (define-key map (kbd "C-c C-x a") 'git-commit-ack)
+ (define-key map (kbd "C-c C-x t") 'git-commit-test)
+ (define-key map (kbd "C-c C-x r") 'git-commit-review)
+ (define-key map (kbd "C-c C-x o") 'git-commit-cc)
+ (define-key map (kbd "C-c C-x p") 'git-commit-reported)
+ map)
+ "Key map used by `git-commit-mode'.")
+
+;;; Menu
+
+(require 'easymenu)
+(easy-menu-define git-commit-mode-menu git-commit-mode-map
+ "Git Commit Mode Menu"
+ '("Commit"
+ ["Previous" git-commit-prev-message t]
+ ["Next" git-commit-next-message t]
+ "-"
+ ["Ack" git-commit-ack :active t
+ :help "Insert an 'Acked-by' header"]
+ ["Sign-Off" git-commit-signoff :active t
+ :help "Insert a 'Signed-off-by' header"]
+ ["Tested-by" git-commit-test :active t
+ :help "Insert a 'Tested-by' header"]
+ ["Reviewed-by" git-commit-review :active t
+ :help "Insert a 'Reviewed-by' header"]
+ ["CC" git-commit-cc t
+ :help "Insert a 'Cc' header"]
+ ["Reported" git-commit-reported :active t
+ :help "Insert a 'Reported-by' header"]
+ ["Suggested" git-commit-suggested t
+ :help "Insert a 'Suggested-by' header"]
+ "-"
+ ["Save" git-commit-save-message t]
+ ["Cancel" with-editor-cancel t]
+ ["Commit" with-editor-finish t]))
+
+;;; Hooks
+
+(defconst git-commit-filename-regexp "/\\(\
+\\(\\(COMMIT\\|NOTES\\|PULLREQ\\|TAG\\)_EDIT\\|MERGE_\\|\\)MSG\
+\\|BRANCH_DESCRIPTION\\)\\'")
+
+(eval-after-load 'recentf
+ '(add-to-list 'recentf-exclude git-commit-filename-regexp))
+
+(defun git-commit-setup-font-lock-in-buffer ()
+ (and buffer-file-name
+ (string-match-p git-commit-filename-regexp buffer-file-name)
+ (git-commit-setup-font-lock)))
+
+(add-hook 'after-change-major-mode-hook 'git-commit-setup-font-lock-in-buffer)
+
+(defun git-commit-setup-check-buffer ()
+ (and buffer-file-name
+ (string-match-p git-commit-filename-regexp buffer-file-name)
+ (git-commit-setup)))
+
+(defun git-commit-setup ()
+ (when git-commit-major-mode
+ (funcall git-commit-major-mode))
+ (setq with-editor-show-usage nil)
+ (with-editor-mode 1)
+ (add-hook 'with-editor-finish-query-functions
+ 'git-commit-finish-query-functions nil t)
+ (add-hook 'with-editor-pre-finish-hook
+ 'git-commit-save-message nil t)
+ (add-hook 'with-editor-pre-cancel-hook
+ 'git-commit-save-message nil t)
+ (setq with-editor-cancel-message
+ 'git-commit-cancel-message)
+ (make-local-variable 'log-edit-comment-ring-index)
+ (git-commit-mode 1)
+ (git-commit-setup-font-lock)
+ (when (boundp 'save-place)
+ (setq save-place nil))
+ (save-excursion
+ (goto-char (point-min))
+ (when (= (line-beginning-position)
+ (line-end-position))
+ (open-line 1)))
+ (run-hooks 'git-commit-setup-hook)
+ (set-buffer-modified-p nil))
+
+(defun git-commit-setup-font-lock ()
+ (let ((table (make-syntax-table (syntax-table))))
+ (when comment-start
+ (modify-syntax-entry (string-to-char comment-start) "." table))
+ (modify-syntax-entry ?# "." table)
+ (modify-syntax-entry ?\" "." table)
+ (modify-syntax-entry ?\' "." table)
+ (modify-syntax-entry ?` "." table)
+ (set-syntax-table table))
+ (setq-local comment-start
+ (or (ignore-errors
+ (car (process-lines "git" "config" "core.commentchar")))
+ "#"))
+ (setq-local comment-start-skip (format "^%s+[\s\t]*" comment-start))
+ (setq-local comment-end-skip "\n")
+ (setq-local comment-use-syntax nil)
+ (setq-local font-lock-multiline t)
+ (font-lock-add-keywords nil (git-commit-mode-font-lock-keywords) t))
+
+(define-minor-mode git-commit-mode
+ "Auxiliary minor mode used when editing Git commit messages.
+This mode is only responsible for setting up some key bindings.
+Don't use it directly, instead enable `global-git-commit-mode'."
+ :lighter "")
+
+(put 'git-commit-mode 'permanent-local t)
+
+(defun git-commit-setup-changelog-support ()
+ "Treat ChangeLog entries as paragraphs."
+ (setq-local paragraph-start (concat paragraph-start "\\|\\*\\|(")))
+
+(defun git-commit-turn-on-auto-fill ()
+ "Unconditionally turn on Auto Fill mode.
+And set `fill-column' to `git-commit-fill-column'."
+ (setq fill-column git-commit-fill-column)
+ (turn-on-auto-fill))
+
+(defun git-commit-turn-on-flyspell ()
+ "Unconditionally turn on Flyspell mode.
+Also prevent comments from being checked and
+finally check current non-comment text."
+ (require 'flyspell)
+ (turn-on-flyspell)
+ (setq flyspell-generic-check-word-predicate
+ 'git-commit-flyspell-verify)
+ (flyspell-buffer))
+
+(defun git-commit-flyspell-verify ()
+ (not (memq (get-text-property (point) 'face)
+ '(font-lock-comment-face font-lock-comment-delimiter-face
+ git-commit-comment-branch git-commit-comment-detached
+ git-commit-comment-heading git-commit-comment-file
+ git-commit-comment-action git-commit-pseudo-header
+ git-commit-known-pseudo-header))))
+
+(defun git-commit-finish-query-functions (force)
+ (run-hook-with-args-until-failure
+ 'git-commit-finish-query-functions force))
+
+(defun git-commit-check-style-conventions (force)
+ "Check for violations of certain basic style conventions.
+For each violation ask the user if she wants to proceed anyway.
+This makes sure the summary line isn't to long and that the
+second line is empty."
+ (or force
+ (save-excursion
+ (goto-char (point-min))
+ (re-search-forward (git-commit-summary-regexp) nil t)
+ (if (equal (match-string 1) "")
+ t ; Just try; we don't know whether --allow-empty-message was used.
+ (and (or (equal (match-string 2) "")
+ (y-or-n-p "Summary line is too long. Commit anyway? "))
+ (or (equal (match-string 3) "")
+ (y-or-n-p "Second line is not empty. Commit anyway? ")))))))
+
+(defun git-commit-cancel-message ()
+ (message
+ (concat "Commit canceled"
+ (and (memq 'git-commit-save-message with-editor-pre-cancel-hook)
+ ". Message saved to `log-edit-comment-ring'"))))
+
+;;; History
+
+(defun git-commit-prev-message (arg)
+ "Cycle backward through message history, after saving current message.
+With a numeric prefix ARG, go back ARG comments."
+ (interactive "*p")
+ (when (and (git-commit-save-message) (> arg 0))
+ (setq log-edit-comment-ring-index
+ (log-edit-new-comment-index
+ arg (ring-length log-edit-comment-ring))))
+ (save-restriction
+ (goto-char (point-min))
+ (narrow-to-region (point)
+ (if (re-search-forward (concat "^" comment-start))
+ (max 1 (- (point) 2))
+ (point-max)))
+ (log-edit-previous-comment arg)))
+
+(defun git-commit-next-message (arg)
+ "Cycle forward through message history, after saving current message.
+With a numeric prefix ARG, go forward ARG comments."
+ (interactive "*p")
+ (git-commit-prev-message (- arg)))
+
+(defun git-commit-save-message ()
+ "Save current message to `log-edit-comment-ring'."
+ (interactive)
+ (--when-let (git-commit-buffer-message)
+ (unless (ring-member log-edit-comment-ring it)
+ (ring-insert log-edit-comment-ring it))))
+
+(defun git-commit-buffer-message ()
+ (let ((flush (concat "^" comment-start))
+ (str (buffer-substring-no-properties (point-min) (point-max))))
+ (with-temp-buffer
+ (insert str)
+ (goto-char (point-min))
+ (flush-lines flush)
+ (goto-char (point-max))
+ (unless (eq (char-before) ?\n)
+ (insert ?\n))
+ (setq str (buffer-string)))
+ (unless (string-match "\\`[ \t\n\r]*\\'" str)
+ (when (string-match "\\`\n\\{2,\\}" str)
+ (setq str (replace-match "\n" t t str)))
+ (when (string-match "\n\\{2,\\}\\'" str)
+ (setq str (replace-match "\n" t t str)))
+ str)))
+
+;;; Headers
+
+(defun git-commit-ack (name mail)
+ "Insert a header acknowledging that you have looked at the commit."
+ (interactive (git-commit-self-ident))
+ (git-commit-insert-header "Acked-by" name mail))
+
+(defun git-commit-review (name mail)
+ "Insert a header acknowledging that you have reviewed the commit."
+ (interactive (git-commit-self-ident))
+ (git-commit-insert-header "Reviewed-by" name mail))
+
+(defun git-commit-signoff (name mail)
+ "Insert a header to sign off the commit."
+ (interactive (git-commit-self-ident))
+ (git-commit-insert-header "Signed-off-by" name mail))
+
+(defun git-commit-test (name mail)
+ "Insert a header acknowledging that you have tested the commit."
+ (interactive (git-commit-self-ident))
+ (git-commit-insert-header "Tested-by" name mail))
+
+(defun git-commit-cc (name mail)
+ "Insert a header mentioning someone who might be interested."
+ (interactive (git-commit-read-ident))
+ (git-commit-insert-header "Cc" name mail))
+
+(defun git-commit-reported (name mail)
+ "Insert a header mentioning the person who reported the issue."
+ (interactive (git-commit-read-ident))
+ (git-commit-insert-header "Reported-by" name mail))
+
+(defun git-commit-suggested (name mail)
+ "Insert a header mentioning the person who suggested the change."
+ (interactive (git-commit-read-ident))
+ (git-commit-insert-header "Suggested-by" name mail))
+
+(defun git-commit-self-ident ()
+ (list (or (getenv "GIT_AUTHOR_NAME")
+ (getenv "GIT_COMMITTER_NAME")
+ (ignore-errors (car (process-lines "git" "config" "user.name")))
+ user-full-name
+ (read-string "Name: "))
+ (or (getenv "GIT_AUTHOR_EMAIL")
+ (getenv "GIT_COMMITTER_EMAIL")
+ (getenv "EMAIL")
+ (ignore-errors (car (process-lines "git" "config" "user.email")))
+ (read-string "Email: "))))
+
+(defun git-commit-read-ident ()
+ (list (read-string "Name: ")
+ (read-string "Email: ")))
+
+(defun git-commit-insert-header (header name email)
+ (setq header (format "%s: %s <%s>" header name email))
+ (save-excursion
+ (goto-char (point-max))
+ (cond ((re-search-backward "^[-a-zA-Z]+: [^<]+? <[^>]+>" nil t)
+ (end-of-line)
+ (insert ?\n header)
+ (unless (= (char-after) ?\n)
+ (insert ?\n)))
+ (t
+ (while (re-search-backward (concat "^" comment-start) nil t))
+ (unless (looking-back "\n\n")
+ (insert ?\n))
+ (insert header ?\n)))
+ (unless (or (eobp) (= (char-after) ?\n))
+ (insert ?\n))))
+
+;;; Font-Lock
+
+(defconst git-commit-comment-headings
+ '("Changes to be committed:"
+ "Untracked files:"
+ "Changed but not updated:"
+ "Changes not staged for commit:"
+ "Unmerged paths:"))
+
+(defun git-commit-summary-regexp ()
+ (concat
+ ;; Leading empty lines and comments
+ (format "\\`\\(?:^\\(?:\\s-*\\|%s.*\\)\n\\)*" comment-start)
+ ;; Summary line
+ (format "\\(.\\{0,%d\\}\\)\\(.*\\)" git-commit-summary-max-length)
+ ;; Non-empty non-comment second line
+ (format "\\(?:\n%s\\|\n\\(.*\\)\\)?" comment-start)))
+
+(defun git-commit-mode-font-lock-keywords ()
+ `(;; Comments
+ (,(format "^%s.*" comment-start)
+ (0 'font-lock-comment-face))
+ (,(format "^%s On branch \\(.*\\)" comment-start)
+ (1 'git-commit-comment-branch t))
+ (,(format "^%s Not currently on any branch." comment-start)
+ (1 'git-commit-comment-detached t))
+ (,(format "^%s %s" comment-start
+ (regexp-opt git-commit-comment-headings t))
+ (1 'git-commit-comment-heading t))
+ (,(format "^%s\t\\(?:\\([^:\n]+\\):\\s-+\\)?\\(.*\\)" comment-start)
+ (1 'git-commit-comment-action t t)
+ (2 'git-commit-comment-file t))
+ ;; Pseudo headers
+ (,(format "^\\(%s:\\)\\( .*\\)"
+ (regexp-opt git-commit-known-pseudo-headers))
+ (1 'git-commit-known-pseudo-header)
+ (2 'git-commit-pseudo-header))
+ ("^[-a-zA-Z]+: [^<]+? <[^>]+>"
+ (0 'git-commit-pseudo-header))
+ ;; Summary
+ (,(git-commit-summary-regexp)
+ (1 'git-commit-summary t))
+ ;; - Note (overrides summary)
+ ("\\[.+?\\]"
+ (0 'git-commit-note t))
+ ;; - Non-empty second line (overrides summary and note)
+ (,(git-commit-summary-regexp)
+ (2 'git-commit-overlong-summary t t)
+ (3 'git-commit-nonempty-second-line t t))))
+
+(defun git-commit-propertize-diff ()
+ (save-excursion
+ (goto-char (point-min))
+ (when (re-search-forward "^diff --git" nil t)
+ (let ((buffer (current-buffer)))
+ (insert
+ (with-temp-buffer
+ (insert
+ (with-current-buffer buffer
+ (prog1 (buffer-substring-no-properties (point) (point-max))
+ (delete-region (point) (point-max)))))
+ (diff-mode)
+ (let (font-lock-verbose font-lock-support-mode)
+ (font-lock-fontify-buffer))
+ (let (next (pos (point-min)))
+ (while (setq next (next-single-property-change pos 'face))
+ (put-text-property pos next 'font-lock-face
+ (get-text-property pos 'face))
+ (setq pos next)))
+ (buffer-string)))))))
+
+;;; git-commit.el ends soon
+(provide 'git-commit)
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
+;;; git-commit.el ends here
--- /dev/null
+;;; helm-aws-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+
+\f
+;;;### (autoloads (helm-aws) "helm-aws" "helm-aws.el" (21889 46341
+;;;;;; 0 0))
+;;; Generated autoloads from helm-aws.el
+
+(autoload 'helm-aws "helm-aws" "\
+
+
+\(fn)" t nil)
+
+;;;***
+\f
+;;;### (autoloads nil nil ("helm-aws-pkg.el") (21889 46341 288795
+;;;;;; 0))
+
+;;;***
+\f
+(provide 'helm-aws-autoloads)
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; coding: utf-8
+;; End:
+;;; helm-aws-autoloads.el ends here
--- /dev/null
+(define-package "helm-aws" "20141206.1208" "Manage AWS EC2 server instances directly from Emacs" (quote ((helm "1.5.3"))))
--- /dev/null
+;;; helm-aws.el --- Manage AWS EC2 server instances directly from Emacs
+
+;; Copyright (C) 2014 istib
+
+;; Author: istib
+;; URL: https://github.com/istib/helm-aws
+;; Package-Version: 20141206.1208
+;; Version: 20141205.1
+;; X-Original-Version: 0.2
+;; Package-Requires: ((helm "1.5.3"))
+;; Keywords:
+
+;; This file is not part of GNU Emacs
+
+;; This file 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.
+
+;; For a full copy of the GNU General Public License
+;; see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Manage AWS EC2 server instances directly from Emacs
+
+;; A call to `helm-aws' will show a list of running instances on your configured
+;; AWS account. Helm actions to launch a terminal, dired, or ping are accessible.
+;;
+;; Requires a configured installation of AWS command-line interface (http://aws.amazon.com/cli/)
+
+
+;;; Code:
+(require 'json)
+
+(defvar aws-user-account
+ "ubuntu"
+ "User account name for AWS servers. Assuming that your PEM keys are placed on each instance")
+
+(defvar aws-ec2-command
+ "aws ec2 describe-instances"
+ "Command to list instances. Run `aws configure` to set up AWS cli")
+
+(defun aws-run-ec2-command ()
+ "returns json"
+ (let ((aws-result-buffer (generate-new-buffer-name "*aws-ec2*")))
+ (with-temp-buffer
+ (shell-command aws-ec2-command (current-buffer) nil)
+ (buffer-substring-no-properties (point-min) (point-max)))))
+
+(defun aws-parse-server-list (input)
+ ""
+ (let* ((json-object-type 'plist)
+ (aws-json (json-read-from-string input))
+ (reservations (plist-get aws-json :Reservations))
+ (instances (mapcar (lambda (el) (plist-get el :Instances)) reservations))
+ (instance-vector (cdr instances)) ;; drop first, as it's metadata
+ (instance-list (mapcar (lambda (x) (elt x 0)) instance-vector)))
+ instance-list))
+
+(defun aws-is-instance-active-p (instance)
+ "predicate that determines whether the given instance is running"
+ (let* ((state (plist-get instance :State))
+ (code (plist-get state :Code)))
+ (eq code 16)))
+
+(defun aws-format-instance-string (instance)
+ "Constructs a human-friendly string of a server instance - showing name, IP and launch date"
+ (let* ((ip (plist-get instance :PrivateIpAddress))
+ (tags (elt (plist-get instance :Tags) 0))
+ (name (plist-get tags :Value))
+ (launch-time (plist-get instance :LaunchTime))
+ (launch-date (car (split-string launch-time "T"))))
+ (concat name " - " ip " - " launch-date)))
+
+(defun aws-get-ip-from-instance-string (instance-str)
+ "extracts IP address back from constructed string"
+ (let* ((components (split-string instance-str " - ")))
+ (cadr components)))
+
+(defun aws-get-active-instances ()
+ "used to populate list in helm-aws"
+ (let* ((aws-result (aws-run-ec2-command))
+ (instance-list (aws-parse-server-list aws-result))
+ (active-instances (-filter 'aws-is-instance-active-p instance-list)))
+ (mapcar 'aws-format-instance-string active-instances)))
+
+(defun aws-ssh-into-instance (ip-address)
+ "Use SSH to connect to remote instance"
+ (let ((switches (list ip-address "-l" aws-user-account)))
+ (set-buffer (apply 'make-term "ssh" "ssh" nil switches))
+ (term-mode)
+ (term-char-mode)
+ (switch-to-buffer "*ssh*")))
+
+(defun aws-find-file-on-instance (ip-address)
+ "Use dired to access directory structure of remote instance"
+ (let ((path (concat "/ssh:" aws-user-account "@" ip-address ":")))
+ (find-file path)))
+
+
+;;;###autoload
+(defun helm-aws ()
+ (interactive)
+ (let ((choices (aws-get-active-instances)))
+ (helm
+ :sources '((name . "EC2 Instances")
+ (candidates . choices)
+ (action . (("SSH" . (lambda (instance-str)
+ (aws-ssh-into-instance (aws-get-ip-from-instance-string instance-str))))
+ ("Dired" . (lambda (instance-str)
+ (aws-find-file-on-instance (aws-get-ip-from-instance-string instance-str))))
+ ("Ping" . (lambda (instance-str)
+ (ping (aws-get-ip-from-instance-string instance-str))))
+ ("Insert IP into buffer" . (lambda (instance-str)
+ (insert (aws-get-ip-from-instance-string instance-str))))))))))
+
+(provide 'helm-aws)
+
+;;; helm-aws.el ends here
--- /dev/null
+Manage AWS EC2 server instances directly from Emacs
+
+A call to `helm-aws' will show a list of running instances on your configured
+AWS account. Helm actions to launch a terminal, dired, or ping are accessible.
+
+Requires a configured installation of AWS command-line interface (http://aws.amazon.com/cli/)
--- /dev/null
+;;; helm-git-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+
+\f
+;;;### (autoloads nil nil ("helm-git-pkg.el" "helm-git.el") (21792
+;;;;;; 6079 675972 0))
+
+;;;***
+\f
+(provide 'helm-git-autoloads)
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; coding: utf-8
+;; End:
+;;; helm-git-autoloads.el ends here
--- /dev/null
+(define-package "helm-git" "20120630.1403" "Helm extension for Git." (quote nil))
--- /dev/null
+;;; helm-git.el --- Helm extension for Git.
+
+;; Copyright (C) 2012 Marian Schubert
+
+;; Author : Marian Schubert <marian.schubertNOSPAMgmail.com>
+;; URL : https://github.com/maio/helm-git
+;; Package-Version: 20120630.1403
+;; Version : 1.1
+;; Keywords : helm, git
+
+;; This file is NOT part of GNU Emacs.
+
+;;; 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 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 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.
+
+;;; Commentary:
+;;
+;; Helm Git extension makes opening files from current Git repository
+;; fast and easy. It uses Git binary (using magit) to get list of
+;; project files so it's prety fast even for large projects with
+;; thousands of files. It also takes into account .gitignore file so
+;; that you only get real project files. Magit should also make it
+;; usable on wide variety of OS.
+;;
+;;; Code:
+
+(require 'helm-locate)
+(require 'magit)
+
+(defun helm-git-file-name-localname (path)
+ (if (file-remote-p path)
+ (tramp-file-name-localname (tramp-dissect-file-name path))
+ path))
+
+(defun helm-git-git-dir ()
+ "This function is required because magit-git-dir doesn't always return tramp path."
+ (if (file-remote-p default-directory)
+ (let ((path (tramp-dissect-file-name default-directory)))
+ (tramp-make-tramp-file-name
+ (tramp-file-name-method path)
+ (tramp-file-name-user path)
+ (tramp-file-name-host path)
+ (helm-git-file-name-localname (magit-git-dir))))
+ (magit-git-dir)))
+
+(defun helm-git-root-dir ()
+ (expand-file-name ".." (helm-git-git-dir)))
+
+(defun helm-git-root-p (candidate)
+ (setq candidate (expand-file-name candidate))
+ (let ((default-directory (if (file-directory-p candidate)
+ (file-name-as-directory candidate)
+ (file-name-as-directory
+ helm-ff-default-directory))))
+ (stringp (condition-case nil
+ (helm-git-root-dir)
+ (error nil)))))
+
+(defun helm-git-file-full-path (name)
+ (expand-file-name name (helm-git-root-dir)))
+
+(defun helm-git-find-file (name)
+ (find-file (helm-git-file-full-path name)))
+
+(defun helm-c-git-files ()
+ (magit-git-lines "ls-files" "--full-name"
+ "--" (helm-git-file-name-localname (helm-git-root-dir))))
+
+;; [NOTE] When value is changed need to restart emacs.
+(defvar helm-git-ff-action-index 4
+ "Add list git files action in `helm-c-source-find-files' at this index.")
+
+(defun helm-ff-git-find-files (candidate)
+ (let ((default-directory (file-name-as-directory
+ (if (file-directory-p candidate)
+ (expand-file-name candidate)
+ (file-name-directory candidate)))))
+ (helm-run-after-quit
+ #'(lambda (d)
+ (let ((default-directory d))
+ (helm-git-find-files)))
+ default-directory)))
+
+(when (require 'helm-files)
+ (helm-add-action-to-source-if
+ "List git files"
+ 'helm-ff-git-find-files
+ helm-c-source-find-files
+ 'helm-git-root-p
+ helm-git-ff-action-index))
+
+(defvar helm-c-source-git-files
+ `((name . "Git files list")
+ (init . (lambda ()
+ (helm-init-candidates-in-buffer
+ "*helm git*" (helm-c-git-files))))
+ (candidates-in-buffer)
+ (keymap . ,helm-generic-files-map)
+ (help-message . helm-generic-file-help-message)
+ (mode-line . helm-generic-file-mode-line-string)
+ (match helm-c-match-on-basename)
+ (type . file)
+ (action . (lambda (candidate)
+ (helm-git-find-file candidate))))
+ "Helm Git files source definition")
+
+(defun helm-git-find-files ()
+ (interactive)
+ (helm :sources '(helm-c-source-git-files)))
+
+(provide 'helm-git)
+
+;;; helm-git.el ends here
--- /dev/null
+Helm Git extension makes opening files from current Git repository
+fast and easy. It uses Git binary (using magit) to get list of
+project files so it's prety fast even for large projects with
+thousands of files. It also takes into account .gitignore file so
+that you only get real project files. Magit should also make it
+usable on wide variety of OS.
--- /dev/null
+Provides a Helm interface to Gitlab
--- /dev/null
+`helm-gtags.el' is a `helm' interface of GNU Global.
+`helm-gtags.el' is not compatible `anything-gtags.el', but `helm-gtags.el'
+is designed for fast search.
+
+
+To use this package, add these lines to your init.el or .emacs file:
+
+ ;; Enable helm-gtags-mode
+ (add-hook 'c-mode-hook 'helm-gtags-mode)
+ (add-hook 'c++-mode-hook 'helm-gtags-mode)
+ (add-hook 'asm-mode-hook 'helm-gtags-mode)
+
+ ;; Set key bindings
+ (eval-after-load "helm-gtags"
+ '(progn
+ (define-key helm-gtags-mode-map (kbd "M-t") 'helm-gtags-find-tag)
+ (define-key helm-gtags-mode-map (kbd "M-r") 'helm-gtags-find-rtag)
+ (define-key helm-gtags-mode-map (kbd "M-s") 'helm-gtags-find-symbol)
+ (define-key helm-gtags-mode-map (kbd "M-g M-p") 'helm-gtags-parse-file)
+ (define-key helm-gtags-mode-map (kbd "C-c <") 'helm-gtags-previous-history)
+ (define-key helm-gtags-mode-map (kbd "C-c >") 'helm-gtags-next-history)
+ (define-key helm-gtags-mode-map (kbd "M-,") 'helm-gtags-pop-stack)))
--- /dev/null
+;;; helm-projectile-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+
+\f
+;;;### (autoloads (helm-projectile helm-projectile-ag helm-projectile-ack
+;;;;;; helm-projectile-grep helm-projectile-off helm-projectile-on
+;;;;;; helm-projectile-find-other-file helm-projectile-find-file-dwim
+;;;;;; helm-projectile-fuzzy-match) "helm-projectile" "helm-projectile.el"
+;;;;;; (21906 14465 0 0))
+;;; Generated autoloads from helm-projectile.el
+
+(defvar helm-projectile-fuzzy-match t "\
+Enable fuzzy matching for Helm Projectile commands.
+This needs to be set before loading helm-projectile.")
+
+(custom-autoload 'helm-projectile-fuzzy-match "helm-projectile" t)
+
+(autoload 'helm-projectile-find-file-dwim "helm-projectile" "\
+Find file at point based on context.
+
+\(fn)" t nil)
+
+(autoload 'helm-projectile-find-other-file "helm-projectile" "\
+Switch between files with the same name but different extensions using Helm.
+With FLEX-MATCHING, match any file that contains the base name of current file.
+Other file extensions can be customized with the variable `projectile-other-file-alist'.
+
+\(fn &optional FLEX-MATCHING)" t nil)
+
+(autoload 'helm-projectile-on "helm-projectile" "\
+Turn on helm-projectile key bindings.
+
+\(fn)" t nil)
+
+(autoload 'helm-projectile-off "helm-projectile" "\
+Turn off helm-projectile key bindings.
+
+\(fn)" t nil)
+
+(autoload 'helm-projectile-grep "helm-projectile" "\
+Helm version of projectile-grep.
+
+\(fn)" t nil)
+
+(autoload 'helm-projectile-ack "helm-projectile" "\
+Helm version of projectile-ack.
+
+\(fn)" t nil)
+
+(autoload 'helm-projectile-ag "helm-projectile" "\
+Helm version of projectile-ag.
+
+\(fn &optional OPTIONS)" t nil)
+
+(autoload 'helm-projectile "helm-projectile" "\
+Use projectile with Helm instead of ido.
+
+With a prefix ARG invalidates the cache first.
+If invoked outside of a project, displays a list of known projects to jump.
+
+\(fn &optional ARG)" t nil)
+
+(eval-after-load 'projectile '(progn (define-key projectile-command-map (kbd "h") 'helm-projectile)))
+
+;;;***
+\f
+;;;### (autoloads nil nil ("helm-projectile-pkg.el") (21906 14465
+;;;;;; 971196 0))
+
+;;;***
+\f
+(provide 'helm-projectile-autoloads)
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; coding: utf-8
+;; End:
+;;; helm-projectile-autoloads.el ends here
--- /dev/null
+(define-package "helm-projectile" "20150621.1322" "Helm integration for Projectile" (quote ((helm "1.4.0") (projectile "0.12.0") (dash "1.5.0") (cl-lib "0.3"))))
--- /dev/null
+;;; helm-projectile.el --- Helm integration for Projectile
+
+;; Copyright (C) 2011-2015 Bozhidar Batsov
+
+;; Author: Bozhidar Batsov
+;; URL: https://github.com/bbatsov/projectile
+;; Package-Version: 20150621.1322
+;; Created: 2011-31-07
+;; Keywords: project, convenience
+;; Version: 0.12.0
+;; Package-Requires: ((helm "1.4.0") (projectile "0.12.0") (dash "1.5.0") (cl-lib "0.3"))
+
+;; This file is NOT part of GNU Emacs.
+
+;;; 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 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 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.
+
+;;; Commentary:
+;;
+;; This library provides easy project management and navigation. The
+;; concept of a project is pretty basic - just a folder containing
+;; special file. Currently git, mercurial and bazaar repos are
+;; considered projects by default. If you want to mark a folder
+;; manually as a project just create an empty .projectile file in
+;; it. See the README for more details.
+;;
+;;; Code:
+
+(require 'projectile)
+(require 'helm-locate)
+(require 'helm-buffers)
+(require 'helm-files)
+(require 'cl-lib)
+
+(declare-function eshell "eshell")
+(declare-function helm-do-ag "helm-ag")
+(defvar helm-ag-base-command)
+
+(defgroup helm-projectile nil
+ "Helm support for projectile."
+ :prefix "helm-projectile-"
+ :group 'projectile
+ :link `(url-link :tag "helm-projectile homepage" "https://github.com/bbatsov/projectile"))
+
+(defvar helm-projectile-current-project-root)
+
+;;;###autoload
+(defcustom helm-projectile-fuzzy-match t
+ "Enable fuzzy matching for Helm Projectile commands.
+This needs to be set before loading helm-projectile."
+ :group 'helm-projectile
+ :type 'boolean)
+
+(defun helm-projectile-coerce-file (candidate)
+ (with-current-buffer (helm-candidate-buffer)
+ (expand-file-name candidate (projectile-project-root))))
+
+(defmacro helm-projectile-define-key (keymap key def &rest bindings)
+ "In KEYMAP, define key sequence KEY1 as DEF1, KEY2 as DEF2 ..."
+ (declare (indent defun))
+ (let ((ret '(progn)))
+ (while key
+ (push
+ `(define-key ,keymap ,key
+ (lambda ()
+ (interactive)
+ (helm-quit-and-execute-action ,def)))
+ ret)
+ (setq key (pop bindings)
+ def (pop bindings)))
+ (reverse ret)))
+
+(defun helm-projectile-hack-actions (actions &rest prescription)
+ "Given a Helm action list and a prescription, return a hacked
+Helm action list, after applying the PRESCRIPTION.
+
+The Helm action list ACTIONS is of the form:
+
+\(\(DESCRIPTION1 . FUNCTION1\)
+ \(DESCRIPTION2 . FUNCTION2\)
+ ...
+ \(DESCRIPTIONn . FUNCTIONn\)\)
+
+PRESCRIPTION is in the form:
+
+\(INSTRUCTION1 INSTRUCTION2 ... INSTRUCTIONn\)
+
+If an INSTRUCTION is a symbol, the action with function name
+INSTRUCTION is deleted.
+
+If an INSTRUCTION is of the form \(FUNCTION1 . FUNCTION2\), the
+action with function name FUNCTION1 will change it's function to
+FUNCTION2.
+
+If an INSTRUCTION is of the form \(FUNCTION . DESCRIPTION\), and
+if an action with function name FUNCTION exists in the original
+Helm action list, the action in the Helm action list, with
+function name FUNCTION will change it's description to
+DESCRIPTION. Otherwise, (FUNCTION . DESCRIPTION) will be added to
+the action list.
+
+Please check out how `helm-projectile-file-actions' is defined
+for an example of how this function is being used."
+ (let* ((to-delete (cl-remove-if (lambda (entry) (listp entry)) prescription))
+ (actions (cl-delete-if (lambda (action) (memq (cdr action) to-delete))
+ (copy-alist actions)))
+ new)
+ (cl-dolist (action actions)
+ (when (setq new (cdr (assq (cdr action) prescription)))
+ (if (stringp new)
+ (setcar action new)
+ (setcdr action new))))
+ ;; Add new actions from PRESCRIPTION
+ (setq new nil)
+ (cl-dolist (instruction prescription)
+ (when (and (listp instruction)
+ (null (rassq (car instruction) actions))
+ (symbolp (car instruction)) (stringp (cdr instruction)))
+ (push (cons (cdr instruction) (car instruction)) new)))
+ (append actions (nreverse new))))
+
+(defun helm-projectile-vc (dir)
+ "A Helm action for jumping to project root using `vc-dir' or Magit.
+DIR is a directory to be switched"
+ (let ((projectile-require-project-root nil))
+ (projectile-vc dir)))
+
+(defun helm-projectile-compile-project (dir)
+ "A Helm action for compile a project.
+DIR is the project root."
+ (let ((helm--reading-passwd-or-string t)
+ (default-directory dir))
+ (projectile-compile-project helm-current-prefix-arg dir)))
+
+(defun helm-projectile-remove-known-project (_ignore)
+ "Delete selected projects.
+_IGNORE means the argument does not matter.
+It is there because Helm requires it."
+ (let* ((projects (helm-marked-candidates :with-wildcard t))
+ (len (length projects)))
+ (with-helm-display-marked-candidates
+ helm-marked-buffer-name
+ projects
+ (if (not (y-or-n-p (format "Delete *%s projects(s)" len)))
+ (message "(No deletion performed)")
+ (progn
+ (mapc (lambda (p)
+ (delete p projectile-known-projects))
+ projects)
+ (projectile-save-known-projects))
+ (message "%s projects(s) deleted" len)))))
+
+(defvar helm-source-projectile-projects
+ (helm-build-in-buffer-source "Projectile projects"
+ :data (lambda ()
+ (if (projectile-project-p)
+ (cons (abbreviate-file-name (projectile-project-root))
+ (projectile-relevant-known-projects))
+ projectile-known-projects))
+ :fuzzy-match helm-projectile-fuzzy-match
+ :keymap (let ((map (make-sparse-keymap)))
+ (set-keymap-parent map helm-map)
+ (helm-projectile-define-key map
+ (kbd "C-d") 'dired
+ (kbd "M-g") 'helm-projectile-vc
+ (kbd "M-e") 'helm-projectile-switch-to-eshell
+ (kbd "C-s") 'helm-find-files-grep
+ (kbd "M-c") 'helm-projectile-compile-project
+ (kbd "M-D") 'helm-projectile-remove-known-project)
+ map)
+ :mode-line helm-ff-mode-line-string
+ :action '(("Switch to project" .
+ (lambda (project)
+ (let ((projectile-completion-system 'helm))
+ (projectile-switch-project-by-name project))))
+ ("Open Dired in project's directory `C-d'" . dired)
+ ("Open project root in vc-dir or magit `M-g'" . helm-projectile-vc)
+ ("Switch to Eshell `M-e'" . helm-projectile-switch-to-eshell)
+ ("Grep in projects `C-s'. With C-u, recurse" . helm-find-files-grep)
+ ("Compile project `M-c'. With C-u, new compile command"
+ . helm-projectile-compile-project)
+ ("Remove project(s) `M-D'" . helm-projectile-remove-known-project)))
+ "Helm source for known projectile projects.")
+
+(define-key helm-etags-map (kbd "C-c p f") (lambda ()
+ (interactive)
+ (helm-run-after-quit 'helm-projectile-find-file nil)))
+
+(defun helm-projectile-find-files-eshell-command-on-file-action (_candidate)
+ (interactive)
+ (let* ((helm-ff-default-directory (file-name-directory _candidate)))
+ (helm-find-files-eshell-command-on-file _candidate)))
+
+(defun helm-projectile-ff-etags-select-action (_candidate)
+ (interactive)
+ (let* ((helm-ff-default-directory (file-name-directory _candidate)))
+ (helm-ff-etags-select _candidate)))
+
+(defun helm-projectile-switch-to-eshell (dir)
+ (interactive)
+ (let* ((projectile-require-project-root nil)
+ (helm-ff-default-directory (file-name-directory (projectile-expand-root dir))))
+ (helm-ff-switch-to-eshell dir)))
+
+(defun helm-projectile-files-in-current-dired-buffer ()
+ "Return a list of files (only) in the current dired buffer."
+ (let (flist)
+ (cl-flet ((fpush (fname) (push fname flist)))
+ (save-excursion
+ (let (file buffer-read-only)
+ (goto-char (point-min))
+ (while (not (eobp))
+ (save-excursion
+ (and (not (eolp))
+ (setq file (dired-get-filename t t)) ; nil on non-file
+ (progn (end-of-line)
+ (funcall #'fpush file))))
+ (forward-line 1)))))
+ (mapcar 'file-truename (nreverse flist))))
+
+(defun helm-projectile-all-dired-buffers ()
+ "Get all current Dired buffers."
+ (mapcar (lambda (b)
+ (with-current-buffer b (buffer-name)))
+ (-filter (lambda (b)
+ (with-current-buffer b
+ (and (eq major-mode 'dired-mode)
+ (buffer-name))))
+ (buffer-list))))
+
+(defvar helm-projectile-virtual-dired-remote-enable nil
+ "Enable virtual Dired manager in remote host. Disabled by default.")
+
+(defun helm-projectile-dired-files-new-action (candidate)
+ "Create a Dired buffer from chosen files.
+CANDIDATE is the selected file, but choose the marked files if available."
+ (if (and (file-remote-p (projectile-project-root))
+ (not helm-projectile-virtual-dired-remote-enable))
+ (message "Virtual Dired manager is disabled in remote host. Enable with %s."
+ (propertize "helm-projectile-virtual-dired-remote-enable" 'face 'font-lock-keyword-face))
+ (let ((new-name (completing-read "Select or enter a new buffer name: "
+ (helm-projectile-all-dired-buffers)))
+ (helm--reading-passwd-or-string t)
+ (files (-filter (lambda (f)
+ (not (string= f "")))
+ (mapcar (lambda (file)
+ (replace-regexp-in-string (projectile-project-root) "" file))
+ (helm-marked-candidates :with-wildcard t))))
+ (default-directory (projectile-project-root)))
+ ;; create a unique buffer that is unique to any directory in default-directory
+ ;; or opened buffer; when Dired is passed with a non-existence directory name,
+ ;; it only creates a buffer and insert everything. If a new name user supplied
+ ;; exists as default-directory, Dired throws error when insert anything that
+ ;; does not exist in current directory.
+ (with-current-buffer (dired (cons (make-temp-name new-name)
+ (if files
+ files
+ (list candidate))))
+ (when (get-buffer new-name)
+ (kill-buffer new-name))
+ (rename-buffer new-name)))))
+
+(defun helm-projectile-dired-files-add-action (candidate)
+ "Add files to a Dired buffer.
+CANDIDATE is the selected file. Used when no file is explicitly marked."
+ (if (and (file-remote-p (projectile-project-root))
+ (not helm-projectile-virtual-dired-remote-enable))
+ (message "Virtual Dired manager is disabled in remote host. Enable with %s."
+ (propertize "helm-projectile-virtual-dired-remote-enable" 'face 'font-lock-keyword-face))
+ (if (eq (with-helm-current-buffer major-mode) 'dired-mode)
+ (let* ((helm--reading-passwd-or-string t)
+ (root (projectile-project-root)) ; store root for later use
+ (dired-buffer-name (or (and (eq major-mode 'dired-mode) (buffer-name))
+ (completing-read "Select a Dired buffer:"
+ (helm-projectile-all-dired-buffers))))
+ (dired-files (with-current-buffer dired-buffer-name
+ (helm-projectile-files-in-current-dired-buffer)))
+ (marked-files (helm-marked-candidates :with-wildcard t))
+ (files (sort (mapcar (lambda (file)
+ (replace-regexp-in-string (projectile-project-root) "" file))
+ (cl-nunion (if marked-files
+ marked-files
+ (list candidate))
+ dired-files
+ :test #'string-equal))
+ 'string-lessp)))
+ (kill-buffer dired-buffer-name)
+ ;; Rebind default-directory because after killing a buffer, we
+ ;; could be in any buffer and default-directory is set to that
+ ;; random buffer
+ ;;
+ ;; Also use saved root directory, because after killing a buffer,
+ ;; we could be outside of current project
+ (let ((default-directory root))
+ (with-current-buffer (dired (cons (make-temp-name dired-buffer-name)
+ (if files
+ (mapcar (lambda (file)
+ (replace-regexp-in-string root "" file))
+ files)
+ (list candidate))))
+ (rename-buffer dired-buffer-name))))
+ (error "You're not in a Dired buffer to add."))))
+
+(defun helm-projectile-dired-files-delete-action (candidate)
+ "Delete selected entries from a Dired buffer.
+CANDIDATE is the selected file. Used when no file is explicitly marked."
+ (if (and (file-remote-p (projectile-project-root))
+ (not helm-projectile-virtual-dired-remote-enable))
+ (message "Virtual Dired manager is disabled in remote host. Enable with %s."
+ (propertize "helm-projectile-virtual-dired-remote-enable" 'face 'font-lock-keyword-face))
+ (let* ((helm--reading-passwd-or-string t)
+ (root (projectile-project-root))
+ (dired-buffer-name (with-helm-current-buffer (buffer-name)))
+ (dired-files (with-current-buffer dired-buffer-name
+ (helm-projectile-files-in-current-dired-buffer)))
+ (files (sort (cl-set-exclusive-or (helm-marked-candidates :with-wildcard t)
+ dired-files
+ :test #'string-equal) #'string-lessp)))
+ (kill-buffer dired-buffer-name)
+ ;; similar reason to `helm-projectile-dired-files-add-action'
+ (let ((default-directory root))
+ (with-current-buffer (dired (cons (make-temp-name dired-buffer-name)
+ (if files
+ (mapcar (lambda (file)
+ (replace-regexp-in-string root "" file))
+ files)
+ (list candidate))))
+ (rename-buffer dired-buffer-name))))))
+
+(defvar helm-projectile-find-file-map
+ (let ((map (copy-keymap helm-find-files-map)))
+ (helm-projectile-define-key map
+ (kbd "C-c f") 'helm-projectile-dired-files-new-action
+ (kbd "C-c a") 'helm-projectile-dired-files-add-action
+ (kbd "M-e") 'helm-projectile-switch-to-eshell
+ (kbd "M-.") 'helm-projectile-ff-etags-select-action
+ (kbd "M-!") 'helm-projectile-find-files-eshell-command-on-file-action)
+ (define-key map (kbd "<left>") 'helm-previous-source)
+ (define-key map (kbd "<right>") 'helm-next-source)
+ map)
+ "Mapping for file commands in Helm Projectile.")
+
+(defvar helm-projectile-file-actions
+ (helm-projectile-hack-actions
+ (cdr (assq 'action (or helm-source-find-files
+ (helm-make-source "Find files" 'helm-source-ffiles))))
+ ;; Delete these actions
+ 'helm-ff-browse-project
+ 'helm-insert-file-name-completion-at-point
+ 'helm-ff-find-sh-command
+ 'helm-ff-cache-add-file
+ ;; Substitute these actions
+ '(helm-ff-switch-to-eshell . helm-projectile-switch-to-eshell)
+ '(helm-ff-etags-select . helm-projectile-ff-etags-select-action)
+ '(helm-find-files-eshell-command-on-file
+ . helm-projectile-find-files-eshell-command-on-file-action)
+ ;; Change action descriptions
+ '(helm-find-file-as-root . "Find file as root `C-c r'")
+ ;; New actions
+ '(helm-projectile-dired-files-new-action
+ . "Create Dired buffer from files `C-c f'")
+ '(helm-projectile-dired-files-add-action
+ . "Add files to Dired buffer `C-c a'"))
+ "Action for files.")
+
+(defun helm-projectile-build-dwim-source (candidates)
+ "Dynamically build a Helm source definition for Projectile files based on context with CANDIDATES."
+ ""
+ (helm-build-in-buffer-source "Projectile files"
+ :data candidates
+ :fuzzy-match helm-projectile-fuzzy-match
+ :coerce 'helm-projectile-coerce-file
+ :action-transformer 'helm-find-files-action-transformer
+ :keymap helm-projectile-find-file-map
+ :help-message helm-ff-help-message
+ :mode-line helm-ff-mode-line-string
+ :action helm-projectile-file-actions))
+
+(defvar helm-source-projectile-files-list
+ (helm-build-in-buffer-source "Projectile files"
+ :data (lambda ()
+ (condition-case nil
+ (projectile-current-project-files)
+ (error nil)))
+ :fuzzy-match helm-projectile-fuzzy-match
+ :coerce 'helm-projectile-coerce-file
+ :keymap helm-projectile-find-file-map
+ :help-message 'helm-ff-help-message
+ :mode-line helm-ff-mode-line-string
+ :action helm-projectile-file-actions
+ )
+ "Helm source definition for Projectile files.")
+
+(defvar helm-source-projectile-files-in-all-projects-list
+ (helm-build-in-buffer-source "Projectile files in all Projects"
+ :data (lambda ()
+ (condition-case nil
+ (let ((projectile-require-project-root nil))
+ (projectile-all-project-files))
+ (error nil)))
+ :coerce 'helm-projectile-coerce-file
+ :keymap helm-find-files-map
+ :help-message 'helm-ff-help-message
+ :mode-line helm-ff-mode-line-string
+ :action helm-projectile-file-actions
+ )
+ "Helm source definition for all Projectile files in all projects.")
+
+(defvar helm-projectile-dired-file-actions
+ (helm-projectile-hack-actions
+ helm-projectile-file-actions
+ ;; New actions
+ '(helm-projectile-dired-files-delete-action . "Remove entry(s) from Dired buffer `C-c d'")))
+
+(defvar helm-source-projectile-dired-files-list
+ (helm-build-in-buffer-source "Projectile files in current Dired buffer"
+ :data (lambda ()
+ (condition-case nil
+ (if (and (file-remote-p (projectile-project-root))
+ (not helm-projectile-virtual-dired-remote-enable))
+ nil
+ (let ((default-directory (projectile-project-root)))
+ (when (eq major-mode 'dired-mode)
+ (mapcar (lambda (file)
+ (replace-regexp-in-string default-directory "" file))
+ (helm-projectile-files-in-current-dired-buffer)))))
+ (error nil)))
+ :coerce 'helm-projectile-coerce-file
+ :filter-one-by-one (lambda (file)
+ (let ((default-directory (projectile-project-root)))
+ (helm-ff-filter-candidate-one-by-one file)))
+ :action-transformer 'helm-find-files-action-transformer
+ :keymap (let ((map (copy-keymap helm-projectile-find-file-map)))
+ (helm-projectile-define-key map
+ (kbd "C-c d") 'helm-projectile-dired-files-delete-action)
+ map)
+ :help-message 'helm-ff-help-message
+ :mode-line helm-ff-mode-line-string
+ :action helm-projectile-dired-file-actions)
+ "Helm source definition for Projectile delete files.")
+
+(defun helm-projectile-dired-find-dir (dir)
+ "Jump to a selected directory DIR from helm-projectile."
+ (dired (expand-file-name dir (projectile-project-root)))
+ (run-hooks 'projectile-find-dir-hook))
+
+(defun helm-projectile-dired-find-dir-other-window (dir)
+ "Jump to a selected directory DIR from helm-projectile."
+ (dired-other-window (expand-file-name dir (projectile-project-root)))
+ (run-hooks 'projectile-find-dir-hook))
+
+(defvar helm-source-projectile-directories-list
+ (helm-build-in-buffer-source "Projectile directories"
+ :data (lambda ()
+ (condition-case nil
+ (if projectile-find-dir-includes-top-level
+ (append '("./") (projectile-current-project-dirs))
+ (projectile-current-project-dirs))
+ (error nil)))
+ :fuzzy-match helm-projectile-fuzzy-match
+ :coerce 'helm-projectile-coerce-file
+ :action-transformer 'helm-find-files-action-transformer
+ :keymap (let ((map (make-sparse-keymap)))
+ (set-keymap-parent map helm-map)
+ (helm-projectile-define-key map
+ (kbd "<left>") 'helm-previous-source
+ (kbd "<right>") 'helm-next-source
+ (kbd "C-c o") 'helm-projectile-dired-find-dir-other-window
+ (kbd "M-e") 'helm-projectile-switch-to-eshell
+ (kbd "C-c f") 'helm-projectile-dired-files-new-action
+ (kbd "C-c a") 'helm-projectile-dired-files-add-action
+ (kbd "C-s") 'helm-find-files-grep)
+ map)
+ :help-message 'helm-ff-help-message
+ :mode-line helm-ff-mode-line-string
+ :action '(("Open Dired" . helm-projectile-dired-find-dir)
+ ("Open Dired in other window `C-c o'" . helm-projectile-dired-find-dir)
+ ("Switch to Eshell `M-e'" . helm-projectile-switch-to-eshell)
+ ("Grep in projects `C-s C-u Recurse'" . helm-find-files-grep)
+ ("Create Dired buffer from files `C-c f'" . helm-projectile-dired-files-new-action)
+ ("Add files to Dired buffer `C-c a'" . helm-projectile-dired-files-add-action)))
+ "Helm source for listing project directories.")
+
+(defvar helm-projectile-buffers-list-cache nil)
+
+(defclass helm-source-projectile-buffer (helm-source-sync helm-type-buffer)
+ ((init :initform (lambda ()
+ ;; Issue #51 Create the list before `helm-buffer' creation.
+ (setq helm-projectile-buffers-list-cache (condition-case nil
+ (cdr (projectile-project-buffer-names))
+ (error nil)))
+ (let ((result (cl-loop for b in helm-projectile-buffers-list-cache
+ maximize (length b) into len-buf
+ maximize (length (with-current-buffer b
+ (symbol-name major-mode)))
+ into len-mode
+ finally return (cons len-buf len-mode))))
+ (unless helm-buffer-max-length
+ (setq helm-buffer-max-length (car result)))
+ (unless helm-buffer-max-len-mode
+ ;; If a new buffer is longer that this value
+ ;; this value will be updated
+ (setq helm-buffer-max-len-mode (cdr result))))))
+ (candidates :initform helm-projectile-buffers-list-cache)
+ (matchplugin :initform nil)
+ (match :initform 'helm-buffers-match-function)
+ (persistent-action :initform 'helm-buffers-list-persistent-action)
+ (keymap :initform helm-buffer-map)
+ (volatile :initform t)
+ (mode-line :initform helm-buffer-mode-line-string)
+ (persistent-help
+ :initform
+ "Show this buffer / C-u \\[helm-execute-persistent-action]: Kill this buffer")))
+
+(defvar helm-source-projectile-buffers-list (helm-make-source "Project buffers" 'helm-source-projectile-buffer))
+
+(defvar helm-source-projectile-recentf-list
+ (helm-build-in-buffer-source "Projectile recent files"
+ :data (lambda ()
+ (condition-case nil
+ (projectile-recentf-files)
+ (error nil)))
+ :fuzzy-match helm-projectile-fuzzy-match
+ :coerce 'helm-projectile-coerce-file
+ :keymap helm-projectile-find-file-map
+ :help-message 'helm-ff-help-message
+ :mode-line helm-ff-mode-line-string
+ :action helm-projectile-file-actions
+ )
+ "Helm source definition for recent files in current project.")
+
+(defvar helm-source-projectile-files-and-dired-list
+ '(helm-source-projectile-dired-files-list
+ helm-source-projectile-files-list))
+
+(defvar helm-source-projectile-directories-and-dired-list
+ '(helm-source-projectile-dired-files-list
+ helm-source-projectile-directories-list))
+
+(defcustom helm-projectile-sources-list
+ '(helm-source-projectile-buffers-list
+ helm-source-projectile-files-list
+ helm-source-projectile-projects
+ )
+ "Default sources for `helm-projectile'."
+ :group 'helm-projectile)
+
+(defmacro helm-projectile-command (command source prompt &optional not-require-root)
+ "Template for generic helm-projectile commands.
+COMMAND is a command name to be appended with \"helm-projectile\" prefix.
+SOURCE is a Helm source that should be Projectile specific.
+PROMPT is a string for displaying as a prompt.
+NOT-REQUIRE-ROOT specifies the command doesn't need to be used in a
+project root."
+ `(defun ,(intern (concat "helm-projectile-" command)) (&optional arg)
+ "Use projectile with Helm for finding files in project
+
+With a prefix ARG invalidates the cache first."
+ (interactive "P")
+ (if (projectile-project-p)
+ (projectile-maybe-invalidate-cache arg)
+ (unless ,not-require-root
+ (error "You're not in a project")))
+ (let ((helm-ff-transformer-show-only-basename nil)
+ ;; for consistency, we should just let Projectile take care of ignored files
+ (helm-boring-file-regexp-list nil))
+ (helm :sources ,source
+ :buffer "*helm projectile*"
+ :prompt (projectile-prepend-project-name ,prompt)))))
+
+(helm-projectile-command "switch-project" 'helm-source-projectile-projects "Switch to project: " t)
+(helm-projectile-command "find-file" helm-source-projectile-files-and-dired-list "Find file: ")
+(helm-projectile-command "find-file-in-known-projects" 'helm-source-projectile-files-in-all-projects-list "Find file in projects: ")
+(helm-projectile-command "find-dir" helm-source-projectile-directories-and-dired-list "Find dir: ")
+(helm-projectile-command "recentf" 'helm-source-projectile-recentf-list "Recently visited file: ")
+(helm-projectile-command "switch-to-buffer" 'helm-source-projectile-buffers-list "Switch to buffer: ")
+
+;;;###autoload
+(defun helm-projectile-find-file-dwim ()
+ "Find file at point based on context."
+ (interactive)
+ (let* ((project-files (projectile-current-project-files))
+ (files (projectile-select-files project-files)))
+ (if (= (length files) 1)
+ (find-file (expand-file-name (car files) (projectile-project-root)))
+ (helm :sources (helm-projectile-build-dwim-source (if (> (length files) 1)
+ files
+ project-files))
+ :buffer "*helm projectile*"
+ :prompt (projectile-prepend-project-name "Find file: ")))))
+
+;;;###autoload
+(defun helm-projectile-find-other-file (&optional flex-matching)
+ "Switch between files with the same name but different extensions using Helm.
+With FLEX-MATCHING, match any file that contains the base name of current file.
+Other file extensions can be customized with the variable `projectile-other-file-alist'."
+ (interactive "P")
+ (-if-let (other-files (projectile-get-other-files (buffer-file-name)
+ (projectile-current-project-files)
+ flex-matching))
+ (if (= (length other-files) 1)
+ (find-file (expand-file-name (car other-files) (projectile-project-root)))
+ (progn
+ (let* ((helm-ff-transformer-show-only-basename nil))
+ (helm :sources (helm-build-in-buffer-source "Projectile other files"
+ :data other-files
+ :coerce 'helm-projectile-coerce-file
+ :keymap (let ((map (copy-keymap helm-find-files-map)))
+ (define-key map (kbd "<left>") 'helm-previous-source)
+ (define-key map (kbd "<right>") 'helm-next-source)
+ map)
+ :help-message helm-ff-help-message
+ :mode-line helm-ff-mode-line-string
+ :action helm-projectile-file-actions)
+ :buffer "*helm projectile*"
+ :prompt (projectile-prepend-project-name "Find other file: ")))))
+ (error "No other file found")))
+
+(defun helm-projectile-grep-or-ack (&optional use-ack-p ack-ignored-pattern ack-executable)
+ "Perform helm-grep at project root.
+USE-ACK-P indicates whether to use ack or not.
+ACK-IGNORED-PATTERN is a file regex to exclude from searching.
+ACK-EXECUTABLE is the actual ack binary name.
+It is usually \"ack\" or \"ack-grep\".
+If it is nil, or ack/ack-grep not found then use default grep command."
+ (let* ((default-directory (projectile-project-root))
+ (helm-ff-default-directory (projectile-project-root))
+ (follow (and helm-follow-mode-persistent
+ (assoc-default 'follow helm-source-grep)))
+ (helm-grep-in-recurse t)
+ (grep-find-ignored-files (-union (projectile-ignored-files-rel) grep-find-ignored-files))
+ (grep-find-ignored-directories (-union (projectile-ignored-directories-rel) grep-find-ignored-directories))
+ (helm-grep-default-command (if use-ack-p
+ (concat ack-executable " -H --no-group --no-color " ack-ignored-pattern " %p %f")
+ "grep -a -r %e -n%cH -e %p %f ."))
+ (helm-grep-default-recurse-command helm-grep-default-command)
+ (helm-source-grep
+ (helm-build-async-source
+ (capitalize (helm-grep-command t))
+ :header-name (lambda (name)
+ (let ((name (if use-ack-p
+ "Helm Projectile Ack"
+ "Helm Projectile Grep")))
+ (concat name " " "(C-c ? Help)")))
+ :candidates-process 'helm-grep-collect-candidates
+ :filter-one-by-one 'helm-grep-filter-one-by-one
+ :candidate-number-limit 9999
+ :nohighlight t
+ :mode-line helm-grep-mode-line-string
+ ;; We need to specify keymap here and as :keymap arg [1]
+ ;; to make it available in further resuming.
+ :keymap helm-grep-map
+ :history 'helm-grep-history
+ :action (helm-make-actions
+ "Find file" 'helm-grep-action
+ "Find file other frame" 'helm-grep-other-frame
+ (lambda () (and (locate-library "elscreen")
+ "Find file in Elscreen"))
+ 'helm-grep-jump-elscreen
+ "Save results in grep buffer" 'helm-grep-save-results
+ "Find file other window" 'helm-grep-other-window)
+ :persistent-action 'helm-grep-persistent-action
+ :persistent-help "Jump to line (`C-u' Record in mark ring)"
+ :requires-pattern 2)))
+ (helm
+ :sources 'helm-source-grep
+ :input (if (region-active-p)
+ (buffer-substring-no-properties (region-beginning) (region-end))
+ (thing-at-point 'symbol))
+ :buffer (format "*helm %s*" (if use-ack-p
+ "ack"
+ "grep"))
+ :default-directory (projectile-project-root)
+ :keymap helm-grep-map
+ :history 'helm-grep-history
+ :truncate-lines t)))
+
+;;;###autoload
+(defun helm-projectile-on ()
+ "Turn on helm-projectile key bindings."
+ (interactive)
+ (message "Turn on helm-projectile key bindings")
+ (helm-projectile-toggle 1))
+
+;;;###autoload
+(defun helm-projectile-off ()
+ "Turn off helm-projectile key bindings."
+ (interactive)
+ (message "Turn off helm-projectile key bindings")
+ (helm-projectile-toggle -1))
+
+;;;###autoload
+(defun helm-projectile-grep ()
+ "Helm version of projectile-grep."
+ (interactive)
+ (if (projectile-project-p)
+ (funcall'run-with-timer 0.01 nil
+ #'helm-projectile-grep-or-ack nil)
+ (error "You're not in a project")))
+
+;;;###autoload
+(defun helm-projectile-ack ()
+ "Helm version of projectile-ack."
+ (interactive)
+ (if (projectile-project-p)
+ (let ((ack-ignored (mapconcat
+ 'identity
+ (-union (-map (lambda (path)
+ (concat "--ignore-dir=" (file-name-nondirectory (directory-file-name path))))
+ (projectile-ignored-directories))
+ (-map (lambda (path)
+ (concat "--ignore-file=match:" (shell-quote-argument path)))
+ (projectile-ignored-files))) " "))
+ (helm-ack-grep-executable (cond
+ ((executable-find "ack") "ack")
+ ((executable-find "ack-grep") "ack-grep")
+ (t (error "ack or ack-grep is not available.")))))
+ (funcall 'run-with-timer 0.01 nil
+ #'helm-projectile-grep-or-ack t ack-ignored helm-ack-grep-executable))
+ (error "You're not in a project")))
+
+;;;###autoload
+(defun helm-projectile-ag (&optional options)
+ "Helm version of projectile-ag."
+ (interactive (if current-prefix-arg (list (read-string "option: " "" 'helm-ag-command-history))))
+ (if (require 'helm-ag nil 'noerror)
+ (if (projectile-project-p)
+ (let* ((grep-find-ignored-files (-union (projectile-ignored-files-rel) grep-find-ignored-files))
+ (grep-find-ignored-directories (-union (projectile-ignored-directories-rel) grep-find-ignored-directories))
+ (ignored (mapconcat (lambda (i)
+ (concat "--ignore " i))
+ (append grep-find-ignored-files grep-find-ignored-directories)
+ " "))
+ (helm-ag-command-option options)
+ (helm-ag-base-command (concat helm-ag-base-command " " ignored)))
+ (helm-do-ag (projectile-project-root)))
+ (error "You're not in a project"))
+ (error "helm-ag not available")))
+
+(defun helm-projectile-commander-bindings ()
+ (def-projectile-commander-method ?a
+ "Run ack on project."
+ (call-interactively 'helm-projectile-ack))
+
+ (def-projectile-commander-method ?A
+ "Find ag on project."
+ (call-interactively 'helm-projectile-ag))
+
+ (def-projectile-commander-method ?f
+ "Find file in project."
+ (helm-projectile-find-file))
+
+ (def-projectile-commander-method ?b
+ "Switch to project buffer."
+ (helm-projectile-switch-to-buffer))
+
+ (def-projectile-commander-method ?d
+ "Find directory in project."
+ (helm-projectile-find-dir))
+
+ (def-projectile-commander-method ?g
+ "Run grep on project."
+ (helm-projectile-grep))
+
+ (def-projectile-commander-method ?s
+ "Switch project."
+ (helm-projectile-switch-project))
+
+ (def-projectile-commander-method ?e
+ "Find recently visited file in project."
+ (helm-projectile-recentf)))
+
+(defun helm-projectile-toggle (toggle)
+ "Toggle Helm version of Projectile commands."
+ (if (> toggle 0)
+ (progn
+ (setq projectile-switch-project-action 'helm-projectile-find-file)
+ (define-key projectile-command-map (kbd "a") 'helm-projectile-find-other-file)
+ (define-key projectile-command-map (kbd "f") 'helm-projectile-find-file)
+ (define-key projectile-command-map (kbd "F") 'helm-projectile-find-file-in-known-projects)
+ (define-key projectile-command-map (kbd "g") 'helm-projectile-find-file-dwim)
+ (define-key projectile-command-map (kbd "d") 'helm-projectile-find-dir)
+ (define-key projectile-command-map (kbd "p") 'helm-projectile-switch-project)
+ (define-key projectile-command-map (kbd "e") 'helm-projectile-recentf)
+ (define-key projectile-command-map (kbd "b") 'helm-projectile-switch-to-buffer)
+ (define-key projectile-command-map (kbd "s g") 'helm-projectile-grep)
+ (define-key projectile-command-map (kbd "s a") 'helm-projectile-ack)
+ (define-key projectile-command-map (kbd "s s") 'helm-projectile-ag)
+ (helm-projectile-commander-bindings))
+ (progn
+ (setq projectile-switch-project-action 'projectile-find-file)
+ (define-key projectile-command-map (kbd "a") 'projectile-find-other-file)
+ (define-key projectile-command-map (kbd "f") 'projectile-find-file)
+ (define-key projectile-command-map (kbd "F") 'projectile-find-file-in-known-projects)
+ (define-key projectile-command-map (kbd "g") 'helm-projectile-find-file-dwim)
+ (define-key projectile-command-map (kbd "d") 'projectile-find-dir)
+ (define-key projectile-command-map (kbd "p") 'projectile-switch-project)
+ (define-key projectile-command-map (kbd "e") 'projectile-recentf)
+ (define-key projectile-command-map (kbd "b") 'projectile-switch-to-buffer)
+ (define-key projectile-command-map (kbd "s g") 'projectile-grep)
+ (define-key projectile-command-map (kbd "s s") 'projectile-ag)
+ (projectile-commander-bindings))))
+
+;;;###autoload
+(defun helm-projectile (&optional arg)
+ "Use projectile with Helm instead of ido.
+
+With a prefix ARG invalidates the cache first.
+If invoked outside of a project, displays a list of known projects to jump."
+ (interactive "P")
+ (if (projectile-project-p)
+ (projectile-maybe-invalidate-cache arg))
+ (let ((helm-ff-transformer-show-only-basename nil))
+ (helm :sources helm-projectile-sources-list
+ :buffer "*helm projectile*"
+ :prompt (projectile-prepend-project-name (if (projectile-project-p)
+ "pattern: "
+ "Switch to project: ")))))
+
+;;;###autoload
+(eval-after-load 'projectile
+ '(progn
+ (define-key projectile-command-map (kbd "h") 'helm-projectile)))
+
+(provide 'helm-projectile)
+
+;;; helm-projectile.el ends here
--- /dev/null
+This library provides easy project management and navigation. The
+concept of a project is pretty basic - just a folder containing
+special file. Currently git, mercurial and bazaar repos are
+considered projects by default. If you want to mark a folder
+manually as a project just create an empty .projectile file in
+it. See the README for more details.
--- /dev/null
+;;; ht-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+
+\f
+;;;### (autoloads nil nil ("ht-pkg.el" "ht.el") (21490 25514 19539
+;;;;;; 0))
+
+;;;***
+\f
+(provide 'ht-autoloads)
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; coding: utf-8
+;; End:
+;;; ht-autoloads.el ends here
--- /dev/null
+(define-package "ht" "20140122.1555" "The missing hash table library for Emacs" (quote nil))
--- /dev/null
+;;; ht.el --- The missing hash table library for Emacs
+
+;; Copyright (C) 2013 Wilfred Hughes
+
+;; Author: Wilfred Hughes <meNOSPAMwilfred.me.uk>
+;; Version: 20140122.1555
+;; X-Original-Version: 2.0
+;; Keywords: hash table, hash map, hash
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; The missing hash table utility library for Emacs.
+;;
+;; See documentation on https://github.com/Wilfred/ht.el
+
+;;; Code:
+
+(eval-when-compile (require 'cl)) ;; dolist
+
+(defmacro ht (&rest pairs)
+ "Create a hash table with the key-value pairs given.
+Keys are compared with `equal'.
+
+\(fn (KEY-1 VALUE-1) (KEY-2 VALUE-2) ...)"
+ (let* ((table-symbol (make-symbol "ht-temp"))
+ (assignments
+ (mapcar
+ (lambda (pair) `(ht-set! ,table-symbol ,@pair))
+ pairs)))
+ `(let ((,table-symbol (ht-create)))
+ ,@assignments
+ ,table-symbol)))
+
+(defun ht-create (&optional test)
+ "Create an empty hash table.
+
+TEST indicates the function used to compare the hash
+keys. Default is `equal'. It can be `eq', `eql', `equal' or a
+user-supplied test created via `define-hash-table-test'."
+ (make-hash-table :test (or test 'equal)))
+
+(defun ht<-alist (alist)
+ "Create a hash table with initial values according to ALIST."
+ (let ((h (ht-create)))
+ ;; the first key-value pair in an alist gets precedence, so we
+ ;; start from the end of the list:
+ (dolist (pair (reverse alist) h)
+ (let ((key (car pair))
+ (value (cdr pair)))
+ (ht-set! h key value)))))
+
+(defalias 'ht-from-alist 'ht<-alist)
+
+;; based on the excellent -partition from dash.el, but we aim to be self-contained
+(defun ht/group-pairs (list)
+ "Return a new list with the items in LIST grouped into pairs.
+Errors if LIST doesn't contain an even number of elements."
+ (let ((result)
+ (sublist)
+ (len 0))
+
+ (while list
+ ;; take the head of LIST and push onto SUBLIST
+ (setq sublist (cons (car list) sublist))
+ (setq list (cdr list))
+
+ (setq len (1+ len))
+
+ (when (= len 2)
+ ;; push this two-item list onto RESULT
+ (setq result (cons (nreverse sublist) result))
+ (setq sublist nil)
+ (setq len 0)))
+
+ (when sublist (error "Expected an even number of elements"))
+ (nreverse result)))
+
+(defun ht<-plist (plist)
+ "Create a hash table with initial values according to PLIST."
+ (let ((h (ht-create)))
+ (dolist (pair (ht/group-pairs plist) h)
+ (let ((key (car pair))
+ (value (cadr pair)))
+ (ht-set! h key value)))))
+
+(defalias 'ht-from-plist 'ht<-plist)
+
+(defun ht-get (table key &optional default)
+ "Look up KEY in TABLE, and return the matching value.
+If KEY isn't present, return DEFAULT (nil if not specified)."
+ (gethash key table default))
+
+(defun ht-set! (table key value)
+ "Associate KEY in TABLE with VALUE."
+ (puthash key value table)
+ nil)
+
+(defalias 'ht-set 'ht-set!)
+
+(defun ht-update! (table from-table)
+ "Update TABLE according to every key-value pair in FROM-TABLE."
+ (maphash
+ (lambda (key value) (puthash key value table))
+ from-table)
+ nil)
+
+(defalias 'ht-update 'ht-update!)
+
+(defun ht-merge (&rest tables)
+ "Crete a new tables that includes all the key-value pairs from TABLES.
+If multiple have tables have the same key, the value in the last
+table is used."
+ (let ((merged (ht-create)))
+ (mapc (lambda (table) (ht-update! merged table)) tables)
+ merged))
+
+(defun ht-remove! (table key)
+ "Remove KEY from TABLE."
+ (remhash key table))
+
+(defalias 'ht-remove 'ht-remove!)
+
+(defun ht-clear! (table)
+ "Remove all keys from TABLE."
+ (clrhash table)
+ nil)
+
+(defalias 'ht-clear 'ht-clear!)
+
+(defun ht-map (function table)
+ "Apply FUNCTION to each key-value pair of TABLE, and make a list of the results.
+FUNCTION is called with two arguments, KEY and VALUE."
+ (let (results)
+ (maphash
+ (lambda (key value)
+ (push (funcall function key value) results))
+ table)
+ results))
+
+(defmacro ht-amap (form table)
+ "Anaphoric version of `ht-map'.
+For every key-value pair in TABLE, evaluate FORM with the
+variables KEY and VALUE bound."
+ `(ht-map (lambda (key value) ,form) ,table))
+
+(defun ht-keys (table)
+ "Return a list of all the keys in TABLE."
+ (ht-amap key table))
+
+(defun ht-values (table)
+ "Return a list of all the values in TABLE."
+ (ht-amap value table))
+
+(defun ht-items (table)
+ "Return a list of two-element lists '(key value) from TABLE."
+ (ht-amap (list key value) table))
+
+(defalias 'ht-each 'maphash
+ "Apply FUNCTION to each key-value pair of TABLE.
+Returns nil, used for side-effects only.")
+
+(defmacro ht-aeach (form table)
+ "Anaphoric version of `ht-each'.
+For every key-value pair in TABLE, evaluate FORM with the
+variables key and value bound."
+ `(ht-each (lambda (key value) ,form) ,table))
+
+(defun ht->plist (table)
+ "Return a flat list '(key1 value1 key2 value2...) from TABLE.
+
+Note that hash tables are unordered, so this cannot be an exact
+inverse of `ht<-plist'. The following is not guaranteed:
+
+\(let ((data '(a b c d)))
+ (equalp data
+ (ht->plist (ht<-plist data))))"
+ (apply 'append (ht-items table)))
+
+(defalias 'ht-to-plist 'ht->plist)
+
+(defun ht-copy (table)
+ "Return a shallow copy of TABLE (keys and values are shared)."
+ (copy-hash-table table))
+
+(defun ht->alist (table)
+ "Return a list of two-element lists '(key . value) from TABLE.
+
+Note that hash tables are unordered, so this cannot be an exact
+inverse of `ht<-alist'. The following is not guaranteed:
+
+\(let ((data '((a . b) (c . d))))
+ (equalp data
+ (ht->alist (ht<-alist data))))"
+ (ht-amap (cons key value) table))
+
+(defalias 'ht-to-alist 'ht->alist)
+
+(defalias 'ht? 'hash-table-p)
+
+(defalias 'ht-p 'hash-table-p)
+
+(defun ht-contains? (table key)
+ "Return 't if TABLE contains KEY."
+ (not (eq (ht-get table key 'ht--not-found) 'ht--not-found)))
+
+(defalias 'ht-contains-p 'ht-contains?)
+
+(defun ht-size (table)
+ "Return the actual number of entries in TABLE."
+ (hash-table-count table))
+
+(defun ht-empty? (table)
+ "Return true if the actual number of entries in TABLE is zero."
+ (zerop (ht-size table)))
+
+(defun ht-select (function table)
+ "Return a hash table containing all entries in TABLE for which
+FUNCTION returns a truthy value.
+
+FUNCTION is called with two arguments, KEY and VALUE."
+ (let ((results (ht-create)))
+ (ht-each
+ (lambda (key value)
+ (when (funcall function key value)
+ (ht-set! results key value)))
+ table)
+ results))
+
+(defun ht-reject (function table)
+ "Return a hash table containing all entries in TABLE for which
+FUNCTION returns a falsy value.
+
+FUNCTION is called with two arguments, KEY and VALUE."
+ (let ((results (ht-create)))
+ (ht-each
+ (lambda (key value)
+ (unless (funcall function key value)
+ (ht-set! results key value)))
+ table)
+ results))
+
+(defun ht-reject! (function table)
+ "Delete entries from TABLE for which FUNCTION returns a falsy value.
+
+FUNCTION is called with two arguments, KEY and VALUE."
+ (ht-each
+ (lambda (key value)
+ (when (funcall function key value)
+ (remhash key table)))
+ table)
+ nil)
+
+(defalias 'ht-delete-if 'ht-reject!)
+
+(defun ht-find (function table)
+ "Return (key, value) from TABLE for which FUNCTION returns a truthy value.
+Return nil otherwise.
+
+FUNCTION is called with two arguments, KEY and VALUE."
+ (catch 'break
+ (ht-each
+ (lambda (key value)
+ (when (funcall function key value)
+ (throw 'break (list key value))))
+ table)))
+
+(provide 'ht)
+;;; ht.el ends here
+++ /dev/null
-;;; impatient-mode-autoloads.el --- automatically extracted autoloads
-;;
-;;; Code:
-
-\f
-;;;### (autoloads (impatient-mode) "impatient-mode" "impatient-mode.el"
-;;;;;; (21595 16735 0 0))
-;;; Generated autoloads from impatient-mode.el
-
-(autoload 'impatient-mode "impatient-mode" "\
-Serves the buffer live over HTTP.
-
-\(fn &optional ARG)" t nil)
-
-;;;***
-\f
-;;;### (autoloads nil nil ("impatient-mode-pkg.el") (21595 16735
-;;;;;; 291260 0))
-
-;;;***
-\f
-(provide 'impatient-mode-autoloads)
-;; Local Variables:
-;; version-control: never
-;; no-byte-compile: t
-;; no-update-autoloads: t
-;; coding: utf-8
-;; End:
-;;; impatient-mode-autoloads.el ends here
+++ /dev/null
-(define-package "impatient-mode" "20140908.2000" "Serve buffers live over HTTP"
- '((cl-lib "0.3")
- (simple-httpd "1.4.0")
- (htmlize "1.40"))
- :url "https://github.com/netguy204/imp.el")
-;; Local Variables:
-;; no-byte-compile: t
-;; End:
+++ /dev/null
-;;; impatient-mode.el --- Serve buffers live over HTTP
-
-;; This is free and unencumbered software released into the public domain.
-
-;; Author: Brian Taylor <el.wuboNOSPAMgmail.com>
-;; Version: 1.0.0
-;; URL: https://github.com/netguy204/imp.el
-;; Package-Requires: ((cl-lib "0.3") (simple-httpd "1.4.0") (htmlize "1.40"))
-
-;;; Commentary:
-
-;; impatient-mode is a minor mode that publishes the live buffer
-;; through the local simple-httpd server under /imp/<buffer-name>. To
-;; unpublish a buffer, toggle impatient-mode off.
-
-;; Start the simple-httpd server (`httpd-start') and visit /imp/ on
-;; the local server. There will be a listing of all the buffers that
-;; currently have impatient-mode enabled. This is likely to be found
-;; here:
-
-;; http://localhost:8080/imp/
-
-;; Except for html-mode buffers, buffers will be prettied up with
-;; htmlize before being sent to clients. This can be toggled at any
-;; time with `imp-toggle-htmlize'.
-
-;; Because html-mode buffers are sent raw, you can use impatient-mode
-;; see your edits to an HTML document live! This is perhaps the
-;; primary motivation of this mode.
-
-;; To receive updates the browser issues a long poll on the client
-;; waiting for the buffer to change -- server push. The response
-;; happens in an `after-change-functions' hook. Buffers that do not
-;; run these hooks will not be displayed live to clients.
-
-;;; Code:
-
-(require 'cl-lib)
-(require 'url-util)
-(require 'simple-httpd)
-(require 'htmlize)
-
-(defgroup impatient-mode nil
- "Serve buffers live over HTTP."
- :group 'comm)
-
-(defvar impatient-mode-map (make-sparse-keymap)
- "Keymap for impatient-mode.")
-
-(make-variable-buffer-local
- (defvar imp-user-filter #'imp-htmlize-filter
- "Per buffer html-producing function by user."))
-
-(make-variable-buffer-local
- (defvar imp-client-list ()
- "List of client processes watching the current buffer."))
-
-(make-variable-buffer-local
- (defvar imp-last-state 0
- "State sequence number."))
-
-(make-variable-buffer-local
- (defvar imp-related-files nil
- "Files that seem to be related to this buffer"))
-
-(defvar imp-default-user-filters
- '((html-mode . nil)
- (web-mode . nil))
- "Alist indicating which filter should be used for which modes.")
-
-;;;###autoload
-(define-minor-mode impatient-mode
- "Serves the buffer live over HTTP."
- :group 'impatient-mode
- :lighter " imp"
- :keymap impatient-mode-map
- (if (not impatient-mode)
- (remove-hook 'after-change-functions 'imp--on-change t)
- (add-hook 'after-change-functions 'imp--on-change nil t)
- (imp-remove-user-filter)))
-
-(defvar imp-shim-root (file-name-directory load-file-name)
- "Location of data files needed by impatient-mode.")
-
-(defun imp-set-user-filter (function)
- "Sets a user-defined filter for this buffer.
-FUNCTION should accept one argument, the buffer to be filtered,
-and will be evaluated with the output buffer set as the current
-buffer."
- (interactive "aCustom filter: ")
- (setq imp-user-filter function)
- (cl-incf imp-last-state)
- (imp--notify-clients))
-
-(defun imp-remove-user-filter ()
- "Sets the user-defined filter for this buffer to the default."
- (interactive)
- (let ((lookup (assoc major-mode imp-default-user-filters)))
- (if lookup
- (imp-set-user-filter (cdr lookup))
- (kill-local-variable 'imp-user-filter)))
- (cl-incf imp-last-state)
- (imp--notify-clients))
-
-(defun imp-htmlize-filter (buffer)
- "Htmlization of buffers before sending to clients."
- (let ((html-buffer (save-match-data (htmlize-buffer buffer))))
- (insert-buffer-substring html-buffer)
- (kill-buffer html-buffer)))
-
-(defun imp-toggle-htmlize ()
- "Toggle htmlize of buffer."
- (interactive)
- (if (eq imp-user-filter 'imp-htmlize-filter)
- (imp-set-user-filter nil)
- (imp-set-user-filter 'imp-htmlize-filter)))
-
-(defun imp-visit-buffer ()
- "Visit the buffer in a browser."
- (interactive)
- (impatient-mode)
- (browse-url (format "http://%s:%d/imp/live/%s"
- system-name httpd-port (buffer-name))))
-
-(defun imp-buffer-enabled-p (buffer)
- "Return t if buffer has impatient-mode enabled."
- (and buffer (with-current-buffer (get-buffer buffer) impatient-mode)))
-
-(defun imp--buffer-list ()
- "List of all buffers with impatient-mode enabled"
- (cl-remove-if-not 'imp-buffer-enabled-p (buffer-list)))
-
-(defun imp--should-not-cache-p (path)
- "True if the path should be stamped with a no-cache header"
- (let ((mime-type (httpd-get-mime (file-name-extension path))))
- (member mime-type '("text/css" "text/html" "text/xml"
- "text/plain" "text/javascript"))))
-
-(defun httpd/imp/static (proc path query req)
- "Serve up static files."
- (let* ((file (file-name-nondirectory path))
- (clean (expand-file-name file imp-shim-root)))
- (if (file-exists-p clean)
- (httpd-send-file proc clean req)
- (httpd-error proc 404))))
-
-(defun imp-serve-buffer-list (proc)
- "Serve a list of published buffers."
- (with-httpd-buffer proc "text/html; charset=utf-8"
- (insert "<html><head>\n")
- (insert "<title>impatient-mode buffer list</title>\n")
- (insert "</head><body>\n")
- (insert "<h1>Public Buffers</h1>\n<hr/>")
- (insert "<ul>\n")
- (dolist (buffer (imp--buffer-list))
- (insert (format "<li><a href=\"live/%s/\">%s</a></li>\n"
- (url-hexify-string (buffer-name buffer))
- (url-insert-entities-in-string (buffer-name buffer)))))
- (insert "</ul>\n<hr/>")
- (insert "Enable <code>impatient-mode</code> in buffers to publish them.")
- (insert "</body></html>")))
-
-(defun imp--private (proc buffer-name)
- (httpd-error proc 403
- (format "Buffer %s is private or doesn't exist." buffer-name)))
-
-(defun httpd/imp/live (proc path query req)
- "Serve up the shim that lets us watch a buffer change"
- (let* ((index (expand-file-name "index.html" imp-shim-root))
- (parts (cdr (split-string path "/")))
- (buffer-name (nth 2 parts))
- (file (httpd-clean-path (mapconcat 'identity (nthcdr 3 parts) "/")))
- (buffer (get-buffer buffer-name))
- (buffer-file (buffer-file-name buffer))
- (buffer-dir (and buffer-file (file-name-directory buffer-file))))
-
- (cond
- ((equal (file-name-directory path) "/imp/live/")
- (httpd-redirect proc (concat path "/")))
- ((not (imp-buffer-enabled-p buffer)) (imp--private proc buffer-name))
- ((and (not (string= file "./")) buffer-dir)
- (let* ((full-file-name (expand-file-name file buffer-dir))
- (live-buffer (cl-remove-if-not
- (lambda (buf) (equal full-file-name (buffer-file-name buf)))
- (imp--buffer-list))))
- (add-to-list 'imp-related-files full-file-name)
- (if live-buffer
- (with-temp-buffer
- (insert-buffer-substring (cl-first live-buffer))
- (if (imp--should-not-cache-p path)
- (httpd-send-header proc "text/plain" 200
- :Cache-Control "no-cache")
- (httpd-send-header proc "text/plain" 200
- :Cache-Control
- "max-age=60, must-revalidate")))
- (httpd-send-file proc full-file-name req))))
- (t (imp-buffer-enabled-p buffer) (httpd-send-file proc index req)))))
-
-(defun httpd/imp (proc path &rest args)
- (cond
- ((equal path "/imp") (httpd-redirect proc "/imp/"))
- ((equal path "/imp/") (imp-serve-buffer-list proc))
- (t (httpd-error proc 403 (format "%s not found" path)))))
-
-(defun imp--send-state (proc)
- (let ((id (number-to-string imp-last-state))
- (user-filter imp-user-filter)
- (buffer (current-buffer)))
- (with-temp-buffer
- (if user-filter
- (funcall user-filter buffer)
- (insert-buffer-substring buffer))
- (httpd-send-header proc "text/html" 200
- :Cache-Control "no-cache"
- :X-Imp-Count id))))
-
-(defun imp--send-state-ignore-errors (proc)
- (condition-case error-case
- (imp--send-state proc)
- (error nil)))
-
-(defun imp--notify-clients ()
- (while imp-client-list
- (imp--send-state-ignore-errors (pop imp-client-list))))
-
-(defun imp--on-change (&rest args)
- "Hook for after-change-functions."
- (cl-incf imp-last-state)
-
- ;; notify our clients
- (imp--notify-clients)
-
- ;; notify any clients that we're in the imp-related-files list for
- (let ((buffer-file (buffer-file-name (current-buffer))))
- (dolist (buffer (imp--buffer-list))
- (with-current-buffer buffer
- (when (member buffer-file imp-related-files)
- (imp--notify-clients))))))
-
-(defun httpd/imp/buffer (proc path query &rest args)
- "Servlet that accepts long poll requests."
- (let* ((buffer-name (file-name-nondirectory path))
- (buffer (get-buffer buffer-name))
- (req-last-id (string-to-number (or (cadr (assoc "id" query)) "0"))))
- (if (imp-buffer-enabled-p buffer)
- (with-current-buffer buffer
- (if (equal req-last-id imp-last-state)
- (push proc imp-client-list) ; this client is sync'd
- (imp--send-state-ignore-errors proc))) ; this client is behind
- (imp--private proc buffer-name))))
-
-(provide 'impatient-mode)
-
-;;; impatient-mode.el ends here
+++ /dev/null
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
- <head>
- <script type="text/javascript" src="/imp/static/jquery.js"></script>
-
- <style type="text/css">
- html {
- overflow: auto;
- }
- html, body, div, iframe {
- margin: 0px;
- padding: 0px;
- height: 100%;
- border: none;
- }
- iframe {
- display: block;
- width: 100%;
- border: none;
- overflow-y: auto;
- overflow-x: hidden;
- }
- </style>
- </head>
-
- <body>
- <iframe id="content" name="content" src="/imp/static/loading.html" frameborder="0" marginheight="0" marginwidth="0" width="100%" height="100%" scrolling="auto">
- </iframe>
-
- <script type="text/javascript">
- var buffer = window.location.pathname.split('/')[3];
- var max_period = 60000;
- var min_period = 1000;
- var next_period = min_period;
- var alpha = 1.2;
- var current_id = '-1';
- $('head').append($('<title/>').text(decodeURI(buffer)));
-
- var nextTimeout = function() {
- var next = next_period;
- next_period = Math.min(max_period, next_period * alpha);
- return next;
- };
-
- var resetTimeout = function() {
- next_period = min_period;
- };
-
- var frameToDocument = function(iframe) {
- return (iframe.contentDocument) ? iframe.contentDocument : iframe.Document;
- };
-
- var printIframe = function(data) {
- var iframeJQ = $('#content');
- var doc = frameToDocument(iframeJQ[0]);
- var iwindow = iframeJQ[0].contentWindow;
- var offX = iwindow.pageXOffset, offY = iwindow.pageYOffset;
- if (offY && offY >= $(doc.body).height() - $(iwindow).height()) {
- offY = null; // follow the end of the buffer
- }
- doc.open();
- doc.write(data);
- doc.close();
- iwindow.scrollTo(offX, offY != null ? offY : $(doc.body).height());
- };
-
- var setIframe = function(count, newText) {
- if(!count) {
- // error parsing client result
- printIframe('0', 'error parsing the response from emacs');
- } else {
- current_id = count;
- printIframe(newText);
- lastText = newText;
- }
- };
-
- var refresh = function() {
- var url = "/imp/buffer/" + buffer;
-
- var gotData = function(data, status, xhr) {
- resetTimeout();
- setIframe(xhr.getResponseHeader("X-Imp-Count"), data);
- refresh();
- };
-
- var errorRetry = function() {
- setTimeout(refresh, nextTimeout());
- };
-
- $.get(url + '?id=' + current_id, gotData).error(errorRetry);
- };
-
- $(document).ready(function() {
- var iframeJQ = $('#content');
- iframeJQ.load(function() {
- // now we need to tweak the stylesheet links so that firefox will refresh them properly
- $('link', frameToDocument(iframeJQ[0])).each(function(index, el) {
- var href = $(el).attr('href');
- // don't refresh the random data:text stylesheets that chrome inserts
- if(href && href.indexOf('data:text') != 0) {
- $(el).attr('href', $(el).attr('href') + '?' + new Date().getTime());
- }
- });
- });
-
- refresh();
- });
- </script>
- </body>
-</html>
+++ /dev/null
-/*!
- * jQuery JavaScript Library v1.5.2
- * http://jquery.com/
- *
- * Copyright 2011, John Resig
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- *
- * Includes Sizzle.js
- * http://sizzlejs.com/
- * Copyright 2011, The Dojo Foundation
- * Released under the MIT, BSD, and GPL Licenses.
- *
- * Date: Thu Mar 31 15:28:23 2011 -0400
- */
-(function( window, undefined ) {
-
-// Use the correct document accordingly with window argument (sandbox)
-var document = window.document;
-var jQuery = (function() {
-
-// Define a local copy of jQuery
-var jQuery = function( selector, context ) {
- // The jQuery object is actually just the init constructor 'enhanced'
- return new jQuery.fn.init( selector, context, rootjQuery );
- },
-
- // Map over jQuery in case of overwrite
- _jQuery = window.jQuery,
-
- // Map over the $ in case of overwrite
- _$ = window.$,
-
- // A central reference to the root jQuery(document)
- rootjQuery,
-
- // A simple way to check for HTML strings or ID strings
- // (both of which we optimize for)
- quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/,
-
- // Check if a string has a non-whitespace character in it
- rnotwhite = /\S/,
-
- // Used for trimming whitespace
- trimLeft = /^\s+/,
- trimRight = /\s+$/,
-
- // Check for digits
- rdigit = /\d/,
-
- // Match a standalone tag
- rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,
-
- // JSON RegExp
- rvalidchars = /^[\],:{}\s]*$/,
- rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
- rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
- rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
-
- // Useragent RegExp
- rwebkit = /(webkit)[ \/]([\w.]+)/,
- ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/,
- rmsie = /(msie) ([\w.]+)/,
- rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/,
-
- // Keep a UserAgent string for use with jQuery.browser
- userAgent = navigator.userAgent,
-
- // For matching the engine and version of the browser
- browserMatch,
-
- // The deferred used on DOM ready
- readyList,
-
- // The ready event handler
- DOMContentLoaded,
-
- // Save a reference to some core methods
- toString = Object.prototype.toString,
- hasOwn = Object.prototype.hasOwnProperty,
- push = Array.prototype.push,
- slice = Array.prototype.slice,
- trim = String.prototype.trim,
- indexOf = Array.prototype.indexOf,
-
- // [[Class]] -> type pairs
- class2type = {};
-
-jQuery.fn = jQuery.prototype = {
- constructor: jQuery,
- init: function( selector, context, rootjQuery ) {
- var match, elem, ret, doc;
-
- // Handle $(""), $(null), or $(undefined)
- if ( !selector ) {
- return this;
- }
-
- // Handle $(DOMElement)
- if ( selector.nodeType ) {
- this.context = this[0] = selector;
- this.length = 1;
- return this;
- }
-
- // The body element only exists once, optimize finding it
- if ( selector === "body" && !context && document.body ) {
- this.context = document;
- this[0] = document.body;
- this.selector = "body";
- this.length = 1;
- return this;
- }
-
- // Handle HTML strings
- if ( typeof selector === "string" ) {
- // Are we dealing with HTML string or an ID?
- match = quickExpr.exec( selector );
-
- // Verify a match, and that no context was specified for #id
- if ( match && (match[1] || !context) ) {
-
- // HANDLE: $(html) -> $(array)
- if ( match[1] ) {
- context = context instanceof jQuery ? context[0] : context;
- doc = (context ? context.ownerDocument || context : document);
-
- // If a single string is passed in and it's a single tag
- // just do a createElement and skip the rest
- ret = rsingleTag.exec( selector );
-
- if ( ret ) {
- if ( jQuery.isPlainObject( context ) ) {
- selector = [ document.createElement( ret[1] ) ];
- jQuery.fn.attr.call( selector, context, true );
-
- } else {
- selector = [ doc.createElement( ret[1] ) ];
- }
-
- } else {
- ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
- selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes;
- }
-
- return jQuery.merge( this, selector );
-
- // HANDLE: $("#id")
- } else {
- elem = document.getElementById( match[2] );
-
- // Check parentNode to catch when Blackberry 4.6 returns
- // nodes that are no longer in the document #6963
- if ( elem && elem.parentNode ) {
- // Handle the case where IE and Opera return items
- // by name instead of ID
- if ( elem.id !== match[2] ) {
- return rootjQuery.find( selector );
- }
-
- // Otherwise, we inject the element directly into the jQuery object
- this.length = 1;
- this[0] = elem;
- }
-
- this.context = document;
- this.selector = selector;
- return this;
- }
-
- // HANDLE: $(expr, $(...))
- } else if ( !context || context.jquery ) {
- return (context || rootjQuery).find( selector );
-
- // HANDLE: $(expr, context)
- // (which is just equivalent to: $(context).find(expr)
- } else {
- return this.constructor( context ).find( selector );
- }
-
- // HANDLE: $(function)
- // Shortcut for document ready
- } else if ( jQuery.isFunction( selector ) ) {
- return rootjQuery.ready( selector );
- }
-
- if (selector.selector !== undefined) {
- this.selector = selector.selector;
- this.context = selector.context;
- }
-
- return jQuery.makeArray( selector, this );
- },
-
- // Start with an empty selector
- selector: "",
-
- // The current version of jQuery being used
- jquery: "1.5.2",
-
- // The default length of a jQuery object is 0
- length: 0,
-
- // The number of elements contained in the matched element set
- size: function() {
- return this.length;
- },
-
- toArray: function() {
- return slice.call( this, 0 );
- },
-
- // Get the Nth element in the matched element set OR
- // Get the whole matched element set as a clean array
- get: function( num ) {
- return num == null ?
-
- // Return a 'clean' array
- this.toArray() :
-
- // Return just the object
- ( num < 0 ? this[ this.length + num ] : this[ num ] );
- },
-
- // Take an array of elements and push it onto the stack
- // (returning the new matched element set)
- pushStack: function( elems, name, selector ) {
- // Build a new jQuery matched element set
- var ret = this.constructor();
-
- if ( jQuery.isArray( elems ) ) {
- push.apply( ret, elems );
-
- } else {
- jQuery.merge( ret, elems );
- }
-
- // Add the old object onto the stack (as a reference)
- ret.prevObject = this;
-
- ret.context = this.context;
-
- if ( name === "find" ) {
- ret.selector = this.selector + (this.selector ? " " : "") + selector;
- } else if ( name ) {
- ret.selector = this.selector + "." + name + "(" + selector + ")";
- }
-
- // Return the newly-formed element set
- return ret;
- },
-
- // Execute a callback for every element in the matched set.
- // (You can seed the arguments with an array of args, but this is
- // only used internally.)
- each: function( callback, args ) {
- return jQuery.each( this, callback, args );
- },
-
- ready: function( fn ) {
- // Attach the listeners
- jQuery.bindReady();
-
- // Add the callback
- readyList.done( fn );
-
- return this;
- },
-
- eq: function( i ) {
- return i === -1 ?
- this.slice( i ) :
- this.slice( i, +i + 1 );
- },
-
- first: function() {
- return this.eq( 0 );
- },
-
- last: function() {
- return this.eq( -1 );
- },
-
- slice: function() {
- return this.pushStack( slice.apply( this, arguments ),
- "slice", slice.call(arguments).join(",") );
- },
-
- map: function( callback ) {
- return this.pushStack( jQuery.map(this, function( elem, i ) {
- return callback.call( elem, i, elem );
- }));
- },
-
- end: function() {
- return this.prevObject || this.constructor(null);
- },
-
- // For internal use only.
- // Behaves like an Array's method, not like a jQuery method.
- push: push,
- sort: [].sort,
- splice: [].splice
-};
-
-// Give the init function the jQuery prototype for later instantiation
-jQuery.fn.init.prototype = jQuery.fn;
-
-jQuery.extend = jQuery.fn.extend = function() {
- var options, name, src, copy, copyIsArray, clone,
- target = arguments[0] || {},
- i = 1,
- length = arguments.length,
- deep = false;
-
- // Handle a deep copy situation
- if ( typeof target === "boolean" ) {
- deep = target;
- target = arguments[1] || {};
- // skip the boolean and the target
- i = 2;
- }
-
- // Handle case when target is a string or something (possible in deep copy)
- if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
- target = {};
- }
-
- // extend jQuery itself if only one argument is passed
- if ( length === i ) {
- target = this;
- --i;
- }
-
- for ( ; i < length; i++ ) {
- // Only deal with non-null/undefined values
- if ( (options = arguments[ i ]) != null ) {
- // Extend the base object
- for ( name in options ) {
- src = target[ name ];
- copy = options[ name ];
-
- // Prevent never-ending loop
- if ( target === copy ) {
- continue;
- }
-
- // Recurse if we're merging plain objects or arrays
- if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
- if ( copyIsArray ) {
- copyIsArray = false;
- clone = src && jQuery.isArray(src) ? src : [];
-
- } else {
- clone = src && jQuery.isPlainObject(src) ? src : {};
- }
-
- // Never move original objects, clone them
- target[ name ] = jQuery.extend( deep, clone, copy );
-
- // Don't bring in undefined values
- } else if ( copy !== undefined ) {
- target[ name ] = copy;
- }
- }
- }
- }
-
- // Return the modified object
- return target;
-};
-
-jQuery.extend({
- noConflict: function( deep ) {
- window.$ = _$;
-
- if ( deep ) {
- window.jQuery = _jQuery;
- }
-
- return jQuery;
- },
-
- // Is the DOM ready to be used? Set to true once it occurs.
- isReady: false,
-
- // A counter to track how many items to wait for before
- // the ready event fires. See #6781
- readyWait: 1,
-
- // Handle when the DOM is ready
- ready: function( wait ) {
- // A third-party is pushing the ready event forwards
- if ( wait === true ) {
- jQuery.readyWait--;
- }
-
- // Make sure that the DOM is not already loaded
- if ( !jQuery.readyWait || (wait !== true && !jQuery.isReady) ) {
- // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
- if ( !document.body ) {
- return setTimeout( jQuery.ready, 1 );
- }
-
- // Remember that the DOM is ready
- jQuery.isReady = true;
-
- // If a normal DOM Ready event fired, decrement, and wait if need be
- if ( wait !== true && --jQuery.readyWait > 0 ) {
- return;
- }
-
- // If there are functions bound, to execute
- readyList.resolveWith( document, [ jQuery ] );
-
- // Trigger any bound ready events
- if ( jQuery.fn.trigger ) {
- jQuery( document ).trigger( "ready" ).unbind( "ready" );
- }
- }
- },
-
- bindReady: function() {
- if ( readyList ) {
- return;
- }
-
- readyList = jQuery._Deferred();
-
- // Catch cases where $(document).ready() is called after the
- // browser event has already occurred.
- if ( document.readyState === "complete" ) {
- // Handle it asynchronously to allow scripts the opportunity to delay ready
- return setTimeout( jQuery.ready, 1 );
- }
-
- // Mozilla, Opera and webkit nightlies currently support this event
- if ( document.addEventListener ) {
- // Use the handy event callback
- document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
-
- // A fallback to window.onload, that will always work
- window.addEventListener( "load", jQuery.ready, false );
-
- // If IE event model is used
- } else if ( document.attachEvent ) {
- // ensure firing before onload,
- // maybe late but safe also for iframes
- document.attachEvent("onreadystatechange", DOMContentLoaded);
-
- // A fallback to window.onload, that will always work
- window.attachEvent( "onload", jQuery.ready );
-
- // If IE and not a frame
- // continually check to see if the document is ready
- var toplevel = false;
-
- try {
- toplevel = window.frameElement == null;
- } catch(e) {}
-
- if ( document.documentElement.doScroll && toplevel ) {
- doScrollCheck();
- }
- }
- },
-
- // See test/unit/core.js for details concerning isFunction.
- // Since version 1.3, DOM methods and functions like alert
- // aren't supported. They return false on IE (#2968).
- isFunction: function( obj ) {
- return jQuery.type(obj) === "function";
- },
-
- isArray: Array.isArray || function( obj ) {
- return jQuery.type(obj) === "array";
- },
-
- // A crude way of determining if an object is a window
- isWindow: function( obj ) {
- return obj && typeof obj === "object" && "setInterval" in obj;
- },
-
- isNaN: function( obj ) {
- return obj == null || !rdigit.test( obj ) || isNaN( obj );
- },
-
- type: function( obj ) {
- return obj == null ?
- String( obj ) :
- class2type[ toString.call(obj) ] || "object";
- },
-
- isPlainObject: function( obj ) {
- // Must be an Object.
- // Because of IE, we also have to check the presence of the constructor property.
- // Make sure that DOM nodes and window objects don't pass through, as well
- if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
- return false;
- }
-
- // Not own constructor property must be Object
- if ( obj.constructor &&
- !hasOwn.call(obj, "constructor") &&
- !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
- return false;
- }
-
- // Own properties are enumerated firstly, so to speed up,
- // if last one is own, then all properties are own.
-
- var key;
- for ( key in obj ) {}
-
- return key === undefined || hasOwn.call( obj, key );
- },
-
- isEmptyObject: function( obj ) {
- for ( var name in obj ) {
- return false;
- }
- return true;
- },
-
- error: function( msg ) {
- throw msg;
- },
-
- parseJSON: function( data ) {
- if ( typeof data !== "string" || !data ) {
- return null;
- }
-
- // Make sure leading/trailing whitespace is removed (IE can't handle it)
- data = jQuery.trim( data );
-
- // Make sure the incoming data is actual JSON
- // Logic borrowed from http://json.org/json2.js
- if ( rvalidchars.test(data.replace(rvalidescape, "@")
- .replace(rvalidtokens, "]")
- .replace(rvalidbraces, "")) ) {
-
- // Try to use the native JSON parser first
- return window.JSON && window.JSON.parse ?
- window.JSON.parse( data ) :
- (new Function("return " + data))();
-
- } else {
- jQuery.error( "Invalid JSON: " + data );
- }
- },
-
- // Cross-browser xml parsing
- // (xml & tmp used internally)
- parseXML: function( data , xml , tmp ) {
-
- if ( window.DOMParser ) { // Standard
- tmp = new DOMParser();
- xml = tmp.parseFromString( data , "text/xml" );
- } else { // IE
- xml = new ActiveXObject( "Microsoft.XMLDOM" );
- xml.async = "false";
- xml.loadXML( data );
- }
-
- tmp = xml.documentElement;
-
- if ( ! tmp || ! tmp.nodeName || tmp.nodeName === "parsererror" ) {
- jQuery.error( "Invalid XML: " + data );
- }
-
- return xml;
- },
-
- noop: function() {},
-
- // Evalulates a script in a global context
- globalEval: function( data ) {
- if ( data && rnotwhite.test(data) ) {
- // Inspired by code by Andrea Giammarchi
- // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
- var head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement,
- script = document.createElement( "script" );
-
- if ( jQuery.support.scriptEval() ) {
- script.appendChild( document.createTextNode( data ) );
- } else {
- script.text = data;
- }
-
- // Use insertBefore instead of appendChild to circumvent an IE6 bug.
- // This arises when a base node is used (#2709).
- head.insertBefore( script, head.firstChild );
- head.removeChild( script );
- }
- },
-
- nodeName: function( elem, name ) {
- return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
- },
-
- // args is for internal usage only
- each: function( object, callback, args ) {
- var name, i = 0,
- length = object.length,
- isObj = length === undefined || jQuery.isFunction(object);
-
- if ( args ) {
- if ( isObj ) {
- for ( name in object ) {
- if ( callback.apply( object[ name ], args ) === false ) {
- break;
- }
- }
- } else {
- for ( ; i < length; ) {
- if ( callback.apply( object[ i++ ], args ) === false ) {
- break;
- }
- }
- }
-
- // A special, fast, case for the most common use of each
- } else {
- if ( isObj ) {
- for ( name in object ) {
- if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
- break;
- }
- }
- } else {
- for ( var value = object[0];
- i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {}
- }
- }
-
- return object;
- },
-
- // Use native String.trim function wherever possible
- trim: trim ?
- function( text ) {
- return text == null ?
- "" :
- trim.call( text );
- } :
-
- // Otherwise use our own trimming functionality
- function( text ) {
- return text == null ?
- "" :
- text.toString().replace( trimLeft, "" ).replace( trimRight, "" );
- },
-
- // results is for internal usage only
- makeArray: function( array, results ) {
- var ret = results || [];
-
- if ( array != null ) {
- // The window, strings (and functions) also have 'length'
- // The extra typeof function check is to prevent crashes
- // in Safari 2 (See: #3039)
- // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
- var type = jQuery.type(array);
-
- if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) {
- push.call( ret, array );
- } else {
- jQuery.merge( ret, array );
- }
- }
-
- return ret;
- },
-
- inArray: function( elem, array ) {
- if ( array.indexOf ) {
- return array.indexOf( elem );
- }
-
- for ( var i = 0, length = array.length; i < length; i++ ) {
- if ( array[ i ] === elem ) {
- return i;
- }
- }
-
- return -1;
- },
-
- merge: function( first, second ) {
- var i = first.length,
- j = 0;
-
- if ( typeof second.length === "number" ) {
- for ( var l = second.length; j < l; j++ ) {
- first[ i++ ] = second[ j ];
- }
-
- } else {
- while ( second[j] !== undefined ) {
- first[ i++ ] = second[ j++ ];
- }
- }
-
- first.length = i;
-
- return first;
- },
-
- grep: function( elems, callback, inv ) {
- var ret = [], retVal;
- inv = !!inv;
-
- // Go through the array, only saving the items
- // that pass the validator function
- for ( var i = 0, length = elems.length; i < length; i++ ) {
- retVal = !!callback( elems[ i ], i );
- if ( inv !== retVal ) {
- ret.push( elems[ i ] );
- }
- }
-
- return ret;
- },
-
- // arg is for internal usage only
- map: function( elems, callback, arg ) {
- var ret = [], value;
-
- // Go through the array, translating each of the items to their
- // new value (or values).
- for ( var i = 0, length = elems.length; i < length; i++ ) {
- value = callback( elems[ i ], i, arg );
-
- if ( value != null ) {
- ret[ ret.length ] = value;
- }
- }
-
- // Flatten any nested arrays
- return ret.concat.apply( [], ret );
- },
-
- // A global GUID counter for objects
- guid: 1,
-
- proxy: function( fn, proxy, thisObject ) {
- if ( arguments.length === 2 ) {
- if ( typeof proxy === "string" ) {
- thisObject = fn;
- fn = thisObject[ proxy ];
- proxy = undefined;
-
- } else if ( proxy && !jQuery.isFunction( proxy ) ) {
- thisObject = proxy;
- proxy = undefined;
- }
- }
-
- if ( !proxy && fn ) {
- proxy = function() {
- return fn.apply( thisObject || this, arguments );
- };
- }
-
- // Set the guid of unique handler to the same of original handler, so it can be removed
- if ( fn ) {
- proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
- }
-
- // So proxy can be declared as an argument
- return proxy;
- },
-
- // Mutifunctional method to get and set values to a collection
- // The value/s can be optionally by executed if its a function
- access: function( elems, key, value, exec, fn, pass ) {
- var length = elems.length;
-
- // Setting many attributes
- if ( typeof key === "object" ) {
- for ( var k in key ) {
- jQuery.access( elems, k, key[k], exec, fn, value );
- }
- return elems;
- }
-
- // Setting one attribute
- if ( value !== undefined ) {
- // Optionally, function values get executed if exec is true
- exec = !pass && exec && jQuery.isFunction(value);
-
- for ( var i = 0; i < length; i++ ) {
- fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
- }
-
- return elems;
- }
-
- // Getting an attribute
- return length ? fn( elems[0], key ) : undefined;
- },
-
- now: function() {
- return (new Date()).getTime();
- },
-
- // Use of jQuery.browser is frowned upon.
- // More details: http://docs.jquery.com/Utilities/jQuery.browser
- uaMatch: function( ua ) {
- ua = ua.toLowerCase();
-
- var match = rwebkit.exec( ua ) ||
- ropera.exec( ua ) ||
- rmsie.exec( ua ) ||
- ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
- [];
-
- return { browser: match[1] || "", version: match[2] || "0" };
- },
-
- sub: function() {
- function jQuerySubclass( selector, context ) {
- return new jQuerySubclass.fn.init( selector, context );
- }
- jQuery.extend( true, jQuerySubclass, this );
- jQuerySubclass.superclass = this;
- jQuerySubclass.fn = jQuerySubclass.prototype = this();
- jQuerySubclass.fn.constructor = jQuerySubclass;
- jQuerySubclass.subclass = this.subclass;
- jQuerySubclass.fn.init = function init( selector, context ) {
- if ( context && context instanceof jQuery && !(context instanceof jQuerySubclass) ) {
- context = jQuerySubclass(context);
- }
-
- return jQuery.fn.init.call( this, selector, context, rootjQuerySubclass );
- };
- jQuerySubclass.fn.init.prototype = jQuerySubclass.fn;
- var rootjQuerySubclass = jQuerySubclass(document);
- return jQuerySubclass;
- },
-
- browser: {}
-});
-
-// Populate the class2type map
-jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
- class2type[ "[object " + name + "]" ] = name.toLowerCase();
-});
-
-browserMatch = jQuery.uaMatch( userAgent );
-if ( browserMatch.browser ) {
- jQuery.browser[ browserMatch.browser ] = true;
- jQuery.browser.version = browserMatch.version;
-}
-
-// Deprecated, use jQuery.browser.webkit instead
-if ( jQuery.browser.webkit ) {
- jQuery.browser.safari = true;
-}
-
-if ( indexOf ) {
- jQuery.inArray = function( elem, array ) {
- return indexOf.call( array, elem );
- };
-}
-
-// IE doesn't match non-breaking spaces with \s
-if ( rnotwhite.test( "\xA0" ) ) {
- trimLeft = /^[\s\xA0]+/;
- trimRight = /[\s\xA0]+$/;
-}
-
-// All jQuery objects should point back to these
-rootjQuery = jQuery(document);
-
-// Cleanup functions for the document ready method
-if ( document.addEventListener ) {
- DOMContentLoaded = function() {
- document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
- jQuery.ready();
- };
-
-} else if ( document.attachEvent ) {
- DOMContentLoaded = function() {
- // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
- if ( document.readyState === "complete" ) {
- document.detachEvent( "onreadystatechange", DOMContentLoaded );
- jQuery.ready();
- }
- };
-}
-
-// The DOM ready check for Internet Explorer
-function doScrollCheck() {
- if ( jQuery.isReady ) {
- return;
- }
-
- try {
- // If IE is used, use the trick by Diego Perini
- // http://javascript.nwbox.com/IEContentLoaded/
- document.documentElement.doScroll("left");
- } catch(e) {
- setTimeout( doScrollCheck, 1 );
- return;
- }
-
- // and execute any waiting functions
- jQuery.ready();
-}
-
-// Expose jQuery to the global object
-return jQuery;
-
-})();
-
-
-var // Promise methods
- promiseMethods = "then done fail isResolved isRejected promise".split( " " ),
- // Static reference to slice
- sliceDeferred = [].slice;
-
-jQuery.extend({
- // Create a simple deferred (one callbacks list)
- _Deferred: function() {
- var // callbacks list
- callbacks = [],
- // stored [ context , args ]
- fired,
- // to avoid firing when already doing so
- firing,
- // flag to know if the deferred has been cancelled
- cancelled,
- // the deferred itself
- deferred = {
-
- // done( f1, f2, ...)
- done: function() {
- if ( !cancelled ) {
- var args = arguments,
- i,
- length,
- elem,
- type,
- _fired;
- if ( fired ) {
- _fired = fired;
- fired = 0;
- }
- for ( i = 0, length = args.length; i < length; i++ ) {
- elem = args[ i ];
- type = jQuery.type( elem );
- if ( type === "array" ) {
- deferred.done.apply( deferred, elem );
- } else if ( type === "function" ) {
- callbacks.push( elem );
- }
- }
- if ( _fired ) {
- deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] );
- }
- }
- return this;
- },
-
- // resolve with given context and args
- resolveWith: function( context, args ) {
- if ( !cancelled && !fired && !firing ) {
- // make sure args are available (#8421)
- args = args || [];
- firing = 1;
- try {
- while( callbacks[ 0 ] ) {
- callbacks.shift().apply( context, args );
- }
- }
- finally {
- fired = [ context, args ];
- firing = 0;
- }
- }
- return this;
- },
-
- // resolve with this as context and given arguments
- resolve: function() {
- deferred.resolveWith( this, arguments );
- return this;
- },
-
- // Has this deferred been resolved?
- isResolved: function() {
- return !!( firing || fired );
- },
-
- // Cancel
- cancel: function() {
- cancelled = 1;
- callbacks = [];
- return this;
- }
- };
-
- return deferred;
- },
-
- // Full fledged deferred (two callbacks list)
- Deferred: function( func ) {
- var deferred = jQuery._Deferred(),
- failDeferred = jQuery._Deferred(),
- promise;
- // Add errorDeferred methods, then and promise
- jQuery.extend( deferred, {
- then: function( doneCallbacks, failCallbacks ) {
- deferred.done( doneCallbacks ).fail( failCallbacks );
- return this;
- },
- fail: failDeferred.done,
- rejectWith: failDeferred.resolveWith,
- reject: failDeferred.resolve,
- isRejected: failDeferred.isResolved,
- // Get a promise for this deferred
- // If obj is provided, the promise aspect is added to the object
- promise: function( obj ) {
- if ( obj == null ) {
- if ( promise ) {
- return promise;
- }
- promise = obj = {};
- }
- var i = promiseMethods.length;
- while( i-- ) {
- obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ];
- }
- return obj;
- }
- } );
- // Make sure only one callback list will be used
- deferred.done( failDeferred.cancel ).fail( deferred.cancel );
- // Unexpose cancel
- delete deferred.cancel;
- // Call given func if any
- if ( func ) {
- func.call( deferred, deferred );
- }
- return deferred;
- },
-
- // Deferred helper
- when: function( firstParam ) {
- var args = arguments,
- i = 0,
- length = args.length,
- count = length,
- deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
- firstParam :
- jQuery.Deferred();
- function resolveFunc( i ) {
- return function( value ) {
- args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
- if ( !( --count ) ) {
- // Strange bug in FF4:
- // Values changed onto the arguments object sometimes end up as undefined values
- // outside the $.when method. Cloning the object into a fresh array solves the issue
- deferred.resolveWith( deferred, sliceDeferred.call( args, 0 ) );
- }
- };
- }
- if ( length > 1 ) {
- for( ; i < length; i++ ) {
- if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) {
- args[ i ].promise().then( resolveFunc(i), deferred.reject );
- } else {
- --count;
- }
- }
- if ( !count ) {
- deferred.resolveWith( deferred, args );
- }
- } else if ( deferred !== firstParam ) {
- deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
- }
- return deferred.promise();
- }
-});
-
-
-
-
-(function() {
-
- jQuery.support = {};
-
- var div = document.createElement("div");
-
- div.style.display = "none";
- div.innerHTML = " <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
-
- var all = div.getElementsByTagName("*"),
- a = div.getElementsByTagName("a")[0],
- select = document.createElement("select"),
- opt = select.appendChild( document.createElement("option") ),
- input = div.getElementsByTagName("input")[0];
-
- // Can't get basic test support
- if ( !all || !all.length || !a ) {
- return;
- }
-
- jQuery.support = {
- // IE strips leading whitespace when .innerHTML is used
- leadingWhitespace: div.firstChild.nodeType === 3,
-
- // Make sure that tbody elements aren't automatically inserted
- // IE will insert them into empty tables
- tbody: !div.getElementsByTagName("tbody").length,
-
- // Make sure that link elements get serialized correctly by innerHTML
- // This requires a wrapper element in IE
- htmlSerialize: !!div.getElementsByTagName("link").length,
-
- // Get the style information from getAttribute
- // (IE uses .cssText insted)
- style: /red/.test( a.getAttribute("style") ),
-
- // Make sure that URLs aren't manipulated
- // (IE normalizes it by default)
- hrefNormalized: a.getAttribute("href") === "/a",
-
- // Make sure that element opacity exists
- // (IE uses filter instead)
- // Use a regex to work around a WebKit issue. See #5145
- opacity: /^0.55$/.test( a.style.opacity ),
-
- // Verify style float existence
- // (IE uses styleFloat instead of cssFloat)
- cssFloat: !!a.style.cssFloat,
-
- // Make sure that if no value is specified for a checkbox
- // that it defaults to "on".
- // (WebKit defaults to "" instead)
- checkOn: input.value === "on",
-
- // Make sure that a selected-by-default option has a working selected property.
- // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
- optSelected: opt.selected,
-
- // Will be defined later
- deleteExpando: true,
- optDisabled: false,
- checkClone: false,
- noCloneEvent: true,
- noCloneChecked: true,
- boxModel: null,
- inlineBlockNeedsLayout: false,
- shrinkWrapBlocks: false,
- reliableHiddenOffsets: true,
- reliableMarginRight: true
- };
-
- input.checked = true;
- jQuery.support.noCloneChecked = input.cloneNode( true ).checked;
-
- // Make sure that the options inside disabled selects aren't marked as disabled
- // (WebKit marks them as diabled)
- select.disabled = true;
- jQuery.support.optDisabled = !opt.disabled;
-
- var _scriptEval = null;
- jQuery.support.scriptEval = function() {
- if ( _scriptEval === null ) {
- var root = document.documentElement,
- script = document.createElement("script"),
- id = "script" + jQuery.now();
-
- // Make sure that the execution of code works by injecting a script
- // tag with appendChild/createTextNode
- // (IE doesn't support this, fails, and uses .text instead)
- try {
- script.appendChild( document.createTextNode( "window." + id + "=1;" ) );
- } catch(e) {}
-
- root.insertBefore( script, root.firstChild );
-
- if ( window[ id ] ) {
- _scriptEval = true;
- delete window[ id ];
- } else {
- _scriptEval = false;
- }
-
- root.removeChild( script );
- }
-
- return _scriptEval;
- };
-
- // Test to see if it's possible to delete an expando from an element
- // Fails in Internet Explorer
- try {
- delete div.test;
-
- } catch(e) {
- jQuery.support.deleteExpando = false;
- }
-
- if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
- div.attachEvent("onclick", function click() {
- // Cloning a node shouldn't copy over any
- // bound event handlers (IE does this)
- jQuery.support.noCloneEvent = false;
- div.detachEvent("onclick", click);
- });
- div.cloneNode(true).fireEvent("onclick");
- }
-
- div = document.createElement("div");
- div.innerHTML = "<input type='radio' name='radiotest' checked='checked'/>";
-
- var fragment = document.createDocumentFragment();
- fragment.appendChild( div.firstChild );
-
- // WebKit doesn't clone checked state correctly in fragments
- jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked;
-
- // Figure out if the W3C box model works as expected
- // document.body must exist before we can do this
- jQuery(function() {
- var div = document.createElement("div"),
- body = document.getElementsByTagName("body")[0];
-
- // Frameset documents with no body should not run this code
- if ( !body ) {
- return;
- }
-
- div.style.width = div.style.paddingLeft = "1px";
- body.appendChild( div );
- jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2;
-
- if ( "zoom" in div.style ) {
- // Check if natively block-level elements act like inline-block
- // elements when setting their display to 'inline' and giving
- // them layout
- // (IE < 8 does this)
- div.style.display = "inline";
- div.style.zoom = 1;
- jQuery.support.inlineBlockNeedsLayout = div.offsetWidth === 2;
-
- // Check if elements with layout shrink-wrap their children
- // (IE 6 does this)
- div.style.display = "";
- div.innerHTML = "<div style='width:4px;'></div>";
- jQuery.support.shrinkWrapBlocks = div.offsetWidth !== 2;
- }
-
- div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>";
- var tds = div.getElementsByTagName("td");
-
- // Check if table cells still have offsetWidth/Height when they are set
- // to display:none and there are still other visible table cells in a
- // table row; if so, offsetWidth/Height are not reliable for use when
- // determining if an element has been hidden directly using
- // display:none (it is still safe to use offsets if a parent element is
- // hidden; don safety goggles and see bug #4512 for more information).
- // (only IE 8 fails this test)
- jQuery.support.reliableHiddenOffsets = tds[0].offsetHeight === 0;
-
- tds[0].style.display = "";
- tds[1].style.display = "none";
-
- // Check if empty table cells still have offsetWidth/Height
- // (IE < 8 fail this test)
- jQuery.support.reliableHiddenOffsets = jQuery.support.reliableHiddenOffsets && tds[0].offsetHeight === 0;
- div.innerHTML = "";
-
- // Check if div with explicit width and no margin-right incorrectly
- // gets computed margin-right based on width of container. For more
- // info see bug #3333
- // Fails in WebKit before Feb 2011 nightlies
- // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
- if ( document.defaultView && document.defaultView.getComputedStyle ) {
- div.style.width = "1px";
- div.style.marginRight = "0";
- jQuery.support.reliableMarginRight = ( parseInt(document.defaultView.getComputedStyle(div, null).marginRight, 10) || 0 ) === 0;
- }
-
- body.removeChild( div ).style.display = "none";
- div = tds = null;
- });
-
- // Technique from Juriy Zaytsev
- // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
- var eventSupported = function( eventName ) {
- var el = document.createElement("div");
- eventName = "on" + eventName;
-
- // We only care about the case where non-standard event systems
- // are used, namely in IE. Short-circuiting here helps us to
- // avoid an eval call (in setAttribute) which can cause CSP
- // to go haywire. See: https://developer.mozilla.org/en/Security/CSP
- if ( !el.attachEvent ) {
- return true;
- }
-
- var isSupported = (eventName in el);
- if ( !isSupported ) {
- el.setAttribute(eventName, "return;");
- isSupported = typeof el[eventName] === "function";
- }
- return isSupported;
- };
-
- jQuery.support.submitBubbles = eventSupported("submit");
- jQuery.support.changeBubbles = eventSupported("change");
-
- // release memory in IE
- div = all = a = null;
-})();
-
-
-
-var rbrace = /^(?:\{.*\}|\[.*\])$/;
-
-jQuery.extend({
- cache: {},
-
- // Please use with caution
- uuid: 0,
-
- // Unique for each copy of jQuery on the page
- // Non-digits removed to match rinlinejQuery
- expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
-
- // The following elements throw uncatchable exceptions if you
- // attempt to add expando properties to them.
- noData: {
- "embed": true,
- // Ban all objects except for Flash (which handle expandos)
- "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
- "applet": true
- },
-
- hasData: function( elem ) {
- elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
-
- return !!elem && !isEmptyDataObject( elem );
- },
-
- data: function( elem, name, data, pvt /* Internal Use Only */ ) {
- if ( !jQuery.acceptData( elem ) ) {
- return;
- }
-
- var internalKey = jQuery.expando, getByName = typeof name === "string", thisCache,
-
- // We have to handle DOM nodes and JS objects differently because IE6-7
- // can't GC object references properly across the DOM-JS boundary
- isNode = elem.nodeType,
-
- // Only DOM nodes need the global jQuery cache; JS object data is
- // attached directly to the object so GC can occur automatically
- cache = isNode ? jQuery.cache : elem,
-
- // Only defining an ID for JS objects if its cache already exists allows
- // the code to shortcut on the same path as a DOM node with no cache
- id = isNode ? elem[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando;
-
- // Avoid doing any more work than we need to when trying to get data on an
- // object that has no data at all
- if ( (!id || (pvt && id && !cache[ id ][ internalKey ])) && getByName && data === undefined ) {
- return;
- }
-
- if ( !id ) {
- // Only DOM nodes need a new unique ID for each element since their data
- // ends up in the global cache
- if ( isNode ) {
- elem[ jQuery.expando ] = id = ++jQuery.uuid;
- } else {
- id = jQuery.expando;
- }
- }
-
- if ( !cache[ id ] ) {
- cache[ id ] = {};
-
- // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery
- // metadata on plain JS objects when the object is serialized using
- // JSON.stringify
- if ( !isNode ) {
- cache[ id ].toJSON = jQuery.noop;
- }
- }
-
- // An object can be passed to jQuery.data instead of a key/value pair; this gets
- // shallow copied over onto the existing cache
- if ( typeof name === "object" || typeof name === "function" ) {
- if ( pvt ) {
- cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name);
- } else {
- cache[ id ] = jQuery.extend(cache[ id ], name);
- }
- }
-
- thisCache = cache[ id ];
-
- // Internal jQuery data is stored in a separate object inside the object's data
- // cache in order to avoid key collisions between internal data and user-defined
- // data
- if ( pvt ) {
- if ( !thisCache[ internalKey ] ) {
- thisCache[ internalKey ] = {};
- }
-
- thisCache = thisCache[ internalKey ];
- }
-
- if ( data !== undefined ) {
- thisCache[ name ] = data;
- }
-
- // TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should
- // not attempt to inspect the internal events object using jQuery.data, as this
- // internal data object is undocumented and subject to change.
- if ( name === "events" && !thisCache[name] ) {
- return thisCache[ internalKey ] && thisCache[ internalKey ].events;
- }
-
- return getByName ? thisCache[ name ] : thisCache;
- },
-
- removeData: function( elem, name, pvt /* Internal Use Only */ ) {
- if ( !jQuery.acceptData( elem ) ) {
- return;
- }
-
- var internalKey = jQuery.expando, isNode = elem.nodeType,
-
- // See jQuery.data for more information
- cache = isNode ? jQuery.cache : elem,
-
- // See jQuery.data for more information
- id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
-
- // If there is already no cache entry for this object, there is no
- // purpose in continuing
- if ( !cache[ id ] ) {
- return;
- }
-
- if ( name ) {
- var thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ];
-
- if ( thisCache ) {
- delete thisCache[ name ];
-
- // If there is no data left in the cache, we want to continue
- // and let the cache object itself get destroyed
- if ( !isEmptyDataObject(thisCache) ) {
- return;
- }
- }
- }
-
- // See jQuery.data for more information
- if ( pvt ) {
- delete cache[ id ][ internalKey ];
-
- // Don't destroy the parent cache unless the internal data object
- // had been the only thing left in it
- if ( !isEmptyDataObject(cache[ id ]) ) {
- return;
- }
- }
-
- var internalCache = cache[ id ][ internalKey ];
-
- // Browsers that fail expando deletion also refuse to delete expandos on
- // the window, but it will allow it on all other JS objects; other browsers
- // don't care
- if ( jQuery.support.deleteExpando || cache != window ) {
- delete cache[ id ];
- } else {
- cache[ id ] = null;
- }
-
- // We destroyed the entire user cache at once because it's faster than
- // iterating through each key, but we need to continue to persist internal
- // data if it existed
- if ( internalCache ) {
- cache[ id ] = {};
- // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery
- // metadata on plain JS objects when the object is serialized using
- // JSON.stringify
- if ( !isNode ) {
- cache[ id ].toJSON = jQuery.noop;
- }
-
- cache[ id ][ internalKey ] = internalCache;
-
- // Otherwise, we need to eliminate the expando on the node to avoid
- // false lookups in the cache for entries that no longer exist
- } else if ( isNode ) {
- // IE does not allow us to delete expando properties from nodes,
- // nor does it have a removeAttribute function on Document nodes;
- // we must handle all of these cases
- if ( jQuery.support.deleteExpando ) {
- delete elem[ jQuery.expando ];
- } else if ( elem.removeAttribute ) {
- elem.removeAttribute( jQuery.expando );
- } else {
- elem[ jQuery.expando ] = null;
- }
- }
- },
-
- // For internal use only.
- _data: function( elem, name, data ) {
- return jQuery.data( elem, name, data, true );
- },
-
- // A method for determining if a DOM node can handle the data expando
- acceptData: function( elem ) {
- if ( elem.nodeName ) {
- var match = jQuery.noData[ elem.nodeName.toLowerCase() ];
-
- if ( match ) {
- return !(match === true || elem.getAttribute("classid") !== match);
- }
- }
-
- return true;
- }
-});
-
-jQuery.fn.extend({
- data: function( key, value ) {
- var data = null;
-
- if ( typeof key === "undefined" ) {
- if ( this.length ) {
- data = jQuery.data( this[0] );
-
- if ( this[0].nodeType === 1 ) {
- var attr = this[0].attributes, name;
- for ( var i = 0, l = attr.length; i < l; i++ ) {
- name = attr[i].name;
-
- if ( name.indexOf( "data-" ) === 0 ) {
- name = name.substr( 5 );
- dataAttr( this[0], name, data[ name ] );
- }
- }
- }
- }
-
- return data;
-
- } else if ( typeof key === "object" ) {
- return this.each(function() {
- jQuery.data( this, key );
- });
- }
-
- var parts = key.split(".");
- parts[1] = parts[1] ? "." + parts[1] : "";
-
- if ( value === undefined ) {
- data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
-
- // Try to fetch any internally stored data first
- if ( data === undefined && this.length ) {
- data = jQuery.data( this[0], key );
- data = dataAttr( this[0], key, data );
- }
-
- return data === undefined && parts[1] ?
- this.data( parts[0] ) :
- data;
-
- } else {
- return this.each(function() {
- var $this = jQuery( this ),
- args = [ parts[0], value ];
-
- $this.triggerHandler( "setData" + parts[1] + "!", args );
- jQuery.data( this, key, value );
- $this.triggerHandler( "changeData" + parts[1] + "!", args );
- });
- }
- },
-
- removeData: function( key ) {
- return this.each(function() {
- jQuery.removeData( this, key );
- });
- }
-});
-
-function dataAttr( elem, key, data ) {
- // If nothing was found internally, try to fetch any
- // data from the HTML5 data-* attribute
- if ( data === undefined && elem.nodeType === 1 ) {
- data = elem.getAttribute( "data-" + key );
-
- if ( typeof data === "string" ) {
- try {
- data = data === "true" ? true :
- data === "false" ? false :
- data === "null" ? null :
- !jQuery.isNaN( data ) ? parseFloat( data ) :
- rbrace.test( data ) ? jQuery.parseJSON( data ) :
- data;
- } catch( e ) {}
-
- // Make sure we set the data so it isn't changed later
- jQuery.data( elem, key, data );
-
- } else {
- data = undefined;
- }
- }
-
- return data;
-}
-
-// TODO: This is a hack for 1.5 ONLY to allow objects with a single toJSON
-// property to be considered empty objects; this property always exists in
-// order to make sure JSON.stringify does not expose internal metadata
-function isEmptyDataObject( obj ) {
- for ( var name in obj ) {
- if ( name !== "toJSON" ) {
- return false;
- }
- }
-
- return true;
-}
-
-
-
-
-jQuery.extend({
- queue: function( elem, type, data ) {
- if ( !elem ) {
- return;
- }
-
- type = (type || "fx") + "queue";
- var q = jQuery._data( elem, type );
-
- // Speed up dequeue by getting out quickly if this is just a lookup
- if ( !data ) {
- return q || [];
- }
-
- if ( !q || jQuery.isArray(data) ) {
- q = jQuery._data( elem, type, jQuery.makeArray(data) );
-
- } else {
- q.push( data );
- }
-
- return q;
- },
-
- dequeue: function( elem, type ) {
- type = type || "fx";
-
- var queue = jQuery.queue( elem, type ),
- fn = queue.shift();
-
- // If the fx queue is dequeued, always remove the progress sentinel
- if ( fn === "inprogress" ) {
- fn = queue.shift();
- }
-
- if ( fn ) {
- // Add a progress sentinel to prevent the fx queue from being
- // automatically dequeued
- if ( type === "fx" ) {
- queue.unshift("inprogress");
- }
-
- fn.call(elem, function() {
- jQuery.dequeue(elem, type);
- });
- }
-
- if ( !queue.length ) {
- jQuery.removeData( elem, type + "queue", true );
- }
- }
-});
-
-jQuery.fn.extend({
- queue: function( type, data ) {
- if ( typeof type !== "string" ) {
- data = type;
- type = "fx";
- }
-
- if ( data === undefined ) {
- return jQuery.queue( this[0], type );
- }
- return this.each(function( i ) {
- var queue = jQuery.queue( this, type, data );
-
- if ( type === "fx" && queue[0] !== "inprogress" ) {
- jQuery.dequeue( this, type );
- }
- });
- },
- dequeue: function( type ) {
- return this.each(function() {
- jQuery.dequeue( this, type );
- });
- },
-
- // Based off of the plugin by Clint Helfers, with permission.
- // http://blindsignals.com/index.php/2009/07/jquery-delay/
- delay: function( time, type ) {
- time = jQuery.fx ? jQuery.fx.speeds[time] || time : time;
- type = type || "fx";
-
- return this.queue( type, function() {
- var elem = this;
- setTimeout(function() {
- jQuery.dequeue( elem, type );
- }, time );
- });
- },
-
- clearQueue: function( type ) {
- return this.queue( type || "fx", [] );
- }
-});
-
-
-
-
-var rclass = /[\n\t\r]/g,
- rspaces = /\s+/,
- rreturn = /\r/g,
- rspecialurl = /^(?:href|src|style)$/,
- rtype = /^(?:button|input)$/i,
- rfocusable = /^(?:button|input|object|select|textarea)$/i,
- rclickable = /^a(?:rea)?$/i,
- rradiocheck = /^(?:radio|checkbox)$/i;
-
-jQuery.props = {
- "for": "htmlFor",
- "class": "className",
- readonly: "readOnly",
- maxlength: "maxLength",
- cellspacing: "cellSpacing",
- rowspan: "rowSpan",
- colspan: "colSpan",
- tabindex: "tabIndex",
- usemap: "useMap",
- frameborder: "frameBorder"
-};
-
-jQuery.fn.extend({
- attr: function( name, value ) {
- return jQuery.access( this, name, value, true, jQuery.attr );
- },
-
- removeAttr: function( name, fn ) {
- return this.each(function(){
- jQuery.attr( this, name, "" );
- if ( this.nodeType === 1 ) {
- this.removeAttribute( name );
- }
- });
- },
-
- addClass: function( value ) {
- if ( jQuery.isFunction(value) ) {
- return this.each(function(i) {
- var self = jQuery(this);
- self.addClass( value.call(this, i, self.attr("class")) );
- });
- }
-
- if ( value && typeof value === "string" ) {
- var classNames = (value || "").split( rspaces );
-
- for ( var i = 0, l = this.length; i < l; i++ ) {
- var elem = this[i];
-
- if ( elem.nodeType === 1 ) {
- if ( !elem.className ) {
- elem.className = value;
-
- } else {
- var className = " " + elem.className + " ",
- setClass = elem.className;
-
- for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
- if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) {
- setClass += " " + classNames[c];
- }
- }
- elem.className = jQuery.trim( setClass );
- }
- }
- }
- }
-
- return this;
- },
-
- removeClass: function( value ) {
- if ( jQuery.isFunction(value) ) {
- return this.each(function(i) {
- var self = jQuery(this);
- self.removeClass( value.call(this, i, self.attr("class")) );
- });
- }
-
- if ( (value && typeof value === "string") || value === undefined ) {
- var classNames = (value || "").split( rspaces );
-
- for ( var i = 0, l = this.length; i < l; i++ ) {
- var elem = this[i];
-
- if ( elem.nodeType === 1 && elem.className ) {
- if ( value ) {
- var className = (" " + elem.className + " ").replace(rclass, " ");
- for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
- className = className.replace(" " + classNames[c] + " ", " ");
- }
- elem.className = jQuery.trim( className );
-
- } else {
- elem.className = "";
- }
- }
- }
- }
-
- return this;
- },
-
- toggleClass: function( value, stateVal ) {
- var type = typeof value,
- isBool = typeof stateVal === "boolean";
-
- if ( jQuery.isFunction( value ) ) {
- return this.each(function(i) {
- var self = jQuery(this);
- self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal );
- });
- }
-
- return this.each(function() {
- if ( type === "string" ) {
- // toggle individual class names
- var className,
- i = 0,
- self = jQuery( this ),
- state = stateVal,
- classNames = value.split( rspaces );
-
- while ( (className = classNames[ i++ ]) ) {
- // check each className given, space seperated list
- state = isBool ? state : !self.hasClass( className );
- self[ state ? "addClass" : "removeClass" ]( className );
- }
-
- } else if ( type === "undefined" || type === "boolean" ) {
- if ( this.className ) {
- // store className if set
- jQuery._data( this, "__className__", this.className );
- }
-
- // toggle whole className
- this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
- }
- });
- },
-
- hasClass: function( selector ) {
- var className = " " + selector + " ";
- for ( var i = 0, l = this.length; i < l; i++ ) {
- if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
- return true;
- }
- }
-
- return false;
- },
-
- val: function( value ) {
- if ( !arguments.length ) {
- var elem = this[0];
-
- if ( elem ) {
- if ( jQuery.nodeName( elem, "option" ) ) {
- // attributes.value is undefined in Blackberry 4.7 but
- // uses .value. See #6932
- var val = elem.attributes.value;
- return !val || val.specified ? elem.value : elem.text;
- }
-
- // We need to handle select boxes special
- if ( jQuery.nodeName( elem, "select" ) ) {
- var index = elem.selectedIndex,
- values = [],
- options = elem.options,
- one = elem.type === "select-one";
-
- // Nothing was selected
- if ( index < 0 ) {
- return null;
- }
-
- // Loop through all the selected options
- for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
- var option = options[ i ];
-
- // Don't return options that are disabled or in a disabled optgroup
- if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
- (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {
-
- // Get the specific value for the option
- value = jQuery(option).val();
-
- // We don't need an array for one selects
- if ( one ) {
- return value;
- }
-
- // Multi-Selects return an array
- values.push( value );
- }
- }
-
- // Fixes Bug #2551 -- select.val() broken in IE after form.reset()
- if ( one && !values.length && options.length ) {
- return jQuery( options[ index ] ).val();
- }
-
- return values;
- }
-
- // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
- if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) {
- return elem.getAttribute("value") === null ? "on" : elem.value;
- }
-
- // Everything else, we just grab the value
- return (elem.value || "").replace(rreturn, "");
-
- }
-
- return undefined;
- }
-
- var isFunction = jQuery.isFunction(value);
-
- return this.each(function(i) {
- var self = jQuery(this), val = value;
-
- if ( this.nodeType !== 1 ) {
- return;
- }
-
- if ( isFunction ) {
- val = value.call(this, i, self.val());
- }
-
- // Treat null/undefined as ""; convert numbers to string
- if ( val == null ) {
- val = "";
- } else if ( typeof val === "number" ) {
- val += "";
- } else if ( jQuery.isArray(val) ) {
- val = jQuery.map(val, function (value) {
- return value == null ? "" : value + "";
- });
- }
-
- if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) {
- this.checked = jQuery.inArray( self.val(), val ) >= 0;
-
- } else if ( jQuery.nodeName( this, "select" ) ) {
- var values = jQuery.makeArray(val);
-
- jQuery( "option", this ).each(function() {
- this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
- });
-
- if ( !values.length ) {
- this.selectedIndex = -1;
- }
-
- } else {
- this.value = val;
- }
- });
- }
-});
-
-jQuery.extend({
- attrFn: {
- val: true,
- css: true,
- html: true,
- text: true,
- data: true,
- width: true,
- height: true,
- offset: true
- },
-
- attr: function( elem, name, value, pass ) {
- // don't get/set attributes on text, comment and attribute nodes
- if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || elem.nodeType === 2 ) {
- return undefined;
- }
-
- if ( pass && name in jQuery.attrFn ) {
- return jQuery(elem)[name](value);
- }
-
- var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ),
- // Whether we are setting (or getting)
- set = value !== undefined;
-
- // Try to normalize/fix the name
- name = notxml && jQuery.props[ name ] || name;
-
- // Only do all the following if this is a node (faster for style)
- if ( elem.nodeType === 1 ) {
- // These attributes require special treatment
- var special = rspecialurl.test( name );
-
- // Safari mis-reports the default selected property of an option
- // Accessing the parent's selectedIndex property fixes it
- if ( name === "selected" && !jQuery.support.optSelected ) {
- var parent = elem.parentNode;
- if ( parent ) {
- parent.selectedIndex;
-
- // Make sure that it also works with optgroups, see #5701
- if ( parent.parentNode ) {
- parent.parentNode.selectedIndex;
- }
- }
- }
-
- // If applicable, access the attribute via the DOM 0 way
- // 'in' checks fail in Blackberry 4.7 #6931
- if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) {
- if ( set ) {
- // We can't allow the type property to be changed (since it causes problems in IE)
- if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) {
- jQuery.error( "type property can't be changed" );
- }
-
- if ( value === null ) {
- if ( elem.nodeType === 1 ) {
- elem.removeAttribute( name );
- }
-
- } else {
- elem[ name ] = value;
- }
- }
-
- // browsers index elements by id/name on forms, give priority to attributes.
- if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) {
- return elem.getAttributeNode( name ).nodeValue;
- }
-
- // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
- // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
- if ( name === "tabIndex" ) {
- var attributeNode = elem.getAttributeNode( "tabIndex" );
-
- return attributeNode && attributeNode.specified ?
- attributeNode.value :
- rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
- 0 :
- undefined;
- }
-
- return elem[ name ];
- }
-
- if ( !jQuery.support.style && notxml && name === "style" ) {
- if ( set ) {
- elem.style.cssText = "" + value;
- }
-
- return elem.style.cssText;
- }
-
- if ( set ) {
- // convert the value to a string (all browsers do this but IE) see #1070
- elem.setAttribute( name, "" + value );
- }
-
- // Ensure that missing attributes return undefined
- // Blackberry 4.7 returns "" from getAttribute #6938
- if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) {
- return undefined;
- }
-
- var attr = !jQuery.support.hrefNormalized && notxml && special ?
- // Some attributes require a special call on IE
- elem.getAttribute( name, 2 ) :
- elem.getAttribute( name );
-
- // Non-existent attributes return null, we normalize to undefined
- return attr === null ? undefined : attr;
- }
- // Handle everything which isn't a DOM element node
- if ( set ) {
- elem[ name ] = value;
- }
- return elem[ name ];
- }
-});
-
-
-
-
-var rnamespaces = /\.(.*)$/,
- rformElems = /^(?:textarea|input|select)$/i,
- rperiod = /\./g,
- rspace = / /g,
- rescape = /[^\w\s.|`]/g,
- fcleanup = function( nm ) {
- return nm.replace(rescape, "\\$&");
- };
-
-/*
- * A number of helper functions used for managing events.
- * Many of the ideas behind this code originated from
- * Dean Edwards' addEvent library.
- */
-jQuery.event = {
-
- // Bind an event to an element
- // Original by Dean Edwards
- add: function( elem, types, handler, data ) {
- if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
- return;
- }
-
- // TODO :: Use a try/catch until it's safe to pull this out (likely 1.6)
- // Minor release fix for bug #8018
- try {
- // For whatever reason, IE has trouble passing the window object
- // around, causing it to be cloned in the process
- if ( jQuery.isWindow( elem ) && ( elem !== window && !elem.frameElement ) ) {
- elem = window;
- }
- }
- catch ( e ) {}
-
- if ( handler === false ) {
- handler = returnFalse;
- } else if ( !handler ) {
- // Fixes bug #7229. Fix recommended by jdalton
- return;
- }
-
- var handleObjIn, handleObj;
-
- if ( handler.handler ) {
- handleObjIn = handler;
- handler = handleObjIn.handler;
- }
-
- // Make sure that the function being executed has a unique ID
- if ( !handler.guid ) {
- handler.guid = jQuery.guid++;
- }
-
- // Init the element's event structure
- var elemData = jQuery._data( elem );
-
- // If no elemData is found then we must be trying to bind to one of the
- // banned noData elements
- if ( !elemData ) {
- return;
- }
-
- var events = elemData.events,
- eventHandle = elemData.handle;
-
- if ( !events ) {
- elemData.events = events = {};
- }
-
- if ( !eventHandle ) {
- elemData.handle = eventHandle = function( e ) {
- // Handle the second event of a trigger and when
- // an event is called after a page has unloaded
- return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
- jQuery.event.handle.apply( eventHandle.elem, arguments ) :
- undefined;
- };
- }
-
- // Add elem as a property of the handle function
- // This is to prevent a memory leak with non-native events in IE.
- eventHandle.elem = elem;
-
- // Handle multiple events separated by a space
- // jQuery(...).bind("mouseover mouseout", fn);
- types = types.split(" ");
-
- var type, i = 0, namespaces;
-
- while ( (type = types[ i++ ]) ) {
- handleObj = handleObjIn ?
- jQuery.extend({}, handleObjIn) :
- { handler: handler, data: data };
-
- // Namespaced event handlers
- if ( type.indexOf(".") > -1 ) {
- namespaces = type.split(".");
- type = namespaces.shift();
- handleObj.namespace = namespaces.slice(0).sort().join(".");
-
- } else {
- namespaces = [];
- handleObj.namespace = "";
- }
-
- handleObj.type = type;
- if ( !handleObj.guid ) {
- handleObj.guid = handler.guid;
- }
-
- // Get the current list of functions bound to this event
- var handlers = events[ type ],
- special = jQuery.event.special[ type ] || {};
-
- // Init the event handler queue
- if ( !handlers ) {
- handlers = events[ type ] = [];
-
- // Check for a special event handler
- // Only use addEventListener/attachEvent if the special
- // events handler returns false
- if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
- // Bind the global event handler to the element
- if ( elem.addEventListener ) {
- elem.addEventListener( type, eventHandle, false );
-
- } else if ( elem.attachEvent ) {
- elem.attachEvent( "on" + type, eventHandle );
- }
- }
- }
-
- if ( special.add ) {
- special.add.call( elem, handleObj );
-
- if ( !handleObj.handler.guid ) {
- handleObj.handler.guid = handler.guid;
- }
- }
-
- // Add the function to the element's handler list
- handlers.push( handleObj );
-
- // Keep track of which events have been used, for global triggering
- jQuery.event.global[ type ] = true;
- }
-
- // Nullify elem to prevent memory leaks in IE
- elem = null;
- },
-
- global: {},
-
- // Detach an event or set of events from an element
- remove: function( elem, types, handler, pos ) {
- // don't do events on text and comment nodes
- if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
- return;
- }
-
- if ( handler === false ) {
- handler = returnFalse;
- }
-
- var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType,
- elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
- events = elemData && elemData.events;
-
- if ( !elemData || !events ) {
- return;
- }
-
- // types is actually an event object here
- if ( types && types.type ) {
- handler = types.handler;
- types = types.type;
- }
-
- // Unbind all events for the element
- if ( !types || typeof types === "string" && types.charAt(0) === "." ) {
- types = types || "";
-
- for ( type in events ) {
- jQuery.event.remove( elem, type + types );
- }
-
- return;
- }
-
- // Handle multiple events separated by a space
- // jQuery(...).unbind("mouseover mouseout", fn);
- types = types.split(" ");
-
- while ( (type = types[ i++ ]) ) {
- origType = type;
- handleObj = null;
- all = type.indexOf(".") < 0;
- namespaces = [];
-
- if ( !all ) {
- // Namespaced event handlers
- namespaces = type.split(".");
- type = namespaces.shift();
-
- namespace = new RegExp("(^|\\.)" +
- jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)");
- }
-
- eventType = events[ type ];
-
- if ( !eventType ) {
- continue;
- }
-
- if ( !handler ) {
- for ( j = 0; j < eventType.length; j++ ) {
- handleObj = eventType[ j ];
-
- if ( all || namespace.test( handleObj.namespace ) ) {
- jQuery.event.remove( elem, origType, handleObj.handler, j );
- eventType.splice( j--, 1 );
- }
- }
-
- continue;
- }
-
- special = jQuery.event.special[ type ] || {};
-
- for ( j = pos || 0; j < eventType.length; j++ ) {
- handleObj = eventType[ j ];
-
- if ( handler.guid === handleObj.guid ) {
- // remove the given handler for the given type
- if ( all || namespace.test( handleObj.namespace ) ) {
- if ( pos == null ) {
- eventType.splice( j--, 1 );
- }
-
- if ( special.remove ) {
- special.remove.call( elem, handleObj );
- }
- }
-
- if ( pos != null ) {
- break;
- }
- }
- }
-
- // remove generic event handler if no more handlers exist
- if ( eventType.length === 0 || pos != null && eventType.length === 1 ) {
- if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
- jQuery.removeEvent( elem, type, elemData.handle );
- }
-
- ret = null;
- delete events[ type ];
- }
- }
-
- // Remove the expando if it's no longer used
- if ( jQuery.isEmptyObject( events ) ) {
- var handle = elemData.handle;
- if ( handle ) {
- handle.elem = null;
- }
-
- delete elemData.events;
- delete elemData.handle;
-
- if ( jQuery.isEmptyObject( elemData ) ) {
- jQuery.removeData( elem, undefined, true );
- }
- }
- },
-
- // bubbling is internal
- trigger: function( event, data, elem /*, bubbling */ ) {
- // Event object or event type
- var type = event.type || event,
- bubbling = arguments[3];
-
- if ( !bubbling ) {
- event = typeof event === "object" ?
- // jQuery.Event object
- event[ jQuery.expando ] ? event :
- // Object literal
- jQuery.extend( jQuery.Event(type), event ) :
- // Just the event type (string)
- jQuery.Event(type);
-
- if ( type.indexOf("!") >= 0 ) {
- event.type = type = type.slice(0, -1);
- event.exclusive = true;
- }
-
- // Handle a global trigger
- if ( !elem ) {
- // Don't bubble custom events when global (to avoid too much overhead)
- event.stopPropagation();
-
- // Only trigger if we've ever bound an event for it
- if ( jQuery.event.global[ type ] ) {
- // XXX This code smells terrible. event.js should not be directly
- // inspecting the data cache
- jQuery.each( jQuery.cache, function() {
- // internalKey variable is just used to make it easier to find
- // and potentially change this stuff later; currently it just
- // points to jQuery.expando
- var internalKey = jQuery.expando,
- internalCache = this[ internalKey ];
- if ( internalCache && internalCache.events && internalCache.events[ type ] ) {
- jQuery.event.trigger( event, data, internalCache.handle.elem );
- }
- });
- }
- }
-
- // Handle triggering a single element
-
- // don't do events on text and comment nodes
- if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) {
- return undefined;
- }
-
- // Clean up in case it is reused
- event.result = undefined;
- event.target = elem;
-
- // Clone the incoming data, if any
- data = jQuery.makeArray( data );
- data.unshift( event );
- }
-
- event.currentTarget = elem;
-
- // Trigger the event, it is assumed that "handle" is a function
- var handle = jQuery._data( elem, "handle" );
-
- if ( handle ) {
- handle.apply( elem, data );
- }
-
- var parent = elem.parentNode || elem.ownerDocument;
-
- // Trigger an inline bound script
- try {
- if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) {
- if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) {
- event.result = false;
- event.preventDefault();
- }
- }
-
- // prevent IE from throwing an error for some elements with some event types, see #3533
- } catch (inlineError) {}
-
- if ( !event.isPropagationStopped() && parent ) {
- jQuery.event.trigger( event, data, parent, true );
-
- } else if ( !event.isDefaultPrevented() ) {
- var old,
- target = event.target,
- targetType = type.replace( rnamespaces, "" ),
- isClick = jQuery.nodeName( target, "a" ) && targetType === "click",
- special = jQuery.event.special[ targetType ] || {};
-
- if ( (!special._default || special._default.call( elem, event ) === false) &&
- !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) {
-
- try {
- if ( target[ targetType ] ) {
- // Make sure that we don't accidentally re-trigger the onFOO events
- old = target[ "on" + targetType ];
-
- if ( old ) {
- target[ "on" + targetType ] = null;
- }
-
- jQuery.event.triggered = event.type;
- target[ targetType ]();
- }
-
- // prevent IE from throwing an error for some elements with some event types, see #3533
- } catch (triggerError) {}
-
- if ( old ) {
- target[ "on" + targetType ] = old;
- }
-
- jQuery.event.triggered = undefined;
- }
- }
- },
-
- handle: function( event ) {
- var all, handlers, namespaces, namespace_re, events,
- namespace_sort = [],
- args = jQuery.makeArray( arguments );
-
- event = args[0] = jQuery.event.fix( event || window.event );
- event.currentTarget = this;
-
- // Namespaced event handlers
- all = event.type.indexOf(".") < 0 && !event.exclusive;
-
- if ( !all ) {
- namespaces = event.type.split(".");
- event.type = namespaces.shift();
- namespace_sort = namespaces.slice(0).sort();
- namespace_re = new RegExp("(^|\\.)" + namespace_sort.join("\\.(?:.*\\.)?") + "(\\.|$)");
- }
-
- event.namespace = event.namespace || namespace_sort.join(".");
-
- events = jQuery._data(this, "events");
-
- handlers = (events || {})[ event.type ];
-
- if ( events && handlers ) {
- // Clone the handlers to prevent manipulation
- handlers = handlers.slice(0);
-
- for ( var j = 0, l = handlers.length; j < l; j++ ) {
- var handleObj = handlers[ j ];
-
- // Filter the functions by class
- if ( all || namespace_re.test( handleObj.namespace ) ) {
- // Pass in a reference to the handler function itself
- // So that we can later remove it
- event.handler = handleObj.handler;
- event.data = handleObj.data;
- event.handleObj = handleObj;
-
- var ret = handleObj.handler.apply( this, args );
-
- if ( ret !== undefined ) {
- event.result = ret;
- if ( ret === false ) {
- event.preventDefault();
- event.stopPropagation();
- }
- }
-
- if ( event.isImmediatePropagationStopped() ) {
- break;
- }
- }
- }
- }
-
- return event.result;
- },
-
- props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
-
- fix: function( event ) {
- if ( event[ jQuery.expando ] ) {
- return event;
- }
-
- // store a copy of the original event object
- // and "clone" to set read-only properties
- var originalEvent = event;
- event = jQuery.Event( originalEvent );
-
- for ( var i = this.props.length, prop; i; ) {
- prop = this.props[ --i ];
- event[ prop ] = originalEvent[ prop ];
- }
-
- // Fix target property, if necessary
- if ( !event.target ) {
- // Fixes #1925 where srcElement might not be defined either
- event.target = event.srcElement || document;
- }
-
- // check if target is a textnode (safari)
- if ( event.target.nodeType === 3 ) {
- event.target = event.target.parentNode;
- }
-
- // Add relatedTarget, if necessary
- if ( !event.relatedTarget && event.fromElement ) {
- event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
- }
-
- // Calculate pageX/Y if missing and clientX/Y available
- if ( event.pageX == null && event.clientX != null ) {
- var doc = document.documentElement,
- body = document.body;
-
- event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
- event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
- }
-
- // Add which for key events
- if ( event.which == null && (event.charCode != null || event.keyCode != null) ) {
- event.which = event.charCode != null ? event.charCode : event.keyCode;
- }
-
- // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
- if ( !event.metaKey && event.ctrlKey ) {
- event.metaKey = event.ctrlKey;
- }
-
- // Add which for click: 1 === left; 2 === middle; 3 === right
- // Note: button is not normalized, so don't use it
- if ( !event.which && event.button !== undefined ) {
- event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
- }
-
- return event;
- },
-
- // Deprecated, use jQuery.guid instead
- guid: 1E8,
-
- // Deprecated, use jQuery.proxy instead
- proxy: jQuery.proxy,
-
- special: {
- ready: {
- // Make sure the ready event is setup
- setup: jQuery.bindReady,
- teardown: jQuery.noop
- },
-
- live: {
- add: function( handleObj ) {
- jQuery.event.add( this,
- liveConvert( handleObj.origType, handleObj.selector ),
- jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) );
- },
-
- remove: function( handleObj ) {
- jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj );
- }
- },
-
- beforeunload: {
- setup: function( data, namespaces, eventHandle ) {
- // We only want to do this special case on windows
- if ( jQuery.isWindow( this ) ) {
- this.onbeforeunload = eventHandle;
- }
- },
-
- teardown: function( namespaces, eventHandle ) {
- if ( this.onbeforeunload === eventHandle ) {
- this.onbeforeunload = null;
- }
- }
- }
- }
-};
-
-jQuery.removeEvent = document.removeEventListener ?
- function( elem, type, handle ) {
- if ( elem.removeEventListener ) {
- elem.removeEventListener( type, handle, false );
- }
- } :
- function( elem, type, handle ) {
- if ( elem.detachEvent ) {
- elem.detachEvent( "on" + type, handle );
- }
- };
-
-jQuery.Event = function( src ) {
- // Allow instantiation without the 'new' keyword
- if ( !this.preventDefault ) {
- return new jQuery.Event( src );
- }
-
- // Event object
- if ( src && src.type ) {
- this.originalEvent = src;
- this.type = src.type;
-
- // Events bubbling up the document may have been marked as prevented
- // by a handler lower down the tree; reflect the correct value.
- this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false ||
- src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse;
-
- // Event type
- } else {
- this.type = src;
- }
-
- // timeStamp is buggy for some events on Firefox(#3843)
- // So we won't rely on the native value
- this.timeStamp = jQuery.now();
-
- // Mark it as fixed
- this[ jQuery.expando ] = true;
-};
-
-function returnFalse() {
- return false;
-}
-function returnTrue() {
- return true;
-}
-
-// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
-// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
-jQuery.Event.prototype = {
- preventDefault: function() {
- this.isDefaultPrevented = returnTrue;
-
- var e = this.originalEvent;
- if ( !e ) {
- return;
- }
-
- // if preventDefault exists run it on the original event
- if ( e.preventDefault ) {
- e.preventDefault();
-
- // otherwise set the returnValue property of the original event to false (IE)
- } else {
- e.returnValue = false;
- }
- },
- stopPropagation: function() {
- this.isPropagationStopped = returnTrue;
-
- var e = this.originalEvent;
- if ( !e ) {
- return;
- }
- // if stopPropagation exists run it on the original event
- if ( e.stopPropagation ) {
- e.stopPropagation();
- }
- // otherwise set the cancelBubble property of the original event to true (IE)
- e.cancelBubble = true;
- },
- stopImmediatePropagation: function() {
- this.isImmediatePropagationStopped = returnTrue;
- this.stopPropagation();
- },
- isDefaultPrevented: returnFalse,
- isPropagationStopped: returnFalse,
- isImmediatePropagationStopped: returnFalse
-};
-
-// Checks if an event happened on an element within another element
-// Used in jQuery.event.special.mouseenter and mouseleave handlers
-var withinElement = function( event ) {
- // Check if mouse(over|out) are still within the same parent element
- var parent = event.relatedTarget;
-
- // Firefox sometimes assigns relatedTarget a XUL element
- // which we cannot access the parentNode property of
- try {
-
- // Chrome does something similar, the parentNode property
- // can be accessed but is null.
- if ( parent && parent !== document && !parent.parentNode ) {
- return;
- }
- // Traverse up the tree
- while ( parent && parent !== this ) {
- parent = parent.parentNode;
- }
-
- if ( parent !== this ) {
- // set the correct event type
- event.type = event.data;
-
- // handle event if we actually just moused on to a non sub-element
- jQuery.event.handle.apply( this, arguments );
- }
-
- // assuming we've left the element since we most likely mousedover a xul element
- } catch(e) { }
-},
-
-// In case of event delegation, we only need to rename the event.type,
-// liveHandler will take care of the rest.
-delegate = function( event ) {
- event.type = event.data;
- jQuery.event.handle.apply( this, arguments );
-};
-
-// Create mouseenter and mouseleave events
-jQuery.each({
- mouseenter: "mouseover",
- mouseleave: "mouseout"
-}, function( orig, fix ) {
- jQuery.event.special[ orig ] = {
- setup: function( data ) {
- jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig );
- },
- teardown: function( data ) {
- jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement );
- }
- };
-});
-
-// submit delegation
-if ( !jQuery.support.submitBubbles ) {
-
- jQuery.event.special.submit = {
- setup: function( data, namespaces ) {
- if ( this.nodeName && this.nodeName.toLowerCase() !== "form" ) {
- jQuery.event.add(this, "click.specialSubmit", function( e ) {
- var elem = e.target,
- type = elem.type;
-
- if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) {
- trigger( "submit", this, arguments );
- }
- });
-
- jQuery.event.add(this, "keypress.specialSubmit", function( e ) {
- var elem = e.target,
- type = elem.type;
-
- if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) {
- trigger( "submit", this, arguments );
- }
- });
-
- } else {
- return false;
- }
- },
-
- teardown: function( namespaces ) {
- jQuery.event.remove( this, ".specialSubmit" );
- }
- };
-
-}
-
-// change delegation, happens here so we have bind.
-if ( !jQuery.support.changeBubbles ) {
-
- var changeFilters,
-
- getVal = function( elem ) {
- var type = elem.type, val = elem.value;
-
- if ( type === "radio" || type === "checkbox" ) {
- val = elem.checked;
-
- } else if ( type === "select-multiple" ) {
- val = elem.selectedIndex > -1 ?
- jQuery.map( elem.options, function( elem ) {
- return elem.selected;
- }).join("-") :
- "";
-
- } else if ( elem.nodeName.toLowerCase() === "select" ) {
- val = elem.selectedIndex;
- }
-
- return val;
- },
-
- testChange = function testChange( e ) {
- var elem = e.target, data, val;
-
- if ( !rformElems.test( elem.nodeName ) || elem.readOnly ) {
- return;
- }
-
- data = jQuery._data( elem, "_change_data" );
- val = getVal(elem);
-
- // the current data will be also retrieved by beforeactivate
- if ( e.type !== "focusout" || elem.type !== "radio" ) {
- jQuery._data( elem, "_change_data", val );
- }
-
- if ( data === undefined || val === data ) {
- return;
- }
-
- if ( data != null || val ) {
- e.type = "change";
- e.liveFired = undefined;
- jQuery.event.trigger( e, arguments[1], elem );
- }
- };
-
- jQuery.event.special.change = {
- filters: {
- focusout: testChange,
-
- beforedeactivate: testChange,
-
- click: function( e ) {
- var elem = e.target, type = elem.type;
-
- if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) {
- testChange.call( this, e );
- }
- },
-
- // Change has to be called before submit
- // Keydown will be called before keypress, which is used in submit-event delegation
- keydown: function( e ) {
- var elem = e.target, type = elem.type;
-
- if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") ||
- (e.keyCode === 32 && (type === "checkbox" || type === "radio")) ||
- type === "select-multiple" ) {
- testChange.call( this, e );
- }
- },
-
- // Beforeactivate happens also before the previous element is blurred
- // with this event you can't trigger a change event, but you can store
- // information
- beforeactivate: function( e ) {
- var elem = e.target;
- jQuery._data( elem, "_change_data", getVal(elem) );
- }
- },
-
- setup: function( data, namespaces ) {
- if ( this.type === "file" ) {
- return false;
- }
-
- for ( var type in changeFilters ) {
- jQuery.event.add( this, type + ".specialChange", changeFilters[type] );
- }
-
- return rformElems.test( this.nodeName );
- },
-
- teardown: function( namespaces ) {
- jQuery.event.remove( this, ".specialChange" );
-
- return rformElems.test( this.nodeName );
- }
- };
-
- changeFilters = jQuery.event.special.change.filters;
-
- // Handle when the input is .focus()'d
- changeFilters.focus = changeFilters.beforeactivate;
-}
-
-function trigger( type, elem, args ) {
- // Piggyback on a donor event to simulate a different one.
- // Fake originalEvent to avoid donor's stopPropagation, but if the
- // simulated event prevents default then we do the same on the donor.
- // Don't pass args or remember liveFired; they apply to the donor event.
- var event = jQuery.extend( {}, args[ 0 ] );
- event.type = type;
- event.originalEvent = {};
- event.liveFired = undefined;
- jQuery.event.handle.call( elem, event );
- if ( event.isDefaultPrevented() ) {
- args[ 0 ].preventDefault();
- }
-}
-
-// Create "bubbling" focus and blur events
-if ( document.addEventListener ) {
- jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
-
- // Attach a single capturing handler while someone wants focusin/focusout
- var attaches = 0;
-
- jQuery.event.special[ fix ] = {
- setup: function() {
- if ( attaches++ === 0 ) {
- document.addEventListener( orig, handler, true );
- }
- },
- teardown: function() {
- if ( --attaches === 0 ) {
- document.removeEventListener( orig, handler, true );
- }
- }
- };
-
- function handler( donor ) {
- // Donor event is always a native one; fix it and switch its type.
- // Let focusin/out handler cancel the donor focus/blur event.
- var e = jQuery.event.fix( donor );
- e.type = fix;
- e.originalEvent = {};
- jQuery.event.trigger( e, null, e.target );
- if ( e.isDefaultPrevented() ) {
- donor.preventDefault();
- }
- }
- });
-}
-
-jQuery.each(["bind", "one"], function( i, name ) {
- jQuery.fn[ name ] = function( type, data, fn ) {
- // Handle object literals
- if ( typeof type === "object" ) {
- for ( var key in type ) {
- this[ name ](key, data, type[key], fn);
- }
- return this;
- }
-
- if ( jQuery.isFunction( data ) || data === false ) {
- fn = data;
- data = undefined;
- }
-
- var handler = name === "one" ? jQuery.proxy( fn, function( event ) {
- jQuery( this ).unbind( event, handler );
- return fn.apply( this, arguments );
- }) : fn;
-
- if ( type === "unload" && name !== "one" ) {
- this.one( type, data, fn );
-
- } else {
- for ( var i = 0, l = this.length; i < l; i++ ) {
- jQuery.event.add( this[i], type, handler, data );
- }
- }
-
- return this;
- };
-});
-
-jQuery.fn.extend({
- unbind: function( type, fn ) {
- // Handle object literals
- if ( typeof type === "object" && !type.preventDefault ) {
- for ( var key in type ) {
- this.unbind(key, type[key]);
- }
-
- } else {
- for ( var i = 0, l = this.length; i < l; i++ ) {
- jQuery.event.remove( this[i], type, fn );
- }
- }
-
- return this;
- },
-
- delegate: function( selector, types, data, fn ) {
- return this.live( types, data, fn, selector );
- },
-
- undelegate: function( selector, types, fn ) {
- if ( arguments.length === 0 ) {
- return this.unbind( "live" );
-
- } else {
- return this.die( types, null, fn, selector );
- }
- },
-
- trigger: function( type, data ) {
- return this.each(function() {
- jQuery.event.trigger( type, data, this );
- });
- },
-
- triggerHandler: function( type, data ) {
- if ( this[0] ) {
- var event = jQuery.Event( type );
- event.preventDefault();
- event.stopPropagation();
- jQuery.event.trigger( event, data, this[0] );
- return event.result;
- }
- },
-
- toggle: function( fn ) {
- // Save reference to arguments for access in closure
- var args = arguments,
- i = 1;
-
- // link all the functions, so any of them can unbind this click handler
- while ( i < args.length ) {
- jQuery.proxy( fn, args[ i++ ] );
- }
-
- return this.click( jQuery.proxy( fn, function( event ) {
- // Figure out which function to execute
- var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
- jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
-
- // Make sure that clicks stop
- event.preventDefault();
-
- // and execute the function
- return args[ lastToggle ].apply( this, arguments ) || false;
- }));
- },
-
- hover: function( fnOver, fnOut ) {
- return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
- }
-});
-
-var liveMap = {
- focus: "focusin",
- blur: "focusout",
- mouseenter: "mouseover",
- mouseleave: "mouseout"
-};
-
-jQuery.each(["live", "die"], function( i, name ) {
- jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) {
- var type, i = 0, match, namespaces, preType,
- selector = origSelector || this.selector,
- context = origSelector ? this : jQuery( this.context );
-
- if ( typeof types === "object" && !types.preventDefault ) {
- for ( var key in types ) {
- context[ name ]( key, data, types[key], selector );
- }
-
- return this;
- }
-
- if ( jQuery.isFunction( data ) ) {
- fn = data;
- data = undefined;
- }
-
- types = (types || "").split(" ");
-
- while ( (type = types[ i++ ]) != null ) {
- match = rnamespaces.exec( type );
- namespaces = "";
-
- if ( match ) {
- namespaces = match[0];
- type = type.replace( rnamespaces, "" );
- }
-
- if ( type === "hover" ) {
- types.push( "mouseenter" + namespaces, "mouseleave" + namespaces );
- continue;
- }
-
- preType = type;
-
- if ( type === "focus" || type === "blur" ) {
- types.push( liveMap[ type ] + namespaces );
- type = type + namespaces;
-
- } else {
- type = (liveMap[ type ] || type) + namespaces;
- }
-
- if ( name === "live" ) {
- // bind live handler
- for ( var j = 0, l = context.length; j < l; j++ ) {
- jQuery.event.add( context[j], "live." + liveConvert( type, selector ),
- { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } );
- }
-
- } else {
- // unbind live handler
- context.unbind( "live." + liveConvert( type, selector ), fn );
- }
- }
-
- return this;
- };
-});
-
-function liveHandler( event ) {
- var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret,
- elems = [],
- selectors = [],
- events = jQuery._data( this, "events" );
-
- // Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911)
- if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) {
- return;
- }
-
- if ( event.namespace ) {
- namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)");
- }
-
- event.liveFired = this;
-
- var live = events.live.slice(0);
-
- for ( j = 0; j < live.length; j++ ) {
- handleObj = live[j];
-
- if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) {
- selectors.push( handleObj.selector );
-
- } else {
- live.splice( j--, 1 );
- }
- }
-
- match = jQuery( event.target ).closest( selectors, event.currentTarget );
-
- for ( i = 0, l = match.length; i < l; i++ ) {
- close = match[i];
-
- for ( j = 0; j < live.length; j++ ) {
- handleObj = live[j];
-
- if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) && !close.elem.disabled ) {
- elem = close.elem;
- related = null;
-
- // Those two events require additional checking
- if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) {
- event.type = handleObj.preType;
- related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0];
- }
-
- if ( !related || related !== elem ) {
- elems.push({ elem: elem, handleObj: handleObj, level: close.level });
- }
- }
- }
- }
-
- for ( i = 0, l = elems.length; i < l; i++ ) {
- match = elems[i];
-
- if ( maxLevel && match.level > maxLevel ) {
- break;
- }
-
- event.currentTarget = match.elem;
- event.data = match.handleObj.data;
- event.handleObj = match.handleObj;
-
- ret = match.handleObj.origHandler.apply( match.elem, arguments );
-
- if ( ret === false || event.isPropagationStopped() ) {
- maxLevel = match.level;
-
- if ( ret === false ) {
- stop = false;
- }
- if ( event.isImmediatePropagationStopped() ) {
- break;
- }
- }
- }
-
- return stop;
-}
-
-function liveConvert( type, selector ) {
- return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspace, "&");
-}
-
-jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
- "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
- "change select submit keydown keypress keyup error").split(" "), function( i, name ) {
-
- // Handle event binding
- jQuery.fn[ name ] = function( data, fn ) {
- if ( fn == null ) {
- fn = data;
- data = null;
- }
-
- return arguments.length > 0 ?
- this.bind( name, data, fn ) :
- this.trigger( name );
- };
-
- if ( jQuery.attrFn ) {
- jQuery.attrFn[ name ] = true;
- }
-});
-
-
-/*!
- * Sizzle CSS Selector Engine
- * Copyright 2011, The Dojo Foundation
- * Released under the MIT, BSD, and GPL Licenses.
- * More information: http://sizzlejs.com/
- */
-(function(){
-
-var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
- done = 0,
- toString = Object.prototype.toString,
- hasDuplicate = false,
- baseHasDuplicate = true,
- rBackslash = /\\/g,
- rNonWord = /\W/;
-
-// Here we check if the JavaScript engine is using some sort of
-// optimization where it does not always call our comparision
-// function. If that is the case, discard the hasDuplicate value.
-// Thus far that includes Google Chrome.
-[0, 0].sort(function() {
- baseHasDuplicate = false;
- return 0;
-});
-
-var Sizzle = function( selector, context, results, seed ) {
- results = results || [];
- context = context || document;
-
- var origContext = context;
-
- if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
- return [];
- }
-
- if ( !selector || typeof selector !== "string" ) {
- return results;
- }
-
- var m, set, checkSet, extra, ret, cur, pop, i,
- prune = true,
- contextXML = Sizzle.isXML( context ),
- parts = [],
- soFar = selector;
-
- // Reset the position of the chunker regexp (start from head)
- do {
- chunker.exec( "" );
- m = chunker.exec( soFar );
-
- if ( m ) {
- soFar = m[3];
-
- parts.push( m[1] );
-
- if ( m[2] ) {
- extra = m[3];
- break;
- }
- }
- } while ( m );
-
- if ( parts.length > 1 && origPOS.exec( selector ) ) {
-
- if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
- set = posProcess( parts[0] + parts[1], context );
-
- } else {
- set = Expr.relative[ parts[0] ] ?
- [ context ] :
- Sizzle( parts.shift(), context );
-
- while ( parts.length ) {
- selector = parts.shift();
-
- if ( Expr.relative[ selector ] ) {
- selector += parts.shift();
- }
-
- set = posProcess( selector, set );
- }
- }
-
- } else {
- // Take a shortcut and set the context if the root selector is an ID
- // (but not if it'll be faster if the inner selector is an ID)
- if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
- Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
-
- ret = Sizzle.find( parts.shift(), context, contextXML );
- context = ret.expr ?
- Sizzle.filter( ret.expr, ret.set )[0] :
- ret.set[0];
- }
-
- if ( context ) {
- ret = seed ?
- { expr: parts.pop(), set: makeArray(seed) } :
- Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
-
- set = ret.expr ?
- Sizzle.filter( ret.expr, ret.set ) :
- ret.set;
-
- if ( parts.length > 0 ) {
- checkSet = makeArray( set );
-
- } else {
- prune = false;
- }
-
- while ( parts.length ) {
- cur = parts.pop();
- pop = cur;
-
- if ( !Expr.relative[ cur ] ) {
- cur = "";
- } else {
- pop = parts.pop();
- }
-
- if ( pop == null ) {
- pop = context;
- }
-
- Expr.relative[ cur ]( checkSet, pop, contextXML );
- }
-
- } else {
- checkSet = parts = [];
- }
- }
-
- if ( !checkSet ) {
- checkSet = set;
- }
-
- if ( !checkSet ) {
- Sizzle.error( cur || selector );
- }
-
- if ( toString.call(checkSet) === "[object Array]" ) {
- if ( !prune ) {
- results.push.apply( results, checkSet );
-
- } else if ( context && context.nodeType === 1 ) {
- for ( i = 0; checkSet[i] != null; i++ ) {
- if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
- results.push( set[i] );
- }
- }
-
- } else {
- for ( i = 0; checkSet[i] != null; i++ ) {
- if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
- results.push( set[i] );
- }
- }
- }
-
- } else {
- makeArray( checkSet, results );
- }
-
- if ( extra ) {
- Sizzle( extra, origContext, results, seed );
- Sizzle.uniqueSort( results );
- }
-
- return results;
-};
-
-Sizzle.uniqueSort = function( results ) {
- if ( sortOrder ) {
- hasDuplicate = baseHasDuplicate;
- results.sort( sortOrder );
-
- if ( hasDuplicate ) {
- for ( var i = 1; i < results.length; i++ ) {
- if ( results[i] === results[ i - 1 ] ) {
- results.splice( i--, 1 );
- }
- }
- }
- }
-
- return results;
-};
-
-Sizzle.matches = function( expr, set ) {
- return Sizzle( expr, null, null, set );
-};
-
-Sizzle.matchesSelector = function( node, expr ) {
- return Sizzle( expr, null, null, [node] ).length > 0;
-};
-
-Sizzle.find = function( expr, context, isXML ) {
- var set;
-
- if ( !expr ) {
- return [];
- }
-
- for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
- var match,
- type = Expr.order[i];
-
- if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
- var left = match[1];
- match.splice( 1, 1 );
-
- if ( left.substr( left.length - 1 ) !== "\\" ) {
- match[1] = (match[1] || "").replace( rBackslash, "" );
- set = Expr.find[ type ]( match, context, isXML );
-
- if ( set != null ) {
- expr = expr.replace( Expr.match[ type ], "" );
- break;
- }
- }
- }
- }
-
- if ( !set ) {
- set = typeof context.getElementsByTagName !== "undefined" ?
- context.getElementsByTagName( "*" ) :
- [];
- }
-
- return { set: set, expr: expr };
-};
-
-Sizzle.filter = function( expr, set, inplace, not ) {
- var match, anyFound,
- old = expr,
- result = [],
- curLoop = set,
- isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
-
- while ( expr && set.length ) {
- for ( var type in Expr.filter ) {
- if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
- var found, item,
- filter = Expr.filter[ type ],
- left = match[1];
-
- anyFound = false;
-
- match.splice(1,1);
-
- if ( left.substr( left.length - 1 ) === "\\" ) {
- continue;
- }
-
- if ( curLoop === result ) {
- result = [];
- }
-
- if ( Expr.preFilter[ type ] ) {
- match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
-
- if ( !match ) {
- anyFound = found = true;
-
- } else if ( match === true ) {
- continue;
- }
- }
-
- if ( match ) {
- for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
- if ( item ) {
- found = filter( item, match, i, curLoop );
- var pass = not ^ !!found;
-
- if ( inplace && found != null ) {
- if ( pass ) {
- anyFound = true;
-
- } else {
- curLoop[i] = false;
- }
-
- } else if ( pass ) {
- result.push( item );
- anyFound = true;
- }
- }
- }
- }
-
- if ( found !== undefined ) {
- if ( !inplace ) {
- curLoop = result;
- }
-
- expr = expr.replace( Expr.match[ type ], "" );
-
- if ( !anyFound ) {
- return [];
- }
-
- break;
- }
- }
- }
-
- // Improper expression
- if ( expr === old ) {
- if ( anyFound == null ) {
- Sizzle.error( expr );
-
- } else {
- break;
- }
- }
-
- old = expr;
- }
-
- return curLoop;
-};
-
-Sizzle.error = function( msg ) {
- throw "Syntax error, unrecognized expression: " + msg;
-};
-
-var Expr = Sizzle.selectors = {
- order: [ "ID", "NAME", "TAG" ],
-
- match: {
- ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
- CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
- NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
- ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
- TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
- CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
- POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
- PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
- },
-
- leftMatch: {},
-
- attrMap: {
- "class": "className",
- "for": "htmlFor"
- },
-
- attrHandle: {
- href: function( elem ) {
- return elem.getAttribute( "href" );
- },
- type: function( elem ) {
- return elem.getAttribute( "type" );
- }
- },
-
- relative: {
- "+": function(checkSet, part){
- var isPartStr = typeof part === "string",
- isTag = isPartStr && !rNonWord.test( part ),
- isPartStrNotTag = isPartStr && !isTag;
-
- if ( isTag ) {
- part = part.toLowerCase();
- }
-
- for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
- if ( (elem = checkSet[i]) ) {
- while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
-
- checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
- elem || false :
- elem === part;
- }
- }
-
- if ( isPartStrNotTag ) {
- Sizzle.filter( part, checkSet, true );
- }
- },
-
- ">": function( checkSet, part ) {
- var elem,
- isPartStr = typeof part === "string",
- i = 0,
- l = checkSet.length;
-
- if ( isPartStr && !rNonWord.test( part ) ) {
- part = part.toLowerCase();
-
- for ( ; i < l; i++ ) {
- elem = checkSet[i];
-
- if ( elem ) {
- var parent = elem.parentNode;
- checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
- }
- }
-
- } else {
- for ( ; i < l; i++ ) {
- elem = checkSet[i];
-
- if ( elem ) {
- checkSet[i] = isPartStr ?
- elem.parentNode :
- elem.parentNode === part;
- }
- }
-
- if ( isPartStr ) {
- Sizzle.filter( part, checkSet, true );
- }
- }
- },
-
- "": function(checkSet, part, isXML){
- var nodeCheck,
- doneName = done++,
- checkFn = dirCheck;
-
- if ( typeof part === "string" && !rNonWord.test( part ) ) {
- part = part.toLowerCase();
- nodeCheck = part;
- checkFn = dirNodeCheck;
- }
-
- checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
- },
-
- "~": function( checkSet, part, isXML ) {
- var nodeCheck,
- doneName = done++,
- checkFn = dirCheck;
-
- if ( typeof part === "string" && !rNonWord.test( part ) ) {
- part = part.toLowerCase();
- nodeCheck = part;
- checkFn = dirNodeCheck;
- }
-
- checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
- }
- },
-
- find: {
- ID: function( match, context, isXML ) {
- if ( typeof context.getElementById !== "undefined" && !isXML ) {
- var m = context.getElementById(match[1]);
- // Check parentNode to catch when Blackberry 4.6 returns
- // nodes that are no longer in the document #6963
- return m && m.parentNode ? [m] : [];
- }
- },
-
- NAME: function( match, context ) {
- if ( typeof context.getElementsByName !== "undefined" ) {
- var ret = [],
- results = context.getElementsByName( match[1] );
-
- for ( var i = 0, l = results.length; i < l; i++ ) {
- if ( results[i].getAttribute("name") === match[1] ) {
- ret.push( results[i] );
- }
- }
-
- return ret.length === 0 ? null : ret;
- }
- },
-
- TAG: function( match, context ) {
- if ( typeof context.getElementsByTagName !== "undefined" ) {
- return context.getElementsByTagName( match[1] );
- }
- }
- },
- preFilter: {
- CLASS: function( match, curLoop, inplace, result, not, isXML ) {
- match = " " + match[1].replace( rBackslash, "" ) + " ";
-
- if ( isXML ) {
- return match;
- }
-
- for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
- if ( elem ) {
- if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
- if ( !inplace ) {
- result.push( elem );
- }
-
- } else if ( inplace ) {
- curLoop[i] = false;
- }
- }
- }
-
- return false;
- },
-
- ID: function( match ) {
- return match[1].replace( rBackslash, "" );
- },
-
- TAG: function( match, curLoop ) {
- return match[1].replace( rBackslash, "" ).toLowerCase();
- },
-
- CHILD: function( match ) {
- if ( match[1] === "nth" ) {
- if ( !match[2] ) {
- Sizzle.error( match[0] );
- }
-
- match[2] = match[2].replace(/^\+|\s*/g, '');
-
- // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
- var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
- match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
- !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
-
- // calculate the numbers (first)n+(last) including if they are negative
- match[2] = (test[1] + (test[2] || 1)) - 0;
- match[3] = test[3] - 0;
- }
- else if ( match[2] ) {
- Sizzle.error( match[0] );
- }
-
- // TODO: Move to normal caching system
- match[0] = done++;
-
- return match;
- },
-
- ATTR: function( match, curLoop, inplace, result, not, isXML ) {
- var name = match[1] = match[1].replace( rBackslash, "" );
-
- if ( !isXML && Expr.attrMap[name] ) {
- match[1] = Expr.attrMap[name];
- }
-
- // Handle if an un-quoted value was used
- match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
-
- if ( match[2] === "~=" ) {
- match[4] = " " + match[4] + " ";
- }
-
- return match;
- },
-
- PSEUDO: function( match, curLoop, inplace, result, not ) {
- if ( match[1] === "not" ) {
- // If we're dealing with a complex expression, or a simple one
- if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
- match[3] = Sizzle(match[3], null, null, curLoop);
-
- } else {
- var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
-
- if ( !inplace ) {
- result.push.apply( result, ret );
- }
-
- return false;
- }
-
- } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
- return true;
- }
-
- return match;
- },
-
- POS: function( match ) {
- match.unshift( true );
-
- return match;
- }
- },
-
- filters: {
- enabled: function( elem ) {
- return elem.disabled === false && elem.type !== "hidden";
- },
-
- disabled: function( elem ) {
- return elem.disabled === true;
- },
-
- checked: function( elem ) {
- return elem.checked === true;
- },
-
- selected: function( elem ) {
- // Accessing this property makes selected-by-default
- // options in Safari work properly
- if ( elem.parentNode ) {
- elem.parentNode.selectedIndex;
- }
-
- return elem.selected === true;
- },
-
- parent: function( elem ) {
- return !!elem.firstChild;
- },
-
- empty: function( elem ) {
- return !elem.firstChild;
- },
-
- has: function( elem, i, match ) {
- return !!Sizzle( match[3], elem ).length;
- },
-
- header: function( elem ) {
- return (/h\d/i).test( elem.nodeName );
- },
-
- text: function( elem ) {
- var attr = elem.getAttribute( "type" ), type = elem.type;
- // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
- // use getAttribute instead to test this case
- return "text" === type && ( attr === type || attr === null );
- },
-
- radio: function( elem ) {
- return "radio" === elem.type;
- },
-
- checkbox: function( elem ) {
- return "checkbox" === elem.type;
- },
-
- file: function( elem ) {
- return "file" === elem.type;
- },
- password: function( elem ) {
- return "password" === elem.type;
- },
-
- submit: function( elem ) {
- return "submit" === elem.type;
- },
-
- image: function( elem ) {
- return "image" === elem.type;
- },
-
- reset: function( elem ) {
- return "reset" === elem.type;
- },
-
- button: function( elem ) {
- return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
- },
-
- input: function( elem ) {
- return (/input|select|textarea|button/i).test( elem.nodeName );
- }
- },
- setFilters: {
- first: function( elem, i ) {
- return i === 0;
- },
-
- last: function( elem, i, match, array ) {
- return i === array.length - 1;
- },
-
- even: function( elem, i ) {
- return i % 2 === 0;
- },
-
- odd: function( elem, i ) {
- return i % 2 === 1;
- },
-
- lt: function( elem, i, match ) {
- return i < match[3] - 0;
- },
-
- gt: function( elem, i, match ) {
- return i > match[3] - 0;
- },
-
- nth: function( elem, i, match ) {
- return match[3] - 0 === i;
- },
-
- eq: function( elem, i, match ) {
- return match[3] - 0 === i;
- }
- },
- filter: {
- PSEUDO: function( elem, match, i, array ) {
- var name = match[1],
- filter = Expr.filters[ name ];
-
- if ( filter ) {
- return filter( elem, i, match, array );
-
- } else if ( name === "contains" ) {
- return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
-
- } else if ( name === "not" ) {
- var not = match[3];
-
- for ( var j = 0, l = not.length; j < l; j++ ) {
- if ( not[j] === elem ) {
- return false;
- }
- }
-
- return true;
-
- } else {
- Sizzle.error( name );
- }
- },
-
- CHILD: function( elem, match ) {
- var type = match[1],
- node = elem;
-
- switch ( type ) {
- case "only":
- case "first":
- while ( (node = node.previousSibling) ) {
- if ( node.nodeType === 1 ) {
- return false;
- }
- }
-
- if ( type === "first" ) {
- return true;
- }
-
- node = elem;
-
- case "last":
- while ( (node = node.nextSibling) ) {
- if ( node.nodeType === 1 ) {
- return false;
- }
- }
-
- return true;
-
- case "nth":
- var first = match[2],
- last = match[3];
-
- if ( first === 1 && last === 0 ) {
- return true;
- }
-
- var doneName = match[0],
- parent = elem.parentNode;
-
- if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
- var count = 0;
-
- for ( node = parent.firstChild; node; node = node.nextSibling ) {
- if ( node.nodeType === 1 ) {
- node.nodeIndex = ++count;
- }
- }
-
- parent.sizcache = doneName;
- }
-
- var diff = elem.nodeIndex - last;
-
- if ( first === 0 ) {
- return diff === 0;
-
- } else {
- return ( diff % first === 0 && diff / first >= 0 );
- }
- }
- },
-
- ID: function( elem, match ) {
- return elem.nodeType === 1 && elem.getAttribute("id") === match;
- },
-
- TAG: function( elem, match ) {
- return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
- },
-
- CLASS: function( elem, match ) {
- return (" " + (elem.className || elem.getAttribute("class")) + " ")
- .indexOf( match ) > -1;
- },
-
- ATTR: function( elem, match ) {
- var name = match[1],
- result = Expr.attrHandle[ name ] ?
- Expr.attrHandle[ name ]( elem ) :
- elem[ name ] != null ?
- elem[ name ] :
- elem.getAttribute( name ),
- value = result + "",
- type = match[2],
- check = match[4];
-
- return result == null ?
- type === "!=" :
- type === "=" ?
- value === check :
- type === "*=" ?
- value.indexOf(check) >= 0 :
- type === "~=" ?
- (" " + value + " ").indexOf(check) >= 0 :
- !check ?
- value && result !== false :
- type === "!=" ?
- value !== check :
- type === "^=" ?
- value.indexOf(check) === 0 :
- type === "$=" ?
- value.substr(value.length - check.length) === check :
- type === "|=" ?
- value === check || value.substr(0, check.length + 1) === check + "-" :
- false;
- },
-
- POS: function( elem, match, i, array ) {
- var name = match[2],
- filter = Expr.setFilters[ name ];
-
- if ( filter ) {
- return filter( elem, i, match, array );
- }
- }
- }
-};
-
-var origPOS = Expr.match.POS,
- fescape = function(all, num){
- return "\\" + (num - 0 + 1);
- };
-
-for ( var type in Expr.match ) {
- Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
- Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
-}
-
-var makeArray = function( array, results ) {
- array = Array.prototype.slice.call( array, 0 );
-
- if ( results ) {
- results.push.apply( results, array );
- return results;
- }
-
- return array;
-};
-
-// Perform a simple check to determine if the browser is capable of
-// converting a NodeList to an array using builtin methods.
-// Also verifies that the returned array holds DOM nodes
-// (which is not the case in the Blackberry browser)
-try {
- Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
-
-// Provide a fallback method if it does not work
-} catch( e ) {
- makeArray = function( array, results ) {
- var i = 0,
- ret = results || [];
-
- if ( toString.call(array) === "[object Array]" ) {
- Array.prototype.push.apply( ret, array );
-
- } else {
- if ( typeof array.length === "number" ) {
- for ( var l = array.length; i < l; i++ ) {
- ret.push( array[i] );
- }
-
- } else {
- for ( ; array[i]; i++ ) {
- ret.push( array[i] );
- }
- }
- }
-
- return ret;
- };
-}
-
-var sortOrder, siblingCheck;
-
-if ( document.documentElement.compareDocumentPosition ) {
- sortOrder = function( a, b ) {
- if ( a === b ) {
- hasDuplicate = true;
- return 0;
- }
-
- if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
- return a.compareDocumentPosition ? -1 : 1;
- }
-
- return a.compareDocumentPosition(b) & 4 ? -1 : 1;
- };
-
-} else {
- sortOrder = function( a, b ) {
- var al, bl,
- ap = [],
- bp = [],
- aup = a.parentNode,
- bup = b.parentNode,
- cur = aup;
-
- // The nodes are identical, we can exit early
- if ( a === b ) {
- hasDuplicate = true;
- return 0;
-
- // If the nodes are siblings (or identical) we can do a quick check
- } else if ( aup === bup ) {
- return siblingCheck( a, b );
-
- // If no parents were found then the nodes are disconnected
- } else if ( !aup ) {
- return -1;
-
- } else if ( !bup ) {
- return 1;
- }
-
- // Otherwise they're somewhere else in the tree so we need
- // to build up a full list of the parentNodes for comparison
- while ( cur ) {
- ap.unshift( cur );
- cur = cur.parentNode;
- }
-
- cur = bup;
-
- while ( cur ) {
- bp.unshift( cur );
- cur = cur.parentNode;
- }
-
- al = ap.length;
- bl = bp.length;
-
- // Start walking down the tree looking for a discrepancy
- for ( var i = 0; i < al && i < bl; i++ ) {
- if ( ap[i] !== bp[i] ) {
- return siblingCheck( ap[i], bp[i] );
- }
- }
-
- // We ended someplace up the tree so do a sibling check
- return i === al ?
- siblingCheck( a, bp[i], -1 ) :
- siblingCheck( ap[i], b, 1 );
- };
-
- siblingCheck = function( a, b, ret ) {
- if ( a === b ) {
- return ret;
- }
-
- var cur = a.nextSibling;
-
- while ( cur ) {
- if ( cur === b ) {
- return -1;
- }
-
- cur = cur.nextSibling;
- }
-
- return 1;
- };
-}
-
-// Utility function for retreiving the text value of an array of DOM nodes
-Sizzle.getText = function( elems ) {
- var ret = "", elem;
-
- for ( var i = 0; elems[i]; i++ ) {
- elem = elems[i];
-
- // Get the text from text nodes and CDATA nodes
- if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
- ret += elem.nodeValue;
-
- // Traverse everything else, except comment nodes
- } else if ( elem.nodeType !== 8 ) {
- ret += Sizzle.getText( elem.childNodes );
- }
- }
-
- return ret;
-};
-
-// Check to see if the browser returns elements by name when
-// querying by getElementById (and provide a workaround)
-(function(){
- // We're going to inject a fake input element with a specified name
- var form = document.createElement("div"),
- id = "script" + (new Date()).getTime(),
- root = document.documentElement;
-
- form.innerHTML = "<a name='" + id + "'/>";
-
- // Inject it into the root element, check its status, and remove it quickly
- root.insertBefore( form, root.firstChild );
-
- // The workaround has to do additional checks after a getElementById
- // Which slows things down for other browsers (hence the branching)
- if ( document.getElementById( id ) ) {
- Expr.find.ID = function( match, context, isXML ) {
- if ( typeof context.getElementById !== "undefined" && !isXML ) {
- var m = context.getElementById(match[1]);
-
- return m ?
- m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
- [m] :
- undefined :
- [];
- }
- };
-
- Expr.filter.ID = function( elem, match ) {
- var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
-
- return elem.nodeType === 1 && node && node.nodeValue === match;
- };
- }
-
- root.removeChild( form );
-
- // release memory in IE
- root = form = null;
-})();
-
-(function(){
- // Check to see if the browser returns only elements
- // when doing getElementsByTagName("*")
-
- // Create a fake element
- var div = document.createElement("div");
- div.appendChild( document.createComment("") );
-
- // Make sure no comments are found
- if ( div.getElementsByTagName("*").length > 0 ) {
- Expr.find.TAG = function( match, context ) {
- var results = context.getElementsByTagName( match[1] );
-
- // Filter out possible comments
- if ( match[1] === "*" ) {
- var tmp = [];
-
- for ( var i = 0; results[i]; i++ ) {
- if ( results[i].nodeType === 1 ) {
- tmp.push( results[i] );
- }
- }
-
- results = tmp;
- }
-
- return results;
- };
- }
-
- // Check to see if an attribute returns normalized href attributes
- div.innerHTML = "<a href='#'></a>";
-
- if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
- div.firstChild.getAttribute("href") !== "#" ) {
-
- Expr.attrHandle.href = function( elem ) {
- return elem.getAttribute( "href", 2 );
- };
- }
-
- // release memory in IE
- div = null;
-})();
-
-if ( document.querySelectorAll ) {
- (function(){
- var oldSizzle = Sizzle,
- div = document.createElement("div"),
- id = "__sizzle__";
-
- div.innerHTML = "<p class='TEST'></p>";
-
- // Safari can't handle uppercase or unicode characters when
- // in quirks mode.
- if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
- return;
- }
-
- Sizzle = function( query, context, extra, seed ) {
- context = context || document;
-
- // Only use querySelectorAll on non-XML documents
- // (ID selectors don't work in non-HTML documents)
- if ( !seed && !Sizzle.isXML(context) ) {
- // See if we find a selector to speed up
- var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
-
- if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
- // Speed-up: Sizzle("TAG")
- if ( match[1] ) {
- return makeArray( context.getElementsByTagName( query ), extra );
-
- // Speed-up: Sizzle(".CLASS")
- } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
- return makeArray( context.getElementsByClassName( match[2] ), extra );
- }
- }
-
- if ( context.nodeType === 9 ) {
- // Speed-up: Sizzle("body")
- // The body element only exists once, optimize finding it
- if ( query === "body" && context.body ) {
- return makeArray( [ context.body ], extra );
-
- // Speed-up: Sizzle("#ID")
- } else if ( match && match[3] ) {
- var elem = context.getElementById( match[3] );
-
- // Check parentNode to catch when Blackberry 4.6 returns
- // nodes that are no longer in the document #6963
- if ( elem && elem.parentNode ) {
- // Handle the case where IE and Opera return items
- // by name instead of ID
- if ( elem.id === match[3] ) {
- return makeArray( [ elem ], extra );
- }
-
- } else {
- return makeArray( [], extra );
- }
- }
-
- try {
- return makeArray( context.querySelectorAll(query), extra );
- } catch(qsaError) {}
-
- // qSA works strangely on Element-rooted queries
- // We can work around this by specifying an extra ID on the root
- // and working up from there (Thanks to Andrew Dupont for the technique)
- // IE 8 doesn't work on object elements
- } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
- var oldContext = context,
- old = context.getAttribute( "id" ),
- nid = old || id,
- hasParent = context.parentNode,
- relativeHierarchySelector = /^\s*[+~]/.test( query );
-
- if ( !old ) {
- context.setAttribute( "id", nid );
- } else {
- nid = nid.replace( /'/g, "\\$&" );
- }
- if ( relativeHierarchySelector && hasParent ) {
- context = context.parentNode;
- }
-
- try {
- if ( !relativeHierarchySelector || hasParent ) {
- return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
- }
-
- } catch(pseudoError) {
- } finally {
- if ( !old ) {
- oldContext.removeAttribute( "id" );
- }
- }
- }
- }
-
- return oldSizzle(query, context, extra, seed);
- };
-
- for ( var prop in oldSizzle ) {
- Sizzle[ prop ] = oldSizzle[ prop ];
- }
-
- // release memory in IE
- div = null;
- })();
-}
-
-(function(){
- var html = document.documentElement,
- matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
-
- if ( matches ) {
- // Check to see if it's possible to do matchesSelector
- // on a disconnected node (IE 9 fails this)
- var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
- pseudoWorks = false;
-
- try {
- // This should fail with an exception
- // Gecko does not error, returns false instead
- matches.call( document.documentElement, "[test!='']:sizzle" );
-
- } catch( pseudoError ) {
- pseudoWorks = true;
- }
-
- Sizzle.matchesSelector = function( node, expr ) {
- // Make sure that attribute selectors are quoted
- expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
-
- if ( !Sizzle.isXML( node ) ) {
- try {
- if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
- var ret = matches.call( node, expr );
-
- // IE 9's matchesSelector returns false on disconnected nodes
- if ( ret || !disconnectedMatch ||
- // As well, disconnected nodes are said to be in a document
- // fragment in IE 9, so check for that
- node.document && node.document.nodeType !== 11 ) {
- return ret;
- }
- }
- } catch(e) {}
- }
-
- return Sizzle(expr, null, null, [node]).length > 0;
- };
- }
-})();
-
-(function(){
- var div = document.createElement("div");
-
- div.innerHTML = "<div class='test e'></div><div class='test'></div>";
-
- // Opera can't find a second classname (in 9.6)
- // Also, make sure that getElementsByClassName actually exists
- if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
- return;
- }
-
- // Safari caches class attributes, doesn't catch changes (in 3.2)
- div.lastChild.className = "e";
-
- if ( div.getElementsByClassName("e").length === 1 ) {
- return;
- }
-
- Expr.order.splice(1, 0, "CLASS");
- Expr.find.CLASS = function( match, context, isXML ) {
- if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
- return context.getElementsByClassName(match[1]);
- }
- };
-
- // release memory in IE
- div = null;
-})();
-
-function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
- for ( var i = 0, l = checkSet.length; i < l; i++ ) {
- var elem = checkSet[i];
-
- if ( elem ) {
- var match = false;
-
- elem = elem[dir];
-
- while ( elem ) {
- if ( elem.sizcache === doneName ) {
- match = checkSet[elem.sizset];
- break;
- }
-
- if ( elem.nodeType === 1 && !isXML ){
- elem.sizcache = doneName;
- elem.sizset = i;
- }
-
- if ( elem.nodeName.toLowerCase() === cur ) {
- match = elem;
- break;
- }
-
- elem = elem[dir];
- }
-
- checkSet[i] = match;
- }
- }
-}
-
-function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
- for ( var i = 0, l = checkSet.length; i < l; i++ ) {
- var elem = checkSet[i];
-
- if ( elem ) {
- var match = false;
-
- elem = elem[dir];
-
- while ( elem ) {
- if ( elem.sizcache === doneName ) {
- match = checkSet[elem.sizset];
- break;
- }
-
- if ( elem.nodeType === 1 ) {
- if ( !isXML ) {
- elem.sizcache = doneName;
- elem.sizset = i;
- }
-
- if ( typeof cur !== "string" ) {
- if ( elem === cur ) {
- match = true;
- break;
- }
-
- } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
- match = elem;
- break;
- }
- }
-
- elem = elem[dir];
- }
-
- checkSet[i] = match;
- }
- }
-}
-
-if ( document.documentElement.contains ) {
- Sizzle.contains = function( a, b ) {
- return a !== b && (a.contains ? a.contains(b) : true);
- };
-
-} else if ( document.documentElement.compareDocumentPosition ) {
- Sizzle.contains = function( a, b ) {
- return !!(a.compareDocumentPosition(b) & 16);
- };
-
-} else {
- Sizzle.contains = function() {
- return false;
- };
-}
-
-Sizzle.isXML = function( elem ) {
- // documentElement is verified for cases where it doesn't yet exist
- // (such as loading iframes in IE - #4833)
- var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
-
- return documentElement ? documentElement.nodeName !== "HTML" : false;
-};
-
-var posProcess = function( selector, context ) {
- var match,
- tmpSet = [],
- later = "",
- root = context.nodeType ? [context] : context;
-
- // Position selectors must be done after the filter
- // And so must :not(positional) so we move all PSEUDOs to the end
- while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
- later += match[0];
- selector = selector.replace( Expr.match.PSEUDO, "" );
- }
-
- selector = Expr.relative[selector] ? selector + "*" : selector;
-
- for ( var i = 0, l = root.length; i < l; i++ ) {
- Sizzle( selector, root[i], tmpSet );
- }
-
- return Sizzle.filter( later, tmpSet );
-};
-
-// EXPOSE
-jQuery.find = Sizzle;
-jQuery.expr = Sizzle.selectors;
-jQuery.expr[":"] = jQuery.expr.filters;
-jQuery.unique = Sizzle.uniqueSort;
-jQuery.text = Sizzle.getText;
-jQuery.isXMLDoc = Sizzle.isXML;
-jQuery.contains = Sizzle.contains;
-
-
-})();
-
-
-var runtil = /Until$/,
- rparentsprev = /^(?:parents|prevUntil|prevAll)/,
- // Note: This RegExp should be improved, or likely pulled from Sizzle
- rmultiselector = /,/,
- isSimple = /^.[^:#\[\.,]*$/,
- slice = Array.prototype.slice,
- POS = jQuery.expr.match.POS,
- // methods guaranteed to produce a unique set when starting from a unique set
- guaranteedUnique = {
- children: true,
- contents: true,
- next: true,
- prev: true
- };
-
-jQuery.fn.extend({
- find: function( selector ) {
- var ret = this.pushStack( "", "find", selector ),
- length = 0;
-
- for ( var i = 0, l = this.length; i < l; i++ ) {
- length = ret.length;
- jQuery.find( selector, this[i], ret );
-
- if ( i > 0 ) {
- // Make sure that the results are unique
- for ( var n = length; n < ret.length; n++ ) {
- for ( var r = 0; r < length; r++ ) {
- if ( ret[r] === ret[n] ) {
- ret.splice(n--, 1);
- break;
- }
- }
- }
- }
- }
-
- return ret;
- },
-
- has: function( target ) {
- var targets = jQuery( target );
- return this.filter(function() {
- for ( var i = 0, l = targets.length; i < l; i++ ) {
- if ( jQuery.contains( this, targets[i] ) ) {
- return true;
- }
- }
- });
- },
-
- not: function( selector ) {
- return this.pushStack( winnow(this, selector, false), "not", selector);
- },
-
- filter: function( selector ) {
- return this.pushStack( winnow(this, selector, true), "filter", selector );
- },
-
- is: function( selector ) {
- return !!selector && jQuery.filter( selector, this ).length > 0;
- },
-
- closest: function( selectors, context ) {
- var ret = [], i, l, cur = this[0];
-
- if ( jQuery.isArray( selectors ) ) {
- var match, selector,
- matches = {},
- level = 1;
-
- if ( cur && selectors.length ) {
- for ( i = 0, l = selectors.length; i < l; i++ ) {
- selector = selectors[i];
-
- if ( !matches[selector] ) {
- matches[selector] = jQuery.expr.match.POS.test( selector ) ?
- jQuery( selector, context || this.context ) :
- selector;
- }
- }
-
- while ( cur && cur.ownerDocument && cur !== context ) {
- for ( selector in matches ) {
- match = matches[selector];
-
- if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) {
- ret.push({ selector: selector, elem: cur, level: level });
- }
- }
-
- cur = cur.parentNode;
- level++;
- }
- }
-
- return ret;
- }
-
- var pos = POS.test( selectors ) ?
- jQuery( selectors, context || this.context ) : null;
-
- for ( i = 0, l = this.length; i < l; i++ ) {
- cur = this[i];
-
- while ( cur ) {
- if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
- ret.push( cur );
- break;
-
- } else {
- cur = cur.parentNode;
- if ( !cur || !cur.ownerDocument || cur === context ) {
- break;
- }
- }
- }
- }
-
- ret = ret.length > 1 ? jQuery.unique(ret) : ret;
-
- return this.pushStack( ret, "closest", selectors );
- },
-
- // Determine the position of an element within
- // the matched set of elements
- index: function( elem ) {
- if ( !elem || typeof elem === "string" ) {
- return jQuery.inArray( this[0],
- // If it receives a string, the selector is used
- // If it receives nothing, the siblings are used
- elem ? jQuery( elem ) : this.parent().children() );
- }
- // Locate the position of the desired element
- return jQuery.inArray(
- // If it receives a jQuery object, the first element is used
- elem.jquery ? elem[0] : elem, this );
- },
-
- add: function( selector, context ) {
- var set = typeof selector === "string" ?
- jQuery( selector, context ) :
- jQuery.makeArray( selector ),
- all = jQuery.merge( this.get(), set );
-
- return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
- all :
- jQuery.unique( all ) );
- },
-
- andSelf: function() {
- return this.add( this.prevObject );
- }
-});
-
-// A painfully simple check to see if an element is disconnected
-// from a document (should be improved, where feasible).
-function isDisconnected( node ) {
- return !node || !node.parentNode || node.parentNode.nodeType === 11;
-}
-
-jQuery.each({
- parent: function( elem ) {
- var parent = elem.parentNode;
- return parent && parent.nodeType !== 11 ? parent : null;
- },
- parents: function( elem ) {
- return jQuery.dir( elem, "parentNode" );
- },
- parentsUntil: function( elem, i, until ) {
- return jQuery.dir( elem, "parentNode", until );
- },
- next: function( elem ) {
- return jQuery.nth( elem, 2, "nextSibling" );
- },
- prev: function( elem ) {
- return jQuery.nth( elem, 2, "previousSibling" );
- },
- nextAll: function( elem ) {
- return jQuery.dir( elem, "nextSibling" );
- },
- prevAll: function( elem ) {
- return jQuery.dir( elem, "previousSibling" );
- },
- nextUntil: function( elem, i, until ) {
- return jQuery.dir( elem, "nextSibling", until );
- },
- prevUntil: function( elem, i, until ) {
- return jQuery.dir( elem, "previousSibling", until );
- },
- siblings: function( elem ) {
- return jQuery.sibling( elem.parentNode.firstChild, elem );
- },
- children: function( elem ) {
- return jQuery.sibling( elem.firstChild );
- },
- contents: function( elem ) {
- return jQuery.nodeName( elem, "iframe" ) ?
- elem.contentDocument || elem.contentWindow.document :
- jQuery.makeArray( elem.childNodes );
- }
-}, function( name, fn ) {
- jQuery.fn[ name ] = function( until, selector ) {
- var ret = jQuery.map( this, fn, until ),
- // The variable 'args' was introduced in
- // https://github.com/jquery/jquery/commit/52a0238
- // to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed.
- // http://code.google.com/p/v8/issues/detail?id=1050
- args = slice.call(arguments);
-
- if ( !runtil.test( name ) ) {
- selector = until;
- }
-
- if ( selector && typeof selector === "string" ) {
- ret = jQuery.filter( selector, ret );
- }
-
- ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
-
- if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) {
- ret = ret.reverse();
- }
-
- return this.pushStack( ret, name, args.join(",") );
- };
-});
-
-jQuery.extend({
- filter: function( expr, elems, not ) {
- if ( not ) {
- expr = ":not(" + expr + ")";
- }
-
- return elems.length === 1 ?
- jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
- jQuery.find.matches(expr, elems);
- },
-
- dir: function( elem, dir, until ) {
- var matched = [],
- cur = elem[ dir ];
-
- while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
- if ( cur.nodeType === 1 ) {
- matched.push( cur );
- }
- cur = cur[dir];
- }
- return matched;
- },
-
- nth: function( cur, result, dir, elem ) {
- result = result || 1;
- var num = 0;
-
- for ( ; cur; cur = cur[dir] ) {
- if ( cur.nodeType === 1 && ++num === result ) {
- break;
- }
- }
-
- return cur;
- },
-
- sibling: function( n, elem ) {
- var r = [];
-
- for ( ; n; n = n.nextSibling ) {
- if ( n.nodeType === 1 && n !== elem ) {
- r.push( n );
- }
- }
-
- return r;
- }
-});
-
-// Implement the identical functionality for filter and not
-function winnow( elements, qualifier, keep ) {
- if ( jQuery.isFunction( qualifier ) ) {
- return jQuery.grep(elements, function( elem, i ) {
- var retVal = !!qualifier.call( elem, i, elem );
- return retVal === keep;
- });
-
- } else if ( qualifier.nodeType ) {
- return jQuery.grep(elements, function( elem, i ) {
- return (elem === qualifier) === keep;
- });
-
- } else if ( typeof qualifier === "string" ) {
- var filtered = jQuery.grep(elements, function( elem ) {
- return elem.nodeType === 1;
- });
-
- if ( isSimple.test( qualifier ) ) {
- return jQuery.filter(qualifier, filtered, !keep);
- } else {
- qualifier = jQuery.filter( qualifier, filtered );
- }
- }
-
- return jQuery.grep(elements, function( elem, i ) {
- return (jQuery.inArray( elem, qualifier ) >= 0) === keep;
- });
-}
-
-
-
-
-var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
- rleadingWhitespace = /^\s+/,
- rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
- rtagName = /<([\w:]+)/,
- rtbody = /<tbody/i,
- rhtml = /<|&#?\w+;/,
- rnocache = /<(?:script|object|embed|option|style)/i,
- // checked="checked" or checked
- rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
- wrapMap = {
- option: [ 1, "<select multiple='multiple'>", "</select>" ],
- legend: [ 1, "<fieldset>", "</fieldset>" ],
- thead: [ 1, "<table>", "</table>" ],
- tr: [ 2, "<table><tbody>", "</tbody></table>" ],
- td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
- col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
- area: [ 1, "<map>", "</map>" ],
- _default: [ 0, "", "" ]
- };
-
-wrapMap.optgroup = wrapMap.option;
-wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
-wrapMap.th = wrapMap.td;
-
-// IE can't serialize <link> and <script> tags normally
-if ( !jQuery.support.htmlSerialize ) {
- wrapMap._default = [ 1, "div<div>", "</div>" ];
-}
-
-jQuery.fn.extend({
- text: function( text ) {
- if ( jQuery.isFunction(text) ) {
- return this.each(function(i) {
- var self = jQuery( this );
-
- self.text( text.call(this, i, self.text()) );
- });
- }
-
- if ( typeof text !== "object" && text !== undefined ) {
- return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
- }
-
- return jQuery.text( this );
- },
-
- wrapAll: function( html ) {
- if ( jQuery.isFunction( html ) ) {
- return this.each(function(i) {
- jQuery(this).wrapAll( html.call(this, i) );
- });
- }
-
- if ( this[0] ) {
- // The elements to wrap the target around
- var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
-
- if ( this[0].parentNode ) {
- wrap.insertBefore( this[0] );
- }
-
- wrap.map(function() {
- var elem = this;
-
- while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
- elem = elem.firstChild;
- }
-
- return elem;
- }).append(this);
- }
-
- return this;
- },
-
- wrapInner: function( html ) {
- if ( jQuery.isFunction( html ) ) {
- return this.each(function(i) {
- jQuery(this).wrapInner( html.call(this, i) );
- });
- }
-
- return this.each(function() {
- var self = jQuery( this ),
- contents = self.contents();
-
- if ( contents.length ) {
- contents.wrapAll( html );
-
- } else {
- self.append( html );
- }
- });
- },
-
- wrap: function( html ) {
- return this.each(function() {
- jQuery( this ).wrapAll( html );
- });
- },
-
- unwrap: function() {
- return this.parent().each(function() {
- if ( !jQuery.nodeName( this, "body" ) ) {
- jQuery( this ).replaceWith( this.childNodes );
- }
- }).end();
- },
-
- append: function() {
- return this.domManip(arguments, true, function( elem ) {
- if ( this.nodeType === 1 ) {
- this.appendChild( elem );
- }
- });
- },
-
- prepend: function() {
- return this.domManip(arguments, true, function( elem ) {
- if ( this.nodeType === 1 ) {
- this.insertBefore( elem, this.firstChild );
- }
- });
- },
-
- before: function() {
- if ( this[0] && this[0].parentNode ) {
- return this.domManip(arguments, false, function( elem ) {
- this.parentNode.insertBefore( elem, this );
- });
- } else if ( arguments.length ) {
- var set = jQuery(arguments[0]);
- set.push.apply( set, this.toArray() );
- return this.pushStack( set, "before", arguments );
- }
- },
-
- after: function() {
- if ( this[0] && this[0].parentNode ) {
- return this.domManip(arguments, false, function( elem ) {
- this.parentNode.insertBefore( elem, this.nextSibling );
- });
- } else if ( arguments.length ) {
- var set = this.pushStack( this, "after", arguments );
- set.push.apply( set, jQuery(arguments[0]).toArray() );
- return set;
- }
- },
-
- // keepData is for internal use only--do not document
- remove: function( selector, keepData ) {
- for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
- if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
- if ( !keepData && elem.nodeType === 1 ) {
- jQuery.cleanData( elem.getElementsByTagName("*") );
- jQuery.cleanData( [ elem ] );
- }
-
- if ( elem.parentNode ) {
- elem.parentNode.removeChild( elem );
- }
- }
- }
-
- return this;
- },
-
- empty: function() {
- for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
- // Remove element nodes and prevent memory leaks
- if ( elem.nodeType === 1 ) {
- jQuery.cleanData( elem.getElementsByTagName("*") );
- }
-
- // Remove any remaining nodes
- while ( elem.firstChild ) {
- elem.removeChild( elem.firstChild );
- }
- }
-
- return this;
- },
-
- clone: function( dataAndEvents, deepDataAndEvents ) {
- dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
- deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
-
- return this.map( function () {
- return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
- });
- },
-
- html: function( value ) {
- if ( value === undefined ) {
- return this[0] && this[0].nodeType === 1 ?
- this[0].innerHTML.replace(rinlinejQuery, "") :
- null;
-
- // See if we can take a shortcut and just use innerHTML
- } else if ( typeof value === "string" && !rnocache.test( value ) &&
- (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
- !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {
-
- value = value.replace(rxhtmlTag, "<$1></$2>");
-
- try {
- for ( var i = 0, l = this.length; i < l; i++ ) {
- // Remove element nodes and prevent memory leaks
- if ( this[i].nodeType === 1 ) {
- jQuery.cleanData( this[i].getElementsByTagName("*") );
- this[i].innerHTML = value;
- }
- }
-
- // If using innerHTML throws an exception, use the fallback method
- } catch(e) {
- this.empty().append( value );
- }
-
- } else if ( jQuery.isFunction( value ) ) {
- this.each(function(i){
- var self = jQuery( this );
-
- self.html( value.call(this, i, self.html()) );
- });
-
- } else {
- this.empty().append( value );
- }
-
- return this;
- },
-
- replaceWith: function( value ) {
- if ( this[0] && this[0].parentNode ) {
- // Make sure that the elements are removed from the DOM before they are inserted
- // this can help fix replacing a parent with child elements
- if ( jQuery.isFunction( value ) ) {
- return this.each(function(i) {
- var self = jQuery(this), old = self.html();
- self.replaceWith( value.call( this, i, old ) );
- });
- }
-
- if ( typeof value !== "string" ) {
- value = jQuery( value ).detach();
- }
-
- return this.each(function() {
- var next = this.nextSibling,
- parent = this.parentNode;
-
- jQuery( this ).remove();
-
- if ( next ) {
- jQuery(next).before( value );
- } else {
- jQuery(parent).append( value );
- }
- });
- } else {
- return this.length ?
- this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :
- this;
- }
- },
-
- detach: function( selector ) {
- return this.remove( selector, true );
- },
-
- domManip: function( args, table, callback ) {
- var results, first, fragment, parent,
- value = args[0],
- scripts = [];
-
- // We can't cloneNode fragments that contain checked, in WebKit
- if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {
- return this.each(function() {
- jQuery(this).domManip( args, table, callback, true );
- });
- }
-
- if ( jQuery.isFunction(value) ) {
- return this.each(function(i) {
- var self = jQuery(this);
- args[0] = value.call(this, i, table ? self.html() : undefined);
- self.domManip( args, table, callback );
- });
- }
-
- if ( this[0] ) {
- parent = value && value.parentNode;
-
- // If we're in a fragment, just use that instead of building a new one
- if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {
- results = { fragment: parent };
-
- } else {
- results = jQuery.buildFragment( args, this, scripts );
- }
-
- fragment = results.fragment;
-
- if ( fragment.childNodes.length === 1 ) {
- first = fragment = fragment.firstChild;
- } else {
- first = fragment.firstChild;
- }
-
- if ( first ) {
- table = table && jQuery.nodeName( first, "tr" );
-
- for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) {
- callback.call(
- table ?
- root(this[i], first) :
- this[i],
- // Make sure that we do not leak memory by inadvertently discarding
- // the original fragment (which might have attached data) instead of
- // using it; in addition, use the original fragment object for the last
- // item instead of first because it can end up being emptied incorrectly
- // in certain situations (Bug #8070).
- // Fragments from the fragment cache must always be cloned and never used
- // in place.
- results.cacheable || (l > 1 && i < lastIndex) ?
- jQuery.clone( fragment, true, true ) :
- fragment
- );
- }
- }
-
- if ( scripts.length ) {
- jQuery.each( scripts, evalScript );
- }
- }
-
- return this;
- }
-});
-
-function root( elem, cur ) {
- return jQuery.nodeName(elem, "table") ?
- (elem.getElementsByTagName("tbody")[0] ||
- elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
- elem;
-}
-
-function cloneCopyEvent( src, dest ) {
-
- if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
- return;
- }
-
- var internalKey = jQuery.expando,
- oldData = jQuery.data( src ),
- curData = jQuery.data( dest, oldData );
-
- // Switch to use the internal data object, if it exists, for the next
- // stage of data copying
- if ( (oldData = oldData[ internalKey ]) ) {
- var events = oldData.events;
- curData = curData[ internalKey ] = jQuery.extend({}, oldData);
-
- if ( events ) {
- delete curData.handle;
- curData.events = {};
-
- for ( var type in events ) {
- for ( var i = 0, l = events[ type ].length; i < l; i++ ) {
- jQuery.event.add( dest, type + ( events[ type ][ i ].namespace ? "." : "" ) + events[ type ][ i ].namespace, events[ type ][ i ], events[ type ][ i ].data );
- }
- }
- }
- }
-}
-
-function cloneFixAttributes(src, dest) {
- // We do not need to do anything for non-Elements
- if ( dest.nodeType !== 1 ) {
- return;
- }
-
- var nodeName = dest.nodeName.toLowerCase();
-
- // clearAttributes removes the attributes, which we don't want,
- // but also removes the attachEvent events, which we *do* want
- dest.clearAttributes();
-
- // mergeAttributes, in contrast, only merges back on the
- // original attributes, not the events
- dest.mergeAttributes(src);
-
- // IE6-8 fail to clone children inside object elements that use
- // the proprietary classid attribute value (rather than the type
- // attribute) to identify the type of content to display
- if ( nodeName === "object" ) {
- dest.outerHTML = src.outerHTML;
-
- } else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) {
- // IE6-8 fails to persist the checked state of a cloned checkbox
- // or radio button. Worse, IE6-7 fail to give the cloned element
- // a checked appearance if the defaultChecked value isn't also set
- if ( src.checked ) {
- dest.defaultChecked = dest.checked = src.checked;
- }
-
- // IE6-7 get confused and end up setting the value of a cloned
- // checkbox/radio button to an empty string instead of "on"
- if ( dest.value !== src.value ) {
- dest.value = src.value;
- }
-
- // IE6-8 fails to return the selected option to the default selected
- // state when cloning options
- } else if ( nodeName === "option" ) {
- dest.selected = src.defaultSelected;
-
- // IE6-8 fails to set the defaultValue to the correct value when
- // cloning other types of input fields
- } else if ( nodeName === "input" || nodeName === "textarea" ) {
- dest.defaultValue = src.defaultValue;
- }
-
- // Event data gets referenced instead of copied if the expando
- // gets copied too
- dest.removeAttribute( jQuery.expando );
-}
-
-jQuery.buildFragment = function( args, nodes, scripts ) {
- var fragment, cacheable, cacheresults,
- doc = (nodes && nodes[0] ? nodes[0].ownerDocument || nodes[0] : document);
-
- // Only cache "small" (1/2 KB) HTML strings that are associated with the main document
- // Cloning options loses the selected state, so don't cache them
- // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
- // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
- if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && doc === document &&
- args[0].charAt(0) === "<" && !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) {
-
- cacheable = true;
- cacheresults = jQuery.fragments[ args[0] ];
- if ( cacheresults ) {
- if ( cacheresults !== 1 ) {
- fragment = cacheresults;
- }
- }
- }
-
- if ( !fragment ) {
- fragment = doc.createDocumentFragment();
- jQuery.clean( args, doc, fragment, scripts );
- }
-
- if ( cacheable ) {
- jQuery.fragments[ args[0] ] = cacheresults ? fragment : 1;
- }
-
- return { fragment: fragment, cacheable: cacheable };
-};
-
-jQuery.fragments = {};
-
-jQuery.each({
- appendTo: "append",
- prependTo: "prepend",
- insertBefore: "before",
- insertAfter: "after",
- replaceAll: "replaceWith"
-}, function( name, original ) {
- jQuery.fn[ name ] = function( selector ) {
- var ret = [],
- insert = jQuery( selector ),
- parent = this.length === 1 && this[0].parentNode;
-
- if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) {
- insert[ original ]( this[0] );
- return this;
-
- } else {
- for ( var i = 0, l = insert.length; i < l; i++ ) {
- var elems = (i > 0 ? this.clone(true) : this).get();
- jQuery( insert[i] )[ original ]( elems );
- ret = ret.concat( elems );
- }
-
- return this.pushStack( ret, name, insert.selector );
- }
- };
-});
-
-function getAll( elem ) {
- if ( "getElementsByTagName" in elem ) {
- return elem.getElementsByTagName( "*" );
-
- } else if ( "querySelectorAll" in elem ) {
- return elem.querySelectorAll( "*" );
-
- } else {
- return [];
- }
-}
-
-jQuery.extend({
- clone: function( elem, dataAndEvents, deepDataAndEvents ) {
- var clone = elem.cloneNode(true),
- srcElements,
- destElements,
- i;
-
- if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
- (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
- // IE copies events bound via attachEvent when using cloneNode.
- // Calling detachEvent on the clone will also remove the events
- // from the original. In order to get around this, we use some
- // proprietary methods to clear the events. Thanks to MooTools
- // guys for this hotness.
-
- cloneFixAttributes( elem, clone );
-
- // Using Sizzle here is crazy slow, so we use getElementsByTagName
- // instead
- srcElements = getAll( elem );
- destElements = getAll( clone );
-
- // Weird iteration because IE will replace the length property
- // with an element if you are cloning the body and one of the
- // elements on the page has a name or id of "length"
- for ( i = 0; srcElements[i]; ++i ) {
- cloneFixAttributes( srcElements[i], destElements[i] );
- }
- }
-
- // Copy the events from the original to the clone
- if ( dataAndEvents ) {
- cloneCopyEvent( elem, clone );
-
- if ( deepDataAndEvents ) {
- srcElements = getAll( elem );
- destElements = getAll( clone );
-
- for ( i = 0; srcElements[i]; ++i ) {
- cloneCopyEvent( srcElements[i], destElements[i] );
- }
- }
- }
-
- // Return the cloned set
- return clone;
-},
- clean: function( elems, context, fragment, scripts ) {
- context = context || document;
-
- // !context.createElement fails in IE with an error but returns typeof 'object'
- if ( typeof context.createElement === "undefined" ) {
- context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
- }
-
- var ret = [];
-
- for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
- if ( typeof elem === "number" ) {
- elem += "";
- }
-
- if ( !elem ) {
- continue;
- }
-
- // Convert html string into DOM nodes
- if ( typeof elem === "string" && !rhtml.test( elem ) ) {
- elem = context.createTextNode( elem );
-
- } else if ( typeof elem === "string" ) {
- // Fix "XHTML"-style tags in all browsers
- elem = elem.replace(rxhtmlTag, "<$1></$2>");
-
- // Trim whitespace, otherwise indexOf won't work as expected
- var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(),
- wrap = wrapMap[ tag ] || wrapMap._default,
- depth = wrap[0],
- div = context.createElement("div");
-
- // Go to html and back, then peel off extra wrappers
- div.innerHTML = wrap[1] + elem + wrap[2];
-
- // Move to the right depth
- while ( depth-- ) {
- div = div.lastChild;
- }
-
- // Remove IE's autoinserted <tbody> from table fragments
- if ( !jQuery.support.tbody ) {
-
- // String was a <table>, *may* have spurious <tbody>
- var hasBody = rtbody.test(elem),
- tbody = tag === "table" && !hasBody ?
- div.firstChild && div.firstChild.childNodes :
-
- // String was a bare <thead> or <tfoot>
- wrap[1] === "<table>" && !hasBody ?
- div.childNodes :
- [];
-
- for ( var j = tbody.length - 1; j >= 0 ; --j ) {
- if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
- tbody[ j ].parentNode.removeChild( tbody[ j ] );
- }
- }
-
- }
-
- // IE completely kills leading whitespace when innerHTML is used
- if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
- div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
- }
-
- elem = div.childNodes;
- }
-
- if ( elem.nodeType ) {
- ret.push( elem );
- } else {
- ret = jQuery.merge( ret, elem );
- }
- }
-
- if ( fragment ) {
- for ( i = 0; ret[i]; i++ ) {
- if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
- scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
-
- } else {
- if ( ret[i].nodeType === 1 ) {
- ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) );
- }
- fragment.appendChild( ret[i] );
- }
- }
- }
-
- return ret;
- },
-
- cleanData: function( elems ) {
- var data, id, cache = jQuery.cache, internalKey = jQuery.expando, special = jQuery.event.special,
- deleteExpando = jQuery.support.deleteExpando;
-
- for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
- if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
- continue;
- }
-
- id = elem[ jQuery.expando ];
-
- if ( id ) {
- data = cache[ id ] && cache[ id ][ internalKey ];
-
- if ( data && data.events ) {
- for ( var type in data.events ) {
- if ( special[ type ] ) {
- jQuery.event.remove( elem, type );
-
- // This is a shortcut to avoid jQuery.event.remove's overhead
- } else {
- jQuery.removeEvent( elem, type, data.handle );
- }
- }
-
- // Null the DOM reference to avoid IE6/7/8 leak (#7054)
- if ( data.handle ) {
- data.handle.elem = null;
- }
- }
-
- if ( deleteExpando ) {
- delete elem[ jQuery.expando ];
-
- } else if ( elem.removeAttribute ) {
- elem.removeAttribute( jQuery.expando );
- }
-
- delete cache[ id ];
- }
- }
- }
-});
-
-function evalScript( i, elem ) {
- if ( elem.src ) {
- jQuery.ajax({
- url: elem.src,
- async: false,
- dataType: "script"
- });
- } else {
- jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
- }
-
- if ( elem.parentNode ) {
- elem.parentNode.removeChild( elem );
- }
-}
-
-
-
-
-var ralpha = /alpha\([^)]*\)/i,
- ropacity = /opacity=([^)]*)/,
- rdashAlpha = /-([a-z])/ig,
- // fixed for IE9, see #8346
- rupper = /([A-Z]|^ms)/g,
- rnumpx = /^-?\d+(?:px)?$/i,
- rnum = /^-?\d/,
-
- cssShow = { position: "absolute", visibility: "hidden", display: "block" },
- cssWidth = [ "Left", "Right" ],
- cssHeight = [ "Top", "Bottom" ],
- curCSS,
-
- getComputedStyle,
- currentStyle,
-
- fcamelCase = function( all, letter ) {
- return letter.toUpperCase();
- };
-
-jQuery.fn.css = function( name, value ) {
- // Setting 'undefined' is a no-op
- if ( arguments.length === 2 && value === undefined ) {
- return this;
- }
-
- return jQuery.access( this, name, value, true, function( elem, name, value ) {
- return value !== undefined ?
- jQuery.style( elem, name, value ) :
- jQuery.css( elem, name );
- });
-};
-
-jQuery.extend({
- // Add in style property hooks for overriding the default
- // behavior of getting and setting a style property
- cssHooks: {
- opacity: {
- get: function( elem, computed ) {
- if ( computed ) {
- // We should always get a number back from opacity
- var ret = curCSS( elem, "opacity", "opacity" );
- return ret === "" ? "1" : ret;
-
- } else {
- return elem.style.opacity;
- }
- }
- }
- },
-
- // Exclude the following css properties to add px
- cssNumber: {
- "zIndex": true,
- "fontWeight": true,
- "opacity": true,
- "zoom": true,
- "lineHeight": true
- },
-
- // Add in properties whose names you wish to fix before
- // setting or getting the value
- cssProps: {
- // normalize float css property
- "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
- },
-
- // Get and set the style property on a DOM Node
- style: function( elem, name, value, extra ) {
- // Don't set styles on text and comment nodes
- if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
- return;
- }
-
- // Make sure that we're working with the right name
- var ret, origName = jQuery.camelCase( name ),
- style = elem.style, hooks = jQuery.cssHooks[ origName ];
-
- name = jQuery.cssProps[ origName ] || origName;
-
- // Check if we're setting a value
- if ( value !== undefined ) {
- // Make sure that NaN and null values aren't set. See: #7116
- if ( typeof value === "number" && isNaN( value ) || value == null ) {
- return;
- }
-
- // If a number was passed in, add 'px' to the (except for certain CSS properties)
- if ( typeof value === "number" && !jQuery.cssNumber[ origName ] ) {
- value += "px";
- }
-
- // If a hook was provided, use that value, otherwise just set the specified value
- if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {
- // Wrapped to prevent IE from throwing errors when 'invalid' values are provided
- // Fixes bug #5509
- try {
- style[ name ] = value;
- } catch(e) {}
- }
-
- } else {
- // If a hook was provided get the non-computed value from there
- if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
- return ret;
- }
-
- // Otherwise just get the value from the style object
- return style[ name ];
- }
- },
-
- css: function( elem, name, extra ) {
- // Make sure that we're working with the right name
- var ret, origName = jQuery.camelCase( name ),
- hooks = jQuery.cssHooks[ origName ];
-
- name = jQuery.cssProps[ origName ] || origName;
-
- // If a hook was provided get the computed value from there
- if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) {
- return ret;
-
- // Otherwise, if a way to get the computed value exists, use that
- } else if ( curCSS ) {
- return curCSS( elem, name, origName );
- }
- },
-
- // A method for quickly swapping in/out CSS properties to get correct calculations
- swap: function( elem, options, callback ) {
- var old = {};
-
- // Remember the old values, and insert the new ones
- for ( var name in options ) {
- old[ name ] = elem.style[ name ];
- elem.style[ name ] = options[ name ];
- }
-
- callback.call( elem );
-
- // Revert the old values
- for ( name in options ) {
- elem.style[ name ] = old[ name ];
- }
- },
-
- camelCase: function( string ) {
- return string.replace( rdashAlpha, fcamelCase );
- }
-});
-
-// DEPRECATED, Use jQuery.css() instead
-jQuery.curCSS = jQuery.css;
-
-jQuery.each(["height", "width"], function( i, name ) {
- jQuery.cssHooks[ name ] = {
- get: function( elem, computed, extra ) {
- var val;
-
- if ( computed ) {
- if ( elem.offsetWidth !== 0 ) {
- val = getWH( elem, name, extra );
-
- } else {
- jQuery.swap( elem, cssShow, function() {
- val = getWH( elem, name, extra );
- });
- }
-
- if ( val <= 0 ) {
- val = curCSS( elem, name, name );
-
- if ( val === "0px" && currentStyle ) {
- val = currentStyle( elem, name, name );
- }
-
- if ( val != null ) {
- // Should return "auto" instead of 0, use 0 for
- // temporary backwards-compat
- return val === "" || val === "auto" ? "0px" : val;
- }
- }
-
- if ( val < 0 || val == null ) {
- val = elem.style[ name ];
-
- // Should return "auto" instead of 0, use 0 for
- // temporary backwards-compat
- return val === "" || val === "auto" ? "0px" : val;
- }
-
- return typeof val === "string" ? val : val + "px";
- }
- },
-
- set: function( elem, value ) {
- if ( rnumpx.test( value ) ) {
- // ignore negative width and height values #1599
- value = parseFloat(value);
-
- if ( value >= 0 ) {
- return value + "px";
- }
-
- } else {
- return value;
- }
- }
- };
-});
-
-if ( !jQuery.support.opacity ) {
- jQuery.cssHooks.opacity = {
- get: function( elem, computed ) {
- // IE uses filters for opacity
- return ropacity.test((computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "") ?
- (parseFloat(RegExp.$1) / 100) + "" :
- computed ? "1" : "";
- },
-
- set: function( elem, value ) {
- var style = elem.style;
-
- // IE has trouble with opacity if it does not have layout
- // Force it by setting the zoom level
- style.zoom = 1;
-
- // Set the alpha filter to set the opacity
- var opacity = jQuery.isNaN(value) ?
- "" :
- "alpha(opacity=" + value * 100 + ")",
- filter = style.filter || "";
-
- style.filter = ralpha.test(filter) ?
- filter.replace(ralpha, opacity) :
- style.filter + ' ' + opacity;
- }
- };
-}
-
-jQuery(function() {
- // This hook cannot be added until DOM ready because the support test
- // for it is not run until after DOM ready
- if ( !jQuery.support.reliableMarginRight ) {
- jQuery.cssHooks.marginRight = {
- get: function( elem, computed ) {
- // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
- // Work around by temporarily setting element display to inline-block
- var ret;
- jQuery.swap( elem, { "display": "inline-block" }, function() {
- if ( computed ) {
- ret = curCSS( elem, "margin-right", "marginRight" );
- } else {
- ret = elem.style.marginRight;
- }
- });
- return ret;
- }
- };
- }
-});
-
-if ( document.defaultView && document.defaultView.getComputedStyle ) {
- getComputedStyle = function( elem, newName, name ) {
- var ret, defaultView, computedStyle;
-
- name = name.replace( rupper, "-$1" ).toLowerCase();
-
- if ( !(defaultView = elem.ownerDocument.defaultView) ) {
- return undefined;
- }
-
- if ( (computedStyle = defaultView.getComputedStyle( elem, null )) ) {
- ret = computedStyle.getPropertyValue( name );
- if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
- ret = jQuery.style( elem, name );
- }
- }
-
- return ret;
- };
-}
-
-if ( document.documentElement.currentStyle ) {
- currentStyle = function( elem, name ) {
- var left,
- ret = elem.currentStyle && elem.currentStyle[ name ],
- rsLeft = elem.runtimeStyle && elem.runtimeStyle[ name ],
- style = elem.style;
-
- // From the awesome hack by Dean Edwards
- // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
-
- // If we're not dealing with a regular pixel number
- // but a number that has a weird ending, we need to convert it to pixels
- if ( !rnumpx.test( ret ) && rnum.test( ret ) ) {
- // Remember the original values
- left = style.left;
-
- // Put in the new values to get a computed value out
- if ( rsLeft ) {
- elem.runtimeStyle.left = elem.currentStyle.left;
- }
- style.left = name === "fontSize" ? "1em" : (ret || 0);
- ret = style.pixelLeft + "px";
-
- // Revert the changed values
- style.left = left;
- if ( rsLeft ) {
- elem.runtimeStyle.left = rsLeft;
- }
- }
-
- return ret === "" ? "auto" : ret;
- };
-}
-
-curCSS = getComputedStyle || currentStyle;
-
-function getWH( elem, name, extra ) {
- var which = name === "width" ? cssWidth : cssHeight,
- val = name === "width" ? elem.offsetWidth : elem.offsetHeight;
-
- if ( extra === "border" ) {
- return val;
- }
-
- jQuery.each( which, function() {
- if ( !extra ) {
- val -= parseFloat(jQuery.css( elem, "padding" + this )) || 0;
- }
-
- if ( extra === "margin" ) {
- val += parseFloat(jQuery.css( elem, "margin" + this )) || 0;
-
- } else {
- val -= parseFloat(jQuery.css( elem, "border" + this + "Width" )) || 0;
- }
- });
-
- return val;
-}
-
-if ( jQuery.expr && jQuery.expr.filters ) {
- jQuery.expr.filters.hidden = function( elem ) {
- var width = elem.offsetWidth,
- height = elem.offsetHeight;
-
- return (width === 0 && height === 0) || (!jQuery.support.reliableHiddenOffsets && (elem.style.display || jQuery.css( elem, "display" )) === "none");
- };
-
- jQuery.expr.filters.visible = function( elem ) {
- return !jQuery.expr.filters.hidden( elem );
- };
-}
-
-
-
-
-var r20 = /%20/g,
- rbracket = /\[\]$/,
- rCRLF = /\r?\n/g,
- rhash = /#.*$/,
- rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
- rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
- // #7653, #8125, #8152: local protocol detection
- rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|widget):$/,
- rnoContent = /^(?:GET|HEAD)$/,
- rprotocol = /^\/\//,
- rquery = /\?/,
- rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
- rselectTextarea = /^(?:select|textarea)/i,
- rspacesAjax = /\s+/,
- rts = /([?&])_=[^&]*/,
- rucHeaders = /(^|\-)([a-z])/g,
- rucHeadersFunc = function( _, $1, $2 ) {
- return $1 + $2.toUpperCase();
- },
- rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,
-
- // Keep a copy of the old load method
- _load = jQuery.fn.load,
-
- /* Prefilters
- * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
- * 2) These are called:
- * - BEFORE asking for a transport
- * - AFTER param serialization (s.data is a string if s.processData is true)
- * 3) key is the dataType
- * 4) the catchall symbol "*" can be used
- * 5) execution will start with transport dataType and THEN continue down to "*" if needed
- */
- prefilters = {},
-
- /* Transports bindings
- * 1) key is the dataType
- * 2) the catchall symbol "*" can be used
- * 3) selection will start with transport dataType and THEN go to "*" if needed
- */
- transports = {},
-
- // Document location
- ajaxLocation,
-
- // Document location segments
- ajaxLocParts;
-
-// #8138, IE may throw an exception when accessing
-// a field from document.location if document.domain has been set
-try {
- ajaxLocation = document.location.href;
-} catch( e ) {
- // Use the href attribute of an A element
- // since IE will modify it given document.location
- ajaxLocation = document.createElement( "a" );
- ajaxLocation.href = "";
- ajaxLocation = ajaxLocation.href;
-}
-
-// Segment location into parts
-ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
-
-// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
-function addToPrefiltersOrTransports( structure ) {
-
- // dataTypeExpression is optional and defaults to "*"
- return function( dataTypeExpression, func ) {
-
- if ( typeof dataTypeExpression !== "string" ) {
- func = dataTypeExpression;
- dataTypeExpression = "*";
- }
-
- if ( jQuery.isFunction( func ) ) {
- var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ),
- i = 0,
- length = dataTypes.length,
- dataType,
- list,
- placeBefore;
-
- // For each dataType in the dataTypeExpression
- for(; i < length; i++ ) {
- dataType = dataTypes[ i ];
- // We control if we're asked to add before
- // any existing element
- placeBefore = /^\+/.test( dataType );
- if ( placeBefore ) {
- dataType = dataType.substr( 1 ) || "*";
- }
- list = structure[ dataType ] = structure[ dataType ] || [];
- // then we add to the structure accordingly
- list[ placeBefore ? "unshift" : "push" ]( func );
- }
- }
- };
-}
-
-//Base inspection function for prefilters and transports
-function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR,
- dataType /* internal */, inspected /* internal */ ) {
-
- dataType = dataType || options.dataTypes[ 0 ];
- inspected = inspected || {};
-
- inspected[ dataType ] = true;
-
- var list = structure[ dataType ],
- i = 0,
- length = list ? list.length : 0,
- executeOnly = ( structure === prefilters ),
- selection;
-
- for(; i < length && ( executeOnly || !selection ); i++ ) {
- selection = list[ i ]( options, originalOptions, jqXHR );
- // If we got redirected to another dataType
- // we try there if executing only and not done already
- if ( typeof selection === "string" ) {
- if ( !executeOnly || inspected[ selection ] ) {
- selection = undefined;
- } else {
- options.dataTypes.unshift( selection );
- selection = inspectPrefiltersOrTransports(
- structure, options, originalOptions, jqXHR, selection, inspected );
- }
- }
- }
- // If we're only executing or nothing was selected
- // we try the catchall dataType if not done already
- if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) {
- selection = inspectPrefiltersOrTransports(
- structure, options, originalOptions, jqXHR, "*", inspected );
- }
- // unnecessary when only executing (prefilters)
- // but it'll be ignored by the caller in that case
- return selection;
-}
-
-jQuery.fn.extend({
- load: function( url, params, callback ) {
- if ( typeof url !== "string" && _load ) {
- return _load.apply( this, arguments );
-
- // Don't do a request if no elements are being requested
- } else if ( !this.length ) {
- return this;
- }
-
- var off = url.indexOf( " " );
- if ( off >= 0 ) {
- var selector = url.slice( off, url.length );
- url = url.slice( 0, off );
- }
-
- // Default to a GET request
- var type = "GET";
-
- // If the second parameter was provided
- if ( params ) {
- // If it's a function
- if ( jQuery.isFunction( params ) ) {
- // We assume that it's the callback
- callback = params;
- params = undefined;
-
- // Otherwise, build a param string
- } else if ( typeof params === "object" ) {
- params = jQuery.param( params, jQuery.ajaxSettings.traditional );
- type = "POST";
- }
- }
-
- var self = this;
-
- // Request the remote document
- jQuery.ajax({
- url: url,
- type: type,
- dataType: "html",
- data: params,
- // Complete callback (responseText is used internally)
- complete: function( jqXHR, status, responseText ) {
- // Store the response as specified by the jqXHR object
- responseText = jqXHR.responseText;
- // If successful, inject the HTML into all the matched elements
- if ( jqXHR.isResolved() ) {
- // #4825: Get the actual response in case
- // a dataFilter is present in ajaxSettings
- jqXHR.done(function( r ) {
- responseText = r;
- });
- // See if a selector was specified
- self.html( selector ?
- // Create a dummy div to hold the results
- jQuery("<div>")
- // inject the contents of the document in, removing the scripts
- // to avoid any 'Permission Denied' errors in IE
- .append(responseText.replace(rscript, ""))
-
- // Locate the specified elements
- .find(selector) :
-
- // If not, just inject the full result
- responseText );
- }
-
- if ( callback ) {
- self.each( callback, [ responseText, status, jqXHR ] );
- }
- }
- });
-
- return this;
- },
-
- serialize: function() {
- return jQuery.param( this.serializeArray() );
- },
-
- serializeArray: function() {
- return this.map(function(){
- return this.elements ? jQuery.makeArray( this.elements ) : this;
- })
- .filter(function(){
- return this.name && !this.disabled &&
- ( this.checked || rselectTextarea.test( this.nodeName ) ||
- rinput.test( this.type ) );
- })
- .map(function( i, elem ){
- var val = jQuery( this ).val();
-
- return val == null ?
- null :
- jQuery.isArray( val ) ?
- jQuery.map( val, function( val, i ){
- return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
- }) :
- { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
- }).get();
- }
-});
-
-// Attach a bunch of functions for handling common AJAX events
-jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){
- jQuery.fn[ o ] = function( f ){
- return this.bind( o, f );
- };
-} );
-
-jQuery.each( [ "get", "post" ], function( i, method ) {
- jQuery[ method ] = function( url, data, callback, type ) {
- // shift arguments if data argument was omitted
- if ( jQuery.isFunction( data ) ) {
- type = type || callback;
- callback = data;
- data = undefined;
- }
-
- return jQuery.ajax({
- type: method,
- url: url,
- data: data,
- success: callback,
- dataType: type
- });
- };
-} );
-
-jQuery.extend({
-
- getScript: function( url, callback ) {
- return jQuery.get( url, undefined, callback, "script" );
- },
-
- getJSON: function( url, data, callback ) {
- return jQuery.get( url, data, callback, "json" );
- },
-
- // Creates a full fledged settings object into target
- // with both ajaxSettings and settings fields.
- // If target is omitted, writes into ajaxSettings.
- ajaxSetup: function ( target, settings ) {
- if ( !settings ) {
- // Only one parameter, we extend ajaxSettings
- settings = target;
- target = jQuery.extend( true, jQuery.ajaxSettings, settings );
- } else {
- // target was provided, we extend into it
- jQuery.extend( true, target, jQuery.ajaxSettings, settings );
- }
- // Flatten fields we don't want deep extended
- for( var field in { context: 1, url: 1 } ) {
- if ( field in settings ) {
- target[ field ] = settings[ field ];
- } else if( field in jQuery.ajaxSettings ) {
- target[ field ] = jQuery.ajaxSettings[ field ];
- }
- }
- return target;
- },
-
- ajaxSettings: {
- url: ajaxLocation,
- isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
- global: true,
- type: "GET",
- contentType: "application/x-www-form-urlencoded",
- processData: true,
- async: true,
- /*
- timeout: 0,
- data: null,
- dataType: null,
- username: null,
- password: null,
- cache: null,
- traditional: false,
- headers: {},
- */
-
- accepts: {
- xml: "application/xml, text/xml",
- html: "text/html",
- text: "text/plain",
- json: "application/json, text/javascript",
- "*": "*/*"
- },
-
- contents: {
- xml: /xml/,
- html: /html/,
- json: /json/
- },
-
- responseFields: {
- xml: "responseXML",
- text: "responseText"
- },
-
- // List of data converters
- // 1) key format is "source_type destination_type" (a single space in-between)
- // 2) the catchall symbol "*" can be used for source_type
- converters: {
-
- // Convert anything to text
- "* text": window.String,
-
- // Text to html (true = no transformation)
- "text html": true,
-
- // Evaluate text as a json expression
- "text json": jQuery.parseJSON,
-
- // Parse text as xml
- "text xml": jQuery.parseXML
- }
- },
-
- ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
- ajaxTransport: addToPrefiltersOrTransports( transports ),
-
- // Main method
- ajax: function( url, options ) {
-
- // If url is an object, simulate pre-1.5 signature
- if ( typeof url === "object" ) {
- options = url;
- url = undefined;
- }
-
- // Force options to be an object
- options = options || {};
-
- var // Create the final options object
- s = jQuery.ajaxSetup( {}, options ),
- // Callbacks context
- callbackContext = s.context || s,
- // Context for global events
- // It's the callbackContext if one was provided in the options
- // and if it's a DOM node or a jQuery collection
- globalEventContext = callbackContext !== s &&
- ( callbackContext.nodeType || callbackContext instanceof jQuery ) ?
- jQuery( callbackContext ) : jQuery.event,
- // Deferreds
- deferred = jQuery.Deferred(),
- completeDeferred = jQuery._Deferred(),
- // Status-dependent callbacks
- statusCode = s.statusCode || {},
- // ifModified key
- ifModifiedKey,
- // Headers (they are sent all at once)
- requestHeaders = {},
- // Response headers
- responseHeadersString,
- responseHeaders,
- // transport
- transport,
- // timeout handle
- timeoutTimer,
- // Cross-domain detection vars
- parts,
- // The jqXHR state
- state = 0,
- // To know if global events are to be dispatched
- fireGlobals,
- // Loop variable
- i,
- // Fake xhr
- jqXHR = {
-
- readyState: 0,
-
- // Caches the header
- setRequestHeader: function( name, value ) {
- if ( !state ) {
- requestHeaders[ name.toLowerCase().replace( rucHeaders, rucHeadersFunc ) ] = value;
- }
- return this;
- },
-
- // Raw string
- getAllResponseHeaders: function() {
- return state === 2 ? responseHeadersString : null;
- },
-
- // Builds headers hashtable if needed
- getResponseHeader: function( key ) {
- var match;
- if ( state === 2 ) {
- if ( !responseHeaders ) {
- responseHeaders = {};
- while( ( match = rheaders.exec( responseHeadersString ) ) ) {
- responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
- }
- }
- match = responseHeaders[ key.toLowerCase() ];
- }
- return match === undefined ? null : match;
- },
-
- // Overrides response content-type header
- overrideMimeType: function( type ) {
- if ( !state ) {
- s.mimeType = type;
- }
- return this;
- },
-
- // Cancel the request
- abort: function( statusText ) {
- statusText = statusText || "abort";
- if ( transport ) {
- transport.abort( statusText );
- }
- done( 0, statusText );
- return this;
- }
- };
-
- // Callback for when everything is done
- // It is defined here because jslint complains if it is declared
- // at the end of the function (which would be more logical and readable)
- function done( status, statusText, responses, headers ) {
-
- // Called once
- if ( state === 2 ) {
- return;
- }
-
- // State is "done" now
- state = 2;
-
- // Clear timeout if it exists
- if ( timeoutTimer ) {
- clearTimeout( timeoutTimer );
- }
-
- // Dereference transport for early garbage collection
- // (no matter how long the jqXHR object will be used)
- transport = undefined;
-
- // Cache response headers
- responseHeadersString = headers || "";
-
- // Set readyState
- jqXHR.readyState = status ? 4 : 0;
-
- var isSuccess,
- success,
- error,
- response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined,
- lastModified,
- etag;
-
- // If successful, handle type chaining
- if ( status >= 200 && status < 300 || status === 304 ) {
-
- // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
- if ( s.ifModified ) {
-
- if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) {
- jQuery.lastModified[ ifModifiedKey ] = lastModified;
- }
- if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) {
- jQuery.etag[ ifModifiedKey ] = etag;
- }
- }
-
- // If not modified
- if ( status === 304 ) {
-
- statusText = "notmodified";
- isSuccess = true;
-
- // If we have data
- } else {
-
- try {
- success = ajaxConvert( s, response );
- statusText = "success";
- isSuccess = true;
- } catch(e) {
- // We have a parsererror
- statusText = "parsererror";
- error = e;
- }
- }
- } else {
- // We extract error from statusText
- // then normalize statusText and status for non-aborts
- error = statusText;
- if( !statusText || status ) {
- statusText = "error";
- if ( status < 0 ) {
- status = 0;
- }
- }
- }
-
- // Set data for the fake xhr object
- jqXHR.status = status;
- jqXHR.statusText = statusText;
-
- // Success/Error
- if ( isSuccess ) {
- deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
- } else {
- deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
- }
-
- // Status-dependent callbacks
- jqXHR.statusCode( statusCode );
- statusCode = undefined;
-
- if ( fireGlobals ) {
- globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ),
- [ jqXHR, s, isSuccess ? success : error ] );
- }
-
- // Complete
- completeDeferred.resolveWith( callbackContext, [ jqXHR, statusText ] );
-
- if ( fireGlobals ) {
- globalEventContext.trigger( "ajaxComplete", [ jqXHR, s] );
- // Handle the global AJAX counter
- if ( !( --jQuery.active ) ) {
- jQuery.event.trigger( "ajaxStop" );
- }
- }
- }
-
- // Attach deferreds
- deferred.promise( jqXHR );
- jqXHR.success = jqXHR.done;
- jqXHR.error = jqXHR.fail;
- jqXHR.complete = completeDeferred.done;
-
- // Status-dependent callbacks
- jqXHR.statusCode = function( map ) {
- if ( map ) {
- var tmp;
- if ( state < 2 ) {
- for( tmp in map ) {
- statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ];
- }
- } else {
- tmp = map[ jqXHR.status ];
- jqXHR.then( tmp, tmp );
- }
- }
- return this;
- };
-
- // Remove hash character (#7531: and string promotion)
- // Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
- // We also use the url parameter if available
- s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
-
- // Extract dataTypes list
- s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax );
-
- // Determine if a cross-domain request is in order
- if ( s.crossDomain == null ) {
- parts = rurl.exec( s.url.toLowerCase() );
- s.crossDomain = !!( parts &&
- ( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] ||
- ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
- ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
- );
- }
-
- // Convert data if not already a string
- if ( s.data && s.processData && typeof s.data !== "string" ) {
- s.data = jQuery.param( s.data, s.traditional );
- }
-
- // Apply prefilters
- inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
-
- // If request was aborted inside a prefiler, stop there
- if ( state === 2 ) {
- return false;
- }
-
- // We can fire global events as of now if asked to
- fireGlobals = s.global;
-
- // Uppercase the type
- s.type = s.type.toUpperCase();
-
- // Determine if request has content
- s.hasContent = !rnoContent.test( s.type );
-
- // Watch for a new set of requests
- if ( fireGlobals && jQuery.active++ === 0 ) {
- jQuery.event.trigger( "ajaxStart" );
- }
-
- // More options handling for requests with no content
- if ( !s.hasContent ) {
-
- // If data is available, append data to url
- if ( s.data ) {
- s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data;
- }
-
- // Get ifModifiedKey before adding the anti-cache parameter
- ifModifiedKey = s.url;
-
- // Add anti-cache in url if needed
- if ( s.cache === false ) {
-
- var ts = jQuery.now(),
- // try replacing _= if it is there
- ret = s.url.replace( rts, "$1_=" + ts );
-
- // if nothing was replaced, add timestamp to the end
- s.url = ret + ( (ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" );
- }
- }
-
- // Set the correct header, if data is being sent
- if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
- requestHeaders[ "Content-Type" ] = s.contentType;
- }
-
- // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
- if ( s.ifModified ) {
- ifModifiedKey = ifModifiedKey || s.url;
- if ( jQuery.lastModified[ ifModifiedKey ] ) {
- requestHeaders[ "If-Modified-Since" ] = jQuery.lastModified[ ifModifiedKey ];
- }
- if ( jQuery.etag[ ifModifiedKey ] ) {
- requestHeaders[ "If-None-Match" ] = jQuery.etag[ ifModifiedKey ];
- }
- }
-
- // Set the Accepts header for the server, depending on the dataType
- requestHeaders.Accept = s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
- s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) :
- s.accepts[ "*" ];
-
- // Check for headers option
- for ( i in s.headers ) {
- jqXHR.setRequestHeader( i, s.headers[ i ] );
- }
-
- // Allow custom headers/mimetypes and early abort
- if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
- // Abort if not done already
- jqXHR.abort();
- return false;
-
- }
-
- // Install callbacks on deferreds
- for ( i in { success: 1, error: 1, complete: 1 } ) {
- jqXHR[ i ]( s[ i ] );
- }
-
- // Get transport
- transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
-
- // If no transport, we auto-abort
- if ( !transport ) {
- done( -1, "No Transport" );
- } else {
- jqXHR.readyState = 1;
- // Send global event
- if ( fireGlobals ) {
- globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
- }
- // Timeout
- if ( s.async && s.timeout > 0 ) {
- timeoutTimer = setTimeout( function(){
- jqXHR.abort( "timeout" );
- }, s.timeout );
- }
-
- try {
- state = 1;
- transport.send( requestHeaders, done );
- } catch (e) {
- // Propagate exception as error if not done
- if ( status < 2 ) {
- done( -1, e );
- // Simply rethrow otherwise
- } else {
- jQuery.error( e );
- }
- }
- }
-
- return jqXHR;
- },
-
- // Serialize an array of form elements or a set of
- // key/values into a query string
- param: function( a, traditional ) {
- var s = [],
- add = function( key, value ) {
- // If value is a function, invoke it and return its value
- value = jQuery.isFunction( value ) ? value() : value;
- s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
- };
-
- // Set traditional to true for jQuery <= 1.3.2 behavior.
- if ( traditional === undefined ) {
- traditional = jQuery.ajaxSettings.traditional;
- }
-
- // If an array was passed in, assume that it is an array of form elements.
- if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
- // Serialize the form elements
- jQuery.each( a, function() {
- add( this.name, this.value );
- } );
-
- } else {
- // If traditional, encode the "old" way (the way 1.3.2 or older
- // did it), otherwise encode params recursively.
- for ( var prefix in a ) {
- buildParams( prefix, a[ prefix ], traditional, add );
- }
- }
-
- // Return the resulting serialization
- return s.join( "&" ).replace( r20, "+" );
- }
-});
-
-function buildParams( prefix, obj, traditional, add ) {
- if ( jQuery.isArray( obj ) && obj.length ) {
- // Serialize array item.
- jQuery.each( obj, function( i, v ) {
- if ( traditional || rbracket.test( prefix ) ) {
- // Treat each array item as a scalar.
- add( prefix, v );
-
- } else {
- // If array item is non-scalar (array or object), encode its
- // numeric index to resolve deserialization ambiguity issues.
- // Note that rack (as of 1.0.0) can't currently deserialize
- // nested arrays properly, and attempting to do so may cause
- // a server error. Possible fixes are to modify rack's
- // deserialization algorithm or to provide an option or flag
- // to force array serialization to be shallow.
- buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add );
- }
- });
-
- } else if ( !traditional && obj != null && typeof obj === "object" ) {
- // If we see an array here, it is empty and should be treated as an empty
- // object
- if ( jQuery.isArray( obj ) || jQuery.isEmptyObject( obj ) ) {
- add( prefix, "" );
-
- // Serialize object item.
- } else {
- for ( var name in obj ) {
- buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
- }
- }
-
- } else {
- // Serialize scalar item.
- add( prefix, obj );
- }
-}
-
-// This is still on the jQuery object... for now
-// Want to move this to jQuery.ajax some day
-jQuery.extend({
-
- // Counter for holding the number of active queries
- active: 0,
-
- // Last-Modified header cache for next request
- lastModified: {},
- etag: {}
-
-});
-
-/* Handles responses to an ajax request:
- * - sets all responseXXX fields accordingly
- * - finds the right dataType (mediates between content-type and expected dataType)
- * - returns the corresponding response
- */
-function ajaxHandleResponses( s, jqXHR, responses ) {
-
- var contents = s.contents,
- dataTypes = s.dataTypes,
- responseFields = s.responseFields,
- ct,
- type,
- finalDataType,
- firstDataType;
-
- // Fill responseXXX fields
- for( type in responseFields ) {
- if ( type in responses ) {
- jqXHR[ responseFields[type] ] = responses[ type ];
- }
- }
-
- // Remove auto dataType and get content-type in the process
- while( dataTypes[ 0 ] === "*" ) {
- dataTypes.shift();
- if ( ct === undefined ) {
- ct = s.mimeType || jqXHR.getResponseHeader( "content-type" );
- }
- }
-
- // Check if we're dealing with a known content-type
- if ( ct ) {
- for ( type in contents ) {
- if ( contents[ type ] && contents[ type ].test( ct ) ) {
- dataTypes.unshift( type );
- break;
- }
- }
- }
-
- // Check to see if we have a response for the expected dataType
- if ( dataTypes[ 0 ] in responses ) {
- finalDataType = dataTypes[ 0 ];
- } else {
- // Try convertible dataTypes
- for ( type in responses ) {
- if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
- finalDataType = type;
- break;
- }
- if ( !firstDataType ) {
- firstDataType = type;
- }
- }
- // Or just use first one
- finalDataType = finalDataType || firstDataType;
- }
-
- // If we found a dataType
- // We add the dataType to the list if needed
- // and return the corresponding response
- if ( finalDataType ) {
- if ( finalDataType !== dataTypes[ 0 ] ) {
- dataTypes.unshift( finalDataType );
- }
- return responses[ finalDataType ];
- }
-}
-
-// Chain conversions given the request and the original response
-function ajaxConvert( s, response ) {
-
- // Apply the dataFilter if provided
- if ( s.dataFilter ) {
- response = s.dataFilter( response, s.dataType );
- }
-
- var dataTypes = s.dataTypes,
- converters = {},
- i,
- key,
- length = dataTypes.length,
- tmp,
- // Current and previous dataTypes
- current = dataTypes[ 0 ],
- prev,
- // Conversion expression
- conversion,
- // Conversion function
- conv,
- // Conversion functions (transitive conversion)
- conv1,
- conv2;
-
- // For each dataType in the chain
- for( i = 1; i < length; i++ ) {
-
- // Create converters map
- // with lowercased keys
- if ( i === 1 ) {
- for( key in s.converters ) {
- if( typeof key === "string" ) {
- converters[ key.toLowerCase() ] = s.converters[ key ];
- }
- }
- }
-
- // Get the dataTypes
- prev = current;
- current = dataTypes[ i ];
-
- // If current is auto dataType, update it to prev
- if( current === "*" ) {
- current = prev;
- // If no auto and dataTypes are actually different
- } else if ( prev !== "*" && prev !== current ) {
-
- // Get the converter
- conversion = prev + " " + current;
- conv = converters[ conversion ] || converters[ "* " + current ];
-
- // If there is no direct converter, search transitively
- if ( !conv ) {
- conv2 = undefined;
- for( conv1 in converters ) {
- tmp = conv1.split( " " );
- if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) {
- conv2 = converters[ tmp[1] + " " + current ];
- if ( conv2 ) {
- conv1 = converters[ conv1 ];
- if ( conv1 === true ) {
- conv = conv2;
- } else if ( conv2 === true ) {
- conv = conv1;
- }
- break;
- }
- }
- }
- }
- // If we found no converter, dispatch an error
- if ( !( conv || conv2 ) ) {
- jQuery.error( "No conversion from " + conversion.replace(" "," to ") );
- }
- // If found converter is not an equivalence
- if ( conv !== true ) {
- // Convert with 1 or 2 converters accordingly
- response = conv ? conv( response ) : conv2( conv1(response) );
- }
- }
- }
- return response;
-}
-
-
-
-
-var jsc = jQuery.now(),
- jsre = /(\=)\?(&|$)|\?\?/i;
-
-// Default jsonp settings
-jQuery.ajaxSetup({
- jsonp: "callback",
- jsonpCallback: function() {
- return jQuery.expando + "_" + ( jsc++ );
- }
-});
-
-// Detect, normalize options and install callbacks for jsonp requests
-jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
-
- var dataIsString = ( typeof s.data === "string" );
-
- if ( s.dataTypes[ 0 ] === "jsonp" ||
- originalSettings.jsonpCallback ||
- originalSettings.jsonp != null ||
- s.jsonp !== false && ( jsre.test( s.url ) ||
- dataIsString && jsre.test( s.data ) ) ) {
-
- var responseContainer,
- jsonpCallback = s.jsonpCallback =
- jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback,
- previous = window[ jsonpCallback ],
- url = s.url,
- data = s.data,
- replace = "$1" + jsonpCallback + "$2",
- cleanUp = function() {
- // Set callback back to previous value
- window[ jsonpCallback ] = previous;
- // Call if it was a function and we have a response
- if ( responseContainer && jQuery.isFunction( previous ) ) {
- window[ jsonpCallback ]( responseContainer[ 0 ] );
- }
- };
-
- if ( s.jsonp !== false ) {
- url = url.replace( jsre, replace );
- if ( s.url === url ) {
- if ( dataIsString ) {
- data = data.replace( jsre, replace );
- }
- if ( s.data === data ) {
- // Add callback manually
- url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback;
- }
- }
- }
-
- s.url = url;
- s.data = data;
-
- // Install callback
- window[ jsonpCallback ] = function( response ) {
- responseContainer = [ response ];
- };
-
- // Install cleanUp function
- jqXHR.then( cleanUp, cleanUp );
-
- // Use data converter to retrieve json after script execution
- s.converters["script json"] = function() {
- if ( !responseContainer ) {
- jQuery.error( jsonpCallback + " was not called" );
- }
- return responseContainer[ 0 ];
- };
-
- // force json dataType
- s.dataTypes[ 0 ] = "json";
-
- // Delegate to script
- return "script";
- }
-} );
-
-
-
-
-// Install script dataType
-jQuery.ajaxSetup({
- accepts: {
- script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
- },
- contents: {
- script: /javascript|ecmascript/
- },
- converters: {
- "text script": function( text ) {
- jQuery.globalEval( text );
- return text;
- }
- }
-});
-
-// Handle cache's special case and global
-jQuery.ajaxPrefilter( "script", function( s ) {
- if ( s.cache === undefined ) {
- s.cache = false;
- }
- if ( s.crossDomain ) {
- s.type = "GET";
- s.global = false;
- }
-} );
-
-// Bind script tag hack transport
-jQuery.ajaxTransport( "script", function(s) {
-
- // This transport only deals with cross domain requests
- if ( s.crossDomain ) {
-
- var script,
- head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement;
-
- return {
-
- send: function( _, callback ) {
-
- script = document.createElement( "script" );
-
- script.async = "async";
-
- if ( s.scriptCharset ) {
- script.charset = s.scriptCharset;
- }
-
- script.src = s.url;
-
- // Attach handlers for all browsers
- script.onload = script.onreadystatechange = function( _, isAbort ) {
-
- if ( !script.readyState || /loaded|complete/.test( script.readyState ) ) {
-
- // Handle memory leak in IE
- script.onload = script.onreadystatechange = null;
-
- // Remove the script
- if ( head && script.parentNode ) {
- head.removeChild( script );
- }
-
- // Dereference the script
- script = undefined;
-
- // Callback if not abort
- if ( !isAbort ) {
- callback( 200, "success" );
- }
- }
- };
- // Use insertBefore instead of appendChild to circumvent an IE6 bug.
- // This arises when a base node is used (#2709 and #4378).
- head.insertBefore( script, head.firstChild );
- },
-
- abort: function() {
- if ( script ) {
- script.onload( 0, 1 );
- }
- }
- };
- }
-} );
-
-
-
-
-var // #5280: next active xhr id and list of active xhrs' callbacks
- xhrId = jQuery.now(),
- xhrCallbacks,
-
- // XHR used to determine supports properties
- testXHR;
-
-// #5280: Internet Explorer will keep connections alive if we don't abort on unload
-function xhrOnUnloadAbort() {
- jQuery( window ).unload(function() {
- // Abort all pending requests
- for ( var key in xhrCallbacks ) {
- xhrCallbacks[ key ]( 0, 1 );
- }
- });
-}
-
-// Functions to create xhrs
-function createStandardXHR() {
- try {
- return new window.XMLHttpRequest();
- } catch( e ) {}
-}
-
-function createActiveXHR() {
- try {
- return new window.ActiveXObject( "Microsoft.XMLHTTP" );
- } catch( e ) {}
-}
-
-// Create the request object
-// (This is still attached to ajaxSettings for backward compatibility)
-jQuery.ajaxSettings.xhr = window.ActiveXObject ?
- /* Microsoft failed to properly
- * implement the XMLHttpRequest in IE7 (can't request local files),
- * so we use the ActiveXObject when it is available
- * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
- * we need a fallback.
- */
- function() {
- return !this.isLocal && createStandardXHR() || createActiveXHR();
- } :
- // For all other browsers, use the standard XMLHttpRequest object
- createStandardXHR;
-
-// Test if we can create an xhr object
-testXHR = jQuery.ajaxSettings.xhr();
-jQuery.support.ajax = !!testXHR;
-
-// Does this browser support crossDomain XHR requests
-jQuery.support.cors = testXHR && ( "withCredentials" in testXHR );
-
-// No need for the temporary xhr anymore
-testXHR = undefined;
-
-// Create transport if the browser can provide an xhr
-if ( jQuery.support.ajax ) {
-
- jQuery.ajaxTransport(function( s ) {
- // Cross domain only allowed if supported through XMLHttpRequest
- if ( !s.crossDomain || jQuery.support.cors ) {
-
- var callback;
-
- return {
- send: function( headers, complete ) {
-
- // Get a new xhr
- var xhr = s.xhr(),
- handle,
- i;
-
- // Open the socket
- // Passing null username, generates a login popup on Opera (#2865)
- if ( s.username ) {
- xhr.open( s.type, s.url, s.async, s.username, s.password );
- } else {
- xhr.open( s.type, s.url, s.async );
- }
-
- // Apply custom fields if provided
- if ( s.xhrFields ) {
- for ( i in s.xhrFields ) {
- xhr[ i ] = s.xhrFields[ i ];
- }
- }
-
- // Override mime type if needed
- if ( s.mimeType && xhr.overrideMimeType ) {
- xhr.overrideMimeType( s.mimeType );
- }
-
- // X-Requested-With header
- // For cross-domain requests, seeing as conditions for a preflight are
- // akin to a jigsaw puzzle, we simply never set it to be sure.
- // (it can always be set on a per-request basis or even using ajaxSetup)
- // For same-domain requests, won't change header if already provided.
- if ( !s.crossDomain && !headers["X-Requested-With"] ) {
- headers[ "X-Requested-With" ] = "XMLHttpRequest";
- }
-
- // Need an extra try/catch for cross domain requests in Firefox 3
- try {
- for ( i in headers ) {
- xhr.setRequestHeader( i, headers[ i ] );
- }
- } catch( _ ) {}
-
- // Do send the request
- // This may raise an exception which is actually
- // handled in jQuery.ajax (so no try/catch here)
- xhr.send( ( s.hasContent && s.data ) || null );
-
- // Listener
- callback = function( _, isAbort ) {
-
- var status,
- statusText,
- responseHeaders,
- responses,
- xml;
-
- // Firefox throws exceptions when accessing properties
- // of an xhr when a network error occured
- // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
- try {
-
- // Was never called and is aborted or complete
- if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
-
- // Only called once
- callback = undefined;
-
- // Do not keep as active anymore
- if ( handle ) {
- xhr.onreadystatechange = jQuery.noop;
- delete xhrCallbacks[ handle ];
- }
-
- // If it's an abort
- if ( isAbort ) {
- // Abort it manually if needed
- if ( xhr.readyState !== 4 ) {
- xhr.abort();
- }
- } else {
- status = xhr.status;
- responseHeaders = xhr.getAllResponseHeaders();
- responses = {};
- xml = xhr.responseXML;
-
- // Construct response list
- if ( xml && xml.documentElement /* #4958 */ ) {
- responses.xml = xml;
- }
- responses.text = xhr.responseText;
-
- // Firefox throws an exception when accessing
- // statusText for faulty cross-domain requests
- try {
- statusText = xhr.statusText;
- } catch( e ) {
- // We normalize with Webkit giving an empty statusText
- statusText = "";
- }
-
- // Filter status for non standard behaviors
-
- // If the request is local and we have data: assume a success
- // (success with no data won't get notified, that's the best we
- // can do given current implementations)
- if ( !status && s.isLocal && !s.crossDomain ) {
- status = responses.text ? 200 : 404;
- // IE - #1450: sometimes returns 1223 when it should be 204
- } else if ( status === 1223 ) {
- status = 204;
- }
- }
- }
- } catch( firefoxAccessException ) {
- if ( !isAbort ) {
- complete( -1, firefoxAccessException );
- }
- }
-
- // Call complete if needed
- if ( responses ) {
- complete( status, statusText, responses, responseHeaders );
- }
- };
-
- // if we're in sync mode or it's in cache
- // and has been retrieved directly (IE6 & IE7)
- // we need to manually fire the callback
- if ( !s.async || xhr.readyState === 4 ) {
- callback();
- } else {
- // Create the active xhrs callbacks list if needed
- // and attach the unload handler
- if ( !xhrCallbacks ) {
- xhrCallbacks = {};
- xhrOnUnloadAbort();
- }
- // Add to list of active xhrs callbacks
- handle = xhrId++;
- xhr.onreadystatechange = xhrCallbacks[ handle ] = callback;
- }
- },
-
- abort: function() {
- if ( callback ) {
- callback(0,1);
- }
- }
- };
- }
- });
-}
-
-
-
-
-var elemdisplay = {},
- rfxtypes = /^(?:toggle|show|hide)$/,
- rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,
- timerId,
- fxAttrs = [
- // height animations
- [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
- // width animations
- [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
- // opacity animations
- [ "opacity" ]
- ];
-
-jQuery.fn.extend({
- show: function( speed, easing, callback ) {
- var elem, display;
-
- if ( speed || speed === 0 ) {
- return this.animate( genFx("show", 3), speed, easing, callback);
-
- } else {
- for ( var i = 0, j = this.length; i < j; i++ ) {
- elem = this[i];
- display = elem.style.display;
-
- // Reset the inline display of this element to learn if it is
- // being hidden by cascaded rules or not
- if ( !jQuery._data(elem, "olddisplay") && display === "none" ) {
- display = elem.style.display = "";
- }
-
- // Set elements which have been overridden with display: none
- // in a stylesheet to whatever the default browser style is
- // for such an element
- if ( display === "" && jQuery.css( elem, "display" ) === "none" ) {
- jQuery._data(elem, "olddisplay", defaultDisplay(elem.nodeName));
- }
- }
-
- // Set the display of most of the elements in a second loop
- // to avoid the constant reflow
- for ( i = 0; i < j; i++ ) {
- elem = this[i];
- display = elem.style.display;
-
- if ( display === "" || display === "none" ) {
- elem.style.display = jQuery._data(elem, "olddisplay") || "";
- }
- }
-
- return this;
- }
- },
-
- hide: function( speed, easing, callback ) {
- if ( speed || speed === 0 ) {
- return this.animate( genFx("hide", 3), speed, easing, callback);
-
- } else {
- for ( var i = 0, j = this.length; i < j; i++ ) {
- var display = jQuery.css( this[i], "display" );
-
- if ( display !== "none" && !jQuery._data( this[i], "olddisplay" ) ) {
- jQuery._data( this[i], "olddisplay", display );
- }
- }
-
- // Set the display of the elements in a second loop
- // to avoid the constant reflow
- for ( i = 0; i < j; i++ ) {
- this[i].style.display = "none";
- }
-
- return this;
- }
- },
-
- // Save the old toggle function
- _toggle: jQuery.fn.toggle,
-
- toggle: function( fn, fn2, callback ) {
- var bool = typeof fn === "boolean";
-
- if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) {
- this._toggle.apply( this, arguments );
-
- } else if ( fn == null || bool ) {
- this.each(function() {
- var state = bool ? fn : jQuery(this).is(":hidden");
- jQuery(this)[ state ? "show" : "hide" ]();
- });
-
- } else {
- this.animate(genFx("toggle", 3), fn, fn2, callback);
- }
-
- return this;
- },
-
- fadeTo: function( speed, to, easing, callback ) {
- return this.filter(":hidden").css("opacity", 0).show().end()
- .animate({opacity: to}, speed, easing, callback);
- },
-
- animate: function( prop, speed, easing, callback ) {
- var optall = jQuery.speed(speed, easing, callback);
-
- if ( jQuery.isEmptyObject( prop ) ) {
- return this.each( optall.complete );
- }
-
- return this[ optall.queue === false ? "each" : "queue" ](function() {
- // XXX 'this' does not always have a nodeName when running the
- // test suite
-
- var opt = jQuery.extend({}, optall), p,
- isElement = this.nodeType === 1,
- hidden = isElement && jQuery(this).is(":hidden"),
- self = this;
-
- for ( p in prop ) {
- var name = jQuery.camelCase( p );
-
- if ( p !== name ) {
- prop[ name ] = prop[ p ];
- delete prop[ p ];
- p = name;
- }
-
- if ( prop[p] === "hide" && hidden || prop[p] === "show" && !hidden ) {
- return opt.complete.call(this);
- }
-
- if ( isElement && ( p === "height" || p === "width" ) ) {
- // Make sure that nothing sneaks out
- // Record all 3 overflow attributes because IE does not
- // change the overflow attribute when overflowX and
- // overflowY are set to the same value
- opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ];
-
- // Set display property to inline-block for height/width
- // animations on inline elements that are having width/height
- // animated
- if ( jQuery.css( this, "display" ) === "inline" &&
- jQuery.css( this, "float" ) === "none" ) {
- if ( !jQuery.support.inlineBlockNeedsLayout ) {
- this.style.display = "inline-block";
-
- } else {
- var display = defaultDisplay(this.nodeName);
-
- // inline-level elements accept inline-block;
- // block-level elements need to be inline with layout
- if ( display === "inline" ) {
- this.style.display = "inline-block";
-
- } else {
- this.style.display = "inline";
- this.style.zoom = 1;
- }
- }
- }
- }
-
- if ( jQuery.isArray( prop[p] ) ) {
- // Create (if needed) and add to specialEasing
- (opt.specialEasing = opt.specialEasing || {})[p] = prop[p][1];
- prop[p] = prop[p][0];
- }
- }
-
- if ( opt.overflow != null ) {
- this.style.overflow = "hidden";
- }
-
- opt.curAnim = jQuery.extend({}, prop);
-
- jQuery.each( prop, function( name, val ) {
- var e = new jQuery.fx( self, opt, name );
-
- if ( rfxtypes.test(val) ) {
- e[ val === "toggle" ? hidden ? "show" : "hide" : val ]( prop );
-
- } else {
- var parts = rfxnum.exec(val),
- start = e.cur();
-
- if ( parts ) {
- var end = parseFloat( parts[2] ),
- unit = parts[3] || ( jQuery.cssNumber[ name ] ? "" : "px" );
-
- // We need to compute starting value
- if ( unit !== "px" ) {
- jQuery.style( self, name, (end || 1) + unit);
- start = ((end || 1) / e.cur()) * start;
- jQuery.style( self, name, start + unit);
- }
-
- // If a +=/-= token was provided, we're doing a relative animation
- if ( parts[1] ) {
- end = ((parts[1] === "-=" ? -1 : 1) * end) + start;
- }
-
- e.custom( start, end, unit );
-
- } else {
- e.custom( start, val, "" );
- }
- }
- });
-
- // For JS strict compliance
- return true;
- });
- },
-
- stop: function( clearQueue, gotoEnd ) {
- var timers = jQuery.timers;
-
- if ( clearQueue ) {
- this.queue([]);
- }
-
- this.each(function() {
- // go in reverse order so anything added to the queue during the loop is ignored
- for ( var i = timers.length - 1; i >= 0; i-- ) {
- if ( timers[i].elem === this ) {
- if (gotoEnd) {
- // force the next step to be the last
- timers[i](true);
- }
-
- timers.splice(i, 1);
- }
- }
- });
-
- // start the next in the queue if the last step wasn't forced
- if ( !gotoEnd ) {
- this.dequeue();
- }
-
- return this;
- }
-
-});
-
-function genFx( type, num ) {
- var obj = {};
-
- jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() {
- obj[ this ] = type;
- });
-
- return obj;
-}
-
-// Generate shortcuts for custom animations
-jQuery.each({
- slideDown: genFx("show", 1),
- slideUp: genFx("hide", 1),
- slideToggle: genFx("toggle", 1),
- fadeIn: { opacity: "show" },
- fadeOut: { opacity: "hide" },
- fadeToggle: { opacity: "toggle" }
-}, function( name, props ) {
- jQuery.fn[ name ] = function( speed, easing, callback ) {
- return this.animate( props, speed, easing, callback );
- };
-});
-
-jQuery.extend({
- speed: function( speed, easing, fn ) {
- var opt = speed && typeof speed === "object" ? jQuery.extend({}, speed) : {
- complete: fn || !fn && easing ||
- jQuery.isFunction( speed ) && speed,
- duration: speed,
- easing: fn && easing || easing && !jQuery.isFunction(easing) && easing
- };
-
- opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
- opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[opt.duration] : jQuery.fx.speeds._default;
-
- // Queueing
- opt.old = opt.complete;
- opt.complete = function() {
- if ( opt.queue !== false ) {
- jQuery(this).dequeue();
- }
- if ( jQuery.isFunction( opt.old ) ) {
- opt.old.call( this );
- }
- };
-
- return opt;
- },
-
- easing: {
- linear: function( p, n, firstNum, diff ) {
- return firstNum + diff * p;
- },
- swing: function( p, n, firstNum, diff ) {
- return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
- }
- },
-
- timers: [],
-
- fx: function( elem, options, prop ) {
- this.options = options;
- this.elem = elem;
- this.prop = prop;
-
- if ( !options.orig ) {
- options.orig = {};
- }
- }
-
-});
-
-jQuery.fx.prototype = {
- // Simple function for setting a style value
- update: function() {
- if ( this.options.step ) {
- this.options.step.call( this.elem, this.now, this );
- }
-
- (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
- },
-
- // Get the current size
- cur: function() {
- if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) {
- return this.elem[ this.prop ];
- }
-
- var parsed,
- r = jQuery.css( this.elem, this.prop );
- // Empty strings, null, undefined and "auto" are converted to 0,
- // complex values such as "rotate(1rad)" are returned as is,
- // simple values such as "10px" are parsed to Float.
- return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed;
- },
-
- // Start an animation from one number to another
- custom: function( from, to, unit ) {
- var self = this,
- fx = jQuery.fx;
-
- this.startTime = jQuery.now();
- this.start = from;
- this.end = to;
- this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" );
- this.now = this.start;
- this.pos = this.state = 0;
-
- function t( gotoEnd ) {
- return self.step(gotoEnd);
- }
-
- t.elem = this.elem;
-
- if ( t() && jQuery.timers.push(t) && !timerId ) {
- timerId = setInterval(fx.tick, fx.interval);
- }
- },
-
- // Simple 'show' function
- show: function() {
- // Remember where we started, so that we can go back to it later
- this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
- this.options.show = true;
-
- // Begin the animation
- // Make sure that we start at a small width/height to avoid any
- // flash of content
- this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur());
-
- // Start by showing the element
- jQuery( this.elem ).show();
- },
-
- // Simple 'hide' function
- hide: function() {
- // Remember where we started, so that we can go back to it later
- this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
- this.options.hide = true;
-
- // Begin the animation
- this.custom(this.cur(), 0);
- },
-
- // Each step of an animation
- step: function( gotoEnd ) {
- var t = jQuery.now(), done = true;
-
- if ( gotoEnd || t >= this.options.duration + this.startTime ) {
- this.now = this.end;
- this.pos = this.state = 1;
- this.update();
-
- this.options.curAnim[ this.prop ] = true;
-
- for ( var i in this.options.curAnim ) {
- if ( this.options.curAnim[i] !== true ) {
- done = false;
- }
- }
-
- if ( done ) {
- // Reset the overflow
- if ( this.options.overflow != null && !jQuery.support.shrinkWrapBlocks ) {
- var elem = this.elem,
- options = this.options;
-
- jQuery.each( [ "", "X", "Y" ], function (index, value) {
- elem.style[ "overflow" + value ] = options.overflow[index];
- } );
- }
-
- // Hide the element if the "hide" operation was done
- if ( this.options.hide ) {
- jQuery(this.elem).hide();
- }
-
- // Reset the properties, if the item has been hidden or shown
- if ( this.options.hide || this.options.show ) {
- for ( var p in this.options.curAnim ) {
- jQuery.style( this.elem, p, this.options.orig[p] );
- }
- }
-
- // Execute the complete function
- this.options.complete.call( this.elem );
- }
-
- return false;
-
- } else {
- var n = t - this.startTime;
- this.state = n / this.options.duration;
-
- // Perform the easing function, defaults to swing
- var specialEasing = this.options.specialEasing && this.options.specialEasing[this.prop];
- var defaultEasing = this.options.easing || (jQuery.easing.swing ? "swing" : "linear");
- this.pos = jQuery.easing[specialEasing || defaultEasing](this.state, n, 0, 1, this.options.duration);
- this.now = this.start + ((this.end - this.start) * this.pos);
-
- // Perform the next step of the animation
- this.update();
- }
-
- return true;
- }
-};
-
-jQuery.extend( jQuery.fx, {
- tick: function() {
- var timers = jQuery.timers;
-
- for ( var i = 0; i < timers.length; i++ ) {
- if ( !timers[i]() ) {
- timers.splice(i--, 1);
- }
- }
-
- if ( !timers.length ) {
- jQuery.fx.stop();
- }
- },
-
- interval: 13,
-
- stop: function() {
- clearInterval( timerId );
- timerId = null;
- },
-
- speeds: {
- slow: 600,
- fast: 200,
- // Default speed
- _default: 400
- },
-
- step: {
- opacity: function( fx ) {
- jQuery.style( fx.elem, "opacity", fx.now );
- },
-
- _default: function( fx ) {
- if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) {
- fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit;
- } else {
- fx.elem[ fx.prop ] = fx.now;
- }
- }
- }
-});
-
-if ( jQuery.expr && jQuery.expr.filters ) {
- jQuery.expr.filters.animated = function( elem ) {
- return jQuery.grep(jQuery.timers, function( fn ) {
- return elem === fn.elem;
- }).length;
- };
-}
-
-function defaultDisplay( nodeName ) {
- if ( !elemdisplay[ nodeName ] ) {
- var elem = jQuery("<" + nodeName + ">").appendTo("body"),
- display = elem.css("display");
-
- elem.remove();
-
- if ( display === "none" || display === "" ) {
- display = "block";
- }
-
- elemdisplay[ nodeName ] = display;
- }
-
- return elemdisplay[ nodeName ];
-}
-
-
-
-
-var rtable = /^t(?:able|d|h)$/i,
- rroot = /^(?:body|html)$/i;
-
-if ( "getBoundingClientRect" in document.documentElement ) {
- jQuery.fn.offset = function( options ) {
- var elem = this[0], box;
-
- if ( options ) {
- return this.each(function( i ) {
- jQuery.offset.setOffset( this, options, i );
- });
- }
-
- if ( !elem || !elem.ownerDocument ) {
- return null;
- }
-
- if ( elem === elem.ownerDocument.body ) {
- return jQuery.offset.bodyOffset( elem );
- }
-
- try {
- box = elem.getBoundingClientRect();
- } catch(e) {}
-
- var doc = elem.ownerDocument,
- docElem = doc.documentElement;
-
- // Make sure we're not dealing with a disconnected DOM node
- if ( !box || !jQuery.contains( docElem, elem ) ) {
- return box ? { top: box.top, left: box.left } : { top: 0, left: 0 };
- }
-
- var body = doc.body,
- win = getWindow(doc),
- clientTop = docElem.clientTop || body.clientTop || 0,
- clientLeft = docElem.clientLeft || body.clientLeft || 0,
- scrollTop = win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop,
- scrollLeft = win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft,
- top = box.top + scrollTop - clientTop,
- left = box.left + scrollLeft - clientLeft;
-
- return { top: top, left: left };
- };
-
-} else {
- jQuery.fn.offset = function( options ) {
- var elem = this[0];
-
- if ( options ) {
- return this.each(function( i ) {
- jQuery.offset.setOffset( this, options, i );
- });
- }
-
- if ( !elem || !elem.ownerDocument ) {
- return null;
- }
-
- if ( elem === elem.ownerDocument.body ) {
- return jQuery.offset.bodyOffset( elem );
- }
-
- jQuery.offset.initialize();
-
- var computedStyle,
- offsetParent = elem.offsetParent,
- prevOffsetParent = elem,
- doc = elem.ownerDocument,
- docElem = doc.documentElement,
- body = doc.body,
- defaultView = doc.defaultView,
- prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle,
- top = elem.offsetTop,
- left = elem.offsetLeft;
-
- while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
- if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) {
- break;
- }
-
- computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle;
- top -= elem.scrollTop;
- left -= elem.scrollLeft;
-
- if ( elem === offsetParent ) {
- top += elem.offsetTop;
- left += elem.offsetLeft;
-
- if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) {
- top += parseFloat( computedStyle.borderTopWidth ) || 0;
- left += parseFloat( computedStyle.borderLeftWidth ) || 0;
- }
-
- prevOffsetParent = offsetParent;
- offsetParent = elem.offsetParent;
- }
-
- if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) {
- top += parseFloat( computedStyle.borderTopWidth ) || 0;
- left += parseFloat( computedStyle.borderLeftWidth ) || 0;
- }
-
- prevComputedStyle = computedStyle;
- }
-
- if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) {
- top += body.offsetTop;
- left += body.offsetLeft;
- }
-
- if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) {
- top += Math.max( docElem.scrollTop, body.scrollTop );
- left += Math.max( docElem.scrollLeft, body.scrollLeft );
- }
-
- return { top: top, left: left };
- };
-}
-
-jQuery.offset = {
- initialize: function() {
- var body = document.body, container = document.createElement("div"), innerDiv, checkDiv, table, td, bodyMarginTop = parseFloat( jQuery.css(body, "marginTop") ) || 0,
- html = "<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
-
- jQuery.extend( container.style, { position: "absolute", top: 0, left: 0, margin: 0, border: 0, width: "1px", height: "1px", visibility: "hidden" } );
-
- container.innerHTML = html;
- body.insertBefore( container, body.firstChild );
- innerDiv = container.firstChild;
- checkDiv = innerDiv.firstChild;
- td = innerDiv.nextSibling.firstChild.firstChild;
-
- this.doesNotAddBorder = (checkDiv.offsetTop !== 5);
- this.doesAddBorderForTableAndCells = (td.offsetTop === 5);
-
- checkDiv.style.position = "fixed";
- checkDiv.style.top = "20px";
-
- // safari subtracts parent border width here which is 5px
- this.supportsFixedPosition = (checkDiv.offsetTop === 20 || checkDiv.offsetTop === 15);
- checkDiv.style.position = checkDiv.style.top = "";
-
- innerDiv.style.overflow = "hidden";
- innerDiv.style.position = "relative";
-
- this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5);
-
- this.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop);
-
- body.removeChild( container );
- jQuery.offset.initialize = jQuery.noop;
- },
-
- bodyOffset: function( body ) {
- var top = body.offsetTop,
- left = body.offsetLeft;
-
- jQuery.offset.initialize();
-
- if ( jQuery.offset.doesNotIncludeMarginInBodyOffset ) {
- top += parseFloat( jQuery.css(body, "marginTop") ) || 0;
- left += parseFloat( jQuery.css(body, "marginLeft") ) || 0;
- }
-
- return { top: top, left: left };
- },
-
- setOffset: function( elem, options, i ) {
- var position = jQuery.css( elem, "position" );
-
- // set position first, in-case top/left are set even on static elem
- if ( position === "static" ) {
- elem.style.position = "relative";
- }
-
- var curElem = jQuery( elem ),
- curOffset = curElem.offset(),
- curCSSTop = jQuery.css( elem, "top" ),
- curCSSLeft = jQuery.css( elem, "left" ),
- calculatePosition = (position === "absolute" || position === "fixed") && jQuery.inArray('auto', [curCSSTop, curCSSLeft]) > -1,
- props = {}, curPosition = {}, curTop, curLeft;
-
- // need to be able to calculate position if either top or left is auto and position is either absolute or fixed
- if ( calculatePosition ) {
- curPosition = curElem.position();
- }
-
- curTop = calculatePosition ? curPosition.top : parseInt( curCSSTop, 10 ) || 0;
- curLeft = calculatePosition ? curPosition.left : parseInt( curCSSLeft, 10 ) || 0;
-
- if ( jQuery.isFunction( options ) ) {
- options = options.call( elem, i, curOffset );
- }
-
- if (options.top != null) {
- props.top = (options.top - curOffset.top) + curTop;
- }
- if (options.left != null) {
- props.left = (options.left - curOffset.left) + curLeft;
- }
-
- if ( "using" in options ) {
- options.using.call( elem, props );
- } else {
- curElem.css( props );
- }
- }
-};
-
-
-jQuery.fn.extend({
- position: function() {
- if ( !this[0] ) {
- return null;
- }
-
- var elem = this[0],
-
- // Get *real* offsetParent
- offsetParent = this.offsetParent(),
-
- // Get correct offsets
- offset = this.offset(),
- parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
-
- // Subtract element margins
- // note: when an element has margin: auto the offsetLeft and marginLeft
- // are the same in Safari causing offset.left to incorrectly be 0
- offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0;
- offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0;
-
- // Add offsetParent borders
- parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0;
- parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0;
-
- // Subtract the two offsets
- return {
- top: offset.top - parentOffset.top,
- left: offset.left - parentOffset.left
- };
- },
-
- offsetParent: function() {
- return this.map(function() {
- var offsetParent = this.offsetParent || document.body;
- while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
- offsetParent = offsetParent.offsetParent;
- }
- return offsetParent;
- });
- }
-});
-
-
-// Create scrollLeft and scrollTop methods
-jQuery.each( ["Left", "Top"], function( i, name ) {
- var method = "scroll" + name;
-
- jQuery.fn[ method ] = function(val) {
- var elem = this[0], win;
-
- if ( !elem ) {
- return null;
- }
-
- if ( val !== undefined ) {
- // Set the scroll offset
- return this.each(function() {
- win = getWindow( this );
-
- if ( win ) {
- win.scrollTo(
- !i ? val : jQuery(win).scrollLeft(),
- i ? val : jQuery(win).scrollTop()
- );
-
- } else {
- this[ method ] = val;
- }
- });
- } else {
- win = getWindow( elem );
-
- // Return the scroll offset
- return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] :
- jQuery.support.boxModel && win.document.documentElement[ method ] ||
- win.document.body[ method ] :
- elem[ method ];
- }
- };
-});
-
-function getWindow( elem ) {
- return jQuery.isWindow( elem ) ?
- elem :
- elem.nodeType === 9 ?
- elem.defaultView || elem.parentWindow :
- false;
-}
-
-
-
-
-// Create innerHeight, innerWidth, outerHeight and outerWidth methods
-jQuery.each([ "Height", "Width" ], function( i, name ) {
-
- var type = name.toLowerCase();
-
- // innerHeight and innerWidth
- jQuery.fn["inner" + name] = function() {
- return this[0] ?
- parseFloat( jQuery.css( this[0], type, "padding" ) ) :
- null;
- };
-
- // outerHeight and outerWidth
- jQuery.fn["outer" + name] = function( margin ) {
- return this[0] ?
- parseFloat( jQuery.css( this[0], type, margin ? "margin" : "border" ) ) :
- null;
- };
-
- jQuery.fn[ type ] = function( size ) {
- // Get window width or height
- var elem = this[0];
- if ( !elem ) {
- return size == null ? null : this;
- }
-
- if ( jQuery.isFunction( size ) ) {
- return this.each(function( i ) {
- var self = jQuery( this );
- self[ type ]( size.call( this, i, self[ type ]() ) );
- });
- }
-
- if ( jQuery.isWindow( elem ) ) {
- // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
- // 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat
- var docElemProp = elem.document.documentElement[ "client" + name ];
- return elem.document.compatMode === "CSS1Compat" && docElemProp ||
- elem.document.body[ "client" + name ] || docElemProp;
-
- // Get document width or height
- } else if ( elem.nodeType === 9 ) {
- // Either scroll[Width/Height] or offset[Width/Height], whichever is greater
- return Math.max(
- elem.documentElement["client" + name],
- elem.body["scroll" + name], elem.documentElement["scroll" + name],
- elem.body["offset" + name], elem.documentElement["offset" + name]
- );
-
- // Get or set width or height on the element
- } else if ( size === undefined ) {
- var orig = jQuery.css( elem, type ),
- ret = parseFloat( orig );
-
- return jQuery.isNaN( ret ) ? orig : ret;
-
- // Set the width or height on the element (default to pixels if value is unitless)
- } else {
- return this.css( type, typeof size === "string" ? size : size + "px" );
- }
- };
-
-});
-
-
-window.jQuery = window.$ = jQuery;
-})(window);
+++ /dev/null
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
- <head>
-
- </head>
-
- <body>
- <h1>Loading Content</h1>
- </body>
-</html>
--- /dev/null
+;;; impatient-mode-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+(add-to-list 'load-path (or (file-name-directory #$) (car load-path)))
+\f
+;;;### (autoloads nil "impatient-mode" "impatient-mode.el" (21908
+;;;;;; 14231 0 0))
+;;; Generated autoloads from impatient-mode.el
+
+(autoload 'impatient-mode "impatient-mode" "\
+Serves the buffer live over HTTP.
+
+\(fn &optional ARG)" t nil)
+
+;;;***
+\f
+;;;### (autoloads nil nil ("impatient-mode-pkg.el") (21908 14231
+;;;;;; 202947 0))
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+;;; impatient-mode-autoloads.el ends here
--- /dev/null
+(define-package "impatient-mode" "20150501.247" "Serve buffers live over HTTP"
+ '((cl-lib "0.3")
+ (simple-httpd "1.4.0")
+ (htmlize "1.40"))
+ :url "https://github.com/netguy204/imp.el")
+;; Local Variables:
+;; no-byte-compile: t
+;; End:
--- /dev/null
+;;; impatient-mode.el --- Serve buffers live over HTTP
+
+;; This is free and unencumbered software released into the public domain.
+
+;; Author: Brian Taylor <el.wuboNOSPAMgmail.com>
+;; Version: 1.0.0
+;; URL: https://github.com/netguy204/imp.el
+;; Package-Requires: ((cl-lib "0.3") (simple-httpd "1.4.0") (htmlize "1.40"))
+
+;;; Commentary:
+
+;; impatient-mode is a minor mode that publishes the live buffer
+;; through the local simple-httpd server under /imp/<buffer-name>. To
+;; unpublish a buffer, toggle impatient-mode off.
+
+;; Start the simple-httpd server (`httpd-start') and visit /imp/ on
+;; the local server. There will be a listing of all the buffers that
+;; currently have impatient-mode enabled. This is likely to be found
+;; here:
+
+;; http://localhost:8080/imp/
+
+;; Except for html-mode buffers, buffers will be prettied up with
+;; htmlize before being sent to clients. This can be toggled at any
+;; time with `imp-toggle-htmlize'.
+
+;; Because html-mode buffers are sent raw, you can use impatient-mode
+;; see your edits to an HTML document live! This is perhaps the
+;; primary motivation of this mode.
+
+;; To receive updates the browser issues a long poll on the client
+;; waiting for the buffer to change -- server push. The response
+;; happens in an `after-change-functions' hook. Buffers that do not
+;; run these hooks will not be displayed live to clients.
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'url-util)
+(require 'simple-httpd)
+(require 'htmlize)
+
+(defgroup impatient-mode nil
+ "Serve buffers live over HTTP."
+ :group 'comm)
+
+(defvar impatient-mode-map (make-sparse-keymap)
+ "Keymap for impatient-mode.")
+
+(make-variable-buffer-local
+ (defvar imp-user-filter #'imp-htmlize-filter
+ "Per buffer html-producing function by user."))
+
+(make-variable-buffer-local
+ (defvar imp-client-list ()
+ "List of client processes watching the current buffer."))
+
+(make-variable-buffer-local
+ (defvar imp-last-state 0
+ "State sequence number."))
+
+(make-variable-buffer-local
+ (defvar imp-related-files nil
+ "Files that seem to be related to this buffer"))
+
+(defvar imp-default-user-filters
+ '((html-mode . nil)
+ (web-mode . nil))
+ "Alist indicating which filter should be used for which modes.")
+
+;;;###autoload
+(define-minor-mode impatient-mode
+ "Serves the buffer live over HTTP."
+ :group 'impatient-mode
+ :lighter " imp"
+ :keymap impatient-mode-map
+ (if (not impatient-mode)
+ (remove-hook 'after-change-functions 'imp--on-change t)
+ (add-hook 'after-change-functions 'imp--on-change nil t)
+ (imp-remove-user-filter)))
+
+(defvar imp-shim-root (file-name-directory load-file-name)
+ "Location of data files needed by impatient-mode.")
+
+(defun imp-set-user-filter (function)
+ "Sets a user-defined filter for this buffer.
+FUNCTION should accept one argument, the buffer to be filtered,
+and will be evaluated with the output buffer set as the current
+buffer."
+ (interactive "aCustom filter: ")
+ (setq imp-user-filter function)
+ (cl-incf imp-last-state)
+ (imp--notify-clients))
+
+(defun imp-remove-user-filter ()
+ "Sets the user-defined filter for this buffer to the default."
+ (interactive)
+ (let ((lookup (assoc major-mode imp-default-user-filters)))
+ (if lookup
+ (imp-set-user-filter (cdr lookup))
+ (kill-local-variable 'imp-user-filter)))
+ (cl-incf imp-last-state)
+ (imp--notify-clients))
+
+(defun imp-htmlize-filter (buffer)
+ "Htmlization of buffers before sending to clients."
+ (let ((html-buffer (save-match-data (htmlize-buffer buffer))))
+ (insert-buffer-substring html-buffer)
+ (kill-buffer html-buffer)))
+
+(defun imp-toggle-htmlize ()
+ "Toggle htmlize of buffer."
+ (interactive)
+ (if (eq imp-user-filter 'imp-htmlize-filter)
+ (imp-set-user-filter nil)
+ (imp-set-user-filter 'imp-htmlize-filter)))
+
+(defun imp-visit-buffer ()
+ "Visit the buffer in a browser."
+ (interactive)
+ (impatient-mode)
+ (browse-url
+ (format "http://%s:%d/imp/live/%s/"
+ (system-name) httpd-port (url-hexify-string (buffer-name)))))
+
+(defun imp-buffer-enabled-p (buffer)
+ "Return t if buffer has impatient-mode enabled."
+ (and buffer (with-current-buffer (get-buffer buffer) impatient-mode)))
+
+(defun imp--buffer-list ()
+ "List of all buffers with impatient-mode enabled"
+ (cl-remove-if-not 'imp-buffer-enabled-p (buffer-list)))
+
+(defun imp--should-not-cache-p (path)
+ "True if the path should be stamped with a no-cache header"
+ (let ((mime-type (httpd-get-mime (file-name-extension path))))
+ (member mime-type '("text/css" "text/html" "text/xml"
+ "text/plain" "text/javascript"))))
+
+(defun httpd/imp/static (proc path query req)
+ "Serve up static files."
+ (let* ((file (file-name-nondirectory path))
+ (clean (expand-file-name file imp-shim-root)))
+ (if (file-exists-p clean)
+ (httpd-send-file proc clean req)
+ (httpd-error proc 404))))
+
+(defun imp-serve-buffer-list (proc)
+ "Serve a list of published buffers."
+ (with-httpd-buffer proc "text/html; charset=utf-8"
+ (insert "<html><head>\n")
+ (insert "<title>impatient-mode buffer list</title>\n")
+ (insert "</head><body>\n")
+ (insert "<h1>Public Buffers</h1>\n<hr/>")
+ (insert "<ul>\n")
+ (dolist (buffer (imp--buffer-list))
+ (insert (format "<li><a href=\"live/%s/\">%s</a></li>\n"
+ (url-hexify-string (buffer-name buffer))
+ (url-insert-entities-in-string (buffer-name buffer)))))
+ (insert "</ul>\n<hr/>")
+ (insert "Enable <code>impatient-mode</code> in buffers to publish them.")
+ (insert "</body></html>")))
+
+(defun imp--private (proc buffer-name)
+ (httpd-error proc 403
+ (format "Buffer %s is private or doesn't exist." buffer-name)))
+
+(defun httpd/imp/live (proc path query req)
+ "Serve up the shim that lets us watch a buffer change"
+ (let* ((index (expand-file-name "index.html" imp-shim-root))
+ (parts (cdr (split-string path "/")))
+ (buffer-name (nth 2 parts))
+ (file (httpd-clean-path (mapconcat 'identity (nthcdr 3 parts) "/")))
+ (buffer (get-buffer buffer-name))
+ (buffer-file (buffer-file-name buffer))
+ (buffer-dir (and buffer-file (file-name-directory buffer-file))))
+
+ (cond
+ ((equal (file-name-directory path) "/imp/live/")
+ (httpd-redirect proc (concat path "/")))
+ ((not (imp-buffer-enabled-p buffer)) (imp--private proc buffer-name))
+ ((and (not (string= file "./")) buffer-dir)
+ (let* ((full-file-name (expand-file-name file buffer-dir))
+ (mime-type (httpd-get-mime (file-name-extension full-file-name)))
+ (live-buffer (cl-remove-if-not
+ (lambda (buf) (equal full-file-name (buffer-file-name buf)))
+ (imp--buffer-list))))
+ (add-to-list 'imp-related-files full-file-name)
+ (if live-buffer
+ (with-temp-buffer
+ (insert-buffer-substring (cl-first live-buffer))
+ (if (imp--should-not-cache-p path)
+ (httpd-send-header proc mime-type 200
+ :Cache-Control "no-cache")
+ (httpd-send-header proc mime-type 200
+ :Cache-Control
+ "max-age=60, must-revalidate")))
+ (httpd-send-file proc full-file-name req))))
+ (t (imp-buffer-enabled-p buffer) (httpd-send-file proc index req)))))
+
+(defun httpd/imp (proc path &rest args)
+ (cond
+ ((equal path "/imp") (httpd-redirect proc "/imp/"))
+ ((equal path "/imp/") (imp-serve-buffer-list proc))
+ (t (httpd-error proc 403 (format "%s not found" path)))))
+
+(defun imp--send-state (proc)
+ (let ((id (number-to-string imp-last-state))
+ (user-filter imp-user-filter)
+ (buffer (current-buffer)))
+ (with-temp-buffer
+ (if user-filter
+ (funcall user-filter buffer)
+ (insert-buffer-substring buffer))
+ (httpd-send-header proc "text/html" 200
+ :Cache-Control "no-cache"
+ :X-Imp-Count id))))
+
+(defun imp--send-state-ignore-errors (proc)
+ (condition-case error-case
+ (imp--send-state proc)
+ (error nil)))
+
+(defun imp--notify-clients ()
+ (while imp-client-list
+ (imp--send-state-ignore-errors (pop imp-client-list))))
+
+(defun imp--on-change (&rest args)
+ "Hook for after-change-functions."
+ (cl-incf imp-last-state)
+
+ ;; notify our clients
+ (imp--notify-clients)
+
+ ;; notify any clients that we're in the imp-related-files list for
+ (let ((buffer-file (buffer-file-name (current-buffer))))
+ (dolist (buffer (imp--buffer-list))
+ (with-current-buffer buffer
+ (when (member buffer-file imp-related-files)
+ (imp--notify-clients))))))
+
+(defun httpd/imp/buffer (proc path query &rest args)
+ "Servlet that accepts long poll requests."
+ (let* ((buffer-name (file-name-nondirectory path))
+ (buffer (get-buffer buffer-name))
+ (req-last-id (string-to-number (or (cadr (assoc "id" query)) "0"))))
+ (if (imp-buffer-enabled-p buffer)
+ (with-current-buffer buffer
+ (if (equal req-last-id imp-last-state)
+ (push proc imp-client-list) ; this client is sync'd
+ (imp--send-state-ignore-errors proc))) ; this client is behind
+ (imp--private proc buffer-name))))
+
+(provide 'impatient-mode)
+
+;;; impatient-mode.el ends here
--- /dev/null
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <script type="text/javascript" src="/imp/static/jquery.js"></script>
+
+ <style type="text/css">
+ html {
+ overflow: auto;
+ }
+ html, body, div, iframe {
+ margin: 0px;
+ padding: 0px;
+ height: 100%;
+ border: none;
+ }
+ iframe {
+ display: block;
+ width: 100%;
+ border: none;
+ overflow-y: auto;
+ overflow-x: hidden;
+ }
+ </style>
+ </head>
+
+ <body>
+ <iframe id="content" name="content" src="/imp/static/loading.html" frameborder="0" marginheight="0" marginwidth="0" width="100%" height="100%" scrolling="auto">
+ </iframe>
+
+ <script type="text/javascript">
+ var buffer = window.location.pathname.split('/')[3];
+ var max_period = 60000;
+ var min_period = 1000;
+ var next_period = min_period;
+ var alpha = 1.2;
+ var current_id = '-1';
+ $('head').append($('<title/>').text(decodeURI(buffer)));
+
+ var nextTimeout = function() {
+ var next = next_period;
+ next_period = Math.min(max_period, next_period * alpha);
+ return next;
+ };
+
+ var resetTimeout = function() {
+ next_period = min_period;
+ };
+
+ var frameToDocument = function(iframe) {
+ return (iframe.contentDocument) ? iframe.contentDocument : iframe.Document;
+ };
+
+ var printIframe = function(data) {
+ var iframeJQ = $('#content');
+ var doc = frameToDocument(iframeJQ[0]);
+ var iwindow = iframeJQ[0].contentWindow;
+ var offX = iwindow.pageXOffset, offY = iwindow.pageYOffset;
+ if (offY && offY >= $(doc.body).height() - $(iwindow).height()) {
+ offY = null; // follow the end of the buffer
+ }
+ doc.open();
+ doc.write(data);
+ doc.close();
+ iwindow.scrollTo(offX, offY != null ? offY : $(doc.body).height());
+ };
+
+ var setIframe = function(count, newText) {
+ if(!count) {
+ // error parsing client result
+ printIframe('0', 'error parsing the response from emacs');
+ } else {
+ current_id = count;
+ printIframe(newText);
+ lastText = newText;
+ }
+ };
+
+ var refresh = function() {
+ var url = "/imp/buffer/" + buffer;
+
+ var gotData = function(data, status, xhr) {
+ resetTimeout();
+ setIframe(xhr.getResponseHeader("X-Imp-Count"), data);
+ refresh();
+ };
+
+ var errorRetry = function() {
+ setTimeout(refresh, nextTimeout());
+ };
+
+ $.get(url + '?id=' + current_id, gotData).error(errorRetry);
+ };
+
+ $(document).ready(function() {
+ var iframeJQ = $('#content');
+ iframeJQ.load(function() {
+ // now we need to tweak the stylesheet links so that firefox will refresh them properly
+ $('link', frameToDocument(iframeJQ[0])).each(function(index, el) {
+ var href = $(el).attr('href');
+ // Only refresh impatient-mode hosted content
+ if(href && !/^[a-zA-z]+:\/\//.exec(href)) {
+ $(el).attr('href', $(el).attr('href') + '?' + new Date().getTime());
+ }
+ });
+ });
+
+ refresh();
+ });
+ </script>
+ </body>
+</html>
--- /dev/null
+/*!
+ * jQuery JavaScript Library v1.5.2
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Thu Mar 31 15:28:23 2011 -0400
+ */
+(function( window, undefined ) {
+
+// Use the correct document accordingly with window argument (sandbox)
+var document = window.document;
+var jQuery = (function() {
+
+// Define a local copy of jQuery
+var jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init( selector, context, rootjQuery );
+ },
+
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+
+ // Map over the $ in case of overwrite
+ _$ = window.$,
+
+ // A central reference to the root jQuery(document)
+ rootjQuery,
+
+ // A simple way to check for HTML strings or ID strings
+ // (both of which we optimize for)
+ quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/,
+
+ // Check if a string has a non-whitespace character in it
+ rnotwhite = /\S/,
+
+ // Used for trimming whitespace
+ trimLeft = /^\s+/,
+ trimRight = /\s+$/,
+
+ // Check for digits
+ rdigit = /\d/,
+
+ // Match a standalone tag
+ rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,
+
+ // JSON RegExp
+ rvalidchars = /^[\],:{}\s]*$/,
+ rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
+ rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
+ rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
+
+ // Useragent RegExp
+ rwebkit = /(webkit)[ \/]([\w.]+)/,
+ ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/,
+ rmsie = /(msie) ([\w.]+)/,
+ rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/,
+
+ // Keep a UserAgent string for use with jQuery.browser
+ userAgent = navigator.userAgent,
+
+ // For matching the engine and version of the browser
+ browserMatch,
+
+ // The deferred used on DOM ready
+ readyList,
+
+ // The ready event handler
+ DOMContentLoaded,
+
+ // Save a reference to some core methods
+ toString = Object.prototype.toString,
+ hasOwn = Object.prototype.hasOwnProperty,
+ push = Array.prototype.push,
+ slice = Array.prototype.slice,
+ trim = String.prototype.trim,
+ indexOf = Array.prototype.indexOf,
+
+ // [[Class]] -> type pairs
+ class2type = {};
+
+jQuery.fn = jQuery.prototype = {
+ constructor: jQuery,
+ init: function( selector, context, rootjQuery ) {
+ var match, elem, ret, doc;
+
+ // Handle $(""), $(null), or $(undefined)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Handle $(DOMElement)
+ if ( selector.nodeType ) {
+ this.context = this[0] = selector;
+ this.length = 1;
+ return this;
+ }
+
+ // The body element only exists once, optimize finding it
+ if ( selector === "body" && !context && document.body ) {
+ this.context = document;
+ this[0] = document.body;
+ this.selector = "body";
+ this.length = 1;
+ return this;
+ }
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ // Are we dealing with HTML string or an ID?
+ match = quickExpr.exec( selector );
+
+ // Verify a match, and that no context was specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] ) {
+ context = context instanceof jQuery ? context[0] : context;
+ doc = (context ? context.ownerDocument || context : document);
+
+ // If a single string is passed in and it's a single tag
+ // just do a createElement and skip the rest
+ ret = rsingleTag.exec( selector );
+
+ if ( ret ) {
+ if ( jQuery.isPlainObject( context ) ) {
+ selector = [ document.createElement( ret[1] ) ];
+ jQuery.fn.attr.call( selector, context, true );
+
+ } else {
+ selector = [ doc.createElement( ret[1] ) ];
+ }
+
+ } else {
+ ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
+ selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes;
+ }
+
+ return jQuery.merge( this, selector );
+
+ // HANDLE: $("#id")
+ } else {
+ elem = document.getElementById( match[2] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id !== match[2] ) {
+ return rootjQuery.find( selector );
+ }
+
+ // Otherwise, we inject the element directly into the jQuery object
+ this.length = 1;
+ this[0] = elem;
+ }
+
+ this.context = document;
+ this.selector = selector;
+ return this;
+ }
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return (context || rootjQuery).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return this.constructor( context ).find( selector );
+ }
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) ) {
+ return rootjQuery.ready( selector );
+ }
+
+ if (selector.selector !== undefined) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
+ return jQuery.makeArray( selector, this );
+ },
+
+ // Start with an empty selector
+ selector: "",
+
+ // The current version of jQuery being used
+ jquery: "1.5.2",
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ // The number of elements contained in the matched element set
+ size: function() {
+ return this.length;
+ },
+
+ toArray: function() {
+ return slice.call( this, 0 );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == null ?
+
+ // Return a 'clean' array
+ this.toArray() :
+
+ // Return just the object
+ ( num < 0 ? this[ this.length + num ] : this[ num ] );
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems, name, selector ) {
+ // Build a new jQuery matched element set
+ var ret = this.constructor();
+
+ if ( jQuery.isArray( elems ) ) {
+ push.apply( ret, elems );
+
+ } else {
+ jQuery.merge( ret, elems );
+ }
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ ret.context = this.context;
+
+ if ( name === "find" ) {
+ ret.selector = this.selector + (this.selector ? " " : "") + selector;
+ } else if ( name ) {
+ ret.selector = this.selector + "." + name + "(" + selector + ")";
+ }
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ ready: function( fn ) {
+ // Attach the listeners
+ jQuery.bindReady();
+
+ // Add the callback
+ readyList.done( fn );
+
+ return this;
+ },
+
+ eq: function( i ) {
+ return i === -1 ?
+ this.slice( i ) :
+ this.slice( i, +i + 1 );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ slice: function() {
+ return this.pushStack( slice.apply( this, arguments ),
+ "slice", slice.call(arguments).join(",") );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ end: function() {
+ return this.prevObject || this.constructor(null);
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: push,
+ sort: [].sort,
+ splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+ var options, name, src, copy, copyIsArray, clone,
+ target = arguments[0] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+ target = {};
+ }
+
+ // extend jQuery itself if only one argument is passed
+ if ( length === i ) {
+ target = this;
+ --i;
+ }
+
+ for ( ; i < length; i++ ) {
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null ) {
+ // Extend the base object
+ for ( name in options ) {
+ src = target[ name ];
+ copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+ if ( copyIsArray ) {
+ copyIsArray = false;
+ clone = src && jQuery.isArray(src) ? src : [];
+
+ } else {
+ clone = src && jQuery.isPlainObject(src) ? src : {};
+ }
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
+
+jQuery.extend({
+ noConflict: function( deep ) {
+ window.$ = _$;
+
+ if ( deep ) {
+ window.jQuery = _jQuery;
+ }
+
+ return jQuery;
+ },
+
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // A counter to track how many items to wait for before
+ // the ready event fires. See #6781
+ readyWait: 1,
+
+ // Handle when the DOM is ready
+ ready: function( wait ) {
+ // A third-party is pushing the ready event forwards
+ if ( wait === true ) {
+ jQuery.readyWait--;
+ }
+
+ // Make sure that the DOM is not already loaded
+ if ( !jQuery.readyWait || (wait !== true && !jQuery.isReady) ) {
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( !document.body ) {
+ return setTimeout( jQuery.ready, 1 );
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
+
+ // If there are functions bound, to execute
+ readyList.resolveWith( document, [ jQuery ] );
+
+ // Trigger any bound ready events
+ if ( jQuery.fn.trigger ) {
+ jQuery( document ).trigger( "ready" ).unbind( "ready" );
+ }
+ }
+ },
+
+ bindReady: function() {
+ if ( readyList ) {
+ return;
+ }
+
+ readyList = jQuery._Deferred();
+
+ // Catch cases where $(document).ready() is called after the
+ // browser event has already occurred.
+ if ( document.readyState === "complete" ) {
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ return setTimeout( jQuery.ready, 1 );
+ }
+
+ // Mozilla, Opera and webkit nightlies currently support this event
+ if ( document.addEventListener ) {
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", jQuery.ready, false );
+
+ // If IE event model is used
+ } else if ( document.attachEvent ) {
+ // ensure firing before onload,
+ // maybe late but safe also for iframes
+ document.attachEvent("onreadystatechange", DOMContentLoaded);
+
+ // A fallback to window.onload, that will always work
+ window.attachEvent( "onload", jQuery.ready );
+
+ // If IE and not a frame
+ // continually check to see if the document is ready
+ var toplevel = false;
+
+ try {
+ toplevel = window.frameElement == null;
+ } catch(e) {}
+
+ if ( document.documentElement.doScroll && toplevel ) {
+ doScrollCheck();
+ }
+ }
+ },
+
+ // See test/unit/core.js for details concerning isFunction.
+ // Since version 1.3, DOM methods and functions like alert
+ // aren't supported. They return false on IE (#2968).
+ isFunction: function( obj ) {
+ return jQuery.type(obj) === "function";
+ },
+
+ isArray: Array.isArray || function( obj ) {
+ return jQuery.type(obj) === "array";
+ },
+
+ // A crude way of determining if an object is a window
+ isWindow: function( obj ) {
+ return obj && typeof obj === "object" && "setInterval" in obj;
+ },
+
+ isNaN: function( obj ) {
+ return obj == null || !rdigit.test( obj ) || isNaN( obj );
+ },
+
+ type: function( obj ) {
+ return obj == null ?
+ String( obj ) :
+ class2type[ toString.call(obj) ] || "object";
+ },
+
+ isPlainObject: function( obj ) {
+ // Must be an Object.
+ // Because of IE, we also have to check the presence of the constructor property.
+ // Make sure that DOM nodes and window objects don't pass through, as well
+ if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ // Not own constructor property must be Object
+ if ( obj.constructor &&
+ !hasOwn.call(obj, "constructor") &&
+ !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
+ return false;
+ }
+
+ // Own properties are enumerated firstly, so to speed up,
+ // if last one is own, then all properties are own.
+
+ var key;
+ for ( key in obj ) {}
+
+ return key === undefined || hasOwn.call( obj, key );
+ },
+
+ isEmptyObject: function( obj ) {
+ for ( var name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ error: function( msg ) {
+ throw msg;
+ },
+
+ parseJSON: function( data ) {
+ if ( typeof data !== "string" || !data ) {
+ return null;
+ }
+
+ // Make sure leading/trailing whitespace is removed (IE can't handle it)
+ data = jQuery.trim( data );
+
+ // Make sure the incoming data is actual JSON
+ // Logic borrowed from http://json.org/json2.js
+ if ( rvalidchars.test(data.replace(rvalidescape, "@")
+ .replace(rvalidtokens, "]")
+ .replace(rvalidbraces, "")) ) {
+
+ // Try to use the native JSON parser first
+ return window.JSON && window.JSON.parse ?
+ window.JSON.parse( data ) :
+ (new Function("return " + data))();
+
+ } else {
+ jQuery.error( "Invalid JSON: " + data );
+ }
+ },
+
+ // Cross-browser xml parsing
+ // (xml & tmp used internally)
+ parseXML: function( data , xml , tmp ) {
+
+ if ( window.DOMParser ) { // Standard
+ tmp = new DOMParser();
+ xml = tmp.parseFromString( data , "text/xml" );
+ } else { // IE
+ xml = new ActiveXObject( "Microsoft.XMLDOM" );
+ xml.async = "false";
+ xml.loadXML( data );
+ }
+
+ tmp = xml.documentElement;
+
+ if ( ! tmp || ! tmp.nodeName || tmp.nodeName === "parsererror" ) {
+ jQuery.error( "Invalid XML: " + data );
+ }
+
+ return xml;
+ },
+
+ noop: function() {},
+
+ // Evalulates a script in a global context
+ globalEval: function( data ) {
+ if ( data && rnotwhite.test(data) ) {
+ // Inspired by code by Andrea Giammarchi
+ // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
+ var head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement,
+ script = document.createElement( "script" );
+
+ if ( jQuery.support.scriptEval() ) {
+ script.appendChild( document.createTextNode( data ) );
+ } else {
+ script.text = data;
+ }
+
+ // Use insertBefore instead of appendChild to circumvent an IE6 bug.
+ // This arises when a base node is used (#2709).
+ head.insertBefore( script, head.firstChild );
+ head.removeChild( script );
+ }
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
+ },
+
+ // args is for internal usage only
+ each: function( object, callback, args ) {
+ var name, i = 0,
+ length = object.length,
+ isObj = length === undefined || jQuery.isFunction(object);
+
+ if ( args ) {
+ if ( isObj ) {
+ for ( name in object ) {
+ if ( callback.apply( object[ name ], args ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.apply( object[ i++ ], args ) === false ) {
+ break;
+ }
+ }
+ }
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( isObj ) {
+ for ( name in object ) {
+ if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( var value = object[0];
+ i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {}
+ }
+ }
+
+ return object;
+ },
+
+ // Use native String.trim function wherever possible
+ trim: trim ?
+ function( text ) {
+ return text == null ?
+ "" :
+ trim.call( text );
+ } :
+
+ // Otherwise use our own trimming functionality
+ function( text ) {
+ return text == null ?
+ "" :
+ text.toString().replace( trimLeft, "" ).replace( trimRight, "" );
+ },
+
+ // results is for internal usage only
+ makeArray: function( array, results ) {
+ var ret = results || [];
+
+ if ( array != null ) {
+ // The window, strings (and functions) also have 'length'
+ // The extra typeof function check is to prevent crashes
+ // in Safari 2 (See: #3039)
+ // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
+ var type = jQuery.type(array);
+
+ if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) {
+ push.call( ret, array );
+ } else {
+ jQuery.merge( ret, array );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, array ) {
+ if ( array.indexOf ) {
+ return array.indexOf( elem );
+ }
+
+ for ( var i = 0, length = array.length; i < length; i++ ) {
+ if ( array[ i ] === elem ) {
+ return i;
+ }
+ }
+
+ return -1;
+ },
+
+ merge: function( first, second ) {
+ var i = first.length,
+ j = 0;
+
+ if ( typeof second.length === "number" ) {
+ for ( var l = second.length; j < l; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+
+ } else {
+ while ( second[j] !== undefined ) {
+ first[ i++ ] = second[ j++ ];
+ }
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, inv ) {
+ var ret = [], retVal;
+ inv = !!inv;
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( var i = 0, length = elems.length; i < length; i++ ) {
+ retVal = !!callback( elems[ i ], i );
+ if ( inv !== retVal ) {
+ ret.push( elems[ i ] );
+ }
+ }
+
+ return ret;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var ret = [], value;
+
+ // Go through the array, translating each of the items to their
+ // new value (or values).
+ for ( var i = 0, length = elems.length; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+
+ // Flatten any nested arrays
+ return ret.concat.apply( [], ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ proxy: function( fn, proxy, thisObject ) {
+ if ( arguments.length === 2 ) {
+ if ( typeof proxy === "string" ) {
+ thisObject = fn;
+ fn = thisObject[ proxy ];
+ proxy = undefined;
+
+ } else if ( proxy && !jQuery.isFunction( proxy ) ) {
+ thisObject = proxy;
+ proxy = undefined;
+ }
+ }
+
+ if ( !proxy && fn ) {
+ proxy = function() {
+ return fn.apply( thisObject || this, arguments );
+ };
+ }
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ if ( fn ) {
+ proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
+ }
+
+ // So proxy can be declared as an argument
+ return proxy;
+ },
+
+ // Mutifunctional method to get and set values to a collection
+ // The value/s can be optionally by executed if its a function
+ access: function( elems, key, value, exec, fn, pass ) {
+ var length = elems.length;
+
+ // Setting many attributes
+ if ( typeof key === "object" ) {
+ for ( var k in key ) {
+ jQuery.access( elems, k, key[k], exec, fn, value );
+ }
+ return elems;
+ }
+
+ // Setting one attribute
+ if ( value !== undefined ) {
+ // Optionally, function values get executed if exec is true
+ exec = !pass && exec && jQuery.isFunction(value);
+
+ for ( var i = 0; i < length; i++ ) {
+ fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
+ }
+
+ return elems;
+ }
+
+ // Getting an attribute
+ return length ? fn( elems[0], key ) : undefined;
+ },
+
+ now: function() {
+ return (new Date()).getTime();
+ },
+
+ // Use of jQuery.browser is frowned upon.
+ // More details: http://docs.jquery.com/Utilities/jQuery.browser
+ uaMatch: function( ua ) {
+ ua = ua.toLowerCase();
+
+ var match = rwebkit.exec( ua ) ||
+ ropera.exec( ua ) ||
+ rmsie.exec( ua ) ||
+ ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
+ [];
+
+ return { browser: match[1] || "", version: match[2] || "0" };
+ },
+
+ sub: function() {
+ function jQuerySubclass( selector, context ) {
+ return new jQuerySubclass.fn.init( selector, context );
+ }
+ jQuery.extend( true, jQuerySubclass, this );
+ jQuerySubclass.superclass = this;
+ jQuerySubclass.fn = jQuerySubclass.prototype = this();
+ jQuerySubclass.fn.constructor = jQuerySubclass;
+ jQuerySubclass.subclass = this.subclass;
+ jQuerySubclass.fn.init = function init( selector, context ) {
+ if ( context && context instanceof jQuery && !(context instanceof jQuerySubclass) ) {
+ context = jQuerySubclass(context);
+ }
+
+ return jQuery.fn.init.call( this, selector, context, rootjQuerySubclass );
+ };
+ jQuerySubclass.fn.init.prototype = jQuerySubclass.fn;
+ var rootjQuerySubclass = jQuerySubclass(document);
+ return jQuerySubclass;
+ },
+
+ browser: {}
+});
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+browserMatch = jQuery.uaMatch( userAgent );
+if ( browserMatch.browser ) {
+ jQuery.browser[ browserMatch.browser ] = true;
+ jQuery.browser.version = browserMatch.version;
+}
+
+// Deprecated, use jQuery.browser.webkit instead
+if ( jQuery.browser.webkit ) {
+ jQuery.browser.safari = true;
+}
+
+if ( indexOf ) {
+ jQuery.inArray = function( elem, array ) {
+ return indexOf.call( array, elem );
+ };
+}
+
+// IE doesn't match non-breaking spaces with \s
+if ( rnotwhite.test( "\xA0" ) ) {
+ trimLeft = /^[\s\xA0]+/;
+ trimRight = /[\s\xA0]+$/;
+}
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+
+// Cleanup functions for the document ready method
+if ( document.addEventListener ) {
+ DOMContentLoaded = function() {
+ document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+ jQuery.ready();
+ };
+
+} else if ( document.attachEvent ) {
+ DOMContentLoaded = function() {
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( document.readyState === "complete" ) {
+ document.detachEvent( "onreadystatechange", DOMContentLoaded );
+ jQuery.ready();
+ }
+ };
+}
+
+// The DOM ready check for Internet Explorer
+function doScrollCheck() {
+ if ( jQuery.isReady ) {
+ return;
+ }
+
+ try {
+ // If IE is used, use the trick by Diego Perini
+ // http://javascript.nwbox.com/IEContentLoaded/
+ document.documentElement.doScroll("left");
+ } catch(e) {
+ setTimeout( doScrollCheck, 1 );
+ return;
+ }
+
+ // and execute any waiting functions
+ jQuery.ready();
+}
+
+// Expose jQuery to the global object
+return jQuery;
+
+})();
+
+
+var // Promise methods
+ promiseMethods = "then done fail isResolved isRejected promise".split( " " ),
+ // Static reference to slice
+ sliceDeferred = [].slice;
+
+jQuery.extend({
+ // Create a simple deferred (one callbacks list)
+ _Deferred: function() {
+ var // callbacks list
+ callbacks = [],
+ // stored [ context , args ]
+ fired,
+ // to avoid firing when already doing so
+ firing,
+ // flag to know if the deferred has been cancelled
+ cancelled,
+ // the deferred itself
+ deferred = {
+
+ // done( f1, f2, ...)
+ done: function() {
+ if ( !cancelled ) {
+ var args = arguments,
+ i,
+ length,
+ elem,
+ type,
+ _fired;
+ if ( fired ) {
+ _fired = fired;
+ fired = 0;
+ }
+ for ( i = 0, length = args.length; i < length; i++ ) {
+ elem = args[ i ];
+ type = jQuery.type( elem );
+ if ( type === "array" ) {
+ deferred.done.apply( deferred, elem );
+ } else if ( type === "function" ) {
+ callbacks.push( elem );
+ }
+ }
+ if ( _fired ) {
+ deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] );
+ }
+ }
+ return this;
+ },
+
+ // resolve with given context and args
+ resolveWith: function( context, args ) {
+ if ( !cancelled && !fired && !firing ) {
+ // make sure args are available (#8421)
+ args = args || [];
+ firing = 1;
+ try {
+ while( callbacks[ 0 ] ) {
+ callbacks.shift().apply( context, args );
+ }
+ }
+ finally {
+ fired = [ context, args ];
+ firing = 0;
+ }
+ }
+ return this;
+ },
+
+ // resolve with this as context and given arguments
+ resolve: function() {
+ deferred.resolveWith( this, arguments );
+ return this;
+ },
+
+ // Has this deferred been resolved?
+ isResolved: function() {
+ return !!( firing || fired );
+ },
+
+ // Cancel
+ cancel: function() {
+ cancelled = 1;
+ callbacks = [];
+ return this;
+ }
+ };
+
+ return deferred;
+ },
+
+ // Full fledged deferred (two callbacks list)
+ Deferred: function( func ) {
+ var deferred = jQuery._Deferred(),
+ failDeferred = jQuery._Deferred(),
+ promise;
+ // Add errorDeferred methods, then and promise
+ jQuery.extend( deferred, {
+ then: function( doneCallbacks, failCallbacks ) {
+ deferred.done( doneCallbacks ).fail( failCallbacks );
+ return this;
+ },
+ fail: failDeferred.done,
+ rejectWith: failDeferred.resolveWith,
+ reject: failDeferred.resolve,
+ isRejected: failDeferred.isResolved,
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ if ( obj == null ) {
+ if ( promise ) {
+ return promise;
+ }
+ promise = obj = {};
+ }
+ var i = promiseMethods.length;
+ while( i-- ) {
+ obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ];
+ }
+ return obj;
+ }
+ } );
+ // Make sure only one callback list will be used
+ deferred.done( failDeferred.cancel ).fail( deferred.cancel );
+ // Unexpose cancel
+ delete deferred.cancel;
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+ return deferred;
+ },
+
+ // Deferred helper
+ when: function( firstParam ) {
+ var args = arguments,
+ i = 0,
+ length = args.length,
+ count = length,
+ deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
+ firstParam :
+ jQuery.Deferred();
+ function resolveFunc( i ) {
+ return function( value ) {
+ args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
+ if ( !( --count ) ) {
+ // Strange bug in FF4:
+ // Values changed onto the arguments object sometimes end up as undefined values
+ // outside the $.when method. Cloning the object into a fresh array solves the issue
+ deferred.resolveWith( deferred, sliceDeferred.call( args, 0 ) );
+ }
+ };
+ }
+ if ( length > 1 ) {
+ for( ; i < length; i++ ) {
+ if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) {
+ args[ i ].promise().then( resolveFunc(i), deferred.reject );
+ } else {
+ --count;
+ }
+ }
+ if ( !count ) {
+ deferred.resolveWith( deferred, args );
+ }
+ } else if ( deferred !== firstParam ) {
+ deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
+ }
+ return deferred.promise();
+ }
+});
+
+
+
+
+(function() {
+
+ jQuery.support = {};
+
+ var div = document.createElement("div");
+
+ div.style.display = "none";
+ div.innerHTML = " <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
+
+ var all = div.getElementsByTagName("*"),
+ a = div.getElementsByTagName("a")[0],
+ select = document.createElement("select"),
+ opt = select.appendChild( document.createElement("option") ),
+ input = div.getElementsByTagName("input")[0];
+
+ // Can't get basic test support
+ if ( !all || !all.length || !a ) {
+ return;
+ }
+
+ jQuery.support = {
+ // IE strips leading whitespace when .innerHTML is used
+ leadingWhitespace: div.firstChild.nodeType === 3,
+
+ // Make sure that tbody elements aren't automatically inserted
+ // IE will insert them into empty tables
+ tbody: !div.getElementsByTagName("tbody").length,
+
+ // Make sure that link elements get serialized correctly by innerHTML
+ // This requires a wrapper element in IE
+ htmlSerialize: !!div.getElementsByTagName("link").length,
+
+ // Get the style information from getAttribute
+ // (IE uses .cssText insted)
+ style: /red/.test( a.getAttribute("style") ),
+
+ // Make sure that URLs aren't manipulated
+ // (IE normalizes it by default)
+ hrefNormalized: a.getAttribute("href") === "/a",
+
+ // Make sure that element opacity exists
+ // (IE uses filter instead)
+ // Use a regex to work around a WebKit issue. See #5145
+ opacity: /^0.55$/.test( a.style.opacity ),
+
+ // Verify style float existence
+ // (IE uses styleFloat instead of cssFloat)
+ cssFloat: !!a.style.cssFloat,
+
+ // Make sure that if no value is specified for a checkbox
+ // that it defaults to "on".
+ // (WebKit defaults to "" instead)
+ checkOn: input.value === "on",
+
+ // Make sure that a selected-by-default option has a working selected property.
+ // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+ optSelected: opt.selected,
+
+ // Will be defined later
+ deleteExpando: true,
+ optDisabled: false,
+ checkClone: false,
+ noCloneEvent: true,
+ noCloneChecked: true,
+ boxModel: null,
+ inlineBlockNeedsLayout: false,
+ shrinkWrapBlocks: false,
+ reliableHiddenOffsets: true,
+ reliableMarginRight: true
+ };
+
+ input.checked = true;
+ jQuery.support.noCloneChecked = input.cloneNode( true ).checked;
+
+ // Make sure that the options inside disabled selects aren't marked as disabled
+ // (WebKit marks them as diabled)
+ select.disabled = true;
+ jQuery.support.optDisabled = !opt.disabled;
+
+ var _scriptEval = null;
+ jQuery.support.scriptEval = function() {
+ if ( _scriptEval === null ) {
+ var root = document.documentElement,
+ script = document.createElement("script"),
+ id = "script" + jQuery.now();
+
+ // Make sure that the execution of code works by injecting a script
+ // tag with appendChild/createTextNode
+ // (IE doesn't support this, fails, and uses .text instead)
+ try {
+ script.appendChild( document.createTextNode( "window." + id + "=1;" ) );
+ } catch(e) {}
+
+ root.insertBefore( script, root.firstChild );
+
+ if ( window[ id ] ) {
+ _scriptEval = true;
+ delete window[ id ];
+ } else {
+ _scriptEval = false;
+ }
+
+ root.removeChild( script );
+ }
+
+ return _scriptEval;
+ };
+
+ // Test to see if it's possible to delete an expando from an element
+ // Fails in Internet Explorer
+ try {
+ delete div.test;
+
+ } catch(e) {
+ jQuery.support.deleteExpando = false;
+ }
+
+ if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
+ div.attachEvent("onclick", function click() {
+ // Cloning a node shouldn't copy over any
+ // bound event handlers (IE does this)
+ jQuery.support.noCloneEvent = false;
+ div.detachEvent("onclick", click);
+ });
+ div.cloneNode(true).fireEvent("onclick");
+ }
+
+ div = document.createElement("div");
+ div.innerHTML = "<input type='radio' name='radiotest' checked='checked'/>";
+
+ var fragment = document.createDocumentFragment();
+ fragment.appendChild( div.firstChild );
+
+ // WebKit doesn't clone checked state correctly in fragments
+ jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked;
+
+ // Figure out if the W3C box model works as expected
+ // document.body must exist before we can do this
+ jQuery(function() {
+ var div = document.createElement("div"),
+ body = document.getElementsByTagName("body")[0];
+
+ // Frameset documents with no body should not run this code
+ if ( !body ) {
+ return;
+ }
+
+ div.style.width = div.style.paddingLeft = "1px";
+ body.appendChild( div );
+ jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2;
+
+ if ( "zoom" in div.style ) {
+ // Check if natively block-level elements act like inline-block
+ // elements when setting their display to 'inline' and giving
+ // them layout
+ // (IE < 8 does this)
+ div.style.display = "inline";
+ div.style.zoom = 1;
+ jQuery.support.inlineBlockNeedsLayout = div.offsetWidth === 2;
+
+ // Check if elements with layout shrink-wrap their children
+ // (IE 6 does this)
+ div.style.display = "";
+ div.innerHTML = "<div style='width:4px;'></div>";
+ jQuery.support.shrinkWrapBlocks = div.offsetWidth !== 2;
+ }
+
+ div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>";
+ var tds = div.getElementsByTagName("td");
+
+ // Check if table cells still have offsetWidth/Height when they are set
+ // to display:none and there are still other visible table cells in a
+ // table row; if so, offsetWidth/Height are not reliable for use when
+ // determining if an element has been hidden directly using
+ // display:none (it is still safe to use offsets if a parent element is
+ // hidden; don safety goggles and see bug #4512 for more information).
+ // (only IE 8 fails this test)
+ jQuery.support.reliableHiddenOffsets = tds[0].offsetHeight === 0;
+
+ tds[0].style.display = "";
+ tds[1].style.display = "none";
+
+ // Check if empty table cells still have offsetWidth/Height
+ // (IE < 8 fail this test)
+ jQuery.support.reliableHiddenOffsets = jQuery.support.reliableHiddenOffsets && tds[0].offsetHeight === 0;
+ div.innerHTML = "";
+
+ // Check if div with explicit width and no margin-right incorrectly
+ // gets computed margin-right based on width of container. For more
+ // info see bug #3333
+ // Fails in WebKit before Feb 2011 nightlies
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ if ( document.defaultView && document.defaultView.getComputedStyle ) {
+ div.style.width = "1px";
+ div.style.marginRight = "0";
+ jQuery.support.reliableMarginRight = ( parseInt(document.defaultView.getComputedStyle(div, null).marginRight, 10) || 0 ) === 0;
+ }
+
+ body.removeChild( div ).style.display = "none";
+ div = tds = null;
+ });
+
+ // Technique from Juriy Zaytsev
+ // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
+ var eventSupported = function( eventName ) {
+ var el = document.createElement("div");
+ eventName = "on" + eventName;
+
+ // We only care about the case where non-standard event systems
+ // are used, namely in IE. Short-circuiting here helps us to
+ // avoid an eval call (in setAttribute) which can cause CSP
+ // to go haywire. See: https://developer.mozilla.org/en/Security/CSP
+ if ( !el.attachEvent ) {
+ return true;
+ }
+
+ var isSupported = (eventName in el);
+ if ( !isSupported ) {
+ el.setAttribute(eventName, "return;");
+ isSupported = typeof el[eventName] === "function";
+ }
+ return isSupported;
+ };
+
+ jQuery.support.submitBubbles = eventSupported("submit");
+ jQuery.support.changeBubbles = eventSupported("change");
+
+ // release memory in IE
+ div = all = a = null;
+})();
+
+
+
+var rbrace = /^(?:\{.*\}|\[.*\])$/;
+
+jQuery.extend({
+ cache: {},
+
+ // Please use with caution
+ uuid: 0,
+
+ // Unique for each copy of jQuery on the page
+ // Non-digits removed to match rinlinejQuery
+ expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
+
+ // The following elements throw uncatchable exceptions if you
+ // attempt to add expando properties to them.
+ noData: {
+ "embed": true,
+ // Ban all objects except for Flash (which handle expandos)
+ "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
+ "applet": true
+ },
+
+ hasData: function( elem ) {
+ elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
+
+ return !!elem && !isEmptyDataObject( elem );
+ },
+
+ data: function( elem, name, data, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var internalKey = jQuery.expando, getByName = typeof name === "string", thisCache,
+
+ // We have to handle DOM nodes and JS objects differently because IE6-7
+ // can't GC object references properly across the DOM-JS boundary
+ isNode = elem.nodeType,
+
+ // Only DOM nodes need the global jQuery cache; JS object data is
+ // attached directly to the object so GC can occur automatically
+ cache = isNode ? jQuery.cache : elem,
+
+ // Only defining an ID for JS objects if its cache already exists allows
+ // the code to shortcut on the same path as a DOM node with no cache
+ id = isNode ? elem[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando;
+
+ // Avoid doing any more work than we need to when trying to get data on an
+ // object that has no data at all
+ if ( (!id || (pvt && id && !cache[ id ][ internalKey ])) && getByName && data === undefined ) {
+ return;
+ }
+
+ if ( !id ) {
+ // Only DOM nodes need a new unique ID for each element since their data
+ // ends up in the global cache
+ if ( isNode ) {
+ elem[ jQuery.expando ] = id = ++jQuery.uuid;
+ } else {
+ id = jQuery.expando;
+ }
+ }
+
+ if ( !cache[ id ] ) {
+ cache[ id ] = {};
+
+ // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery
+ // metadata on plain JS objects when the object is serialized using
+ // JSON.stringify
+ if ( !isNode ) {
+ cache[ id ].toJSON = jQuery.noop;
+ }
+ }
+
+ // An object can be passed to jQuery.data instead of a key/value pair; this gets
+ // shallow copied over onto the existing cache
+ if ( typeof name === "object" || typeof name === "function" ) {
+ if ( pvt ) {
+ cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name);
+ } else {
+ cache[ id ] = jQuery.extend(cache[ id ], name);
+ }
+ }
+
+ thisCache = cache[ id ];
+
+ // Internal jQuery data is stored in a separate object inside the object's data
+ // cache in order to avoid key collisions between internal data and user-defined
+ // data
+ if ( pvt ) {
+ if ( !thisCache[ internalKey ] ) {
+ thisCache[ internalKey ] = {};
+ }
+
+ thisCache = thisCache[ internalKey ];
+ }
+
+ if ( data !== undefined ) {
+ thisCache[ name ] = data;
+ }
+
+ // TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should
+ // not attempt to inspect the internal events object using jQuery.data, as this
+ // internal data object is undocumented and subject to change.
+ if ( name === "events" && !thisCache[name] ) {
+ return thisCache[ internalKey ] && thisCache[ internalKey ].events;
+ }
+
+ return getByName ? thisCache[ name ] : thisCache;
+ },
+
+ removeData: function( elem, name, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var internalKey = jQuery.expando, isNode = elem.nodeType,
+
+ // See jQuery.data for more information
+ cache = isNode ? jQuery.cache : elem,
+
+ // See jQuery.data for more information
+ id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
+
+ // If there is already no cache entry for this object, there is no
+ // purpose in continuing
+ if ( !cache[ id ] ) {
+ return;
+ }
+
+ if ( name ) {
+ var thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ];
+
+ if ( thisCache ) {
+ delete thisCache[ name ];
+
+ // If there is no data left in the cache, we want to continue
+ // and let the cache object itself get destroyed
+ if ( !isEmptyDataObject(thisCache) ) {
+ return;
+ }
+ }
+ }
+
+ // See jQuery.data for more information
+ if ( pvt ) {
+ delete cache[ id ][ internalKey ];
+
+ // Don't destroy the parent cache unless the internal data object
+ // had been the only thing left in it
+ if ( !isEmptyDataObject(cache[ id ]) ) {
+ return;
+ }
+ }
+
+ var internalCache = cache[ id ][ internalKey ];
+
+ // Browsers that fail expando deletion also refuse to delete expandos on
+ // the window, but it will allow it on all other JS objects; other browsers
+ // don't care
+ if ( jQuery.support.deleteExpando || cache != window ) {
+ delete cache[ id ];
+ } else {
+ cache[ id ] = null;
+ }
+
+ // We destroyed the entire user cache at once because it's faster than
+ // iterating through each key, but we need to continue to persist internal
+ // data if it existed
+ if ( internalCache ) {
+ cache[ id ] = {};
+ // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery
+ // metadata on plain JS objects when the object is serialized using
+ // JSON.stringify
+ if ( !isNode ) {
+ cache[ id ].toJSON = jQuery.noop;
+ }
+
+ cache[ id ][ internalKey ] = internalCache;
+
+ // Otherwise, we need to eliminate the expando on the node to avoid
+ // false lookups in the cache for entries that no longer exist
+ } else if ( isNode ) {
+ // IE does not allow us to delete expando properties from nodes,
+ // nor does it have a removeAttribute function on Document nodes;
+ // we must handle all of these cases
+ if ( jQuery.support.deleteExpando ) {
+ delete elem[ jQuery.expando ];
+ } else if ( elem.removeAttribute ) {
+ elem.removeAttribute( jQuery.expando );
+ } else {
+ elem[ jQuery.expando ] = null;
+ }
+ }
+ },
+
+ // For internal use only.
+ _data: function( elem, name, data ) {
+ return jQuery.data( elem, name, data, true );
+ },
+
+ // A method for determining if a DOM node can handle the data expando
+ acceptData: function( elem ) {
+ if ( elem.nodeName ) {
+ var match = jQuery.noData[ elem.nodeName.toLowerCase() ];
+
+ if ( match ) {
+ return !(match === true || elem.getAttribute("classid") !== match);
+ }
+ }
+
+ return true;
+ }
+});
+
+jQuery.fn.extend({
+ data: function( key, value ) {
+ var data = null;
+
+ if ( typeof key === "undefined" ) {
+ if ( this.length ) {
+ data = jQuery.data( this[0] );
+
+ if ( this[0].nodeType === 1 ) {
+ var attr = this[0].attributes, name;
+ for ( var i = 0, l = attr.length; i < l; i++ ) {
+ name = attr[i].name;
+
+ if ( name.indexOf( "data-" ) === 0 ) {
+ name = name.substr( 5 );
+ dataAttr( this[0], name, data[ name ] );
+ }
+ }
+ }
+ }
+
+ return data;
+
+ } else if ( typeof key === "object" ) {
+ return this.each(function() {
+ jQuery.data( this, key );
+ });
+ }
+
+ var parts = key.split(".");
+ parts[1] = parts[1] ? "." + parts[1] : "";
+
+ if ( value === undefined ) {
+ data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
+
+ // Try to fetch any internally stored data first
+ if ( data === undefined && this.length ) {
+ data = jQuery.data( this[0], key );
+ data = dataAttr( this[0], key, data );
+ }
+
+ return data === undefined && parts[1] ?
+ this.data( parts[0] ) :
+ data;
+
+ } else {
+ return this.each(function() {
+ var $this = jQuery( this ),
+ args = [ parts[0], value ];
+
+ $this.triggerHandler( "setData" + parts[1] + "!", args );
+ jQuery.data( this, key, value );
+ $this.triggerHandler( "changeData" + parts[1] + "!", args );
+ });
+ }
+ },
+
+ removeData: function( key ) {
+ return this.each(function() {
+ jQuery.removeData( this, key );
+ });
+ }
+});
+
+function dataAttr( elem, key, data ) {
+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
+ data = elem.getAttribute( "data-" + key );
+
+ if ( typeof data === "string" ) {
+ try {
+ data = data === "true" ? true :
+ data === "false" ? false :
+ data === "null" ? null :
+ !jQuery.isNaN( data ) ? parseFloat( data ) :
+ rbrace.test( data ) ? jQuery.parseJSON( data ) :
+ data;
+ } catch( e ) {}
+
+ // Make sure we set the data so it isn't changed later
+ jQuery.data( elem, key, data );
+
+ } else {
+ data = undefined;
+ }
+ }
+
+ return data;
+}
+
+// TODO: This is a hack for 1.5 ONLY to allow objects with a single toJSON
+// property to be considered empty objects; this property always exists in
+// order to make sure JSON.stringify does not expose internal metadata
+function isEmptyDataObject( obj ) {
+ for ( var name in obj ) {
+ if ( name !== "toJSON" ) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+
+
+jQuery.extend({
+ queue: function( elem, type, data ) {
+ if ( !elem ) {
+ return;
+ }
+
+ type = (type || "fx") + "queue";
+ var q = jQuery._data( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( !data ) {
+ return q || [];
+ }
+
+ if ( !q || jQuery.isArray(data) ) {
+ q = jQuery._data( elem, type, jQuery.makeArray(data) );
+
+ } else {
+ q.push( data );
+ }
+
+ return q;
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ),
+ fn = queue.shift();
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ }
+
+ if ( fn ) {
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift("inprogress");
+ }
+
+ fn.call(elem, function() {
+ jQuery.dequeue(elem, type);
+ });
+ }
+
+ if ( !queue.length ) {
+ jQuery.removeData( elem, type + "queue", true );
+ }
+ }
+});
+
+jQuery.fn.extend({
+ queue: function( type, data ) {
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ }
+
+ if ( data === undefined ) {
+ return jQuery.queue( this[0], type );
+ }
+ return this.each(function( i ) {
+ var queue = jQuery.queue( this, type, data );
+
+ if ( type === "fx" && queue[0] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ dequeue: function( type ) {
+ return this.each(function() {
+ jQuery.dequeue( this, type );
+ });
+ },
+
+ // Based off of the plugin by Clint Helfers, with permission.
+ // http://blindsignals.com/index.php/2009/07/jquery-delay/
+ delay: function( time, type ) {
+ time = jQuery.fx ? jQuery.fx.speeds[time] || time : time;
+ type = type || "fx";
+
+ return this.queue( type, function() {
+ var elem = this;
+ setTimeout(function() {
+ jQuery.dequeue( elem, type );
+ }, time );
+ });
+ },
+
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ }
+});
+
+
+
+
+var rclass = /[\n\t\r]/g,
+ rspaces = /\s+/,
+ rreturn = /\r/g,
+ rspecialurl = /^(?:href|src|style)$/,
+ rtype = /^(?:button|input)$/i,
+ rfocusable = /^(?:button|input|object|select|textarea)$/i,
+ rclickable = /^a(?:rea)?$/i,
+ rradiocheck = /^(?:radio|checkbox)$/i;
+
+jQuery.props = {
+ "for": "htmlFor",
+ "class": "className",
+ readonly: "readOnly",
+ maxlength: "maxLength",
+ cellspacing: "cellSpacing",
+ rowspan: "rowSpan",
+ colspan: "colSpan",
+ tabindex: "tabIndex",
+ usemap: "useMap",
+ frameborder: "frameBorder"
+};
+
+jQuery.fn.extend({
+ attr: function( name, value ) {
+ return jQuery.access( this, name, value, true, jQuery.attr );
+ },
+
+ removeAttr: function( name, fn ) {
+ return this.each(function(){
+ jQuery.attr( this, name, "" );
+ if ( this.nodeType === 1 ) {
+ this.removeAttribute( name );
+ }
+ });
+ },
+
+ addClass: function( value ) {
+ if ( jQuery.isFunction(value) ) {
+ return this.each(function(i) {
+ var self = jQuery(this);
+ self.addClass( value.call(this, i, self.attr("class")) );
+ });
+ }
+
+ if ( value && typeof value === "string" ) {
+ var classNames = (value || "").split( rspaces );
+
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ var elem = this[i];
+
+ if ( elem.nodeType === 1 ) {
+ if ( !elem.className ) {
+ elem.className = value;
+
+ } else {
+ var className = " " + elem.className + " ",
+ setClass = elem.className;
+
+ for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
+ if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) {
+ setClass += " " + classNames[c];
+ }
+ }
+ elem.className = jQuery.trim( setClass );
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ removeClass: function( value ) {
+ if ( jQuery.isFunction(value) ) {
+ return this.each(function(i) {
+ var self = jQuery(this);
+ self.removeClass( value.call(this, i, self.attr("class")) );
+ });
+ }
+
+ if ( (value && typeof value === "string") || value === undefined ) {
+ var classNames = (value || "").split( rspaces );
+
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ var elem = this[i];
+
+ if ( elem.nodeType === 1 && elem.className ) {
+ if ( value ) {
+ var className = (" " + elem.className + " ").replace(rclass, " ");
+ for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
+ className = className.replace(" " + classNames[c] + " ", " ");
+ }
+ elem.className = jQuery.trim( className );
+
+ } else {
+ elem.className = "";
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ toggleClass: function( value, stateVal ) {
+ var type = typeof value,
+ isBool = typeof stateVal === "boolean";
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function(i) {
+ var self = jQuery(this);
+ self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal );
+ });
+ }
+
+ return this.each(function() {
+ if ( type === "string" ) {
+ // toggle individual class names
+ var className,
+ i = 0,
+ self = jQuery( this ),
+ state = stateVal,
+ classNames = value.split( rspaces );
+
+ while ( (className = classNames[ i++ ]) ) {
+ // check each className given, space seperated list
+ state = isBool ? state : !self.hasClass( className );
+ self[ state ? "addClass" : "removeClass" ]( className );
+ }
+
+ } else if ( type === "undefined" || type === "boolean" ) {
+ if ( this.className ) {
+ // store className if set
+ jQuery._data( this, "__className__", this.className );
+ }
+
+ // toggle whole className
+ this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
+ }
+ });
+ },
+
+ hasClass: function( selector ) {
+ var className = " " + selector + " ";
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ val: function( value ) {
+ if ( !arguments.length ) {
+ var elem = this[0];
+
+ if ( elem ) {
+ if ( jQuery.nodeName( elem, "option" ) ) {
+ // attributes.value is undefined in Blackberry 4.7 but
+ // uses .value. See #6932
+ var val = elem.attributes.value;
+ return !val || val.specified ? elem.value : elem.text;
+ }
+
+ // We need to handle select boxes special
+ if ( jQuery.nodeName( elem, "select" ) ) {
+ var index = elem.selectedIndex,
+ values = [],
+ options = elem.options,
+ one = elem.type === "select-one";
+
+ // Nothing was selected
+ if ( index < 0 ) {
+ return null;
+ }
+
+ // Loop through all the selected options
+ for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
+ var option = options[ i ];
+
+ // Don't return options that are disabled or in a disabled optgroup
+ if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
+ (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {
+
+ // Get the specific value for the option
+ value = jQuery(option).val();
+
+ // We don't need an array for one selects
+ if ( one ) {
+ return value;
+ }
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ // Fixes Bug #2551 -- select.val() broken in IE after form.reset()
+ if ( one && !values.length && options.length ) {
+ return jQuery( options[ index ] ).val();
+ }
+
+ return values;
+ }
+
+ // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
+ if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) {
+ return elem.getAttribute("value") === null ? "on" : elem.value;
+ }
+
+ // Everything else, we just grab the value
+ return (elem.value || "").replace(rreturn, "");
+
+ }
+
+ return undefined;
+ }
+
+ var isFunction = jQuery.isFunction(value);
+
+ return this.each(function(i) {
+ var self = jQuery(this), val = value;
+
+ if ( this.nodeType !== 1 ) {
+ return;
+ }
+
+ if ( isFunction ) {
+ val = value.call(this, i, self.val());
+ }
+
+ // Treat null/undefined as ""; convert numbers to string
+ if ( val == null ) {
+ val = "";
+ } else if ( typeof val === "number" ) {
+ val += "";
+ } else if ( jQuery.isArray(val) ) {
+ val = jQuery.map(val, function (value) {
+ return value == null ? "" : value + "";
+ });
+ }
+
+ if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) {
+ this.checked = jQuery.inArray( self.val(), val ) >= 0;
+
+ } else if ( jQuery.nodeName( this, "select" ) ) {
+ var values = jQuery.makeArray(val);
+
+ jQuery( "option", this ).each(function() {
+ this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
+ });
+
+ if ( !values.length ) {
+ this.selectedIndex = -1;
+ }
+
+ } else {
+ this.value = val;
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ attrFn: {
+ val: true,
+ css: true,
+ html: true,
+ text: true,
+ data: true,
+ width: true,
+ height: true,
+ offset: true
+ },
+
+ attr: function( elem, name, value, pass ) {
+ // don't get/set attributes on text, comment and attribute nodes
+ if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || elem.nodeType === 2 ) {
+ return undefined;
+ }
+
+ if ( pass && name in jQuery.attrFn ) {
+ return jQuery(elem)[name](value);
+ }
+
+ var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ),
+ // Whether we are setting (or getting)
+ set = value !== undefined;
+
+ // Try to normalize/fix the name
+ name = notxml && jQuery.props[ name ] || name;
+
+ // Only do all the following if this is a node (faster for style)
+ if ( elem.nodeType === 1 ) {
+ // These attributes require special treatment
+ var special = rspecialurl.test( name );
+
+ // Safari mis-reports the default selected property of an option
+ // Accessing the parent's selectedIndex property fixes it
+ if ( name === "selected" && !jQuery.support.optSelected ) {
+ var parent = elem.parentNode;
+ if ( parent ) {
+ parent.selectedIndex;
+
+ // Make sure that it also works with optgroups, see #5701
+ if ( parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ }
+ }
+
+ // If applicable, access the attribute via the DOM 0 way
+ // 'in' checks fail in Blackberry 4.7 #6931
+ if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) {
+ if ( set ) {
+ // We can't allow the type property to be changed (since it causes problems in IE)
+ if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) {
+ jQuery.error( "type property can't be changed" );
+ }
+
+ if ( value === null ) {
+ if ( elem.nodeType === 1 ) {
+ elem.removeAttribute( name );
+ }
+
+ } else {
+ elem[ name ] = value;
+ }
+ }
+
+ // browsers index elements by id/name on forms, give priority to attributes.
+ if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) {
+ return elem.getAttributeNode( name ).nodeValue;
+ }
+
+ // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+ // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+ if ( name === "tabIndex" ) {
+ var attributeNode = elem.getAttributeNode( "tabIndex" );
+
+ return attributeNode && attributeNode.specified ?
+ attributeNode.value :
+ rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+ 0 :
+ undefined;
+ }
+
+ return elem[ name ];
+ }
+
+ if ( !jQuery.support.style && notxml && name === "style" ) {
+ if ( set ) {
+ elem.style.cssText = "" + value;
+ }
+
+ return elem.style.cssText;
+ }
+
+ if ( set ) {
+ // convert the value to a string (all browsers do this but IE) see #1070
+ elem.setAttribute( name, "" + value );
+ }
+
+ // Ensure that missing attributes return undefined
+ // Blackberry 4.7 returns "" from getAttribute #6938
+ if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) {
+ return undefined;
+ }
+
+ var attr = !jQuery.support.hrefNormalized && notxml && special ?
+ // Some attributes require a special call on IE
+ elem.getAttribute( name, 2 ) :
+ elem.getAttribute( name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return attr === null ? undefined : attr;
+ }
+ // Handle everything which isn't a DOM element node
+ if ( set ) {
+ elem[ name ] = value;
+ }
+ return elem[ name ];
+ }
+});
+
+
+
+
+var rnamespaces = /\.(.*)$/,
+ rformElems = /^(?:textarea|input|select)$/i,
+ rperiod = /\./g,
+ rspace = / /g,
+ rescape = /[^\w\s.|`]/g,
+ fcleanup = function( nm ) {
+ return nm.replace(rescape, "\\$&");
+ };
+
+/*
+ * A number of helper functions used for managing events.
+ * Many of the ideas behind this code originated from
+ * Dean Edwards' addEvent library.
+ */
+jQuery.event = {
+
+ // Bind an event to an element
+ // Original by Dean Edwards
+ add: function( elem, types, handler, data ) {
+ if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return;
+ }
+
+ // TODO :: Use a try/catch until it's safe to pull this out (likely 1.6)
+ // Minor release fix for bug #8018
+ try {
+ // For whatever reason, IE has trouble passing the window object
+ // around, causing it to be cloned in the process
+ if ( jQuery.isWindow( elem ) && ( elem !== window && !elem.frameElement ) ) {
+ elem = window;
+ }
+ }
+ catch ( e ) {}
+
+ if ( handler === false ) {
+ handler = returnFalse;
+ } else if ( !handler ) {
+ // Fixes bug #7229. Fix recommended by jdalton
+ return;
+ }
+
+ var handleObjIn, handleObj;
+
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ }
+
+ // Make sure that the function being executed has a unique ID
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure
+ var elemData = jQuery._data( elem );
+
+ // If no elemData is found then we must be trying to bind to one of the
+ // banned noData elements
+ if ( !elemData ) {
+ return;
+ }
+
+ var events = elemData.events,
+ eventHandle = elemData.handle;
+
+ if ( !events ) {
+ elemData.events = events = {};
+ }
+
+ if ( !eventHandle ) {
+ elemData.handle = eventHandle = function( e ) {
+ // Handle the second event of a trigger and when
+ // an event is called after a page has unloaded
+ return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
+ jQuery.event.handle.apply( eventHandle.elem, arguments ) :
+ undefined;
+ };
+ }
+
+ // Add elem as a property of the handle function
+ // This is to prevent a memory leak with non-native events in IE.
+ eventHandle.elem = elem;
+
+ // Handle multiple events separated by a space
+ // jQuery(...).bind("mouseover mouseout", fn);
+ types = types.split(" ");
+
+ var type, i = 0, namespaces;
+
+ while ( (type = types[ i++ ]) ) {
+ handleObj = handleObjIn ?
+ jQuery.extend({}, handleObjIn) :
+ { handler: handler, data: data };
+
+ // Namespaced event handlers
+ if ( type.indexOf(".") > -1 ) {
+ namespaces = type.split(".");
+ type = namespaces.shift();
+ handleObj.namespace = namespaces.slice(0).sort().join(".");
+
+ } else {
+ namespaces = [];
+ handleObj.namespace = "";
+ }
+
+ handleObj.type = type;
+ if ( !handleObj.guid ) {
+ handleObj.guid = handler.guid;
+ }
+
+ // Get the current list of functions bound to this event
+ var handlers = events[ type ],
+ special = jQuery.event.special[ type ] || {};
+
+ // Init the event handler queue
+ if ( !handlers ) {
+ handlers = events[ type ] = [];
+
+ // Check for a special event handler
+ // Only use addEventListener/attachEvent if the special
+ // events handler returns false
+ if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ // Bind the global event handler to the element
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle, false );
+
+ } else if ( elem.attachEvent ) {
+ elem.attachEvent( "on" + type, eventHandle );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add the function to the element's handler list
+ handlers.push( handleObj );
+
+ // Keep track of which events have been used, for global triggering
+ jQuery.event.global[ type ] = true;
+ }
+
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
+ },
+
+ global: {},
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, pos ) {
+ // don't do events on text and comment nodes
+ if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return;
+ }
+
+ if ( handler === false ) {
+ handler = returnFalse;
+ }
+
+ var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType,
+ elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
+ events = elemData && elemData.events;
+
+ if ( !elemData || !events ) {
+ return;
+ }
+
+ // types is actually an event object here
+ if ( types && types.type ) {
+ handler = types.handler;
+ types = types.type;
+ }
+
+ // Unbind all events for the element
+ if ( !types || typeof types === "string" && types.charAt(0) === "." ) {
+ types = types || "";
+
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types );
+ }
+
+ return;
+ }
+
+ // Handle multiple events separated by a space
+ // jQuery(...).unbind("mouseover mouseout", fn);
+ types = types.split(" ");
+
+ while ( (type = types[ i++ ]) ) {
+ origType = type;
+ handleObj = null;
+ all = type.indexOf(".") < 0;
+ namespaces = [];
+
+ if ( !all ) {
+ // Namespaced event handlers
+ namespaces = type.split(".");
+ type = namespaces.shift();
+
+ namespace = new RegExp("(^|\\.)" +
+ jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)");
+ }
+
+ eventType = events[ type ];
+
+ if ( !eventType ) {
+ continue;
+ }
+
+ if ( !handler ) {
+ for ( j = 0; j < eventType.length; j++ ) {
+ handleObj = eventType[ j ];
+
+ if ( all || namespace.test( handleObj.namespace ) ) {
+ jQuery.event.remove( elem, origType, handleObj.handler, j );
+ eventType.splice( j--, 1 );
+ }
+ }
+
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+
+ for ( j = pos || 0; j < eventType.length; j++ ) {
+ handleObj = eventType[ j ];
+
+ if ( handler.guid === handleObj.guid ) {
+ // remove the given handler for the given type
+ if ( all || namespace.test( handleObj.namespace ) ) {
+ if ( pos == null ) {
+ eventType.splice( j--, 1 );
+ }
+
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+
+ if ( pos != null ) {
+ break;
+ }
+ }
+ }
+
+ // remove generic event handler if no more handlers exist
+ if ( eventType.length === 0 || pos != null && eventType.length === 1 ) {
+ if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
+ jQuery.removeEvent( elem, type, elemData.handle );
+ }
+
+ ret = null;
+ delete events[ type ];
+ }
+ }
+
+ // Remove the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ var handle = elemData.handle;
+ if ( handle ) {
+ handle.elem = null;
+ }
+
+ delete elemData.events;
+ delete elemData.handle;
+
+ if ( jQuery.isEmptyObject( elemData ) ) {
+ jQuery.removeData( elem, undefined, true );
+ }
+ }
+ },
+
+ // bubbling is internal
+ trigger: function( event, data, elem /*, bubbling */ ) {
+ // Event object or event type
+ var type = event.type || event,
+ bubbling = arguments[3];
+
+ if ( !bubbling ) {
+ event = typeof event === "object" ?
+ // jQuery.Event object
+ event[ jQuery.expando ] ? event :
+ // Object literal
+ jQuery.extend( jQuery.Event(type), event ) :
+ // Just the event type (string)
+ jQuery.Event(type);
+
+ if ( type.indexOf("!") >= 0 ) {
+ event.type = type = type.slice(0, -1);
+ event.exclusive = true;
+ }
+
+ // Handle a global trigger
+ if ( !elem ) {
+ // Don't bubble custom events when global (to avoid too much overhead)
+ event.stopPropagation();
+
+ // Only trigger if we've ever bound an event for it
+ if ( jQuery.event.global[ type ] ) {
+ // XXX This code smells terrible. event.js should not be directly
+ // inspecting the data cache
+ jQuery.each( jQuery.cache, function() {
+ // internalKey variable is just used to make it easier to find
+ // and potentially change this stuff later; currently it just
+ // points to jQuery.expando
+ var internalKey = jQuery.expando,
+ internalCache = this[ internalKey ];
+ if ( internalCache && internalCache.events && internalCache.events[ type ] ) {
+ jQuery.event.trigger( event, data, internalCache.handle.elem );
+ }
+ });
+ }
+ }
+
+ // Handle triggering a single element
+
+ // don't do events on text and comment nodes
+ if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return undefined;
+ }
+
+ // Clean up in case it is reused
+ event.result = undefined;
+ event.target = elem;
+
+ // Clone the incoming data, if any
+ data = jQuery.makeArray( data );
+ data.unshift( event );
+ }
+
+ event.currentTarget = elem;
+
+ // Trigger the event, it is assumed that "handle" is a function
+ var handle = jQuery._data( elem, "handle" );
+
+ if ( handle ) {
+ handle.apply( elem, data );
+ }
+
+ var parent = elem.parentNode || elem.ownerDocument;
+
+ // Trigger an inline bound script
+ try {
+ if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) {
+ if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) {
+ event.result = false;
+ event.preventDefault();
+ }
+ }
+
+ // prevent IE from throwing an error for some elements with some event types, see #3533
+ } catch (inlineError) {}
+
+ if ( !event.isPropagationStopped() && parent ) {
+ jQuery.event.trigger( event, data, parent, true );
+
+ } else if ( !event.isDefaultPrevented() ) {
+ var old,
+ target = event.target,
+ targetType = type.replace( rnamespaces, "" ),
+ isClick = jQuery.nodeName( target, "a" ) && targetType === "click",
+ special = jQuery.event.special[ targetType ] || {};
+
+ if ( (!special._default || special._default.call( elem, event ) === false) &&
+ !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) {
+
+ try {
+ if ( target[ targetType ] ) {
+ // Make sure that we don't accidentally re-trigger the onFOO events
+ old = target[ "on" + targetType ];
+
+ if ( old ) {
+ target[ "on" + targetType ] = null;
+ }
+
+ jQuery.event.triggered = event.type;
+ target[ targetType ]();
+ }
+
+ // prevent IE from throwing an error for some elements with some event types, see #3533
+ } catch (triggerError) {}
+
+ if ( old ) {
+ target[ "on" + targetType ] = old;
+ }
+
+ jQuery.event.triggered = undefined;
+ }
+ }
+ },
+
+ handle: function( event ) {
+ var all, handlers, namespaces, namespace_re, events,
+ namespace_sort = [],
+ args = jQuery.makeArray( arguments );
+
+ event = args[0] = jQuery.event.fix( event || window.event );
+ event.currentTarget = this;
+
+ // Namespaced event handlers
+ all = event.type.indexOf(".") < 0 && !event.exclusive;
+
+ if ( !all ) {
+ namespaces = event.type.split(".");
+ event.type = namespaces.shift();
+ namespace_sort = namespaces.slice(0).sort();
+ namespace_re = new RegExp("(^|\\.)" + namespace_sort.join("\\.(?:.*\\.)?") + "(\\.|$)");
+ }
+
+ event.namespace = event.namespace || namespace_sort.join(".");
+
+ events = jQuery._data(this, "events");
+
+ handlers = (events || {})[ event.type ];
+
+ if ( events && handlers ) {
+ // Clone the handlers to prevent manipulation
+ handlers = handlers.slice(0);
+
+ for ( var j = 0, l = handlers.length; j < l; j++ ) {
+ var handleObj = handlers[ j ];
+
+ // Filter the functions by class
+ if ( all || namespace_re.test( handleObj.namespace ) ) {
+ // Pass in a reference to the handler function itself
+ // So that we can later remove it
+ event.handler = handleObj.handler;
+ event.data = handleObj.data;
+ event.handleObj = handleObj;
+
+ var ret = handleObj.handler.apply( this, args );
+
+ if ( ret !== undefined ) {
+ event.result = ret;
+ if ( ret === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+
+ if ( event.isImmediatePropagationStopped() ) {
+ break;
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
+
+ fix: function( event ) {
+ if ( event[ jQuery.expando ] ) {
+ return event;
+ }
+
+ // store a copy of the original event object
+ // and "clone" to set read-only properties
+ var originalEvent = event;
+ event = jQuery.Event( originalEvent );
+
+ for ( var i = this.props.length, prop; i; ) {
+ prop = this.props[ --i ];
+ event[ prop ] = originalEvent[ prop ];
+ }
+
+ // Fix target property, if necessary
+ if ( !event.target ) {
+ // Fixes #1925 where srcElement might not be defined either
+ event.target = event.srcElement || document;
+ }
+
+ // check if target is a textnode (safari)
+ if ( event.target.nodeType === 3 ) {
+ event.target = event.target.parentNode;
+ }
+
+ // Add relatedTarget, if necessary
+ if ( !event.relatedTarget && event.fromElement ) {
+ event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
+ }
+
+ // Calculate pageX/Y if missing and clientX/Y available
+ if ( event.pageX == null && event.clientX != null ) {
+ var doc = document.documentElement,
+ body = document.body;
+
+ event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
+ event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
+ }
+
+ // Add which for key events
+ if ( event.which == null && (event.charCode != null || event.keyCode != null) ) {
+ event.which = event.charCode != null ? event.charCode : event.keyCode;
+ }
+
+ // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
+ if ( !event.metaKey && event.ctrlKey ) {
+ event.metaKey = event.ctrlKey;
+ }
+
+ // Add which for click: 1 === left; 2 === middle; 3 === right
+ // Note: button is not normalized, so don't use it
+ if ( !event.which && event.button !== undefined ) {
+ event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
+ }
+
+ return event;
+ },
+
+ // Deprecated, use jQuery.guid instead
+ guid: 1E8,
+
+ // Deprecated, use jQuery.proxy instead
+ proxy: jQuery.proxy,
+
+ special: {
+ ready: {
+ // Make sure the ready event is setup
+ setup: jQuery.bindReady,
+ teardown: jQuery.noop
+ },
+
+ live: {
+ add: function( handleObj ) {
+ jQuery.event.add( this,
+ liveConvert( handleObj.origType, handleObj.selector ),
+ jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) );
+ },
+
+ remove: function( handleObj ) {
+ jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj );
+ }
+ },
+
+ beforeunload: {
+ setup: function( data, namespaces, eventHandle ) {
+ // We only want to do this special case on windows
+ if ( jQuery.isWindow( this ) ) {
+ this.onbeforeunload = eventHandle;
+ }
+ },
+
+ teardown: function( namespaces, eventHandle ) {
+ if ( this.onbeforeunload === eventHandle ) {
+ this.onbeforeunload = null;
+ }
+ }
+ }
+ }
+};
+
+jQuery.removeEvent = document.removeEventListener ?
+ function( elem, type, handle ) {
+ if ( elem.removeEventListener ) {
+ elem.removeEventListener( type, handle, false );
+ }
+ } :
+ function( elem, type, handle ) {
+ if ( elem.detachEvent ) {
+ elem.detachEvent( "on" + type, handle );
+ }
+ };
+
+jQuery.Event = function( src ) {
+ // Allow instantiation without the 'new' keyword
+ if ( !this.preventDefault ) {
+ return new jQuery.Event( src );
+ }
+
+ // Event object
+ if ( src && src.type ) {
+ this.originalEvent = src;
+ this.type = src.type;
+
+ // Events bubbling up the document may have been marked as prevented
+ // by a handler lower down the tree; reflect the correct value.
+ this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false ||
+ src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse;
+
+ // Event type
+ } else {
+ this.type = src;
+ }
+
+ // timeStamp is buggy for some events on Firefox(#3843)
+ // So we won't rely on the native value
+ this.timeStamp = jQuery.now();
+
+ // Mark it as fixed
+ this[ jQuery.expando ] = true;
+};
+
+function returnFalse() {
+ return false;
+}
+function returnTrue() {
+ return true;
+}
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+ preventDefault: function() {
+ this.isDefaultPrevented = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+
+ // if preventDefault exists run it on the original event
+ if ( e.preventDefault ) {
+ e.preventDefault();
+
+ // otherwise set the returnValue property of the original event to false (IE)
+ } else {
+ e.returnValue = false;
+ }
+ },
+ stopPropagation: function() {
+ this.isPropagationStopped = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+ // if stopPropagation exists run it on the original event
+ if ( e.stopPropagation ) {
+ e.stopPropagation();
+ }
+ // otherwise set the cancelBubble property of the original event to true (IE)
+ e.cancelBubble = true;
+ },
+ stopImmediatePropagation: function() {
+ this.isImmediatePropagationStopped = returnTrue;
+ this.stopPropagation();
+ },
+ isDefaultPrevented: returnFalse,
+ isPropagationStopped: returnFalse,
+ isImmediatePropagationStopped: returnFalse
+};
+
+// Checks if an event happened on an element within another element
+// Used in jQuery.event.special.mouseenter and mouseleave handlers
+var withinElement = function( event ) {
+ // Check if mouse(over|out) are still within the same parent element
+ var parent = event.relatedTarget;
+
+ // Firefox sometimes assigns relatedTarget a XUL element
+ // which we cannot access the parentNode property of
+ try {
+
+ // Chrome does something similar, the parentNode property
+ // can be accessed but is null.
+ if ( parent && parent !== document && !parent.parentNode ) {
+ return;
+ }
+ // Traverse up the tree
+ while ( parent && parent !== this ) {
+ parent = parent.parentNode;
+ }
+
+ if ( parent !== this ) {
+ // set the correct event type
+ event.type = event.data;
+
+ // handle event if we actually just moused on to a non sub-element
+ jQuery.event.handle.apply( this, arguments );
+ }
+
+ // assuming we've left the element since we most likely mousedover a xul element
+ } catch(e) { }
+},
+
+// In case of event delegation, we only need to rename the event.type,
+// liveHandler will take care of the rest.
+delegate = function( event ) {
+ event.type = event.data;
+ jQuery.event.handle.apply( this, arguments );
+};
+
+// Create mouseenter and mouseleave events
+jQuery.each({
+ mouseenter: "mouseover",
+ mouseleave: "mouseout"
+}, function( orig, fix ) {
+ jQuery.event.special[ orig ] = {
+ setup: function( data ) {
+ jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig );
+ },
+ teardown: function( data ) {
+ jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement );
+ }
+ };
+});
+
+// submit delegation
+if ( !jQuery.support.submitBubbles ) {
+
+ jQuery.event.special.submit = {
+ setup: function( data, namespaces ) {
+ if ( this.nodeName && this.nodeName.toLowerCase() !== "form" ) {
+ jQuery.event.add(this, "click.specialSubmit", function( e ) {
+ var elem = e.target,
+ type = elem.type;
+
+ if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) {
+ trigger( "submit", this, arguments );
+ }
+ });
+
+ jQuery.event.add(this, "keypress.specialSubmit", function( e ) {
+ var elem = e.target,
+ type = elem.type;
+
+ if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) {
+ trigger( "submit", this, arguments );
+ }
+ });
+
+ } else {
+ return false;
+ }
+ },
+
+ teardown: function( namespaces ) {
+ jQuery.event.remove( this, ".specialSubmit" );
+ }
+ };
+
+}
+
+// change delegation, happens here so we have bind.
+if ( !jQuery.support.changeBubbles ) {
+
+ var changeFilters,
+
+ getVal = function( elem ) {
+ var type = elem.type, val = elem.value;
+
+ if ( type === "radio" || type === "checkbox" ) {
+ val = elem.checked;
+
+ } else if ( type === "select-multiple" ) {
+ val = elem.selectedIndex > -1 ?
+ jQuery.map( elem.options, function( elem ) {
+ return elem.selected;
+ }).join("-") :
+ "";
+
+ } else if ( elem.nodeName.toLowerCase() === "select" ) {
+ val = elem.selectedIndex;
+ }
+
+ return val;
+ },
+
+ testChange = function testChange( e ) {
+ var elem = e.target, data, val;
+
+ if ( !rformElems.test( elem.nodeName ) || elem.readOnly ) {
+ return;
+ }
+
+ data = jQuery._data( elem, "_change_data" );
+ val = getVal(elem);
+
+ // the current data will be also retrieved by beforeactivate
+ if ( e.type !== "focusout" || elem.type !== "radio" ) {
+ jQuery._data( elem, "_change_data", val );
+ }
+
+ if ( data === undefined || val === data ) {
+ return;
+ }
+
+ if ( data != null || val ) {
+ e.type = "change";
+ e.liveFired = undefined;
+ jQuery.event.trigger( e, arguments[1], elem );
+ }
+ };
+
+ jQuery.event.special.change = {
+ filters: {
+ focusout: testChange,
+
+ beforedeactivate: testChange,
+
+ click: function( e ) {
+ var elem = e.target, type = elem.type;
+
+ if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) {
+ testChange.call( this, e );
+ }
+ },
+
+ // Change has to be called before submit
+ // Keydown will be called before keypress, which is used in submit-event delegation
+ keydown: function( e ) {
+ var elem = e.target, type = elem.type;
+
+ if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") ||
+ (e.keyCode === 32 && (type === "checkbox" || type === "radio")) ||
+ type === "select-multiple" ) {
+ testChange.call( this, e );
+ }
+ },
+
+ // Beforeactivate happens also before the previous element is blurred
+ // with this event you can't trigger a change event, but you can store
+ // information
+ beforeactivate: function( e ) {
+ var elem = e.target;
+ jQuery._data( elem, "_change_data", getVal(elem) );
+ }
+ },
+
+ setup: function( data, namespaces ) {
+ if ( this.type === "file" ) {
+ return false;
+ }
+
+ for ( var type in changeFilters ) {
+ jQuery.event.add( this, type + ".specialChange", changeFilters[type] );
+ }
+
+ return rformElems.test( this.nodeName );
+ },
+
+ teardown: function( namespaces ) {
+ jQuery.event.remove( this, ".specialChange" );
+
+ return rformElems.test( this.nodeName );
+ }
+ };
+
+ changeFilters = jQuery.event.special.change.filters;
+
+ // Handle when the input is .focus()'d
+ changeFilters.focus = changeFilters.beforeactivate;
+}
+
+function trigger( type, elem, args ) {
+ // Piggyback on a donor event to simulate a different one.
+ // Fake originalEvent to avoid donor's stopPropagation, but if the
+ // simulated event prevents default then we do the same on the donor.
+ // Don't pass args or remember liveFired; they apply to the donor event.
+ var event = jQuery.extend( {}, args[ 0 ] );
+ event.type = type;
+ event.originalEvent = {};
+ event.liveFired = undefined;
+ jQuery.event.handle.call( elem, event );
+ if ( event.isDefaultPrevented() ) {
+ args[ 0 ].preventDefault();
+ }
+}
+
+// Create "bubbling" focus and blur events
+if ( document.addEventListener ) {
+ jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+ // Attach a single capturing handler while someone wants focusin/focusout
+ var attaches = 0;
+
+ jQuery.event.special[ fix ] = {
+ setup: function() {
+ if ( attaches++ === 0 ) {
+ document.addEventListener( orig, handler, true );
+ }
+ },
+ teardown: function() {
+ if ( --attaches === 0 ) {
+ document.removeEventListener( orig, handler, true );
+ }
+ }
+ };
+
+ function handler( donor ) {
+ // Donor event is always a native one; fix it and switch its type.
+ // Let focusin/out handler cancel the donor focus/blur event.
+ var e = jQuery.event.fix( donor );
+ e.type = fix;
+ e.originalEvent = {};
+ jQuery.event.trigger( e, null, e.target );
+ if ( e.isDefaultPrevented() ) {
+ donor.preventDefault();
+ }
+ }
+ });
+}
+
+jQuery.each(["bind", "one"], function( i, name ) {
+ jQuery.fn[ name ] = function( type, data, fn ) {
+ // Handle object literals
+ if ( typeof type === "object" ) {
+ for ( var key in type ) {
+ this[ name ](key, data, type[key], fn);
+ }
+ return this;
+ }
+
+ if ( jQuery.isFunction( data ) || data === false ) {
+ fn = data;
+ data = undefined;
+ }
+
+ var handler = name === "one" ? jQuery.proxy( fn, function( event ) {
+ jQuery( this ).unbind( event, handler );
+ return fn.apply( this, arguments );
+ }) : fn;
+
+ if ( type === "unload" && name !== "one" ) {
+ this.one( type, data, fn );
+
+ } else {
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ jQuery.event.add( this[i], type, handler, data );
+ }
+ }
+
+ return this;
+ };
+});
+
+jQuery.fn.extend({
+ unbind: function( type, fn ) {
+ // Handle object literals
+ if ( typeof type === "object" && !type.preventDefault ) {
+ for ( var key in type ) {
+ this.unbind(key, type[key]);
+ }
+
+ } else {
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ jQuery.event.remove( this[i], type, fn );
+ }
+ }
+
+ return this;
+ },
+
+ delegate: function( selector, types, data, fn ) {
+ return this.live( types, data, fn, selector );
+ },
+
+ undelegate: function( selector, types, fn ) {
+ if ( arguments.length === 0 ) {
+ return this.unbind( "live" );
+
+ } else {
+ return this.die( types, null, fn, selector );
+ }
+ },
+
+ trigger: function( type, data ) {
+ return this.each(function() {
+ jQuery.event.trigger( type, data, this );
+ });
+ },
+
+ triggerHandler: function( type, data ) {
+ if ( this[0] ) {
+ var event = jQuery.Event( type );
+ event.preventDefault();
+ event.stopPropagation();
+ jQuery.event.trigger( event, data, this[0] );
+ return event.result;
+ }
+ },
+
+ toggle: function( fn ) {
+ // Save reference to arguments for access in closure
+ var args = arguments,
+ i = 1;
+
+ // link all the functions, so any of them can unbind this click handler
+ while ( i < args.length ) {
+ jQuery.proxy( fn, args[ i++ ] );
+ }
+
+ return this.click( jQuery.proxy( fn, function( event ) {
+ // Figure out which function to execute
+ var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+ jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+
+ // Make sure that clicks stop
+ event.preventDefault();
+
+ // and execute the function
+ return args[ lastToggle ].apply( this, arguments ) || false;
+ }));
+ },
+
+ hover: function( fnOver, fnOut ) {
+ return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+ }
+});
+
+var liveMap = {
+ focus: "focusin",
+ blur: "focusout",
+ mouseenter: "mouseover",
+ mouseleave: "mouseout"
+};
+
+jQuery.each(["live", "die"], function( i, name ) {
+ jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) {
+ var type, i = 0, match, namespaces, preType,
+ selector = origSelector || this.selector,
+ context = origSelector ? this : jQuery( this.context );
+
+ if ( typeof types === "object" && !types.preventDefault ) {
+ for ( var key in types ) {
+ context[ name ]( key, data, types[key], selector );
+ }
+
+ return this;
+ }
+
+ if ( jQuery.isFunction( data ) ) {
+ fn = data;
+ data = undefined;
+ }
+
+ types = (types || "").split(" ");
+
+ while ( (type = types[ i++ ]) != null ) {
+ match = rnamespaces.exec( type );
+ namespaces = "";
+
+ if ( match ) {
+ namespaces = match[0];
+ type = type.replace( rnamespaces, "" );
+ }
+
+ if ( type === "hover" ) {
+ types.push( "mouseenter" + namespaces, "mouseleave" + namespaces );
+ continue;
+ }
+
+ preType = type;
+
+ if ( type === "focus" || type === "blur" ) {
+ types.push( liveMap[ type ] + namespaces );
+ type = type + namespaces;
+
+ } else {
+ type = (liveMap[ type ] || type) + namespaces;
+ }
+
+ if ( name === "live" ) {
+ // bind live handler
+ for ( var j = 0, l = context.length; j < l; j++ ) {
+ jQuery.event.add( context[j], "live." + liveConvert( type, selector ),
+ { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } );
+ }
+
+ } else {
+ // unbind live handler
+ context.unbind( "live." + liveConvert( type, selector ), fn );
+ }
+ }
+
+ return this;
+ };
+});
+
+function liveHandler( event ) {
+ var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret,
+ elems = [],
+ selectors = [],
+ events = jQuery._data( this, "events" );
+
+ // Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911)
+ if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) {
+ return;
+ }
+
+ if ( event.namespace ) {
+ namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)");
+ }
+
+ event.liveFired = this;
+
+ var live = events.live.slice(0);
+
+ for ( j = 0; j < live.length; j++ ) {
+ handleObj = live[j];
+
+ if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) {
+ selectors.push( handleObj.selector );
+
+ } else {
+ live.splice( j--, 1 );
+ }
+ }
+
+ match = jQuery( event.target ).closest( selectors, event.currentTarget );
+
+ for ( i = 0, l = match.length; i < l; i++ ) {
+ close = match[i];
+
+ for ( j = 0; j < live.length; j++ ) {
+ handleObj = live[j];
+
+ if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) && !close.elem.disabled ) {
+ elem = close.elem;
+ related = null;
+
+ // Those two events require additional checking
+ if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) {
+ event.type = handleObj.preType;
+ related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0];
+ }
+
+ if ( !related || related !== elem ) {
+ elems.push({ elem: elem, handleObj: handleObj, level: close.level });
+ }
+ }
+ }
+ }
+
+ for ( i = 0, l = elems.length; i < l; i++ ) {
+ match = elems[i];
+
+ if ( maxLevel && match.level > maxLevel ) {
+ break;
+ }
+
+ event.currentTarget = match.elem;
+ event.data = match.handleObj.data;
+ event.handleObj = match.handleObj;
+
+ ret = match.handleObj.origHandler.apply( match.elem, arguments );
+
+ if ( ret === false || event.isPropagationStopped() ) {
+ maxLevel = match.level;
+
+ if ( ret === false ) {
+ stop = false;
+ }
+ if ( event.isImmediatePropagationStopped() ) {
+ break;
+ }
+ }
+ }
+
+ return stop;
+}
+
+function liveConvert( type, selector ) {
+ return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspace, "&");
+}
+
+jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
+ "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+ "change select submit keydown keypress keyup error").split(" "), function( i, name ) {
+
+ // Handle event binding
+ jQuery.fn[ name ] = function( data, fn ) {
+ if ( fn == null ) {
+ fn = data;
+ data = null;
+ }
+
+ return arguments.length > 0 ?
+ this.bind( name, data, fn ) :
+ this.trigger( name );
+ };
+
+ if ( jQuery.attrFn ) {
+ jQuery.attrFn[ name ] = true;
+ }
+});
+
+
+/*!
+ * Sizzle CSS Selector Engine
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ * More information: http://sizzlejs.com/
+ */
+(function(){
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+ done = 0,
+ toString = Object.prototype.toString,
+ hasDuplicate = false,
+ baseHasDuplicate = true,
+ rBackslash = /\\/g,
+ rNonWord = /\W/;
+
+// Here we check if the JavaScript engine is using some sort of
+// optimization where it does not always call our comparision
+// function. If that is the case, discard the hasDuplicate value.
+// Thus far that includes Google Chrome.
+[0, 0].sort(function() {
+ baseHasDuplicate = false;
+ return 0;
+});
+
+var Sizzle = function( selector, context, results, seed ) {
+ results = results || [];
+ context = context || document;
+
+ var origContext = context;
+
+ if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
+ return [];
+ }
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ var m, set, checkSet, extra, ret, cur, pop, i,
+ prune = true,
+ contextXML = Sizzle.isXML( context ),
+ parts = [],
+ soFar = selector;
+
+ // Reset the position of the chunker regexp (start from head)
+ do {
+ chunker.exec( "" );
+ m = chunker.exec( soFar );
+
+ if ( m ) {
+ soFar = m[3];
+
+ parts.push( m[1] );
+
+ if ( m[2] ) {
+ extra = m[3];
+ break;
+ }
+ }
+ } while ( m );
+
+ if ( parts.length > 1 && origPOS.exec( selector ) ) {
+
+ if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+ set = posProcess( parts[0] + parts[1], context );
+
+ } else {
+ set = Expr.relative[ parts[0] ] ?
+ [ context ] :
+ Sizzle( parts.shift(), context );
+
+ while ( parts.length ) {
+ selector = parts.shift();
+
+ if ( Expr.relative[ selector ] ) {
+ selector += parts.shift();
+ }
+
+ set = posProcess( selector, set );
+ }
+ }
+
+ } else {
+ // Take a shortcut and set the context if the root selector is an ID
+ // (but not if it'll be faster if the inner selector is an ID)
+ if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
+ Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
+
+ ret = Sizzle.find( parts.shift(), context, contextXML );
+ context = ret.expr ?
+ Sizzle.filter( ret.expr, ret.set )[0] :
+ ret.set[0];
+ }
+
+ if ( context ) {
+ ret = seed ?
+ { expr: parts.pop(), set: makeArray(seed) } :
+ Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
+
+ set = ret.expr ?
+ Sizzle.filter( ret.expr, ret.set ) :
+ ret.set;
+
+ if ( parts.length > 0 ) {
+ checkSet = makeArray( set );
+
+ } else {
+ prune = false;
+ }
+
+ while ( parts.length ) {
+ cur = parts.pop();
+ pop = cur;
+
+ if ( !Expr.relative[ cur ] ) {
+ cur = "";
+ } else {
+ pop = parts.pop();
+ }
+
+ if ( pop == null ) {
+ pop = context;
+ }
+
+ Expr.relative[ cur ]( checkSet, pop, contextXML );
+ }
+
+ } else {
+ checkSet = parts = [];
+ }
+ }
+
+ if ( !checkSet ) {
+ checkSet = set;
+ }
+
+ if ( !checkSet ) {
+ Sizzle.error( cur || selector );
+ }
+
+ if ( toString.call(checkSet) === "[object Array]" ) {
+ if ( !prune ) {
+ results.push.apply( results, checkSet );
+
+ } else if ( context && context.nodeType === 1 ) {
+ for ( i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
+ results.push( set[i] );
+ }
+ }
+
+ } else {
+ for ( i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+ results.push( set[i] );
+ }
+ }
+ }
+
+ } else {
+ makeArray( checkSet, results );
+ }
+
+ if ( extra ) {
+ Sizzle( extra, origContext, results, seed );
+ Sizzle.uniqueSort( results );
+ }
+
+ return results;
+};
+
+Sizzle.uniqueSort = function( results ) {
+ if ( sortOrder ) {
+ hasDuplicate = baseHasDuplicate;
+ results.sort( sortOrder );
+
+ if ( hasDuplicate ) {
+ for ( var i = 1; i < results.length; i++ ) {
+ if ( results[i] === results[ i - 1 ] ) {
+ results.splice( i--, 1 );
+ }
+ }
+ }
+ }
+
+ return results;
+};
+
+Sizzle.matches = function( expr, set ) {
+ return Sizzle( expr, null, null, set );
+};
+
+Sizzle.matchesSelector = function( node, expr ) {
+ return Sizzle( expr, null, null, [node] ).length > 0;
+};
+
+Sizzle.find = function( expr, context, isXML ) {
+ var set;
+
+ if ( !expr ) {
+ return [];
+ }
+
+ for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
+ var match,
+ type = Expr.order[i];
+
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
+ var left = match[1];
+ match.splice( 1, 1 );
+
+ if ( left.substr( left.length - 1 ) !== "\\" ) {
+ match[1] = (match[1] || "").replace( rBackslash, "" );
+ set = Expr.find[ type ]( match, context, isXML );
+
+ if ( set != null ) {
+ expr = expr.replace( Expr.match[ type ], "" );
+ break;
+ }
+ }
+ }
+ }
+
+ if ( !set ) {
+ set = typeof context.getElementsByTagName !== "undefined" ?
+ context.getElementsByTagName( "*" ) :
+ [];
+ }
+
+ return { set: set, expr: expr };
+};
+
+Sizzle.filter = function( expr, set, inplace, not ) {
+ var match, anyFound,
+ old = expr,
+ result = [],
+ curLoop = set,
+ isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
+
+ while ( expr && set.length ) {
+ for ( var type in Expr.filter ) {
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
+ var found, item,
+ filter = Expr.filter[ type ],
+ left = match[1];
+
+ anyFound = false;
+
+ match.splice(1,1);
+
+ if ( left.substr( left.length - 1 ) === "\\" ) {
+ continue;
+ }
+
+ if ( curLoop === result ) {
+ result = [];
+ }
+
+ if ( Expr.preFilter[ type ] ) {
+ match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+ if ( !match ) {
+ anyFound = found = true;
+
+ } else if ( match === true ) {
+ continue;
+ }
+ }
+
+ if ( match ) {
+ for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
+ if ( item ) {
+ found = filter( item, match, i, curLoop );
+ var pass = not ^ !!found;
+
+ if ( inplace && found != null ) {
+ if ( pass ) {
+ anyFound = true;
+
+ } else {
+ curLoop[i] = false;
+ }
+
+ } else if ( pass ) {
+ result.push( item );
+ anyFound = true;
+ }
+ }
+ }
+ }
+
+ if ( found !== undefined ) {
+ if ( !inplace ) {
+ curLoop = result;
+ }
+
+ expr = expr.replace( Expr.match[ type ], "" );
+
+ if ( !anyFound ) {
+ return [];
+ }
+
+ break;
+ }
+ }
+ }
+
+ // Improper expression
+ if ( expr === old ) {
+ if ( anyFound == null ) {
+ Sizzle.error( expr );
+
+ } else {
+ break;
+ }
+ }
+
+ old = expr;
+ }
+
+ return curLoop;
+};
+
+Sizzle.error = function( msg ) {
+ throw "Syntax error, unrecognized expression: " + msg;
+};
+
+var Expr = Sizzle.selectors = {
+ order: [ "ID", "NAME", "TAG" ],
+
+ match: {
+ ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+ CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+ NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
+ ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
+ TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
+ CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
+ POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
+ PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
+ },
+
+ leftMatch: {},
+
+ attrMap: {
+ "class": "className",
+ "for": "htmlFor"
+ },
+
+ attrHandle: {
+ href: function( elem ) {
+ return elem.getAttribute( "href" );
+ },
+ type: function( elem ) {
+ return elem.getAttribute( "type" );
+ }
+ },
+
+ relative: {
+ "+": function(checkSet, part){
+ var isPartStr = typeof part === "string",
+ isTag = isPartStr && !rNonWord.test( part ),
+ isPartStrNotTag = isPartStr && !isTag;
+
+ if ( isTag ) {
+ part = part.toLowerCase();
+ }
+
+ for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+ if ( (elem = checkSet[i]) ) {
+ while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+ checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
+ elem || false :
+ elem === part;
+ }
+ }
+
+ if ( isPartStrNotTag ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ },
+
+ ">": function( checkSet, part ) {
+ var elem,
+ isPartStr = typeof part === "string",
+ i = 0,
+ l = checkSet.length;
+
+ if ( isPartStr && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+
+ for ( ; i < l; i++ ) {
+ elem = checkSet[i];
+
+ if ( elem ) {
+ var parent = elem.parentNode;
+ checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
+ }
+ }
+
+ } else {
+ for ( ; i < l; i++ ) {
+ elem = checkSet[i];
+
+ if ( elem ) {
+ checkSet[i] = isPartStr ?
+ elem.parentNode :
+ elem.parentNode === part;
+ }
+ }
+
+ if ( isPartStr ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ }
+ },
+
+ "": function(checkSet, part, isXML){
+ var nodeCheck,
+ doneName = done++,
+ checkFn = dirCheck;
+
+ if ( typeof part === "string" && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+ nodeCheck = part;
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
+ },
+
+ "~": function( checkSet, part, isXML ) {
+ var nodeCheck,
+ doneName = done++,
+ checkFn = dirCheck;
+
+ if ( typeof part === "string" && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+ nodeCheck = part;
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
+ }
+ },
+
+ find: {
+ ID: function( match, context, isXML ) {
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ return m && m.parentNode ? [m] : [];
+ }
+ },
+
+ NAME: function( match, context ) {
+ if ( typeof context.getElementsByName !== "undefined" ) {
+ var ret = [],
+ results = context.getElementsByName( match[1] );
+
+ for ( var i = 0, l = results.length; i < l; i++ ) {
+ if ( results[i].getAttribute("name") === match[1] ) {
+ ret.push( results[i] );
+ }
+ }
+
+ return ret.length === 0 ? null : ret;
+ }
+ },
+
+ TAG: function( match, context ) {
+ if ( typeof context.getElementsByTagName !== "undefined" ) {
+ return context.getElementsByTagName( match[1] );
+ }
+ }
+ },
+ preFilter: {
+ CLASS: function( match, curLoop, inplace, result, not, isXML ) {
+ match = " " + match[1].replace( rBackslash, "" ) + " ";
+
+ if ( isXML ) {
+ return match;
+ }
+
+ for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+ if ( elem ) {
+ if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
+ if ( !inplace ) {
+ result.push( elem );
+ }
+
+ } else if ( inplace ) {
+ curLoop[i] = false;
+ }
+ }
+ }
+
+ return false;
+ },
+
+ ID: function( match ) {
+ return match[1].replace( rBackslash, "" );
+ },
+
+ TAG: function( match, curLoop ) {
+ return match[1].replace( rBackslash, "" ).toLowerCase();
+ },
+
+ CHILD: function( match ) {
+ if ( match[1] === "nth" ) {
+ if ( !match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ match[2] = match[2].replace(/^\+|\s*/g, '');
+
+ // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+ var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
+ match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
+ !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+ // calculate the numbers (first)n+(last) including if they are negative
+ match[2] = (test[1] + (test[2] || 1)) - 0;
+ match[3] = test[3] - 0;
+ }
+ else if ( match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ // TODO: Move to normal caching system
+ match[0] = done++;
+
+ return match;
+ },
+
+ ATTR: function( match, curLoop, inplace, result, not, isXML ) {
+ var name = match[1] = match[1].replace( rBackslash, "" );
+
+ if ( !isXML && Expr.attrMap[name] ) {
+ match[1] = Expr.attrMap[name];
+ }
+
+ // Handle if an un-quoted value was used
+ match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
+
+ if ( match[2] === "~=" ) {
+ match[4] = " " + match[4] + " ";
+ }
+
+ return match;
+ },
+
+ PSEUDO: function( match, curLoop, inplace, result, not ) {
+ if ( match[1] === "not" ) {
+ // If we're dealing with a complex expression, or a simple one
+ if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
+ match[3] = Sizzle(match[3], null, null, curLoop);
+
+ } else {
+ var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+
+ if ( !inplace ) {
+ result.push.apply( result, ret );
+ }
+
+ return false;
+ }
+
+ } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+ return true;
+ }
+
+ return match;
+ },
+
+ POS: function( match ) {
+ match.unshift( true );
+
+ return match;
+ }
+ },
+
+ filters: {
+ enabled: function( elem ) {
+ return elem.disabled === false && elem.type !== "hidden";
+ },
+
+ disabled: function( elem ) {
+ return elem.disabled === true;
+ },
+
+ checked: function( elem ) {
+ return elem.checked === true;
+ },
+
+ selected: function( elem ) {
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ if ( elem.parentNode ) {
+ elem.parentNode.selectedIndex;
+ }
+
+ return elem.selected === true;
+ },
+
+ parent: function( elem ) {
+ return !!elem.firstChild;
+ },
+
+ empty: function( elem ) {
+ return !elem.firstChild;
+ },
+
+ has: function( elem, i, match ) {
+ return !!Sizzle( match[3], elem ).length;
+ },
+
+ header: function( elem ) {
+ return (/h\d/i).test( elem.nodeName );
+ },
+
+ text: function( elem ) {
+ var attr = elem.getAttribute( "type" ), type = elem.type;
+ // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
+ // use getAttribute instead to test this case
+ return "text" === type && ( attr === type || attr === null );
+ },
+
+ radio: function( elem ) {
+ return "radio" === elem.type;
+ },
+
+ checkbox: function( elem ) {
+ return "checkbox" === elem.type;
+ },
+
+ file: function( elem ) {
+ return "file" === elem.type;
+ },
+ password: function( elem ) {
+ return "password" === elem.type;
+ },
+
+ submit: function( elem ) {
+ return "submit" === elem.type;
+ },
+
+ image: function( elem ) {
+ return "image" === elem.type;
+ },
+
+ reset: function( elem ) {
+ return "reset" === elem.type;
+ },
+
+ button: function( elem ) {
+ return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
+ },
+
+ input: function( elem ) {
+ return (/input|select|textarea|button/i).test( elem.nodeName );
+ }
+ },
+ setFilters: {
+ first: function( elem, i ) {
+ return i === 0;
+ },
+
+ last: function( elem, i, match, array ) {
+ return i === array.length - 1;
+ },
+
+ even: function( elem, i ) {
+ return i % 2 === 0;
+ },
+
+ odd: function( elem, i ) {
+ return i % 2 === 1;
+ },
+
+ lt: function( elem, i, match ) {
+ return i < match[3] - 0;
+ },
+
+ gt: function( elem, i, match ) {
+ return i > match[3] - 0;
+ },
+
+ nth: function( elem, i, match ) {
+ return match[3] - 0 === i;
+ },
+
+ eq: function( elem, i, match ) {
+ return match[3] - 0 === i;
+ }
+ },
+ filter: {
+ PSEUDO: function( elem, match, i, array ) {
+ var name = match[1],
+ filter = Expr.filters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+
+ } else if ( name === "contains" ) {
+ return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
+
+ } else if ( name === "not" ) {
+ var not = match[3];
+
+ for ( var j = 0, l = not.length; j < l; j++ ) {
+ if ( not[j] === elem ) {
+ return false;
+ }
+ }
+
+ return true;
+
+ } else {
+ Sizzle.error( name );
+ }
+ },
+
+ CHILD: function( elem, match ) {
+ var type = match[1],
+ node = elem;
+
+ switch ( type ) {
+ case "only":
+ case "first":
+ while ( (node = node.previousSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ if ( type === "first" ) {
+ return true;
+ }
+
+ node = elem;
+
+ case "last":
+ while ( (node = node.nextSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ return true;
+
+ case "nth":
+ var first = match[2],
+ last = match[3];
+
+ if ( first === 1 && last === 0 ) {
+ return true;
+ }
+
+ var doneName = match[0],
+ parent = elem.parentNode;
+
+ if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
+ var count = 0;
+
+ for ( node = parent.firstChild; node; node = node.nextSibling ) {
+ if ( node.nodeType === 1 ) {
+ node.nodeIndex = ++count;
+ }
+ }
+
+ parent.sizcache = doneName;
+ }
+
+ var diff = elem.nodeIndex - last;
+
+ if ( first === 0 ) {
+ return diff === 0;
+
+ } else {
+ return ( diff % first === 0 && diff / first >= 0 );
+ }
+ }
+ },
+
+ ID: function( elem, match ) {
+ return elem.nodeType === 1 && elem.getAttribute("id") === match;
+ },
+
+ TAG: function( elem, match ) {
+ return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
+ },
+
+ CLASS: function( elem, match ) {
+ return (" " + (elem.className || elem.getAttribute("class")) + " ")
+ .indexOf( match ) > -1;
+ },
+
+ ATTR: function( elem, match ) {
+ var name = match[1],
+ result = Expr.attrHandle[ name ] ?
+ Expr.attrHandle[ name ]( elem ) :
+ elem[ name ] != null ?
+ elem[ name ] :
+ elem.getAttribute( name ),
+ value = result + "",
+ type = match[2],
+ check = match[4];
+
+ return result == null ?
+ type === "!=" :
+ type === "=" ?
+ value === check :
+ type === "*=" ?
+ value.indexOf(check) >= 0 :
+ type === "~=" ?
+ (" " + value + " ").indexOf(check) >= 0 :
+ !check ?
+ value && result !== false :
+ type === "!=" ?
+ value !== check :
+ type === "^=" ?
+ value.indexOf(check) === 0 :
+ type === "$=" ?
+ value.substr(value.length - check.length) === check :
+ type === "|=" ?
+ value === check || value.substr(0, check.length + 1) === check + "-" :
+ false;
+ },
+
+ POS: function( elem, match, i, array ) {
+ var name = match[2],
+ filter = Expr.setFilters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+ }
+ }
+ }
+};
+
+var origPOS = Expr.match.POS,
+ fescape = function(all, num){
+ return "\\" + (num - 0 + 1);
+ };
+
+for ( var type in Expr.match ) {
+ Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
+ Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
+}
+
+var makeArray = function( array, results ) {
+ array = Array.prototype.slice.call( array, 0 );
+
+ if ( results ) {
+ results.push.apply( results, array );
+ return results;
+ }
+
+ return array;
+};
+
+// Perform a simple check to determine if the browser is capable of
+// converting a NodeList to an array using builtin methods.
+// Also verifies that the returned array holds DOM nodes
+// (which is not the case in the Blackberry browser)
+try {
+ Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
+
+// Provide a fallback method if it does not work
+} catch( e ) {
+ makeArray = function( array, results ) {
+ var i = 0,
+ ret = results || [];
+
+ if ( toString.call(array) === "[object Array]" ) {
+ Array.prototype.push.apply( ret, array );
+
+ } else {
+ if ( typeof array.length === "number" ) {
+ for ( var l = array.length; i < l; i++ ) {
+ ret.push( array[i] );
+ }
+
+ } else {
+ for ( ; array[i]; i++ ) {
+ ret.push( array[i] );
+ }
+ }
+ }
+
+ return ret;
+ };
+}
+
+var sortOrder, siblingCheck;
+
+if ( document.documentElement.compareDocumentPosition ) {
+ sortOrder = function( a, b ) {
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
+ return a.compareDocumentPosition ? -1 : 1;
+ }
+
+ return a.compareDocumentPosition(b) & 4 ? -1 : 1;
+ };
+
+} else {
+ sortOrder = function( a, b ) {
+ var al, bl,
+ ap = [],
+ bp = [],
+ aup = a.parentNode,
+ bup = b.parentNode,
+ cur = aup;
+
+ // The nodes are identical, we can exit early
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+
+ // If the nodes are siblings (or identical) we can do a quick check
+ } else if ( aup === bup ) {
+ return siblingCheck( a, b );
+
+ // If no parents were found then the nodes are disconnected
+ } else if ( !aup ) {
+ return -1;
+
+ } else if ( !bup ) {
+ return 1;
+ }
+
+ // Otherwise they're somewhere else in the tree so we need
+ // to build up a full list of the parentNodes for comparison
+ while ( cur ) {
+ ap.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ cur = bup;
+
+ while ( cur ) {
+ bp.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ al = ap.length;
+ bl = bp.length;
+
+ // Start walking down the tree looking for a discrepancy
+ for ( var i = 0; i < al && i < bl; i++ ) {
+ if ( ap[i] !== bp[i] ) {
+ return siblingCheck( ap[i], bp[i] );
+ }
+ }
+
+ // We ended someplace up the tree so do a sibling check
+ return i === al ?
+ siblingCheck( a, bp[i], -1 ) :
+ siblingCheck( ap[i], b, 1 );
+ };
+
+ siblingCheck = function( a, b, ret ) {
+ if ( a === b ) {
+ return ret;
+ }
+
+ var cur = a.nextSibling;
+
+ while ( cur ) {
+ if ( cur === b ) {
+ return -1;
+ }
+
+ cur = cur.nextSibling;
+ }
+
+ return 1;
+ };
+}
+
+// Utility function for retreiving the text value of an array of DOM nodes
+Sizzle.getText = function( elems ) {
+ var ret = "", elem;
+
+ for ( var i = 0; elems[i]; i++ ) {
+ elem = elems[i];
+
+ // Get the text from text nodes and CDATA nodes
+ if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
+ ret += elem.nodeValue;
+
+ // Traverse everything else, except comment nodes
+ } else if ( elem.nodeType !== 8 ) {
+ ret += Sizzle.getText( elem.childNodes );
+ }
+ }
+
+ return ret;
+};
+
+// Check to see if the browser returns elements by name when
+// querying by getElementById (and provide a workaround)
+(function(){
+ // We're going to inject a fake input element with a specified name
+ var form = document.createElement("div"),
+ id = "script" + (new Date()).getTime(),
+ root = document.documentElement;
+
+ form.innerHTML = "<a name='" + id + "'/>";
+
+ // Inject it into the root element, check its status, and remove it quickly
+ root.insertBefore( form, root.firstChild );
+
+ // The workaround has to do additional checks after a getElementById
+ // Which slows things down for other browsers (hence the branching)
+ if ( document.getElementById( id ) ) {
+ Expr.find.ID = function( match, context, isXML ) {
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+
+ return m ?
+ m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
+ [m] :
+ undefined :
+ [];
+ }
+ };
+
+ Expr.filter.ID = function( elem, match ) {
+ var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+
+ return elem.nodeType === 1 && node && node.nodeValue === match;
+ };
+ }
+
+ root.removeChild( form );
+
+ // release memory in IE
+ root = form = null;
+})();
+
+(function(){
+ // Check to see if the browser returns only elements
+ // when doing getElementsByTagName("*")
+
+ // Create a fake element
+ var div = document.createElement("div");
+ div.appendChild( document.createComment("") );
+
+ // Make sure no comments are found
+ if ( div.getElementsByTagName("*").length > 0 ) {
+ Expr.find.TAG = function( match, context ) {
+ var results = context.getElementsByTagName( match[1] );
+
+ // Filter out possible comments
+ if ( match[1] === "*" ) {
+ var tmp = [];
+
+ for ( var i = 0; results[i]; i++ ) {
+ if ( results[i].nodeType === 1 ) {
+ tmp.push( results[i] );
+ }
+ }
+
+ results = tmp;
+ }
+
+ return results;
+ };
+ }
+
+ // Check to see if an attribute returns normalized href attributes
+ div.innerHTML = "<a href='#'></a>";
+
+ if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+ div.firstChild.getAttribute("href") !== "#" ) {
+
+ Expr.attrHandle.href = function( elem ) {
+ return elem.getAttribute( "href", 2 );
+ };
+ }
+
+ // release memory in IE
+ div = null;
+})();
+
+if ( document.querySelectorAll ) {
+ (function(){
+ var oldSizzle = Sizzle,
+ div = document.createElement("div"),
+ id = "__sizzle__";
+
+ div.innerHTML = "<p class='TEST'></p>";
+
+ // Safari can't handle uppercase or unicode characters when
+ // in quirks mode.
+ if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+ return;
+ }
+
+ Sizzle = function( query, context, extra, seed ) {
+ context = context || document;
+
+ // Only use querySelectorAll on non-XML documents
+ // (ID selectors don't work in non-HTML documents)
+ if ( !seed && !Sizzle.isXML(context) ) {
+ // See if we find a selector to speed up
+ var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
+
+ if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
+ // Speed-up: Sizzle("TAG")
+ if ( match[1] ) {
+ return makeArray( context.getElementsByTagName( query ), extra );
+
+ // Speed-up: Sizzle(".CLASS")
+ } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
+ return makeArray( context.getElementsByClassName( match[2] ), extra );
+ }
+ }
+
+ if ( context.nodeType === 9 ) {
+ // Speed-up: Sizzle("body")
+ // The body element only exists once, optimize finding it
+ if ( query === "body" && context.body ) {
+ return makeArray( [ context.body ], extra );
+
+ // Speed-up: Sizzle("#ID")
+ } else if ( match && match[3] ) {
+ var elem = context.getElementById( match[3] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id === match[3] ) {
+ return makeArray( [ elem ], extra );
+ }
+
+ } else {
+ return makeArray( [], extra );
+ }
+ }
+
+ try {
+ return makeArray( context.querySelectorAll(query), extra );
+ } catch(qsaError) {}
+
+ // qSA works strangely on Element-rooted queries
+ // We can work around this by specifying an extra ID on the root
+ // and working up from there (Thanks to Andrew Dupont for the technique)
+ // IE 8 doesn't work on object elements
+ } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+ var oldContext = context,
+ old = context.getAttribute( "id" ),
+ nid = old || id,
+ hasParent = context.parentNode,
+ relativeHierarchySelector = /^\s*[+~]/.test( query );
+
+ if ( !old ) {
+ context.setAttribute( "id", nid );
+ } else {
+ nid = nid.replace( /'/g, "\\$&" );
+ }
+ if ( relativeHierarchySelector && hasParent ) {
+ context = context.parentNode;
+ }
+
+ try {
+ if ( !relativeHierarchySelector || hasParent ) {
+ return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
+ }
+
+ } catch(pseudoError) {
+ } finally {
+ if ( !old ) {
+ oldContext.removeAttribute( "id" );
+ }
+ }
+ }
+ }
+
+ return oldSizzle(query, context, extra, seed);
+ };
+
+ for ( var prop in oldSizzle ) {
+ Sizzle[ prop ] = oldSizzle[ prop ];
+ }
+
+ // release memory in IE
+ div = null;
+ })();
+}
+
+(function(){
+ var html = document.documentElement,
+ matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
+
+ if ( matches ) {
+ // Check to see if it's possible to do matchesSelector
+ // on a disconnected node (IE 9 fails this)
+ var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
+ pseudoWorks = false;
+
+ try {
+ // This should fail with an exception
+ // Gecko does not error, returns false instead
+ matches.call( document.documentElement, "[test!='']:sizzle" );
+
+ } catch( pseudoError ) {
+ pseudoWorks = true;
+ }
+
+ Sizzle.matchesSelector = function( node, expr ) {
+ // Make sure that attribute selectors are quoted
+ expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
+
+ if ( !Sizzle.isXML( node ) ) {
+ try {
+ if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
+ var ret = matches.call( node, expr );
+
+ // IE 9's matchesSelector returns false on disconnected nodes
+ if ( ret || !disconnectedMatch ||
+ // As well, disconnected nodes are said to be in a document
+ // fragment in IE 9, so check for that
+ node.document && node.document.nodeType !== 11 ) {
+ return ret;
+ }
+ }
+ } catch(e) {}
+ }
+
+ return Sizzle(expr, null, null, [node]).length > 0;
+ };
+ }
+})();
+
+(function(){
+ var div = document.createElement("div");
+
+ div.innerHTML = "<div class='test e'></div><div class='test'></div>";
+
+ // Opera can't find a second classname (in 9.6)
+ // Also, make sure that getElementsByClassName actually exists
+ if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
+ return;
+ }
+
+ // Safari caches class attributes, doesn't catch changes (in 3.2)
+ div.lastChild.className = "e";
+
+ if ( div.getElementsByClassName("e").length === 1 ) {
+ return;
+ }
+
+ Expr.order.splice(1, 0, "CLASS");
+ Expr.find.CLASS = function( match, context, isXML ) {
+ if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
+ return context.getElementsByClassName(match[1]);
+ }
+ };
+
+ // release memory in IE
+ div = null;
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+
+ if ( elem ) {
+ var match = false;
+
+ elem = elem[dir];
+
+ while ( elem ) {
+ if ( elem.sizcache === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 && !isXML ){
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+
+ if ( elem.nodeName.toLowerCase() === cur ) {
+ match = elem;
+ break;
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+
+ if ( elem ) {
+ var match = false;
+
+ elem = elem[dir];
+
+ while ( elem ) {
+ if ( elem.sizcache === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 ) {
+ if ( !isXML ) {
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+
+ if ( typeof cur !== "string" ) {
+ if ( elem === cur ) {
+ match = true;
+ break;
+ }
+
+ } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
+ match = elem;
+ break;
+ }
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+if ( document.documentElement.contains ) {
+ Sizzle.contains = function( a, b ) {
+ return a !== b && (a.contains ? a.contains(b) : true);
+ };
+
+} else if ( document.documentElement.compareDocumentPosition ) {
+ Sizzle.contains = function( a, b ) {
+ return !!(a.compareDocumentPosition(b) & 16);
+ };
+
+} else {
+ Sizzle.contains = function() {
+ return false;
+ };
+}
+
+Sizzle.isXML = function( elem ) {
+ // documentElement is verified for cases where it doesn't yet exist
+ // (such as loading iframes in IE - #4833)
+ var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
+
+ return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+var posProcess = function( selector, context ) {
+ var match,
+ tmpSet = [],
+ later = "",
+ root = context.nodeType ? [context] : context;
+
+ // Position selectors must be done after the filter
+ // And so must :not(positional) so we move all PSEUDOs to the end
+ while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+ later += match[0];
+ selector = selector.replace( Expr.match.PSEUDO, "" );
+ }
+
+ selector = Expr.relative[selector] ? selector + "*" : selector;
+
+ for ( var i = 0, l = root.length; i < l; i++ ) {
+ Sizzle( selector, root[i], tmpSet );
+ }
+
+ return Sizzle.filter( later, tmpSet );
+};
+
+// EXPOSE
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.filters;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+})();
+
+
+var runtil = /Until$/,
+ rparentsprev = /^(?:parents|prevUntil|prevAll)/,
+ // Note: This RegExp should be improved, or likely pulled from Sizzle
+ rmultiselector = /,/,
+ isSimple = /^.[^:#\[\.,]*$/,
+ slice = Array.prototype.slice,
+ POS = jQuery.expr.match.POS,
+ // methods guaranteed to produce a unique set when starting from a unique set
+ guaranteedUnique = {
+ children: true,
+ contents: true,
+ next: true,
+ prev: true
+ };
+
+jQuery.fn.extend({
+ find: function( selector ) {
+ var ret = this.pushStack( "", "find", selector ),
+ length = 0;
+
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ length = ret.length;
+ jQuery.find( selector, this[i], ret );
+
+ if ( i > 0 ) {
+ // Make sure that the results are unique
+ for ( var n = length; n < ret.length; n++ ) {
+ for ( var r = 0; r < length; r++ ) {
+ if ( ret[r] === ret[n] ) {
+ ret.splice(n--, 1);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ has: function( target ) {
+ var targets = jQuery( target );
+ return this.filter(function() {
+ for ( var i = 0, l = targets.length; i < l; i++ ) {
+ if ( jQuery.contains( this, targets[i] ) ) {
+ return true;
+ }
+ }
+ });
+ },
+
+ not: function( selector ) {
+ return this.pushStack( winnow(this, selector, false), "not", selector);
+ },
+
+ filter: function( selector ) {
+ return this.pushStack( winnow(this, selector, true), "filter", selector );
+ },
+
+ is: function( selector ) {
+ return !!selector && jQuery.filter( selector, this ).length > 0;
+ },
+
+ closest: function( selectors, context ) {
+ var ret = [], i, l, cur = this[0];
+
+ if ( jQuery.isArray( selectors ) ) {
+ var match, selector,
+ matches = {},
+ level = 1;
+
+ if ( cur && selectors.length ) {
+ for ( i = 0, l = selectors.length; i < l; i++ ) {
+ selector = selectors[i];
+
+ if ( !matches[selector] ) {
+ matches[selector] = jQuery.expr.match.POS.test( selector ) ?
+ jQuery( selector, context || this.context ) :
+ selector;
+ }
+ }
+
+ while ( cur && cur.ownerDocument && cur !== context ) {
+ for ( selector in matches ) {
+ match = matches[selector];
+
+ if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) {
+ ret.push({ selector: selector, elem: cur, level: level });
+ }
+ }
+
+ cur = cur.parentNode;
+ level++;
+ }
+ }
+
+ return ret;
+ }
+
+ var pos = POS.test( selectors ) ?
+ jQuery( selectors, context || this.context ) : null;
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ cur = this[i];
+
+ while ( cur ) {
+ if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
+ ret.push( cur );
+ break;
+
+ } else {
+ cur = cur.parentNode;
+ if ( !cur || !cur.ownerDocument || cur === context ) {
+ break;
+ }
+ }
+ }
+ }
+
+ ret = ret.length > 1 ? jQuery.unique(ret) : ret;
+
+ return this.pushStack( ret, "closest", selectors );
+ },
+
+ // Determine the position of an element within
+ // the matched set of elements
+ index: function( elem ) {
+ if ( !elem || typeof elem === "string" ) {
+ return jQuery.inArray( this[0],
+ // If it receives a string, the selector is used
+ // If it receives nothing, the siblings are used
+ elem ? jQuery( elem ) : this.parent().children() );
+ }
+ // Locate the position of the desired element
+ return jQuery.inArray(
+ // If it receives a jQuery object, the first element is used
+ elem.jquery ? elem[0] : elem, this );
+ },
+
+ add: function( selector, context ) {
+ var set = typeof selector === "string" ?
+ jQuery( selector, context ) :
+ jQuery.makeArray( selector ),
+ all = jQuery.merge( this.get(), set );
+
+ return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
+ all :
+ jQuery.unique( all ) );
+ },
+
+ andSelf: function() {
+ return this.add( this.prevObject );
+ }
+});
+
+// A painfully simple check to see if an element is disconnected
+// from a document (should be improved, where feasible).
+function isDisconnected( node ) {
+ return !node || !node.parentNode || node.parentNode.nodeType === 11;
+}
+
+jQuery.each({
+ parent: function( elem ) {
+ var parent = elem.parentNode;
+ return parent && parent.nodeType !== 11 ? parent : null;
+ },
+ parents: function( elem ) {
+ return jQuery.dir( elem, "parentNode" );
+ },
+ parentsUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "parentNode", until );
+ },
+ next: function( elem ) {
+ return jQuery.nth( elem, 2, "nextSibling" );
+ },
+ prev: function( elem ) {
+ return jQuery.nth( elem, 2, "previousSibling" );
+ },
+ nextAll: function( elem ) {
+ return jQuery.dir( elem, "nextSibling" );
+ },
+ prevAll: function( elem ) {
+ return jQuery.dir( elem, "previousSibling" );
+ },
+ nextUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "nextSibling", until );
+ },
+ prevUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "previousSibling", until );
+ },
+ siblings: function( elem ) {
+ return jQuery.sibling( elem.parentNode.firstChild, elem );
+ },
+ children: function( elem ) {
+ return jQuery.sibling( elem.firstChild );
+ },
+ contents: function( elem ) {
+ return jQuery.nodeName( elem, "iframe" ) ?
+ elem.contentDocument || elem.contentWindow.document :
+ jQuery.makeArray( elem.childNodes );
+ }
+}, function( name, fn ) {
+ jQuery.fn[ name ] = function( until, selector ) {
+ var ret = jQuery.map( this, fn, until ),
+ // The variable 'args' was introduced in
+ // https://github.com/jquery/jquery/commit/52a0238
+ // to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed.
+ // http://code.google.com/p/v8/issues/detail?id=1050
+ args = slice.call(arguments);
+
+ if ( !runtil.test( name ) ) {
+ selector = until;
+ }
+
+ if ( selector && typeof selector === "string" ) {
+ ret = jQuery.filter( selector, ret );
+ }
+
+ ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
+
+ if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) {
+ ret = ret.reverse();
+ }
+
+ return this.pushStack( ret, name, args.join(",") );
+ };
+});
+
+jQuery.extend({
+ filter: function( expr, elems, not ) {
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ return elems.length === 1 ?
+ jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
+ jQuery.find.matches(expr, elems);
+ },
+
+ dir: function( elem, dir, until ) {
+ var matched = [],
+ cur = elem[ dir ];
+
+ while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
+ if ( cur.nodeType === 1 ) {
+ matched.push( cur );
+ }
+ cur = cur[dir];
+ }
+ return matched;
+ },
+
+ nth: function( cur, result, dir, elem ) {
+ result = result || 1;
+ var num = 0;
+
+ for ( ; cur; cur = cur[dir] ) {
+ if ( cur.nodeType === 1 && ++num === result ) {
+ break;
+ }
+ }
+
+ return cur;
+ },
+
+ sibling: function( n, elem ) {
+ var r = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ r.push( n );
+ }
+ }
+
+ return r;
+ }
+});
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, keep ) {
+ if ( jQuery.isFunction( qualifier ) ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ var retVal = !!qualifier.call( elem, i, elem );
+ return retVal === keep;
+ });
+
+ } else if ( qualifier.nodeType ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ return (elem === qualifier) === keep;
+ });
+
+ } else if ( typeof qualifier === "string" ) {
+ var filtered = jQuery.grep(elements, function( elem ) {
+ return elem.nodeType === 1;
+ });
+
+ if ( isSimple.test( qualifier ) ) {
+ return jQuery.filter(qualifier, filtered, !keep);
+ } else {
+ qualifier = jQuery.filter( qualifier, filtered );
+ }
+ }
+
+ return jQuery.grep(elements, function( elem, i ) {
+ return (jQuery.inArray( elem, qualifier ) >= 0) === keep;
+ });
+}
+
+
+
+
+var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
+ rleadingWhitespace = /^\s+/,
+ rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
+ rtagName = /<([\w:]+)/,
+ rtbody = /<tbody/i,
+ rhtml = /<|&#?\w+;/,
+ rnocache = /<(?:script|object|embed|option|style)/i,
+ // checked="checked" or checked
+ rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+ wrapMap = {
+ option: [ 1, "<select multiple='multiple'>", "</select>" ],
+ legend: [ 1, "<fieldset>", "</fieldset>" ],
+ thead: [ 1, "<table>", "</table>" ],
+ tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+ td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+ col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
+ area: [ 1, "<map>", "</map>" ],
+ _default: [ 0, "", "" ]
+ };
+
+wrapMap.optgroup = wrapMap.option;
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// IE can't serialize <link> and <script> tags normally
+if ( !jQuery.support.htmlSerialize ) {
+ wrapMap._default = [ 1, "div<div>", "</div>" ];
+}
+
+jQuery.fn.extend({
+ text: function( text ) {
+ if ( jQuery.isFunction(text) ) {
+ return this.each(function(i) {
+ var self = jQuery( this );
+
+ self.text( text.call(this, i, self.text()) );
+ });
+ }
+
+ if ( typeof text !== "object" && text !== undefined ) {
+ return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
+ }
+
+ return jQuery.text( this );
+ },
+
+ wrapAll: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapAll( html.call(this, i) );
+ });
+ }
+
+ if ( this[0] ) {
+ // The elements to wrap the target around
+ var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
+
+ if ( this[0].parentNode ) {
+ wrap.insertBefore( this[0] );
+ }
+
+ wrap.map(function() {
+ var elem = this;
+
+ while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
+ elem = elem.firstChild;
+ }
+
+ return elem;
+ }).append(this);
+ }
+
+ return this;
+ },
+
+ wrapInner: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapInner( html.call(this, i) );
+ });
+ }
+
+ return this.each(function() {
+ var self = jQuery( this ),
+ contents = self.contents();
+
+ if ( contents.length ) {
+ contents.wrapAll( html );
+
+ } else {
+ self.append( html );
+ }
+ });
+ },
+
+ wrap: function( html ) {
+ return this.each(function() {
+ jQuery( this ).wrapAll( html );
+ });
+ },
+
+ unwrap: function() {
+ return this.parent().each(function() {
+ if ( !jQuery.nodeName( this, "body" ) ) {
+ jQuery( this ).replaceWith( this.childNodes );
+ }
+ }).end();
+ },
+
+ append: function() {
+ return this.domManip(arguments, true, function( elem ) {
+ if ( this.nodeType === 1 ) {
+ this.appendChild( elem );
+ }
+ });
+ },
+
+ prepend: function() {
+ return this.domManip(arguments, true, function( elem ) {
+ if ( this.nodeType === 1 ) {
+ this.insertBefore( elem, this.firstChild );
+ }
+ });
+ },
+
+ before: function() {
+ if ( this[0] && this[0].parentNode ) {
+ return this.domManip(arguments, false, function( elem ) {
+ this.parentNode.insertBefore( elem, this );
+ });
+ } else if ( arguments.length ) {
+ var set = jQuery(arguments[0]);
+ set.push.apply( set, this.toArray() );
+ return this.pushStack( set, "before", arguments );
+ }
+ },
+
+ after: function() {
+ if ( this[0] && this[0].parentNode ) {
+ return this.domManip(arguments, false, function( elem ) {
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ });
+ } else if ( arguments.length ) {
+ var set = this.pushStack( this, "after", arguments );
+ set.push.apply( set, jQuery(arguments[0]).toArray() );
+ return set;
+ }
+ },
+
+ // keepData is for internal use only--do not document
+ remove: function( selector, keepData ) {
+ for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+ if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
+ if ( !keepData && elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName("*") );
+ jQuery.cleanData( [ elem ] );
+ }
+
+ if ( elem.parentNode ) {
+ elem.parentNode.removeChild( elem );
+ }
+ }
+ }
+
+ return this;
+ },
+
+ empty: function() {
+ for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName("*") );
+ }
+
+ // Remove any remaining nodes
+ while ( elem.firstChild ) {
+ elem.removeChild( elem.firstChild );
+ }
+ }
+
+ return this;
+ },
+
+ clone: function( dataAndEvents, deepDataAndEvents ) {
+ dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+ deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+ return this.map( function () {
+ return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+ });
+ },
+
+ html: function( value ) {
+ if ( value === undefined ) {
+ return this[0] && this[0].nodeType === 1 ?
+ this[0].innerHTML.replace(rinlinejQuery, "") :
+ null;
+
+ // See if we can take a shortcut and just use innerHTML
+ } else if ( typeof value === "string" && !rnocache.test( value ) &&
+ (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
+ !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {
+
+ value = value.replace(rxhtmlTag, "<$1></$2>");
+
+ try {
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ if ( this[i].nodeType === 1 ) {
+ jQuery.cleanData( this[i].getElementsByTagName("*") );
+ this[i].innerHTML = value;
+ }
+ }
+
+ // If using innerHTML throws an exception, use the fallback method
+ } catch(e) {
+ this.empty().append( value );
+ }
+
+ } else if ( jQuery.isFunction( value ) ) {
+ this.each(function(i){
+ var self = jQuery( this );
+
+ self.html( value.call(this, i, self.html()) );
+ });
+
+ } else {
+ this.empty().append( value );
+ }
+
+ return this;
+ },
+
+ replaceWith: function( value ) {
+ if ( this[0] && this[0].parentNode ) {
+ // Make sure that the elements are removed from the DOM before they are inserted
+ // this can help fix replacing a parent with child elements
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function(i) {
+ var self = jQuery(this), old = self.html();
+ self.replaceWith( value.call( this, i, old ) );
+ });
+ }
+
+ if ( typeof value !== "string" ) {
+ value = jQuery( value ).detach();
+ }
+
+ return this.each(function() {
+ var next = this.nextSibling,
+ parent = this.parentNode;
+
+ jQuery( this ).remove();
+
+ if ( next ) {
+ jQuery(next).before( value );
+ } else {
+ jQuery(parent).append( value );
+ }
+ });
+ } else {
+ return this.length ?
+ this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :
+ this;
+ }
+ },
+
+ detach: function( selector ) {
+ return this.remove( selector, true );
+ },
+
+ domManip: function( args, table, callback ) {
+ var results, first, fragment, parent,
+ value = args[0],
+ scripts = [];
+
+ // We can't cloneNode fragments that contain checked, in WebKit
+ if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {
+ return this.each(function() {
+ jQuery(this).domManip( args, table, callback, true );
+ });
+ }
+
+ if ( jQuery.isFunction(value) ) {
+ return this.each(function(i) {
+ var self = jQuery(this);
+ args[0] = value.call(this, i, table ? self.html() : undefined);
+ self.domManip( args, table, callback );
+ });
+ }
+
+ if ( this[0] ) {
+ parent = value && value.parentNode;
+
+ // If we're in a fragment, just use that instead of building a new one
+ if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {
+ results = { fragment: parent };
+
+ } else {
+ results = jQuery.buildFragment( args, this, scripts );
+ }
+
+ fragment = results.fragment;
+
+ if ( fragment.childNodes.length === 1 ) {
+ first = fragment = fragment.firstChild;
+ } else {
+ first = fragment.firstChild;
+ }
+
+ if ( first ) {
+ table = table && jQuery.nodeName( first, "tr" );
+
+ for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) {
+ callback.call(
+ table ?
+ root(this[i], first) :
+ this[i],
+ // Make sure that we do not leak memory by inadvertently discarding
+ // the original fragment (which might have attached data) instead of
+ // using it; in addition, use the original fragment object for the last
+ // item instead of first because it can end up being emptied incorrectly
+ // in certain situations (Bug #8070).
+ // Fragments from the fragment cache must always be cloned and never used
+ // in place.
+ results.cacheable || (l > 1 && i < lastIndex) ?
+ jQuery.clone( fragment, true, true ) :
+ fragment
+ );
+ }
+ }
+
+ if ( scripts.length ) {
+ jQuery.each( scripts, evalScript );
+ }
+ }
+
+ return this;
+ }
+});
+
+function root( elem, cur ) {
+ return jQuery.nodeName(elem, "table") ?
+ (elem.getElementsByTagName("tbody")[0] ||
+ elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
+ elem;
+}
+
+function cloneCopyEvent( src, dest ) {
+
+ if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
+ return;
+ }
+
+ var internalKey = jQuery.expando,
+ oldData = jQuery.data( src ),
+ curData = jQuery.data( dest, oldData );
+
+ // Switch to use the internal data object, if it exists, for the next
+ // stage of data copying
+ if ( (oldData = oldData[ internalKey ]) ) {
+ var events = oldData.events;
+ curData = curData[ internalKey ] = jQuery.extend({}, oldData);
+
+ if ( events ) {
+ delete curData.handle;
+ curData.events = {};
+
+ for ( var type in events ) {
+ for ( var i = 0, l = events[ type ].length; i < l; i++ ) {
+ jQuery.event.add( dest, type + ( events[ type ][ i ].namespace ? "." : "" ) + events[ type ][ i ].namespace, events[ type ][ i ], events[ type ][ i ].data );
+ }
+ }
+ }
+ }
+}
+
+function cloneFixAttributes(src, dest) {
+ // We do not need to do anything for non-Elements
+ if ( dest.nodeType !== 1 ) {
+ return;
+ }
+
+ var nodeName = dest.nodeName.toLowerCase();
+
+ // clearAttributes removes the attributes, which we don't want,
+ // but also removes the attachEvent events, which we *do* want
+ dest.clearAttributes();
+
+ // mergeAttributes, in contrast, only merges back on the
+ // original attributes, not the events
+ dest.mergeAttributes(src);
+
+ // IE6-8 fail to clone children inside object elements that use
+ // the proprietary classid attribute value (rather than the type
+ // attribute) to identify the type of content to display
+ if ( nodeName === "object" ) {
+ dest.outerHTML = src.outerHTML;
+
+ } else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) {
+ // IE6-8 fails to persist the checked state of a cloned checkbox
+ // or radio button. Worse, IE6-7 fail to give the cloned element
+ // a checked appearance if the defaultChecked value isn't also set
+ if ( src.checked ) {
+ dest.defaultChecked = dest.checked = src.checked;
+ }
+
+ // IE6-7 get confused and end up setting the value of a cloned
+ // checkbox/radio button to an empty string instead of "on"
+ if ( dest.value !== src.value ) {
+ dest.value = src.value;
+ }
+
+ // IE6-8 fails to return the selected option to the default selected
+ // state when cloning options
+ } else if ( nodeName === "option" ) {
+ dest.selected = src.defaultSelected;
+
+ // IE6-8 fails to set the defaultValue to the correct value when
+ // cloning other types of input fields
+ } else if ( nodeName === "input" || nodeName === "textarea" ) {
+ dest.defaultValue = src.defaultValue;
+ }
+
+ // Event data gets referenced instead of copied if the expando
+ // gets copied too
+ dest.removeAttribute( jQuery.expando );
+}
+
+jQuery.buildFragment = function( args, nodes, scripts ) {
+ var fragment, cacheable, cacheresults,
+ doc = (nodes && nodes[0] ? nodes[0].ownerDocument || nodes[0] : document);
+
+ // Only cache "small" (1/2 KB) HTML strings that are associated with the main document
+ // Cloning options loses the selected state, so don't cache them
+ // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
+ // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
+ if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && doc === document &&
+ args[0].charAt(0) === "<" && !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) {
+
+ cacheable = true;
+ cacheresults = jQuery.fragments[ args[0] ];
+ if ( cacheresults ) {
+ if ( cacheresults !== 1 ) {
+ fragment = cacheresults;
+ }
+ }
+ }
+
+ if ( !fragment ) {
+ fragment = doc.createDocumentFragment();
+ jQuery.clean( args, doc, fragment, scripts );
+ }
+
+ if ( cacheable ) {
+ jQuery.fragments[ args[0] ] = cacheresults ? fragment : 1;
+ }
+
+ return { fragment: fragment, cacheable: cacheable };
+};
+
+jQuery.fragments = {};
+
+jQuery.each({
+ appendTo: "append",
+ prependTo: "prepend",
+ insertBefore: "before",
+ insertAfter: "after",
+ replaceAll: "replaceWith"
+}, function( name, original ) {
+ jQuery.fn[ name ] = function( selector ) {
+ var ret = [],
+ insert = jQuery( selector ),
+ parent = this.length === 1 && this[0].parentNode;
+
+ if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) {
+ insert[ original ]( this[0] );
+ return this;
+
+ } else {
+ for ( var i = 0, l = insert.length; i < l; i++ ) {
+ var elems = (i > 0 ? this.clone(true) : this).get();
+ jQuery( insert[i] )[ original ]( elems );
+ ret = ret.concat( elems );
+ }
+
+ return this.pushStack( ret, name, insert.selector );
+ }
+ };
+});
+
+function getAll( elem ) {
+ if ( "getElementsByTagName" in elem ) {
+ return elem.getElementsByTagName( "*" );
+
+ } else if ( "querySelectorAll" in elem ) {
+ return elem.querySelectorAll( "*" );
+
+ } else {
+ return [];
+ }
+}
+
+jQuery.extend({
+ clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+ var clone = elem.cloneNode(true),
+ srcElements,
+ destElements,
+ i;
+
+ if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
+ (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
+ // IE copies events bound via attachEvent when using cloneNode.
+ // Calling detachEvent on the clone will also remove the events
+ // from the original. In order to get around this, we use some
+ // proprietary methods to clear the events. Thanks to MooTools
+ // guys for this hotness.
+
+ cloneFixAttributes( elem, clone );
+
+ // Using Sizzle here is crazy slow, so we use getElementsByTagName
+ // instead
+ srcElements = getAll( elem );
+ destElements = getAll( clone );
+
+ // Weird iteration because IE will replace the length property
+ // with an element if you are cloning the body and one of the
+ // elements on the page has a name or id of "length"
+ for ( i = 0; srcElements[i]; ++i ) {
+ cloneFixAttributes( srcElements[i], destElements[i] );
+ }
+ }
+
+ // Copy the events from the original to the clone
+ if ( dataAndEvents ) {
+ cloneCopyEvent( elem, clone );
+
+ if ( deepDataAndEvents ) {
+ srcElements = getAll( elem );
+ destElements = getAll( clone );
+
+ for ( i = 0; srcElements[i]; ++i ) {
+ cloneCopyEvent( srcElements[i], destElements[i] );
+ }
+ }
+ }
+
+ // Return the cloned set
+ return clone;
+},
+ clean: function( elems, context, fragment, scripts ) {
+ context = context || document;
+
+ // !context.createElement fails in IE with an error but returns typeof 'object'
+ if ( typeof context.createElement === "undefined" ) {
+ context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+ }
+
+ var ret = [];
+
+ for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+ if ( typeof elem === "number" ) {
+ elem += "";
+ }
+
+ if ( !elem ) {
+ continue;
+ }
+
+ // Convert html string into DOM nodes
+ if ( typeof elem === "string" && !rhtml.test( elem ) ) {
+ elem = context.createTextNode( elem );
+
+ } else if ( typeof elem === "string" ) {
+ // Fix "XHTML"-style tags in all browsers
+ elem = elem.replace(rxhtmlTag, "<$1></$2>");
+
+ // Trim whitespace, otherwise indexOf won't work as expected
+ var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(),
+ wrap = wrapMap[ tag ] || wrapMap._default,
+ depth = wrap[0],
+ div = context.createElement("div");
+
+ // Go to html and back, then peel off extra wrappers
+ div.innerHTML = wrap[1] + elem + wrap[2];
+
+ // Move to the right depth
+ while ( depth-- ) {
+ div = div.lastChild;
+ }
+
+ // Remove IE's autoinserted <tbody> from table fragments
+ if ( !jQuery.support.tbody ) {
+
+ // String was a <table>, *may* have spurious <tbody>
+ var hasBody = rtbody.test(elem),
+ tbody = tag === "table" && !hasBody ?
+ div.firstChild && div.firstChild.childNodes :
+
+ // String was a bare <thead> or <tfoot>
+ wrap[1] === "<table>" && !hasBody ?
+ div.childNodes :
+ [];
+
+ for ( var j = tbody.length - 1; j >= 0 ; --j ) {
+ if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
+ tbody[ j ].parentNode.removeChild( tbody[ j ] );
+ }
+ }
+
+ }
+
+ // IE completely kills leading whitespace when innerHTML is used
+ if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
+ div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
+ }
+
+ elem = div.childNodes;
+ }
+
+ if ( elem.nodeType ) {
+ ret.push( elem );
+ } else {
+ ret = jQuery.merge( ret, elem );
+ }
+ }
+
+ if ( fragment ) {
+ for ( i = 0; ret[i]; i++ ) {
+ if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
+ scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
+
+ } else {
+ if ( ret[i].nodeType === 1 ) {
+ ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) );
+ }
+ fragment.appendChild( ret[i] );
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ cleanData: function( elems ) {
+ var data, id, cache = jQuery.cache, internalKey = jQuery.expando, special = jQuery.event.special,
+ deleteExpando = jQuery.support.deleteExpando;
+
+ for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+ if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
+ continue;
+ }
+
+ id = elem[ jQuery.expando ];
+
+ if ( id ) {
+ data = cache[ id ] && cache[ id ][ internalKey ];
+
+ if ( data && data.events ) {
+ for ( var type in data.events ) {
+ if ( special[ type ] ) {
+ jQuery.event.remove( elem, type );
+
+ // This is a shortcut to avoid jQuery.event.remove's overhead
+ } else {
+ jQuery.removeEvent( elem, type, data.handle );
+ }
+ }
+
+ // Null the DOM reference to avoid IE6/7/8 leak (#7054)
+ if ( data.handle ) {
+ data.handle.elem = null;
+ }
+ }
+
+ if ( deleteExpando ) {
+ delete elem[ jQuery.expando ];
+
+ } else if ( elem.removeAttribute ) {
+ elem.removeAttribute( jQuery.expando );
+ }
+
+ delete cache[ id ];
+ }
+ }
+ }
+});
+
+function evalScript( i, elem ) {
+ if ( elem.src ) {
+ jQuery.ajax({
+ url: elem.src,
+ async: false,
+ dataType: "script"
+ });
+ } else {
+ jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
+ }
+
+ if ( elem.parentNode ) {
+ elem.parentNode.removeChild( elem );
+ }
+}
+
+
+
+
+var ralpha = /alpha\([^)]*\)/i,
+ ropacity = /opacity=([^)]*)/,
+ rdashAlpha = /-([a-z])/ig,
+ // fixed for IE9, see #8346
+ rupper = /([A-Z]|^ms)/g,
+ rnumpx = /^-?\d+(?:px)?$/i,
+ rnum = /^-?\d/,
+
+ cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+ cssWidth = [ "Left", "Right" ],
+ cssHeight = [ "Top", "Bottom" ],
+ curCSS,
+
+ getComputedStyle,
+ currentStyle,
+
+ fcamelCase = function( all, letter ) {
+ return letter.toUpperCase();
+ };
+
+jQuery.fn.css = function( name, value ) {
+ // Setting 'undefined' is a no-op
+ if ( arguments.length === 2 && value === undefined ) {
+ return this;
+ }
+
+ return jQuery.access( this, name, value, true, function( elem, name, value ) {
+ return value !== undefined ?
+ jQuery.style( elem, name, value ) :
+ jQuery.css( elem, name );
+ });
+};
+
+jQuery.extend({
+ // Add in style property hooks for overriding the default
+ // behavior of getting and setting a style property
+ cssHooks: {
+ opacity: {
+ get: function( elem, computed ) {
+ if ( computed ) {
+ // We should always get a number back from opacity
+ var ret = curCSS( elem, "opacity", "opacity" );
+ return ret === "" ? "1" : ret;
+
+ } else {
+ return elem.style.opacity;
+ }
+ }
+ }
+ },
+
+ // Exclude the following css properties to add px
+ cssNumber: {
+ "zIndex": true,
+ "fontWeight": true,
+ "opacity": true,
+ "zoom": true,
+ "lineHeight": true
+ },
+
+ // Add in properties whose names you wish to fix before
+ // setting or getting the value
+ cssProps: {
+ // normalize float css property
+ "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
+ },
+
+ // Get and set the style property on a DOM Node
+ style: function( elem, name, value, extra ) {
+ // Don't set styles on text and comment nodes
+ if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+ return;
+ }
+
+ // Make sure that we're working with the right name
+ var ret, origName = jQuery.camelCase( name ),
+ style = elem.style, hooks = jQuery.cssHooks[ origName ];
+
+ name = jQuery.cssProps[ origName ] || origName;
+
+ // Check if we're setting a value
+ if ( value !== undefined ) {
+ // Make sure that NaN and null values aren't set. See: #7116
+ if ( typeof value === "number" && isNaN( value ) || value == null ) {
+ return;
+ }
+
+ // If a number was passed in, add 'px' to the (except for certain CSS properties)
+ if ( typeof value === "number" && !jQuery.cssNumber[ origName ] ) {
+ value += "px";
+ }
+
+ // If a hook was provided, use that value, otherwise just set the specified value
+ if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {
+ // Wrapped to prevent IE from throwing errors when 'invalid' values are provided
+ // Fixes bug #5509
+ try {
+ style[ name ] = value;
+ } catch(e) {}
+ }
+
+ } else {
+ // If a hook was provided get the non-computed value from there
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
+ return ret;
+ }
+
+ // Otherwise just get the value from the style object
+ return style[ name ];
+ }
+ },
+
+ css: function( elem, name, extra ) {
+ // Make sure that we're working with the right name
+ var ret, origName = jQuery.camelCase( name ),
+ hooks = jQuery.cssHooks[ origName ];
+
+ name = jQuery.cssProps[ origName ] || origName;
+
+ // If a hook was provided get the computed value from there
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) {
+ return ret;
+
+ // Otherwise, if a way to get the computed value exists, use that
+ } else if ( curCSS ) {
+ return curCSS( elem, name, origName );
+ }
+ },
+
+ // A method for quickly swapping in/out CSS properties to get correct calculations
+ swap: function( elem, options, callback ) {
+ var old = {};
+
+ // Remember the old values, and insert the new ones
+ for ( var name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ callback.call( elem );
+
+ // Revert the old values
+ for ( name in options ) {
+ elem.style[ name ] = old[ name ];
+ }
+ },
+
+ camelCase: function( string ) {
+ return string.replace( rdashAlpha, fcamelCase );
+ }
+});
+
+// DEPRECATED, Use jQuery.css() instead
+jQuery.curCSS = jQuery.css;
+
+jQuery.each(["height", "width"], function( i, name ) {
+ jQuery.cssHooks[ name ] = {
+ get: function( elem, computed, extra ) {
+ var val;
+
+ if ( computed ) {
+ if ( elem.offsetWidth !== 0 ) {
+ val = getWH( elem, name, extra );
+
+ } else {
+ jQuery.swap( elem, cssShow, function() {
+ val = getWH( elem, name, extra );
+ });
+ }
+
+ if ( val <= 0 ) {
+ val = curCSS( elem, name, name );
+
+ if ( val === "0px" && currentStyle ) {
+ val = currentStyle( elem, name, name );
+ }
+
+ if ( val != null ) {
+ // Should return "auto" instead of 0, use 0 for
+ // temporary backwards-compat
+ return val === "" || val === "auto" ? "0px" : val;
+ }
+ }
+
+ if ( val < 0 || val == null ) {
+ val = elem.style[ name ];
+
+ // Should return "auto" instead of 0, use 0 for
+ // temporary backwards-compat
+ return val === "" || val === "auto" ? "0px" : val;
+ }
+
+ return typeof val === "string" ? val : val + "px";
+ }
+ },
+
+ set: function( elem, value ) {
+ if ( rnumpx.test( value ) ) {
+ // ignore negative width and height values #1599
+ value = parseFloat(value);
+
+ if ( value >= 0 ) {
+ return value + "px";
+ }
+
+ } else {
+ return value;
+ }
+ }
+ };
+});
+
+if ( !jQuery.support.opacity ) {
+ jQuery.cssHooks.opacity = {
+ get: function( elem, computed ) {
+ // IE uses filters for opacity
+ return ropacity.test((computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "") ?
+ (parseFloat(RegExp.$1) / 100) + "" :
+ computed ? "1" : "";
+ },
+
+ set: function( elem, value ) {
+ var style = elem.style;
+
+ // IE has trouble with opacity if it does not have layout
+ // Force it by setting the zoom level
+ style.zoom = 1;
+
+ // Set the alpha filter to set the opacity
+ var opacity = jQuery.isNaN(value) ?
+ "" :
+ "alpha(opacity=" + value * 100 + ")",
+ filter = style.filter || "";
+
+ style.filter = ralpha.test(filter) ?
+ filter.replace(ralpha, opacity) :
+ style.filter + ' ' + opacity;
+ }
+ };
+}
+
+jQuery(function() {
+ // This hook cannot be added until DOM ready because the support test
+ // for it is not run until after DOM ready
+ if ( !jQuery.support.reliableMarginRight ) {
+ jQuery.cssHooks.marginRight = {
+ get: function( elem, computed ) {
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ // Work around by temporarily setting element display to inline-block
+ var ret;
+ jQuery.swap( elem, { "display": "inline-block" }, function() {
+ if ( computed ) {
+ ret = curCSS( elem, "margin-right", "marginRight" );
+ } else {
+ ret = elem.style.marginRight;
+ }
+ });
+ return ret;
+ }
+ };
+ }
+});
+
+if ( document.defaultView && document.defaultView.getComputedStyle ) {
+ getComputedStyle = function( elem, newName, name ) {
+ var ret, defaultView, computedStyle;
+
+ name = name.replace( rupper, "-$1" ).toLowerCase();
+
+ if ( !(defaultView = elem.ownerDocument.defaultView) ) {
+ return undefined;
+ }
+
+ if ( (computedStyle = defaultView.getComputedStyle( elem, null )) ) {
+ ret = computedStyle.getPropertyValue( name );
+ if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
+ ret = jQuery.style( elem, name );
+ }
+ }
+
+ return ret;
+ };
+}
+
+if ( document.documentElement.currentStyle ) {
+ currentStyle = function( elem, name ) {
+ var left,
+ ret = elem.currentStyle && elem.currentStyle[ name ],
+ rsLeft = elem.runtimeStyle && elem.runtimeStyle[ name ],
+ style = elem.style;
+
+ // From the awesome hack by Dean Edwards
+ // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+ // If we're not dealing with a regular pixel number
+ // but a number that has a weird ending, we need to convert it to pixels
+ if ( !rnumpx.test( ret ) && rnum.test( ret ) ) {
+ // Remember the original values
+ left = style.left;
+
+ // Put in the new values to get a computed value out
+ if ( rsLeft ) {
+ elem.runtimeStyle.left = elem.currentStyle.left;
+ }
+ style.left = name === "fontSize" ? "1em" : (ret || 0);
+ ret = style.pixelLeft + "px";
+
+ // Revert the changed values
+ style.left = left;
+ if ( rsLeft ) {
+ elem.runtimeStyle.left = rsLeft;
+ }
+ }
+
+ return ret === "" ? "auto" : ret;
+ };
+}
+
+curCSS = getComputedStyle || currentStyle;
+
+function getWH( elem, name, extra ) {
+ var which = name === "width" ? cssWidth : cssHeight,
+ val = name === "width" ? elem.offsetWidth : elem.offsetHeight;
+
+ if ( extra === "border" ) {
+ return val;
+ }
+
+ jQuery.each( which, function() {
+ if ( !extra ) {
+ val -= parseFloat(jQuery.css( elem, "padding" + this )) || 0;
+ }
+
+ if ( extra === "margin" ) {
+ val += parseFloat(jQuery.css( elem, "margin" + this )) || 0;
+
+ } else {
+ val -= parseFloat(jQuery.css( elem, "border" + this + "Width" )) || 0;
+ }
+ });
+
+ return val;
+}
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.hidden = function( elem ) {
+ var width = elem.offsetWidth,
+ height = elem.offsetHeight;
+
+ return (width === 0 && height === 0) || (!jQuery.support.reliableHiddenOffsets && (elem.style.display || jQuery.css( elem, "display" )) === "none");
+ };
+
+ jQuery.expr.filters.visible = function( elem ) {
+ return !jQuery.expr.filters.hidden( elem );
+ };
+}
+
+
+
+
+var r20 = /%20/g,
+ rbracket = /\[\]$/,
+ rCRLF = /\r?\n/g,
+ rhash = /#.*$/,
+ rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
+ rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
+ // #7653, #8125, #8152: local protocol detection
+ rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|widget):$/,
+ rnoContent = /^(?:GET|HEAD)$/,
+ rprotocol = /^\/\//,
+ rquery = /\?/,
+ rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
+ rselectTextarea = /^(?:select|textarea)/i,
+ rspacesAjax = /\s+/,
+ rts = /([?&])_=[^&]*/,
+ rucHeaders = /(^|\-)([a-z])/g,
+ rucHeadersFunc = function( _, $1, $2 ) {
+ return $1 + $2.toUpperCase();
+ },
+ rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,
+
+ // Keep a copy of the old load method
+ _load = jQuery.fn.load,
+
+ /* Prefilters
+ * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+ * 2) These are called:
+ * - BEFORE asking for a transport
+ * - AFTER param serialization (s.data is a string if s.processData is true)
+ * 3) key is the dataType
+ * 4) the catchall symbol "*" can be used
+ * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+ */
+ prefilters = {},
+
+ /* Transports bindings
+ * 1) key is the dataType
+ * 2) the catchall symbol "*" can be used
+ * 3) selection will start with transport dataType and THEN go to "*" if needed
+ */
+ transports = {},
+
+ // Document location
+ ajaxLocation,
+
+ // Document location segments
+ ajaxLocParts;
+
+// #8138, IE may throw an exception when accessing
+// a field from document.location if document.domain has been set
+try {
+ ajaxLocation = document.location.href;
+} catch( e ) {
+ // Use the href attribute of an A element
+ // since IE will modify it given document.location
+ ajaxLocation = document.createElement( "a" );
+ ajaxLocation.href = "";
+ ajaxLocation = ajaxLocation.href;
+}
+
+// Segment location into parts
+ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+ // dataTypeExpression is optional and defaults to "*"
+ return function( dataTypeExpression, func ) {
+
+ if ( typeof dataTypeExpression !== "string" ) {
+ func = dataTypeExpression;
+ dataTypeExpression = "*";
+ }
+
+ if ( jQuery.isFunction( func ) ) {
+ var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ),
+ i = 0,
+ length = dataTypes.length,
+ dataType,
+ list,
+ placeBefore;
+
+ // For each dataType in the dataTypeExpression
+ for(; i < length; i++ ) {
+ dataType = dataTypes[ i ];
+ // We control if we're asked to add before
+ // any existing element
+ placeBefore = /^\+/.test( dataType );
+ if ( placeBefore ) {
+ dataType = dataType.substr( 1 ) || "*";
+ }
+ list = structure[ dataType ] = structure[ dataType ] || [];
+ // then we add to the structure accordingly
+ list[ placeBefore ? "unshift" : "push" ]( func );
+ }
+ }
+ };
+}
+
+//Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR,
+ dataType /* internal */, inspected /* internal */ ) {
+
+ dataType = dataType || options.dataTypes[ 0 ];
+ inspected = inspected || {};
+
+ inspected[ dataType ] = true;
+
+ var list = structure[ dataType ],
+ i = 0,
+ length = list ? list.length : 0,
+ executeOnly = ( structure === prefilters ),
+ selection;
+
+ for(; i < length && ( executeOnly || !selection ); i++ ) {
+ selection = list[ i ]( options, originalOptions, jqXHR );
+ // If we got redirected to another dataType
+ // we try there if executing only and not done already
+ if ( typeof selection === "string" ) {
+ if ( !executeOnly || inspected[ selection ] ) {
+ selection = undefined;
+ } else {
+ options.dataTypes.unshift( selection );
+ selection = inspectPrefiltersOrTransports(
+ structure, options, originalOptions, jqXHR, selection, inspected );
+ }
+ }
+ }
+ // If we're only executing or nothing was selected
+ // we try the catchall dataType if not done already
+ if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) {
+ selection = inspectPrefiltersOrTransports(
+ structure, options, originalOptions, jqXHR, "*", inspected );
+ }
+ // unnecessary when only executing (prefilters)
+ // but it'll be ignored by the caller in that case
+ return selection;
+}
+
+jQuery.fn.extend({
+ load: function( url, params, callback ) {
+ if ( typeof url !== "string" && _load ) {
+ return _load.apply( this, arguments );
+
+ // Don't do a request if no elements are being requested
+ } else if ( !this.length ) {
+ return this;
+ }
+
+ var off = url.indexOf( " " );
+ if ( off >= 0 ) {
+ var selector = url.slice( off, url.length );
+ url = url.slice( 0, off );
+ }
+
+ // Default to a GET request
+ var type = "GET";
+
+ // If the second parameter was provided
+ if ( params ) {
+ // If it's a function
+ if ( jQuery.isFunction( params ) ) {
+ // We assume that it's the callback
+ callback = params;
+ params = undefined;
+
+ // Otherwise, build a param string
+ } else if ( typeof params === "object" ) {
+ params = jQuery.param( params, jQuery.ajaxSettings.traditional );
+ type = "POST";
+ }
+ }
+
+ var self = this;
+
+ // Request the remote document
+ jQuery.ajax({
+ url: url,
+ type: type,
+ dataType: "html",
+ data: params,
+ // Complete callback (responseText is used internally)
+ complete: function( jqXHR, status, responseText ) {
+ // Store the response as specified by the jqXHR object
+ responseText = jqXHR.responseText;
+ // If successful, inject the HTML into all the matched elements
+ if ( jqXHR.isResolved() ) {
+ // #4825: Get the actual response in case
+ // a dataFilter is present in ajaxSettings
+ jqXHR.done(function( r ) {
+ responseText = r;
+ });
+ // See if a selector was specified
+ self.html( selector ?
+ // Create a dummy div to hold the results
+ jQuery("<div>")
+ // inject the contents of the document in, removing the scripts
+ // to avoid any 'Permission Denied' errors in IE
+ .append(responseText.replace(rscript, ""))
+
+ // Locate the specified elements
+ .find(selector) :
+
+ // If not, just inject the full result
+ responseText );
+ }
+
+ if ( callback ) {
+ self.each( callback, [ responseText, status, jqXHR ] );
+ }
+ }
+ });
+
+ return this;
+ },
+
+ serialize: function() {
+ return jQuery.param( this.serializeArray() );
+ },
+
+ serializeArray: function() {
+ return this.map(function(){
+ return this.elements ? jQuery.makeArray( this.elements ) : this;
+ })
+ .filter(function(){
+ return this.name && !this.disabled &&
+ ( this.checked || rselectTextarea.test( this.nodeName ) ||
+ rinput.test( this.type ) );
+ })
+ .map(function( i, elem ){
+ var val = jQuery( this ).val();
+
+ return val == null ?
+ null :
+ jQuery.isArray( val ) ?
+ jQuery.map( val, function( val, i ){
+ return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }) :
+ { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }).get();
+ }
+});
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){
+ jQuery.fn[ o ] = function( f ){
+ return this.bind( o, f );
+ };
+} );
+
+jQuery.each( [ "get", "post" ], function( i, method ) {
+ jQuery[ method ] = function( url, data, callback, type ) {
+ // shift arguments if data argument was omitted
+ if ( jQuery.isFunction( data ) ) {
+ type = type || callback;
+ callback = data;
+ data = undefined;
+ }
+
+ return jQuery.ajax({
+ type: method,
+ url: url,
+ data: data,
+ success: callback,
+ dataType: type
+ });
+ };
+} );
+
+jQuery.extend({
+
+ getScript: function( url, callback ) {
+ return jQuery.get( url, undefined, callback, "script" );
+ },
+
+ getJSON: function( url, data, callback ) {
+ return jQuery.get( url, data, callback, "json" );
+ },
+
+ // Creates a full fledged settings object into target
+ // with both ajaxSettings and settings fields.
+ // If target is omitted, writes into ajaxSettings.
+ ajaxSetup: function ( target, settings ) {
+ if ( !settings ) {
+ // Only one parameter, we extend ajaxSettings
+ settings = target;
+ target = jQuery.extend( true, jQuery.ajaxSettings, settings );
+ } else {
+ // target was provided, we extend into it
+ jQuery.extend( true, target, jQuery.ajaxSettings, settings );
+ }
+ // Flatten fields we don't want deep extended
+ for( var field in { context: 1, url: 1 } ) {
+ if ( field in settings ) {
+ target[ field ] = settings[ field ];
+ } else if( field in jQuery.ajaxSettings ) {
+ target[ field ] = jQuery.ajaxSettings[ field ];
+ }
+ }
+ return target;
+ },
+
+ ajaxSettings: {
+ url: ajaxLocation,
+ isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
+ global: true,
+ type: "GET",
+ contentType: "application/x-www-form-urlencoded",
+ processData: true,
+ async: true,
+ /*
+ timeout: 0,
+ data: null,
+ dataType: null,
+ username: null,
+ password: null,
+ cache: null,
+ traditional: false,
+ headers: {},
+ */
+
+ accepts: {
+ xml: "application/xml, text/xml",
+ html: "text/html",
+ text: "text/plain",
+ json: "application/json, text/javascript",
+ "*": "*/*"
+ },
+
+ contents: {
+ xml: /xml/,
+ html: /html/,
+ json: /json/
+ },
+
+ responseFields: {
+ xml: "responseXML",
+ text: "responseText"
+ },
+
+ // List of data converters
+ // 1) key format is "source_type destination_type" (a single space in-between)
+ // 2) the catchall symbol "*" can be used for source_type
+ converters: {
+
+ // Convert anything to text
+ "* text": window.String,
+
+ // Text to html (true = no transformation)
+ "text html": true,
+
+ // Evaluate text as a json expression
+ "text json": jQuery.parseJSON,
+
+ // Parse text as xml
+ "text xml": jQuery.parseXML
+ }
+ },
+
+ ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+ ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+ // Main method
+ ajax: function( url, options ) {
+
+ // If url is an object, simulate pre-1.5 signature
+ if ( typeof url === "object" ) {
+ options = url;
+ url = undefined;
+ }
+
+ // Force options to be an object
+ options = options || {};
+
+ var // Create the final options object
+ s = jQuery.ajaxSetup( {}, options ),
+ // Callbacks context
+ callbackContext = s.context || s,
+ // Context for global events
+ // It's the callbackContext if one was provided in the options
+ // and if it's a DOM node or a jQuery collection
+ globalEventContext = callbackContext !== s &&
+ ( callbackContext.nodeType || callbackContext instanceof jQuery ) ?
+ jQuery( callbackContext ) : jQuery.event,
+ // Deferreds
+ deferred = jQuery.Deferred(),
+ completeDeferred = jQuery._Deferred(),
+ // Status-dependent callbacks
+ statusCode = s.statusCode || {},
+ // ifModified key
+ ifModifiedKey,
+ // Headers (they are sent all at once)
+ requestHeaders = {},
+ // Response headers
+ responseHeadersString,
+ responseHeaders,
+ // transport
+ transport,
+ // timeout handle
+ timeoutTimer,
+ // Cross-domain detection vars
+ parts,
+ // The jqXHR state
+ state = 0,
+ // To know if global events are to be dispatched
+ fireGlobals,
+ // Loop variable
+ i,
+ // Fake xhr
+ jqXHR = {
+
+ readyState: 0,
+
+ // Caches the header
+ setRequestHeader: function( name, value ) {
+ if ( !state ) {
+ requestHeaders[ name.toLowerCase().replace( rucHeaders, rucHeadersFunc ) ] = value;
+ }
+ return this;
+ },
+
+ // Raw string
+ getAllResponseHeaders: function() {
+ return state === 2 ? responseHeadersString : null;
+ },
+
+ // Builds headers hashtable if needed
+ getResponseHeader: function( key ) {
+ var match;
+ if ( state === 2 ) {
+ if ( !responseHeaders ) {
+ responseHeaders = {};
+ while( ( match = rheaders.exec( responseHeadersString ) ) ) {
+ responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+ }
+ }
+ match = responseHeaders[ key.toLowerCase() ];
+ }
+ return match === undefined ? null : match;
+ },
+
+ // Overrides response content-type header
+ overrideMimeType: function( type ) {
+ if ( !state ) {
+ s.mimeType = type;
+ }
+ return this;
+ },
+
+ // Cancel the request
+ abort: function( statusText ) {
+ statusText = statusText || "abort";
+ if ( transport ) {
+ transport.abort( statusText );
+ }
+ done( 0, statusText );
+ return this;
+ }
+ };
+
+ // Callback for when everything is done
+ // It is defined here because jslint complains if it is declared
+ // at the end of the function (which would be more logical and readable)
+ function done( status, statusText, responses, headers ) {
+
+ // Called once
+ if ( state === 2 ) {
+ return;
+ }
+
+ // State is "done" now
+ state = 2;
+
+ // Clear timeout if it exists
+ if ( timeoutTimer ) {
+ clearTimeout( timeoutTimer );
+ }
+
+ // Dereference transport for early garbage collection
+ // (no matter how long the jqXHR object will be used)
+ transport = undefined;
+
+ // Cache response headers
+ responseHeadersString = headers || "";
+
+ // Set readyState
+ jqXHR.readyState = status ? 4 : 0;
+
+ var isSuccess,
+ success,
+ error,
+ response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined,
+ lastModified,
+ etag;
+
+ // If successful, handle type chaining
+ if ( status >= 200 && status < 300 || status === 304 ) {
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+
+ if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) {
+ jQuery.lastModified[ ifModifiedKey ] = lastModified;
+ }
+ if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) {
+ jQuery.etag[ ifModifiedKey ] = etag;
+ }
+ }
+
+ // If not modified
+ if ( status === 304 ) {
+
+ statusText = "notmodified";
+ isSuccess = true;
+
+ // If we have data
+ } else {
+
+ try {
+ success = ajaxConvert( s, response );
+ statusText = "success";
+ isSuccess = true;
+ } catch(e) {
+ // We have a parsererror
+ statusText = "parsererror";
+ error = e;
+ }
+ }
+ } else {
+ // We extract error from statusText
+ // then normalize statusText and status for non-aborts
+ error = statusText;
+ if( !statusText || status ) {
+ statusText = "error";
+ if ( status < 0 ) {
+ status = 0;
+ }
+ }
+ }
+
+ // Set data for the fake xhr object
+ jqXHR.status = status;
+ jqXHR.statusText = statusText;
+
+ // Success/Error
+ if ( isSuccess ) {
+ deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+ } else {
+ deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+ }
+
+ // Status-dependent callbacks
+ jqXHR.statusCode( statusCode );
+ statusCode = undefined;
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ),
+ [ jqXHR, s, isSuccess ? success : error ] );
+ }
+
+ // Complete
+ completeDeferred.resolveWith( callbackContext, [ jqXHR, statusText ] );
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxComplete", [ jqXHR, s] );
+ // Handle the global AJAX counter
+ if ( !( --jQuery.active ) ) {
+ jQuery.event.trigger( "ajaxStop" );
+ }
+ }
+ }
+
+ // Attach deferreds
+ deferred.promise( jqXHR );
+ jqXHR.success = jqXHR.done;
+ jqXHR.error = jqXHR.fail;
+ jqXHR.complete = completeDeferred.done;
+
+ // Status-dependent callbacks
+ jqXHR.statusCode = function( map ) {
+ if ( map ) {
+ var tmp;
+ if ( state < 2 ) {
+ for( tmp in map ) {
+ statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ];
+ }
+ } else {
+ tmp = map[ jqXHR.status ];
+ jqXHR.then( tmp, tmp );
+ }
+ }
+ return this;
+ };
+
+ // Remove hash character (#7531: and string promotion)
+ // Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
+ // We also use the url parameter if available
+ s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
+
+ // Extract dataTypes list
+ s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax );
+
+ // Determine if a cross-domain request is in order
+ if ( s.crossDomain == null ) {
+ parts = rurl.exec( s.url.toLowerCase() );
+ s.crossDomain = !!( parts &&
+ ( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] ||
+ ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
+ ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
+ );
+ }
+
+ // Convert data if not already a string
+ if ( s.data && s.processData && typeof s.data !== "string" ) {
+ s.data = jQuery.param( s.data, s.traditional );
+ }
+
+ // Apply prefilters
+ inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+ // If request was aborted inside a prefiler, stop there
+ if ( state === 2 ) {
+ return false;
+ }
+
+ // We can fire global events as of now if asked to
+ fireGlobals = s.global;
+
+ // Uppercase the type
+ s.type = s.type.toUpperCase();
+
+ // Determine if request has content
+ s.hasContent = !rnoContent.test( s.type );
+
+ // Watch for a new set of requests
+ if ( fireGlobals && jQuery.active++ === 0 ) {
+ jQuery.event.trigger( "ajaxStart" );
+ }
+
+ // More options handling for requests with no content
+ if ( !s.hasContent ) {
+
+ // If data is available, append data to url
+ if ( s.data ) {
+ s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data;
+ }
+
+ // Get ifModifiedKey before adding the anti-cache parameter
+ ifModifiedKey = s.url;
+
+ // Add anti-cache in url if needed
+ if ( s.cache === false ) {
+
+ var ts = jQuery.now(),
+ // try replacing _= if it is there
+ ret = s.url.replace( rts, "$1_=" + ts );
+
+ // if nothing was replaced, add timestamp to the end
+ s.url = ret + ( (ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" );
+ }
+ }
+
+ // Set the correct header, if data is being sent
+ if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+ requestHeaders[ "Content-Type" ] = s.contentType;
+ }
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ ifModifiedKey = ifModifiedKey || s.url;
+ if ( jQuery.lastModified[ ifModifiedKey ] ) {
+ requestHeaders[ "If-Modified-Since" ] = jQuery.lastModified[ ifModifiedKey ];
+ }
+ if ( jQuery.etag[ ifModifiedKey ] ) {
+ requestHeaders[ "If-None-Match" ] = jQuery.etag[ ifModifiedKey ];
+ }
+ }
+
+ // Set the Accepts header for the server, depending on the dataType
+ requestHeaders.Accept = s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+ s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) :
+ s.accepts[ "*" ];
+
+ // Check for headers option
+ for ( i in s.headers ) {
+ jqXHR.setRequestHeader( i, s.headers[ i ] );
+ }
+
+ // Allow custom headers/mimetypes and early abort
+ if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+ // Abort if not done already
+ jqXHR.abort();
+ return false;
+
+ }
+
+ // Install callbacks on deferreds
+ for ( i in { success: 1, error: 1, complete: 1 } ) {
+ jqXHR[ i ]( s[ i ] );
+ }
+
+ // Get transport
+ transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+ // If no transport, we auto-abort
+ if ( !transport ) {
+ done( -1, "No Transport" );
+ } else {
+ jqXHR.readyState = 1;
+ // Send global event
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+ }
+ // Timeout
+ if ( s.async && s.timeout > 0 ) {
+ timeoutTimer = setTimeout( function(){
+ jqXHR.abort( "timeout" );
+ }, s.timeout );
+ }
+
+ try {
+ state = 1;
+ transport.send( requestHeaders, done );
+ } catch (e) {
+ // Propagate exception as error if not done
+ if ( status < 2 ) {
+ done( -1, e );
+ // Simply rethrow otherwise
+ } else {
+ jQuery.error( e );
+ }
+ }
+ }
+
+ return jqXHR;
+ },
+
+ // Serialize an array of form elements or a set of
+ // key/values into a query string
+ param: function( a, traditional ) {
+ var s = [],
+ add = function( key, value ) {
+ // If value is a function, invoke it and return its value
+ value = jQuery.isFunction( value ) ? value() : value;
+ s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+ };
+
+ // Set traditional to true for jQuery <= 1.3.2 behavior.
+ if ( traditional === undefined ) {
+ traditional = jQuery.ajaxSettings.traditional;
+ }
+
+ // If an array was passed in, assume that it is an array of form elements.
+ if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+ // Serialize the form elements
+ jQuery.each( a, function() {
+ add( this.name, this.value );
+ } );
+
+ } else {
+ // If traditional, encode the "old" way (the way 1.3.2 or older
+ // did it), otherwise encode params recursively.
+ for ( var prefix in a ) {
+ buildParams( prefix, a[ prefix ], traditional, add );
+ }
+ }
+
+ // Return the resulting serialization
+ return s.join( "&" ).replace( r20, "+" );
+ }
+});
+
+function buildParams( prefix, obj, traditional, add ) {
+ if ( jQuery.isArray( obj ) && obj.length ) {
+ // Serialize array item.
+ jQuery.each( obj, function( i, v ) {
+ if ( traditional || rbracket.test( prefix ) ) {
+ // Treat each array item as a scalar.
+ add( prefix, v );
+
+ } else {
+ // If array item is non-scalar (array or object), encode its
+ // numeric index to resolve deserialization ambiguity issues.
+ // Note that rack (as of 1.0.0) can't currently deserialize
+ // nested arrays properly, and attempting to do so may cause
+ // a server error. Possible fixes are to modify rack's
+ // deserialization algorithm or to provide an option or flag
+ // to force array serialization to be shallow.
+ buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add );
+ }
+ });
+
+ } else if ( !traditional && obj != null && typeof obj === "object" ) {
+ // If we see an array here, it is empty and should be treated as an empty
+ // object
+ if ( jQuery.isArray( obj ) || jQuery.isEmptyObject( obj ) ) {
+ add( prefix, "" );
+
+ // Serialize object item.
+ } else {
+ for ( var name in obj ) {
+ buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+ }
+ }
+
+ } else {
+ // Serialize scalar item.
+ add( prefix, obj );
+ }
+}
+
+// This is still on the jQuery object... for now
+// Want to move this to jQuery.ajax some day
+jQuery.extend({
+
+ // Counter for holding the number of active queries
+ active: 0,
+
+ // Last-Modified header cache for next request
+ lastModified: {},
+ etag: {}
+
+});
+
+/* Handles responses to an ajax request:
+ * - sets all responseXXX fields accordingly
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+
+ var contents = s.contents,
+ dataTypes = s.dataTypes,
+ responseFields = s.responseFields,
+ ct,
+ type,
+ finalDataType,
+ firstDataType;
+
+ // Fill responseXXX fields
+ for( type in responseFields ) {
+ if ( type in responses ) {
+ jqXHR[ responseFields[type] ] = responses[ type ];
+ }
+ }
+
+ // Remove auto dataType and get content-type in the process
+ while( dataTypes[ 0 ] === "*" ) {
+ dataTypes.shift();
+ if ( ct === undefined ) {
+ ct = s.mimeType || jqXHR.getResponseHeader( "content-type" );
+ }
+ }
+
+ // Check if we're dealing with a known content-type
+ if ( ct ) {
+ for ( type in contents ) {
+ if ( contents[ type ] && contents[ type ].test( ct ) ) {
+ dataTypes.unshift( type );
+ break;
+ }
+ }
+ }
+
+ // Check to see if we have a response for the expected dataType
+ if ( dataTypes[ 0 ] in responses ) {
+ finalDataType = dataTypes[ 0 ];
+ } else {
+ // Try convertible dataTypes
+ for ( type in responses ) {
+ if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+ finalDataType = type;
+ break;
+ }
+ if ( !firstDataType ) {
+ firstDataType = type;
+ }
+ }
+ // Or just use first one
+ finalDataType = finalDataType || firstDataType;
+ }
+
+ // If we found a dataType
+ // We add the dataType to the list if needed
+ // and return the corresponding response
+ if ( finalDataType ) {
+ if ( finalDataType !== dataTypes[ 0 ] ) {
+ dataTypes.unshift( finalDataType );
+ }
+ return responses[ finalDataType ];
+ }
+}
+
+// Chain conversions given the request and the original response
+function ajaxConvert( s, response ) {
+
+ // Apply the dataFilter if provided
+ if ( s.dataFilter ) {
+ response = s.dataFilter( response, s.dataType );
+ }
+
+ var dataTypes = s.dataTypes,
+ converters = {},
+ i,
+ key,
+ length = dataTypes.length,
+ tmp,
+ // Current and previous dataTypes
+ current = dataTypes[ 0 ],
+ prev,
+ // Conversion expression
+ conversion,
+ // Conversion function
+ conv,
+ // Conversion functions (transitive conversion)
+ conv1,
+ conv2;
+
+ // For each dataType in the chain
+ for( i = 1; i < length; i++ ) {
+
+ // Create converters map
+ // with lowercased keys
+ if ( i === 1 ) {
+ for( key in s.converters ) {
+ if( typeof key === "string" ) {
+ converters[ key.toLowerCase() ] = s.converters[ key ];
+ }
+ }
+ }
+
+ // Get the dataTypes
+ prev = current;
+ current = dataTypes[ i ];
+
+ // If current is auto dataType, update it to prev
+ if( current === "*" ) {
+ current = prev;
+ // If no auto and dataTypes are actually different
+ } else if ( prev !== "*" && prev !== current ) {
+
+ // Get the converter
+ conversion = prev + " " + current;
+ conv = converters[ conversion ] || converters[ "* " + current ];
+
+ // If there is no direct converter, search transitively
+ if ( !conv ) {
+ conv2 = undefined;
+ for( conv1 in converters ) {
+ tmp = conv1.split( " " );
+ if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) {
+ conv2 = converters[ tmp[1] + " " + current ];
+ if ( conv2 ) {
+ conv1 = converters[ conv1 ];
+ if ( conv1 === true ) {
+ conv = conv2;
+ } else if ( conv2 === true ) {
+ conv = conv1;
+ }
+ break;
+ }
+ }
+ }
+ }
+ // If we found no converter, dispatch an error
+ if ( !( conv || conv2 ) ) {
+ jQuery.error( "No conversion from " + conversion.replace(" "," to ") );
+ }
+ // If found converter is not an equivalence
+ if ( conv !== true ) {
+ // Convert with 1 or 2 converters accordingly
+ response = conv ? conv( response ) : conv2( conv1(response) );
+ }
+ }
+ }
+ return response;
+}
+
+
+
+
+var jsc = jQuery.now(),
+ jsre = /(\=)\?(&|$)|\?\?/i;
+
+// Default jsonp settings
+jQuery.ajaxSetup({
+ jsonp: "callback",
+ jsonpCallback: function() {
+ return jQuery.expando + "_" + ( jsc++ );
+ }
+});
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+ var dataIsString = ( typeof s.data === "string" );
+
+ if ( s.dataTypes[ 0 ] === "jsonp" ||
+ originalSettings.jsonpCallback ||
+ originalSettings.jsonp != null ||
+ s.jsonp !== false && ( jsre.test( s.url ) ||
+ dataIsString && jsre.test( s.data ) ) ) {
+
+ var responseContainer,
+ jsonpCallback = s.jsonpCallback =
+ jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback,
+ previous = window[ jsonpCallback ],
+ url = s.url,
+ data = s.data,
+ replace = "$1" + jsonpCallback + "$2",
+ cleanUp = function() {
+ // Set callback back to previous value
+ window[ jsonpCallback ] = previous;
+ // Call if it was a function and we have a response
+ if ( responseContainer && jQuery.isFunction( previous ) ) {
+ window[ jsonpCallback ]( responseContainer[ 0 ] );
+ }
+ };
+
+ if ( s.jsonp !== false ) {
+ url = url.replace( jsre, replace );
+ if ( s.url === url ) {
+ if ( dataIsString ) {
+ data = data.replace( jsre, replace );
+ }
+ if ( s.data === data ) {
+ // Add callback manually
+ url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback;
+ }
+ }
+ }
+
+ s.url = url;
+ s.data = data;
+
+ // Install callback
+ window[ jsonpCallback ] = function( response ) {
+ responseContainer = [ response ];
+ };
+
+ // Install cleanUp function
+ jqXHR.then( cleanUp, cleanUp );
+
+ // Use data converter to retrieve json after script execution
+ s.converters["script json"] = function() {
+ if ( !responseContainer ) {
+ jQuery.error( jsonpCallback + " was not called" );
+ }
+ return responseContainer[ 0 ];
+ };
+
+ // force json dataType
+ s.dataTypes[ 0 ] = "json";
+
+ // Delegate to script
+ return "script";
+ }
+} );
+
+
+
+
+// Install script dataType
+jQuery.ajaxSetup({
+ accepts: {
+ script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
+ },
+ contents: {
+ script: /javascript|ecmascript/
+ },
+ converters: {
+ "text script": function( text ) {
+ jQuery.globalEval( text );
+ return text;
+ }
+ }
+});
+
+// Handle cache's special case and global
+jQuery.ajaxPrefilter( "script", function( s ) {
+ if ( s.cache === undefined ) {
+ s.cache = false;
+ }
+ if ( s.crossDomain ) {
+ s.type = "GET";
+ s.global = false;
+ }
+} );
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function(s) {
+
+ // This transport only deals with cross domain requests
+ if ( s.crossDomain ) {
+
+ var script,
+ head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement;
+
+ return {
+
+ send: function( _, callback ) {
+
+ script = document.createElement( "script" );
+
+ script.async = "async";
+
+ if ( s.scriptCharset ) {
+ script.charset = s.scriptCharset;
+ }
+
+ script.src = s.url;
+
+ // Attach handlers for all browsers
+ script.onload = script.onreadystatechange = function( _, isAbort ) {
+
+ if ( !script.readyState || /loaded|complete/.test( script.readyState ) ) {
+
+ // Handle memory leak in IE
+ script.onload = script.onreadystatechange = null;
+
+ // Remove the script
+ if ( head && script.parentNode ) {
+ head.removeChild( script );
+ }
+
+ // Dereference the script
+ script = undefined;
+
+ // Callback if not abort
+ if ( !isAbort ) {
+ callback( 200, "success" );
+ }
+ }
+ };
+ // Use insertBefore instead of appendChild to circumvent an IE6 bug.
+ // This arises when a base node is used (#2709 and #4378).
+ head.insertBefore( script, head.firstChild );
+ },
+
+ abort: function() {
+ if ( script ) {
+ script.onload( 0, 1 );
+ }
+ }
+ };
+ }
+} );
+
+
+
+
+var // #5280: next active xhr id and list of active xhrs' callbacks
+ xhrId = jQuery.now(),
+ xhrCallbacks,
+
+ // XHR used to determine supports properties
+ testXHR;
+
+// #5280: Internet Explorer will keep connections alive if we don't abort on unload
+function xhrOnUnloadAbort() {
+ jQuery( window ).unload(function() {
+ // Abort all pending requests
+ for ( var key in xhrCallbacks ) {
+ xhrCallbacks[ key ]( 0, 1 );
+ }
+ });
+}
+
+// Functions to create xhrs
+function createStandardXHR() {
+ try {
+ return new window.XMLHttpRequest();
+ } catch( e ) {}
+}
+
+function createActiveXHR() {
+ try {
+ return new window.ActiveXObject( "Microsoft.XMLHTTP" );
+ } catch( e ) {}
+}
+
+// Create the request object
+// (This is still attached to ajaxSettings for backward compatibility)
+jQuery.ajaxSettings.xhr = window.ActiveXObject ?
+ /* Microsoft failed to properly
+ * implement the XMLHttpRequest in IE7 (can't request local files),
+ * so we use the ActiveXObject when it is available
+ * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
+ * we need a fallback.
+ */
+ function() {
+ return !this.isLocal && createStandardXHR() || createActiveXHR();
+ } :
+ // For all other browsers, use the standard XMLHttpRequest object
+ createStandardXHR;
+
+// Test if we can create an xhr object
+testXHR = jQuery.ajaxSettings.xhr();
+jQuery.support.ajax = !!testXHR;
+
+// Does this browser support crossDomain XHR requests
+jQuery.support.cors = testXHR && ( "withCredentials" in testXHR );
+
+// No need for the temporary xhr anymore
+testXHR = undefined;
+
+// Create transport if the browser can provide an xhr
+if ( jQuery.support.ajax ) {
+
+ jQuery.ajaxTransport(function( s ) {
+ // Cross domain only allowed if supported through XMLHttpRequest
+ if ( !s.crossDomain || jQuery.support.cors ) {
+
+ var callback;
+
+ return {
+ send: function( headers, complete ) {
+
+ // Get a new xhr
+ var xhr = s.xhr(),
+ handle,
+ i;
+
+ // Open the socket
+ // Passing null username, generates a login popup on Opera (#2865)
+ if ( s.username ) {
+ xhr.open( s.type, s.url, s.async, s.username, s.password );
+ } else {
+ xhr.open( s.type, s.url, s.async );
+ }
+
+ // Apply custom fields if provided
+ if ( s.xhrFields ) {
+ for ( i in s.xhrFields ) {
+ xhr[ i ] = s.xhrFields[ i ];
+ }
+ }
+
+ // Override mime type if needed
+ if ( s.mimeType && xhr.overrideMimeType ) {
+ xhr.overrideMimeType( s.mimeType );
+ }
+
+ // X-Requested-With header
+ // For cross-domain requests, seeing as conditions for a preflight are
+ // akin to a jigsaw puzzle, we simply never set it to be sure.
+ // (it can always be set on a per-request basis or even using ajaxSetup)
+ // For same-domain requests, won't change header if already provided.
+ if ( !s.crossDomain && !headers["X-Requested-With"] ) {
+ headers[ "X-Requested-With" ] = "XMLHttpRequest";
+ }
+
+ // Need an extra try/catch for cross domain requests in Firefox 3
+ try {
+ for ( i in headers ) {
+ xhr.setRequestHeader( i, headers[ i ] );
+ }
+ } catch( _ ) {}
+
+ // Do send the request
+ // This may raise an exception which is actually
+ // handled in jQuery.ajax (so no try/catch here)
+ xhr.send( ( s.hasContent && s.data ) || null );
+
+ // Listener
+ callback = function( _, isAbort ) {
+
+ var status,
+ statusText,
+ responseHeaders,
+ responses,
+ xml;
+
+ // Firefox throws exceptions when accessing properties
+ // of an xhr when a network error occured
+ // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
+ try {
+
+ // Was never called and is aborted or complete
+ if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
+
+ // Only called once
+ callback = undefined;
+
+ // Do not keep as active anymore
+ if ( handle ) {
+ xhr.onreadystatechange = jQuery.noop;
+ delete xhrCallbacks[ handle ];
+ }
+
+ // If it's an abort
+ if ( isAbort ) {
+ // Abort it manually if needed
+ if ( xhr.readyState !== 4 ) {
+ xhr.abort();
+ }
+ } else {
+ status = xhr.status;
+ responseHeaders = xhr.getAllResponseHeaders();
+ responses = {};
+ xml = xhr.responseXML;
+
+ // Construct response list
+ if ( xml && xml.documentElement /* #4958 */ ) {
+ responses.xml = xml;
+ }
+ responses.text = xhr.responseText;
+
+ // Firefox throws an exception when accessing
+ // statusText for faulty cross-domain requests
+ try {
+ statusText = xhr.statusText;
+ } catch( e ) {
+ // We normalize with Webkit giving an empty statusText
+ statusText = "";
+ }
+
+ // Filter status for non standard behaviors
+
+ // If the request is local and we have data: assume a success
+ // (success with no data won't get notified, that's the best we
+ // can do given current implementations)
+ if ( !status && s.isLocal && !s.crossDomain ) {
+ status = responses.text ? 200 : 404;
+ // IE - #1450: sometimes returns 1223 when it should be 204
+ } else if ( status === 1223 ) {
+ status = 204;
+ }
+ }
+ }
+ } catch( firefoxAccessException ) {
+ if ( !isAbort ) {
+ complete( -1, firefoxAccessException );
+ }
+ }
+
+ // Call complete if needed
+ if ( responses ) {
+ complete( status, statusText, responses, responseHeaders );
+ }
+ };
+
+ // if we're in sync mode or it's in cache
+ // and has been retrieved directly (IE6 & IE7)
+ // we need to manually fire the callback
+ if ( !s.async || xhr.readyState === 4 ) {
+ callback();
+ } else {
+ // Create the active xhrs callbacks list if needed
+ // and attach the unload handler
+ if ( !xhrCallbacks ) {
+ xhrCallbacks = {};
+ xhrOnUnloadAbort();
+ }
+ // Add to list of active xhrs callbacks
+ handle = xhrId++;
+ xhr.onreadystatechange = xhrCallbacks[ handle ] = callback;
+ }
+ },
+
+ abort: function() {
+ if ( callback ) {
+ callback(0,1);
+ }
+ }
+ };
+ }
+ });
+}
+
+
+
+
+var elemdisplay = {},
+ rfxtypes = /^(?:toggle|show|hide)$/,
+ rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,
+ timerId,
+ fxAttrs = [
+ // height animations
+ [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
+ // width animations
+ [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
+ // opacity animations
+ [ "opacity" ]
+ ];
+
+jQuery.fn.extend({
+ show: function( speed, easing, callback ) {
+ var elem, display;
+
+ if ( speed || speed === 0 ) {
+ return this.animate( genFx("show", 3), speed, easing, callback);
+
+ } else {
+ for ( var i = 0, j = this.length; i < j; i++ ) {
+ elem = this[i];
+ display = elem.style.display;
+
+ // Reset the inline display of this element to learn if it is
+ // being hidden by cascaded rules or not
+ if ( !jQuery._data(elem, "olddisplay") && display === "none" ) {
+ display = elem.style.display = "";
+ }
+
+ // Set elements which have been overridden with display: none
+ // in a stylesheet to whatever the default browser style is
+ // for such an element
+ if ( display === "" && jQuery.css( elem, "display" ) === "none" ) {
+ jQuery._data(elem, "olddisplay", defaultDisplay(elem.nodeName));
+ }
+ }
+
+ // Set the display of most of the elements in a second loop
+ // to avoid the constant reflow
+ for ( i = 0; i < j; i++ ) {
+ elem = this[i];
+ display = elem.style.display;
+
+ if ( display === "" || display === "none" ) {
+ elem.style.display = jQuery._data(elem, "olddisplay") || "";
+ }
+ }
+
+ return this;
+ }
+ },
+
+ hide: function( speed, easing, callback ) {
+ if ( speed || speed === 0 ) {
+ return this.animate( genFx("hide", 3), speed, easing, callback);
+
+ } else {
+ for ( var i = 0, j = this.length; i < j; i++ ) {
+ var display = jQuery.css( this[i], "display" );
+
+ if ( display !== "none" && !jQuery._data( this[i], "olddisplay" ) ) {
+ jQuery._data( this[i], "olddisplay", display );
+ }
+ }
+
+ // Set the display of the elements in a second loop
+ // to avoid the constant reflow
+ for ( i = 0; i < j; i++ ) {
+ this[i].style.display = "none";
+ }
+
+ return this;
+ }
+ },
+
+ // Save the old toggle function
+ _toggle: jQuery.fn.toggle,
+
+ toggle: function( fn, fn2, callback ) {
+ var bool = typeof fn === "boolean";
+
+ if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) {
+ this._toggle.apply( this, arguments );
+
+ } else if ( fn == null || bool ) {
+ this.each(function() {
+ var state = bool ? fn : jQuery(this).is(":hidden");
+ jQuery(this)[ state ? "show" : "hide" ]();
+ });
+
+ } else {
+ this.animate(genFx("toggle", 3), fn, fn2, callback);
+ }
+
+ return this;
+ },
+
+ fadeTo: function( speed, to, easing, callback ) {
+ return this.filter(":hidden").css("opacity", 0).show().end()
+ .animate({opacity: to}, speed, easing, callback);
+ },
+
+ animate: function( prop, speed, easing, callback ) {
+ var optall = jQuery.speed(speed, easing, callback);
+
+ if ( jQuery.isEmptyObject( prop ) ) {
+ return this.each( optall.complete );
+ }
+
+ return this[ optall.queue === false ? "each" : "queue" ](function() {
+ // XXX 'this' does not always have a nodeName when running the
+ // test suite
+
+ var opt = jQuery.extend({}, optall), p,
+ isElement = this.nodeType === 1,
+ hidden = isElement && jQuery(this).is(":hidden"),
+ self = this;
+
+ for ( p in prop ) {
+ var name = jQuery.camelCase( p );
+
+ if ( p !== name ) {
+ prop[ name ] = prop[ p ];
+ delete prop[ p ];
+ p = name;
+ }
+
+ if ( prop[p] === "hide" && hidden || prop[p] === "show" && !hidden ) {
+ return opt.complete.call(this);
+ }
+
+ if ( isElement && ( p === "height" || p === "width" ) ) {
+ // Make sure that nothing sneaks out
+ // Record all 3 overflow attributes because IE does not
+ // change the overflow attribute when overflowX and
+ // overflowY are set to the same value
+ opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ];
+
+ // Set display property to inline-block for height/width
+ // animations on inline elements that are having width/height
+ // animated
+ if ( jQuery.css( this, "display" ) === "inline" &&
+ jQuery.css( this, "float" ) === "none" ) {
+ if ( !jQuery.support.inlineBlockNeedsLayout ) {
+ this.style.display = "inline-block";
+
+ } else {
+ var display = defaultDisplay(this.nodeName);
+
+ // inline-level elements accept inline-block;
+ // block-level elements need to be inline with layout
+ if ( display === "inline" ) {
+ this.style.display = "inline-block";
+
+ } else {
+ this.style.display = "inline";
+ this.style.zoom = 1;
+ }
+ }
+ }
+ }
+
+ if ( jQuery.isArray( prop[p] ) ) {
+ // Create (if needed) and add to specialEasing
+ (opt.specialEasing = opt.specialEasing || {})[p] = prop[p][1];
+ prop[p] = prop[p][0];
+ }
+ }
+
+ if ( opt.overflow != null ) {
+ this.style.overflow = "hidden";
+ }
+
+ opt.curAnim = jQuery.extend({}, prop);
+
+ jQuery.each( prop, function( name, val ) {
+ var e = new jQuery.fx( self, opt, name );
+
+ if ( rfxtypes.test(val) ) {
+ e[ val === "toggle" ? hidden ? "show" : "hide" : val ]( prop );
+
+ } else {
+ var parts = rfxnum.exec(val),
+ start = e.cur();
+
+ if ( parts ) {
+ var end = parseFloat( parts[2] ),
+ unit = parts[3] || ( jQuery.cssNumber[ name ] ? "" : "px" );
+
+ // We need to compute starting value
+ if ( unit !== "px" ) {
+ jQuery.style( self, name, (end || 1) + unit);
+ start = ((end || 1) / e.cur()) * start;
+ jQuery.style( self, name, start + unit);
+ }
+
+ // If a +=/-= token was provided, we're doing a relative animation
+ if ( parts[1] ) {
+ end = ((parts[1] === "-=" ? -1 : 1) * end) + start;
+ }
+
+ e.custom( start, end, unit );
+
+ } else {
+ e.custom( start, val, "" );
+ }
+ }
+ });
+
+ // For JS strict compliance
+ return true;
+ });
+ },
+
+ stop: function( clearQueue, gotoEnd ) {
+ var timers = jQuery.timers;
+
+ if ( clearQueue ) {
+ this.queue([]);
+ }
+
+ this.each(function() {
+ // go in reverse order so anything added to the queue during the loop is ignored
+ for ( var i = timers.length - 1; i >= 0; i-- ) {
+ if ( timers[i].elem === this ) {
+ if (gotoEnd) {
+ // force the next step to be the last
+ timers[i](true);
+ }
+
+ timers.splice(i, 1);
+ }
+ }
+ });
+
+ // start the next in the queue if the last step wasn't forced
+ if ( !gotoEnd ) {
+ this.dequeue();
+ }
+
+ return this;
+ }
+
+});
+
+function genFx( type, num ) {
+ var obj = {};
+
+ jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() {
+ obj[ this ] = type;
+ });
+
+ return obj;
+}
+
+// Generate shortcuts for custom animations
+jQuery.each({
+ slideDown: genFx("show", 1),
+ slideUp: genFx("hide", 1),
+ slideToggle: genFx("toggle", 1),
+ fadeIn: { opacity: "show" },
+ fadeOut: { opacity: "hide" },
+ fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+ jQuery.fn[ name ] = function( speed, easing, callback ) {
+ return this.animate( props, speed, easing, callback );
+ };
+});
+
+jQuery.extend({
+ speed: function( speed, easing, fn ) {
+ var opt = speed && typeof speed === "object" ? jQuery.extend({}, speed) : {
+ complete: fn || !fn && easing ||
+ jQuery.isFunction( speed ) && speed,
+ duration: speed,
+ easing: fn && easing || easing && !jQuery.isFunction(easing) && easing
+ };
+
+ opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+ opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[opt.duration] : jQuery.fx.speeds._default;
+
+ // Queueing
+ opt.old = opt.complete;
+ opt.complete = function() {
+ if ( opt.queue !== false ) {
+ jQuery(this).dequeue();
+ }
+ if ( jQuery.isFunction( opt.old ) ) {
+ opt.old.call( this );
+ }
+ };
+
+ return opt;
+ },
+
+ easing: {
+ linear: function( p, n, firstNum, diff ) {
+ return firstNum + diff * p;
+ },
+ swing: function( p, n, firstNum, diff ) {
+ return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
+ }
+ },
+
+ timers: [],
+
+ fx: function( elem, options, prop ) {
+ this.options = options;
+ this.elem = elem;
+ this.prop = prop;
+
+ if ( !options.orig ) {
+ options.orig = {};
+ }
+ }
+
+});
+
+jQuery.fx.prototype = {
+ // Simple function for setting a style value
+ update: function() {
+ if ( this.options.step ) {
+ this.options.step.call( this.elem, this.now, this );
+ }
+
+ (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
+ },
+
+ // Get the current size
+ cur: function() {
+ if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) {
+ return this.elem[ this.prop ];
+ }
+
+ var parsed,
+ r = jQuery.css( this.elem, this.prop );
+ // Empty strings, null, undefined and "auto" are converted to 0,
+ // complex values such as "rotate(1rad)" are returned as is,
+ // simple values such as "10px" are parsed to Float.
+ return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed;
+ },
+
+ // Start an animation from one number to another
+ custom: function( from, to, unit ) {
+ var self = this,
+ fx = jQuery.fx;
+
+ this.startTime = jQuery.now();
+ this.start = from;
+ this.end = to;
+ this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" );
+ this.now = this.start;
+ this.pos = this.state = 0;
+
+ function t( gotoEnd ) {
+ return self.step(gotoEnd);
+ }
+
+ t.elem = this.elem;
+
+ if ( t() && jQuery.timers.push(t) && !timerId ) {
+ timerId = setInterval(fx.tick, fx.interval);
+ }
+ },
+
+ // Simple 'show' function
+ show: function() {
+ // Remember where we started, so that we can go back to it later
+ this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
+ this.options.show = true;
+
+ // Begin the animation
+ // Make sure that we start at a small width/height to avoid any
+ // flash of content
+ this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur());
+
+ // Start by showing the element
+ jQuery( this.elem ).show();
+ },
+
+ // Simple 'hide' function
+ hide: function() {
+ // Remember where we started, so that we can go back to it later
+ this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
+ this.options.hide = true;
+
+ // Begin the animation
+ this.custom(this.cur(), 0);
+ },
+
+ // Each step of an animation
+ step: function( gotoEnd ) {
+ var t = jQuery.now(), done = true;
+
+ if ( gotoEnd || t >= this.options.duration + this.startTime ) {
+ this.now = this.end;
+ this.pos = this.state = 1;
+ this.update();
+
+ this.options.curAnim[ this.prop ] = true;
+
+ for ( var i in this.options.curAnim ) {
+ if ( this.options.curAnim[i] !== true ) {
+ done = false;
+ }
+ }
+
+ if ( done ) {
+ // Reset the overflow
+ if ( this.options.overflow != null && !jQuery.support.shrinkWrapBlocks ) {
+ var elem = this.elem,
+ options = this.options;
+
+ jQuery.each( [ "", "X", "Y" ], function (index, value) {
+ elem.style[ "overflow" + value ] = options.overflow[index];
+ } );
+ }
+
+ // Hide the element if the "hide" operation was done
+ if ( this.options.hide ) {
+ jQuery(this.elem).hide();
+ }
+
+ // Reset the properties, if the item has been hidden or shown
+ if ( this.options.hide || this.options.show ) {
+ for ( var p in this.options.curAnim ) {
+ jQuery.style( this.elem, p, this.options.orig[p] );
+ }
+ }
+
+ // Execute the complete function
+ this.options.complete.call( this.elem );
+ }
+
+ return false;
+
+ } else {
+ var n = t - this.startTime;
+ this.state = n / this.options.duration;
+
+ // Perform the easing function, defaults to swing
+ var specialEasing = this.options.specialEasing && this.options.specialEasing[this.prop];
+ var defaultEasing = this.options.easing || (jQuery.easing.swing ? "swing" : "linear");
+ this.pos = jQuery.easing[specialEasing || defaultEasing](this.state, n, 0, 1, this.options.duration);
+ this.now = this.start + ((this.end - this.start) * this.pos);
+
+ // Perform the next step of the animation
+ this.update();
+ }
+
+ return true;
+ }
+};
+
+jQuery.extend( jQuery.fx, {
+ tick: function() {
+ var timers = jQuery.timers;
+
+ for ( var i = 0; i < timers.length; i++ ) {
+ if ( !timers[i]() ) {
+ timers.splice(i--, 1);
+ }
+ }
+
+ if ( !timers.length ) {
+ jQuery.fx.stop();
+ }
+ },
+
+ interval: 13,
+
+ stop: function() {
+ clearInterval( timerId );
+ timerId = null;
+ },
+
+ speeds: {
+ slow: 600,
+ fast: 200,
+ // Default speed
+ _default: 400
+ },
+
+ step: {
+ opacity: function( fx ) {
+ jQuery.style( fx.elem, "opacity", fx.now );
+ },
+
+ _default: function( fx ) {
+ if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) {
+ fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit;
+ } else {
+ fx.elem[ fx.prop ] = fx.now;
+ }
+ }
+ }
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.animated = function( elem ) {
+ return jQuery.grep(jQuery.timers, function( fn ) {
+ return elem === fn.elem;
+ }).length;
+ };
+}
+
+function defaultDisplay( nodeName ) {
+ if ( !elemdisplay[ nodeName ] ) {
+ var elem = jQuery("<" + nodeName + ">").appendTo("body"),
+ display = elem.css("display");
+
+ elem.remove();
+
+ if ( display === "none" || display === "" ) {
+ display = "block";
+ }
+
+ elemdisplay[ nodeName ] = display;
+ }
+
+ return elemdisplay[ nodeName ];
+}
+
+
+
+
+var rtable = /^t(?:able|d|h)$/i,
+ rroot = /^(?:body|html)$/i;
+
+if ( "getBoundingClientRect" in document.documentElement ) {
+ jQuery.fn.offset = function( options ) {
+ var elem = this[0], box;
+
+ if ( options ) {
+ return this.each(function( i ) {
+ jQuery.offset.setOffset( this, options, i );
+ });
+ }
+
+ if ( !elem || !elem.ownerDocument ) {
+ return null;
+ }
+
+ if ( elem === elem.ownerDocument.body ) {
+ return jQuery.offset.bodyOffset( elem );
+ }
+
+ try {
+ box = elem.getBoundingClientRect();
+ } catch(e) {}
+
+ var doc = elem.ownerDocument,
+ docElem = doc.documentElement;
+
+ // Make sure we're not dealing with a disconnected DOM node
+ if ( !box || !jQuery.contains( docElem, elem ) ) {
+ return box ? { top: box.top, left: box.left } : { top: 0, left: 0 };
+ }
+
+ var body = doc.body,
+ win = getWindow(doc),
+ clientTop = docElem.clientTop || body.clientTop || 0,
+ clientLeft = docElem.clientLeft || body.clientLeft || 0,
+ scrollTop = win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop,
+ scrollLeft = win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft,
+ top = box.top + scrollTop - clientTop,
+ left = box.left + scrollLeft - clientLeft;
+
+ return { top: top, left: left };
+ };
+
+} else {
+ jQuery.fn.offset = function( options ) {
+ var elem = this[0];
+
+ if ( options ) {
+ return this.each(function( i ) {
+ jQuery.offset.setOffset( this, options, i );
+ });
+ }
+
+ if ( !elem || !elem.ownerDocument ) {
+ return null;
+ }
+
+ if ( elem === elem.ownerDocument.body ) {
+ return jQuery.offset.bodyOffset( elem );
+ }
+
+ jQuery.offset.initialize();
+
+ var computedStyle,
+ offsetParent = elem.offsetParent,
+ prevOffsetParent = elem,
+ doc = elem.ownerDocument,
+ docElem = doc.documentElement,
+ body = doc.body,
+ defaultView = doc.defaultView,
+ prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle,
+ top = elem.offsetTop,
+ left = elem.offsetLeft;
+
+ while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
+ if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) {
+ break;
+ }
+
+ computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle;
+ top -= elem.scrollTop;
+ left -= elem.scrollLeft;
+
+ if ( elem === offsetParent ) {
+ top += elem.offsetTop;
+ left += elem.offsetLeft;
+
+ if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) {
+ top += parseFloat( computedStyle.borderTopWidth ) || 0;
+ left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+ }
+
+ prevOffsetParent = offsetParent;
+ offsetParent = elem.offsetParent;
+ }
+
+ if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) {
+ top += parseFloat( computedStyle.borderTopWidth ) || 0;
+ left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+ }
+
+ prevComputedStyle = computedStyle;
+ }
+
+ if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) {
+ top += body.offsetTop;
+ left += body.offsetLeft;
+ }
+
+ if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) {
+ top += Math.max( docElem.scrollTop, body.scrollTop );
+ left += Math.max( docElem.scrollLeft, body.scrollLeft );
+ }
+
+ return { top: top, left: left };
+ };
+}
+
+jQuery.offset = {
+ initialize: function() {
+ var body = document.body, container = document.createElement("div"), innerDiv, checkDiv, table, td, bodyMarginTop = parseFloat( jQuery.css(body, "marginTop") ) || 0,
+ html = "<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
+
+ jQuery.extend( container.style, { position: "absolute", top: 0, left: 0, margin: 0, border: 0, width: "1px", height: "1px", visibility: "hidden" } );
+
+ container.innerHTML = html;
+ body.insertBefore( container, body.firstChild );
+ innerDiv = container.firstChild;
+ checkDiv = innerDiv.firstChild;
+ td = innerDiv.nextSibling.firstChild.firstChild;
+
+ this.doesNotAddBorder = (checkDiv.offsetTop !== 5);
+ this.doesAddBorderForTableAndCells = (td.offsetTop === 5);
+
+ checkDiv.style.position = "fixed";
+ checkDiv.style.top = "20px";
+
+ // safari subtracts parent border width here which is 5px
+ this.supportsFixedPosition = (checkDiv.offsetTop === 20 || checkDiv.offsetTop === 15);
+ checkDiv.style.position = checkDiv.style.top = "";
+
+ innerDiv.style.overflow = "hidden";
+ innerDiv.style.position = "relative";
+
+ this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5);
+
+ this.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop);
+
+ body.removeChild( container );
+ jQuery.offset.initialize = jQuery.noop;
+ },
+
+ bodyOffset: function( body ) {
+ var top = body.offsetTop,
+ left = body.offsetLeft;
+
+ jQuery.offset.initialize();
+
+ if ( jQuery.offset.doesNotIncludeMarginInBodyOffset ) {
+ top += parseFloat( jQuery.css(body, "marginTop") ) || 0;
+ left += parseFloat( jQuery.css(body, "marginLeft") ) || 0;
+ }
+
+ return { top: top, left: left };
+ },
+
+ setOffset: function( elem, options, i ) {
+ var position = jQuery.css( elem, "position" );
+
+ // set position first, in-case top/left are set even on static elem
+ if ( position === "static" ) {
+ elem.style.position = "relative";
+ }
+
+ var curElem = jQuery( elem ),
+ curOffset = curElem.offset(),
+ curCSSTop = jQuery.css( elem, "top" ),
+ curCSSLeft = jQuery.css( elem, "left" ),
+ calculatePosition = (position === "absolute" || position === "fixed") && jQuery.inArray('auto', [curCSSTop, curCSSLeft]) > -1,
+ props = {}, curPosition = {}, curTop, curLeft;
+
+ // need to be able to calculate position if either top or left is auto and position is either absolute or fixed
+ if ( calculatePosition ) {
+ curPosition = curElem.position();
+ }
+
+ curTop = calculatePosition ? curPosition.top : parseInt( curCSSTop, 10 ) || 0;
+ curLeft = calculatePosition ? curPosition.left : parseInt( curCSSLeft, 10 ) || 0;
+
+ if ( jQuery.isFunction( options ) ) {
+ options = options.call( elem, i, curOffset );
+ }
+
+ if (options.top != null) {
+ props.top = (options.top - curOffset.top) + curTop;
+ }
+ if (options.left != null) {
+ props.left = (options.left - curOffset.left) + curLeft;
+ }
+
+ if ( "using" in options ) {
+ options.using.call( elem, props );
+ } else {
+ curElem.css( props );
+ }
+ }
+};
+
+
+jQuery.fn.extend({
+ position: function() {
+ if ( !this[0] ) {
+ return null;
+ }
+
+ var elem = this[0],
+
+ // Get *real* offsetParent
+ offsetParent = this.offsetParent(),
+
+ // Get correct offsets
+ offset = this.offset(),
+ parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
+
+ // Subtract element margins
+ // note: when an element has margin: auto the offsetLeft and marginLeft
+ // are the same in Safari causing offset.left to incorrectly be 0
+ offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0;
+ offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0;
+
+ // Add offsetParent borders
+ parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0;
+ parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0;
+
+ // Subtract the two offsets
+ return {
+ top: offset.top - parentOffset.top,
+ left: offset.left - parentOffset.left
+ };
+ },
+
+ offsetParent: function() {
+ return this.map(function() {
+ var offsetParent = this.offsetParent || document.body;
+ while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
+ offsetParent = offsetParent.offsetParent;
+ }
+ return offsetParent;
+ });
+ }
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( ["Left", "Top"], function( i, name ) {
+ var method = "scroll" + name;
+
+ jQuery.fn[ method ] = function(val) {
+ var elem = this[0], win;
+
+ if ( !elem ) {
+ return null;
+ }
+
+ if ( val !== undefined ) {
+ // Set the scroll offset
+ return this.each(function() {
+ win = getWindow( this );
+
+ if ( win ) {
+ win.scrollTo(
+ !i ? val : jQuery(win).scrollLeft(),
+ i ? val : jQuery(win).scrollTop()
+ );
+
+ } else {
+ this[ method ] = val;
+ }
+ });
+ } else {
+ win = getWindow( elem );
+
+ // Return the scroll offset
+ return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] :
+ jQuery.support.boxModel && win.document.documentElement[ method ] ||
+ win.document.body[ method ] :
+ elem[ method ];
+ }
+ };
+});
+
+function getWindow( elem ) {
+ return jQuery.isWindow( elem ) ?
+ elem :
+ elem.nodeType === 9 ?
+ elem.defaultView || elem.parentWindow :
+ false;
+}
+
+
+
+
+// Create innerHeight, innerWidth, outerHeight and outerWidth methods
+jQuery.each([ "Height", "Width" ], function( i, name ) {
+
+ var type = name.toLowerCase();
+
+ // innerHeight and innerWidth
+ jQuery.fn["inner" + name] = function() {
+ return this[0] ?
+ parseFloat( jQuery.css( this[0], type, "padding" ) ) :
+ null;
+ };
+
+ // outerHeight and outerWidth
+ jQuery.fn["outer" + name] = function( margin ) {
+ return this[0] ?
+ parseFloat( jQuery.css( this[0], type, margin ? "margin" : "border" ) ) :
+ null;
+ };
+
+ jQuery.fn[ type ] = function( size ) {
+ // Get window width or height
+ var elem = this[0];
+ if ( !elem ) {
+ return size == null ? null : this;
+ }
+
+ if ( jQuery.isFunction( size ) ) {
+ return this.each(function( i ) {
+ var self = jQuery( this );
+ self[ type ]( size.call( this, i, self[ type ]() ) );
+ });
+ }
+
+ if ( jQuery.isWindow( elem ) ) {
+ // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
+ // 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat
+ var docElemProp = elem.document.documentElement[ "client" + name ];
+ return elem.document.compatMode === "CSS1Compat" && docElemProp ||
+ elem.document.body[ "client" + name ] || docElemProp;
+
+ // Get document width or height
+ } else if ( elem.nodeType === 9 ) {
+ // Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+ return Math.max(
+ elem.documentElement["client" + name],
+ elem.body["scroll" + name], elem.documentElement["scroll" + name],
+ elem.body["offset" + name], elem.documentElement["offset" + name]
+ );
+
+ // Get or set width or height on the element
+ } else if ( size === undefined ) {
+ var orig = jQuery.css( elem, type ),
+ ret = parseFloat( orig );
+
+ return jQuery.isNaN( ret ) ? orig : ret;
+
+ // Set the width or height on the element (default to pixels if value is unitless)
+ } else {
+ return this.css( type, typeof size === "string" ? size : size + "px" );
+ }
+ };
+
+});
+
+
+window.jQuery = window.$ = jQuery;
+})(window);
--- /dev/null
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+
+ </head>
+
+ <body>
+ <h1>Loading Content</h1>
+ </body>
+</html>
--- /dev/null
+;;; keyfreq-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+(add-to-list 'load-path (or (file-name-directory #$) (car load-path)))
+\f
+;;;### (autoloads nil "keyfreq" "keyfreq.el" (21908 14229 0 0))
+;;; Generated autoloads from keyfreq.el
+
+(defvar keyfreq-mode nil "\
+Non-nil if Keyfreq mode is enabled.
+See the command `keyfreq-mode' for a description of this minor mode.
+Setting this variable directly does not take effect;
+either customize it (see the info node `Easy Customization')
+or call the function `keyfreq-mode'.")
+
+(custom-autoload 'keyfreq-mode "keyfreq" nil)
+
+(autoload 'keyfreq-mode "keyfreq" "\
+Keyfreq mode records number of times each command was
+called making it possible to access usage statistics through
+various keyfreq-* functions.
+
+\(fn &optional ARG)" t nil)
+
+(defvar keyfreq-autosave-mode nil "\
+Non-nil if Keyfreq-Autosave mode is enabled.
+See the command `keyfreq-autosave-mode' for a description of this minor mode.
+Setting this variable directly does not take effect;
+either customize it (see the info node `Easy Customization')
+or call the function `keyfreq-autosave-mode'.")
+
+(custom-autoload 'keyfreq-autosave-mode "keyfreq" nil)
+
+(autoload 'keyfreq-autosave-mode "keyfreq" "\
+Keyfreq Autosave mode automatically saves
+`keyfreq-table' every `keyfreq-autosave-timeout' seconds
+and when emacs is killed.
+
+\(fn &optional ARG)" t nil)
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+;;; keyfreq-autoloads.el ends here
--- /dev/null
+(define-package "keyfreq" "20141124.805" "track command frequencies" 'nil)
--- /dev/null
+;;; keyfreq.el --- track command frequencies
+;; Package-Version: 20141124.805
+;; -*- coding: utf-8 -*-
+;;
+;; Copyright 2009-2010 by David Capello
+;; Copyright 2008 by Xah Lee
+;; Copyright 2006 by Michal Nazarewicz
+;; Copyright 2006 by Ryan Yeske
+;;
+;; Author: Ryan Yeske, Michal Nazarewicz (mina86/AT/mina86.com)
+;; Maintainer: David Capello, Xah lee
+;; Created: 2006
+;;
+;; Keyfreq 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.
+;;
+;; Version 1.5 - 2014-11 - David Capello
+;; * Support cl-lib or cl
+;; * Minor doc fixes
+;;
+;; Version 1.4 - 2010-09 - David Capello
+;; * Renamed from command-frequency to keyfreq
+;; * Now keyfreq-table holds "deltas"
+;; * Removed a lot of "facility" functions to keep the code simple.
+;; * Rename keyfreq-summarize-all-modes -> keyfreq-groups-major-modes.
+;; * Added keyfreq-filter-major-mode
+;; * Added lock file to avoid overwrite the file by two processes at
+;; the same time.
+;;
+;; Version 1.3 - 2009-09 - David Capello
+;; * Added keyfreq-summarize-all-modes
+;;
+;; Version 1.2 - 2009-09 - David Capello
+;; * Now each hash hash-key is a (major-mode . command) cons. Now only
+;; symbols are recorded.
+;;
+;; Version 1.1 - 2008-09
+;; - Replaced the use of this-command var by real-last-command, so
+;; that the commands backward-kill-word, kill-word, kill-line,
+;; kill-region, do not all get counted as kill-region. Changed
+;; post-command-hook to pre-command-hook
+;;
+;; Version 1.0 - 2007
+;; - Made into a full featured minor mode. Added full doc
+;; strings. Added feature to save and read to disk the frequency
+;; hash table. Added ability to set user preference using emacs's
+;; customization system. Code is ~400 lines. This version is made by
+;; Michal Nazarewicz in 2007.
+;;
+;; Version 0.1 - 2006
+;; - First version by Ryan Yeske. A quick hack of about 40 lines.
+;;
+
+;;; Commentary:
+;;
+;; HOW TO USE IT?
+;;
+;; Include the following lines in your .emacs file:
+;;
+;; (require 'keyfreq)
+;; (keyfreq-mode 1)
+;; (keyfreq-autosave-mode 1)
+;;
+;; And use `keyfreq-show' to see how many times you used a command.
+;;
+;;; Code:
+
+(if (featurep 'cl-lib)
+ (require 'cl-lib)
+ (require 'cl))
+;; (require 'json)?
+
+(defgroup keyfreq nil
+ "Customization group for Keyfreq mode.
+This mode stores number of times each command was called and
+provides it as a statistical data."
+ :package-version '(keyfreq . "1.5")
+ :group 'local
+ :prefix "keyfreq")
+
+;;;###autoload
+(define-minor-mode keyfreq-mode
+ "Keyfreq mode records number of times each command was
+called making it possible to access usage statistics through
+various keyfreq-* functions."
+ :global t
+ :init-value nil
+ :lighter nil
+ :keymap nil
+ :group 'keyfreq
+
+ (if keyfreq-mode
+ (add-hook 'pre-command-hook 'keyfreq-pre-command-hook)
+ (remove-hook 'pre-command-hook 'keyfreq-pre-command-hook)))
+
+
+(defcustom keyfreq-buffer "*frequencies*"
+ "Buffer where frequencies are displayed."
+ :group 'keyfreq
+ :type 'string)
+
+
+(defcustom keyfreq-file "~/.emacs.keyfreq"
+ "File `keyfreq-table' is saved to/loaded from by
+`keyfreq-table-save' and `keyfreq-table-load' functions
+by default."
+ :group 'keyfreq
+ :type 'file)
+
+(defcustom keyfreq-file-lock "~/.emacs.keyfreq.lock"
+ "Lock file to update the `keyfreq-file'."
+ :group 'keyfreq
+ :type 'file)
+
+
+(defvar keyfreq-table (make-hash-table :test 'equal :size 128)
+ "Hash table storing number of times each command was called in each major mode
+since the last time the frequencies were saved in `keyfreq-file'.")
+
+
+(defun keyfreq-pre-command-hook ()
+ "Record command execution in `keyfreq-table' hash."
+ (let ((command real-last-command) count)
+ (when (and command (symbolp command))
+ (setq count (gethash (cons major-mode command) keyfreq-table))
+ (puthash (cons major-mode command) (if count (1+ count) 1)
+ keyfreq-table))))
+
+
+(defun keyfreq-groups-major-modes (table)
+ "Group major modes in TABLE by command.
+Return a hash table where each entry has COMMAND as key and
+COUNTER as value."
+ (let ((new-table (make-hash-table :test 'equal :size 128)))
+ (maphash (lambda (k v)
+ (puthash (cdr k) (+ (gethash (cdr k) new-table 0) v) new-table))
+ table)
+ new-table))
+
+
+(defun keyfreq-filter-major-mode (table major-mode)
+ "Leave the frequencies of the specified major mode.
+Return a hash table where each entry has COMMAND as key and
+COUNTER as value."
+
+ (let ((new-table (make-hash-table :test 'equal :size 128)))
+ (maphash (lambda (k v)
+ (when (eq (car k) major-mode)
+ (puthash (cdr k) (+ (gethash (cdr k) new-table 0) v) new-table)))
+ table)
+ new-table))
+
+
+(defun keyfreq-used-major-modes (table)
+ "Return a list with the used major modes (major modes
+contained in the TABLE)."
+ (let ((list))
+ (maphash (lambda (k v)
+ (add-to-list 'list (car k)))
+ table)
+ list))
+
+
+(defun keyfreq-list (table &optional reverse limit)
+ "Return a cons which car is sum of times any command was used
+and cdr is a list of (command . count) pairs.
+
+If REVERSE is nil, sort it starting from the most used command;
+if it is `no-sort' the list is not sorted; if it is non-nil and
+not `no-sort', sort it from the least used commands. If LIMIT is
+a positive number, only commands which were used more then LIMIT
+times will be added. If it is a negative number, only commands
+which were used less then -LIMIT times will be added."
+
+ (let (l (sum 0))
+ (maphash
+ (cond
+ ((or (not (numberp limit)) (= limit 0))
+ (lambda (k v) (setq l (cons (cons k v) l) sum (+ sum v))))
+ ((= limit -1) (lambda (k v) (setq sum (+ sum v))))
+ ((< limit 0)
+ (setq limit (- limit))
+ (lambda (k v) (setq sum (+ sum v))
+ (if (< v limit) (setq l (cons (cons k v) l)))))
+ (t
+ (lambda (k v) (setq sum (+ sum v))
+ (if (> v limit) (setq l (cons (cons k v) l))))))
+ table)
+ (cons sum
+ (cond
+ ((equal reverse 'no-sort) l)
+ (reverse (sort l (lambda (a b) (< (cdr a) (cdr b)))))
+ (t (sort l (lambda (a b) (> (cdr a) (cdr b)))))))))
+
+
+(defun keyfreq-format-list (list &optional func)
+ "Return formatted string with command usage statistics.
+
+The LIST is the `keyfreq-table' converted to a list using the `keyfreq-list'.
+
+If FUNC is nil each line contains number of times command was
+called and the command; if it is t percentage usage is added in
+the middle; if it is 'raw each line will contain number an
+command separated by single line (with no formatting) otherwise
+FUNC must be a function returning a string which will be called
+for each entry with three arguments: number of times command was
+called, percentage usage and the command."
+ (let* ((sum (car list))
+ (max-len
+ (reduce (lambda (a b) (max a (length (symbol-name (car b)))))
+ (cdr list)
+ :initial-value 0)))
+ (mapconcat
+ (cond
+ ((not func) (lambda (e) (format "%7d %s\n" (cdr e) (car e))))
+ ((equal func t)
+ (lambda (e) (format (concat "%7d %6.2f%% %- "
+ (format "%d" max-len)
+ "s %s\n")
+ (cdr e) (/ (* 1e2 (cdr e)) sum) (car e)
+ (ignore-errors (keyfreq-where-is (car e))))))
+ ((equal func 'raw) (lambda (e) (format "%d %s\n" (cdr e) (car e))))
+ (t (lambda (e) (funcall func (cdr e) (/ (* 1e2 (cdr e)) sum) (car e)))))
+ (cdr list) "")))
+
+(defun keyfreq-where-is (command)
+ (mapconcat 'key-description
+ (where-is-internal command)
+ ", "))
+
+(defun keyfreq-show (&optional major-mode-symbol)
+ "Show command usage statistics in `keyfreq-buffer'.
+
+If MAJOR-MODE-SYMBOL is given, the function shows the statistics
+for that particular major mode only.
+
+With a universal argument, the major-mode of the current buffer
+is used as MAJOR-MODE-SYMBOL argument."
+ (interactive (list (cond (current-prefix-arg major-mode)
+ (t nil))))
+
+ (let ((table (copy-hash-table keyfreq-table)))
+ ;; Merge with the values in `keyfreq-file'
+ (keyfreq-table-load table)
+
+ (let* ((list (keyfreq-list
+ (cond
+ (major-mode-symbol (keyfreq-filter-major-mode table major-mode-symbol))
+ (t (keyfreq-groups-major-modes table)))))
+ (formatted-list (keyfreq-format-list list t)))
+
+ ;; Display the table
+ (display-message-or-buffer (concat (if major-mode-symbol
+ (concat "For " (symbol-name major-mode))
+ (concat "For all major modes"))
+ ":\n\n"
+ formatted-list)
+ keyfreq-buffer)
+ )))
+
+
+(defun keyfreq-html (filename &optional confirm)
+ "Save an HTML file as FILENAME with all the statistics of each mode."
+
+ (interactive
+ (list (if buffer-file-name
+ (read-file-name "Write HTML file: "
+ nil nil nil nil)
+ (read-file-name "Write HTML file: " default-directory
+ (expand-file-name
+ (file-name-nondirectory (buffer-name))
+ default-directory)
+ nil nil))
+ (not current-prefix-arg)))
+
+ (and confirm
+ (file-exists-p filename)
+ (or (y-or-n-p (format "File `%s' exists; overwrite? " filename))
+ (error "Canceled")))
+
+ (let ((table (copy-hash-table keyfreq-table))
+ (htmltable (lambda (list)
+ (insert "<table>\n")
+ (insert "<thead><tr><th>Times</th><th>Percetage</th><th>Command</th></tr></thead>\n")
+ (insert "<tbody>\n")
+ (keyfreq-format-list list
+ (lambda (count perc command)
+ (insert (format "<tr><td>%d</td><td>%.2f%%</td><td>%s</td></tr>\n" count perc command))))
+ (insert "</tbody>\n")
+ (insert "</table>\n"))))
+
+ ;; Merge with the values in `keyfreq-file'
+ (keyfreq-table-load table)
+
+ (with-temp-file filename
+ (insert "<html>\n<body>\n")
+ (insert "<h1>Keyfreq Report</h1>\n")
+ (insert "<ul>\n")
+ (insert "<li><a href=\"#all\">All major modes</a></li>\n")
+ (mapc
+ (lambda (major-mode-symbol)
+ (insert (format "<li><a href=\"#%s\">%s</a></li>\n"
+ (symbol-name major-mode-symbol)
+ (symbol-name major-mode-symbol))))
+ (keyfreq-used-major-modes table))
+ (insert "</ul>\n")
+
+ (insert "<h2><a name=\"all\">All major modes</a></h2>\n")
+ (funcall htmltable (keyfreq-list (keyfreq-groups-major-modes table)))
+
+ (mapc
+ (lambda (major-mode-symbol)
+ (insert (format "<h2><a name=\"%s\">%s</a></h2>\n"
+ (symbol-name major-mode-symbol)
+ (symbol-name major-mode-symbol)))
+ (funcall htmltable (keyfreq-list (keyfreq-filter-major-mode table major-mode-symbol))))
+ (keyfreq-used-major-modes table))
+
+ (insert "</body>\n</html>\n")
+ )
+ ))
+
+
+(defun keyfreq-json-encode (table)
+ "Return a JSON representation of the table of frequencies."
+ (require 'json)
+ (let ((commands-indexes (make-hash-table :test 'equal :size 128))
+ commands-list frequencies-matrix i)
+
+ ;; Build the "commands" property of the JSON object.
+ (setq i 0)
+ (maphash
+ (lambda (command-symbol counter)
+ (add-to-list 'commands-list command-symbol t)
+ (puthash command-symbol i commands-indexes)
+ (setq i (+ i 1))
+ )
+ (keyfreq-groups-major-modes table))
+
+ ;; Build the "frequencies" property.
+ (mapc
+ (lambda (major-mode-symbol)
+ (let ((cmd-count-pairs '()))
+ (keyfreq-format-list (keyfreq-list (keyfreq-filter-major-mode table major-mode-symbol))
+ (lambda (count perc command)
+ (add-to-list 'cmd-count-pairs (gethash command commands-indexes) t)
+ (add-to-list 'cmd-count-pairs count t)))
+ (add-to-list 'frequencies-matrix major-mode-symbol t)
+ (add-to-list 'frequencies-matrix (append cmd-count-pairs nil) t)))
+ (keyfreq-used-major-modes table))
+
+ (json-encode `((:format . 1)
+ (:commands . ,commands-list)
+ (:frequencies . ,frequencies-matrix)))))
+
+
+(defun keyfreq-json (filename &optional confirm)
+ "Save a file (as FILENAME) with a JSON structure of the data."
+
+ (interactive
+ (list (if buffer-file-name
+ (read-file-name "Write JSON file: "
+ nil nil nil nil)
+ (read-file-name "Write JSON file: " default-directory
+ (expand-file-name
+ (file-name-nondirectory (buffer-name))
+ default-directory)
+ nil nil))
+ (not current-prefix-arg)))
+
+ (and confirm
+ (file-exists-p filename)
+ (or (y-or-n-p (format "File `%s' exists; overwrite? " filename))
+ (error "Canceled")))
+
+ (let ((table (copy-hash-table keyfreq-table)))
+
+ ;; Merge with the values in `keyfreq-file'
+ (keyfreq-table-load table)
+
+ (with-temp-file filename
+ (insert (keyfreq-json-encode table)))))
+
+
+(defun keyfreq-file-owner ()
+ "Return the PID of the Emacs process that owns the table file lock file."
+ (let (owner)
+ (and (file-exists-p keyfreq-file-lock)
+ (with-temp-buffer
+ (insert-file-contents-literally keyfreq-file-lock)
+ (goto-char (point-min))
+ (setq owner (read (current-buffer)))
+ (integerp owner))
+ owner)))
+
+
+(defun keyfreq-file-claim-lock ()
+ (write-region (number-to-string (emacs-pid)) nil
+ keyfreq-file-lock nil 'nomessage))
+
+
+(defun keyfreq-file-release-lock ()
+ (when (file-exists-p keyfreq-file-lock)
+ (delete-file keyfreq-file-lock)))
+
+
+(defun keyfreq-file-is-unlocked ()
+ ;; If the lock file exists....
+ (if (file-exists-p keyfreq-file-lock)
+ ;; If the process which has the lock does not exist
+ (if (not (memql (keyfreq-file-owner) (list-system-processes)))
+ ;; Delete the lock
+ (delete-file keyfreq-file-lock)))
+ ;; Check again the lock existence (just in case...)
+ (not (file-exists-p keyfreq-file-lock)))
+
+
+(defun keyfreq-table-save (table)
+ "Append all values from the specified TABLE into the
+`keyfreq-file' as a sexp of an alist. Then resets the TABLE
+if it was successfully merged."
+
+ ;; Check that the lock file does not exist
+ (when (keyfreq-file-is-unlocked)
+ ;; Lock the file
+ (keyfreq-file-claim-lock)
+
+ ;; Check that we have the lock
+ (if (eq (keyfreq-file-owner) (emacs-pid))
+ (unwind-protect
+ (progn
+ ;; Load values and merge them with the current keyfreq-table
+ (keyfreq-table-load table)
+
+ ;; Write the new frequencies
+ (with-temp-file keyfreq-file
+ (let ((print-level nil)
+ (print-length nil))
+ (prin1 (cdr (keyfreq-list table 'no-sort)) (current-buffer)))))
+
+ ;; Release the lock and reset the hash table.
+ (keyfreq-file-release-lock)
+ (clrhash table))
+ )))
+
+
+(defun keyfreq-table-load (table)
+ "Load all values from the `keyfreq-file' and add them in the TABLE.
+The table is not reset, so the values are appended to the table."
+
+ ;; Does `keyfreq-file' exist?
+ (if (file-exists-p keyfreq-file)
+ ;; Load sexp
+ (let ((l (with-temp-buffer
+ (insert-file-contents keyfreq-file)
+ (goto-char (point-min))
+ (read (current-buffer)))))
+
+ ;; Add the values in the table
+ (while (and (listp l) l)
+ (if (listp (car l))
+ (puthash (caar l) (+ (gethash (caar l) table 0) (cdar l)) table))
+ (setq l (cdr l)))
+ )))
+
+;;;###autoload
+(define-minor-mode keyfreq-autosave-mode
+ "Keyfreq Autosave mode automatically saves
+`keyfreq-table' every `keyfreq-autosave-timeout' seconds
+and when emacs is killed."
+ :global t
+ :init-value nil
+ :lighter nil
+ :keymap nil
+ :group 'keyfreq
+
+ (when keyfreq-autosave--timer
+ (cancel-timer keyfreq-autosave--timer)
+ (setq keyfreq-autosave--timer nil))
+
+ (if keyfreq-autosave-mode
+ (progn
+ (setq keyfreq-autosave--timer
+ (run-at-time t keyfreq-autosave-timeout
+ 'keyfreq-autosave--do))
+ (add-hook 'kill-emacs-hook 'keyfreq-autosave--do))
+ (remove-hook 'kill-emacs-hook 'keyfreq-autosave--do)))
+
+
+(defcustom keyfreq-autosave-timeout 600
+ "How often in seconds `keyfreq-table' should be saved
+when `keyfreq-autosave-mode' is enabled. Setting this
+value will take effect only after (re)enabling
+`keyfreq-autosave-mode'."
+ :group 'keyfreq
+ :type 'number)
+
+
+(defvar keyfreq-autosave--timer nil)
+
+
+(defun keyfreq-autosave--do ()
+ "Function executed periodically to save the `keyfreq-table' in `keyfreq-file'."
+ (keyfreq-table-save keyfreq-table))
+
+
+(provide 'keyfreq)
+
+;;; keyfreq.el ends here
--- /dev/null
+HOW TO USE IT?
+
+Include the following lines in your .emacs file:
+
+ (require 'keyfreq)
+ (keyfreq-mode 1)
+ (keyfreq-autosave-mode 1)
+
+And use `keyfreq-show' to see how many times you used a command.
--- /dev/null
+;;; langtool-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+(add-to-list 'load-path (or (file-name-directory #$) (car load-path)))
+\f
+;;;### (autoloads nil "langtool" "langtool.el" (21908 14228 0 0))
+;;; Generated autoloads from langtool.el
+
+(defalias 'langtool-check 'langtool-check-buffer)
+
+(autoload 'langtool-check-buffer "langtool" "\
+Check context current buffer and light up errors.
+Optional \\[universal-argument] read LANG name.
+
+You can change the `langtool-default-language' to apply all session.
+Restrict to selection when region is activated.
+
+\(fn &optional LANG)" t nil)
+
+(autoload 'langtool-switch-default-language "langtool" "\
+Switch `langtool-read-lang-name' to LANG
+
+\(fn LANG)" t nil)
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+;;; langtool-autoloads.el ends here
--- /dev/null
+(define-package "langtool" "20150609.2131" "Grammar check utility using LanguageTool" '((cl-lib "0.3")) :url "https://github.com/mhayashi1120/Emacs-langtool" :keywords '("docs"))
--- /dev/null
+;;; langtool.el --- Grammar check utility using LanguageTool
+
+;; Author: Masahiro Hayashi <mhayashi1120NOSPAMgmail.com>
+;; Keywords: docs
+;; Package-Version: 20150609.2131
+;; URL: https://github.com/mhayashi1120/Emacs-langtool
+;; Emacs: GNU Emacs 24 or later
+;; Version: 1.3.1
+;; Package-Requires: ((cl-lib "0.3"))
+
+;; 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 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.
+
+;;; Commentary:
+
+;; ## Install:
+
+;; Install LanguageTool (and java)
+;; http://www.languagetool.org/
+
+;; Put this file into load-path'ed directory, and byte compile it if
+;; desired. And put the following expression into your ~/.emacs.
+;;
+;; (require 'langtool)
+;; (setq langtool-language-tool-jar "/path/to/languagetool-commandline.jar")
+;;
+;; If you use old version of LanguageTool, may be:
+;;
+;; (setq langtool-language-tool-jar "/path/to/LanguageTool.jar")
+;;
+;; Alternatively, you can set the classpath where LanguageTool's jars reside:
+;;
+;; (require 'langtool)
+;; (setq langtool-java-classpath
+;; "/usr/share/languagetool:/usr/share/java/languagetool/*")
+
+;; This setting is optional
+;;
+;; (global-set-key "\C-x4w" 'langtool-check)
+;; (global-set-key "\C-x4W" 'langtool-check-done)
+;; (global-set-key "\C-x4l" 'langtool-switch-default-language)
+;; (global-set-key "\C-x44" 'langtool-show-message-at-point)
+;; (global-set-key "\C-x4c" 'langtool-correct-buffer)
+;;
+;; Currently GNU java version not works.
+;;
+;; (setq langtool-java-bin "/path/to/java")
+;;
+;; Maybe you want to specify your mother tongue.
+;;
+;; (setq langtool-mother-tongue "en")
+
+;; ## Usage:
+
+;; * To check current buffer and show warnings.
+;;
+;; M-x langtool-check
+
+;; * To correct marker follow LanguageTool suggestions.
+;;
+;; M-x langtool-correct-buffer
+
+;; * Goto warning point and
+;;
+;; M-x langtool-show-message-at-point
+
+;; * To finish checking. All langtool marker is removed.
+;;
+;; M-x langtool-check-done
+
+;;; TODO:
+
+;; * process coding system (test on Windows)
+;; * check only docstring (emacs-lisp-mode)
+;; or using (derived-mode-p 'prog-mode) and only string and comment
+;; * I don't know well about java. But GNU libgcj version not works..
+;; * java encoding <-> elisp encoding
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'compile)
+
+(defgroup langtool nil
+ "Customize langtool"
+ :prefix "langtool-"
+ :group 'applications)
+
+(defvar current-prefix-arg)
+(defvar unread-command-events)
+(defvar locale-language-names)
+
+(defface langtool-errline
+ '((((class color) (background dark)) (:background "Firebrick4"))
+ (((class color) (background light)) (:background "LightPink"))
+ (t (:bold t)))
+ "Face used for marking error lines."
+ :group 'langtool)
+
+(defcustom langtool-java-bin "java"
+ "*Executing java command."
+ :group 'langtool
+ :type 'file)
+
+(defcustom langtool-language-tool-jar nil
+ "*LanguageTool jar file."
+ :group 'langtool
+ :type 'file)
+
+(defcustom langtool-java-classpath nil
+ "Custom classpath to use when executing languagetool"
+ :group 'langtool
+ :type 'string)
+
+(defcustom langtool-default-language nil
+ "*Language name pass to LanguageTool."
+ :group 'langtool
+ :type 'string)
+
+(defcustom langtool-mother-tongue nil
+ "*Your mothertongue Language name pass to LanguageTool."
+ :group 'langtool
+ :type 'string)
+
+(defcustom langtool-disabled-rules nil
+ "*Disabled rules pass to LanguageTool.
+String that separated by comma or list of string.
+"
+ :group 'langtool
+ :type '(choice
+ (list string)
+ string))
+
+(defvar langtool-local-disabled-rules nil)
+(make-variable-buffer-local 'langtool-local-disabled-rules)
+
+(defvar langtool-temp-file nil)
+(make-variable-buffer-local 'langtool-temp-file)
+
+(defconst langtool-output-regexp
+ (eval-when-compile
+ (concat
+ "^[0-9]+\\.) Line \\([0-9]+\\), column \\([0-9]+\\), Rule ID: \\(.*\\)\n"
+ "Message: \\(.*\\)\n"
+ "\\(?:Suggestion: \\(.*\\)\n\\)?"
+ ;; As long as i can read
+ ;; src/dev/de/danielnaber/languagetool/dev/wikipedia/OutputDumpHandler.java
+ "\\(\\(?:.*\\)\n\\(?:[ ^]+\\)\\)\n"
+ "\n?" ; last result have no new-line
+ )))
+
+(defvar langtool-buffer-process nil)
+(make-variable-buffer-local 'langtool-buffer-process)
+
+(defvar langtool-mode-line-message nil)
+(make-variable-buffer-local 'langtool-mode-line-message)
+(put 'langtool-mode-line-message 'risky-local-variable t)
+
+(defvar langtool-error-buffer-name " *LanguageTool Errors* ")
+
+(defun langtool-region-active-p ()
+ (cond
+ ((fboundp 'region-active-p)
+ (funcall 'region-active-p))
+ (t
+ (and transient-mark-mode mark-active))))
+
+(defun langtool-goto-next-error ()
+ "Obsoleted function. Should use `langtool-correct-buffer'.
+Goto next error."
+ (interactive)
+ (let ((overlays (langtool--overlays-region (point) (point-max))))
+ (langtool--goto-error
+ overlays
+ (lambda (ov) (< (point) (overlay-start ov))))))
+
+(defun langtool-goto-previous-error ()
+ "Obsoleted function. Should use `langtool-correct-buffer'.
+Goto previous error."
+ (interactive)
+ (let ((overlays (langtool--overlays-region (point-min) (point))))
+ (langtool--goto-error
+ (reverse overlays)
+ (lambda (ov) (< (overlay-end ov) (point))))))
+
+(defun langtool-show-message-at-point ()
+ "Show error details at point"
+ (interactive)
+ (let ((msgs (langtool--current-error-messages)))
+ (if (null msgs)
+ (message "No errors")
+ (let ((buf (get-buffer-create langtool-error-buffer-name)))
+ (with-current-buffer buf
+ (erase-buffer)
+ (mapc
+ (lambda (msg) (insert msg "\n"))
+ msgs))
+ (save-window-excursion
+ (display-buffer buf)
+ (let* ((echo-keystrokes)
+ (event (read-event)))
+ (setq unread-command-events (list event))))))))
+
+(defun langtool-check-done ()
+ "Finish LanguageTool process and cleanup existing colorized texts."
+ (interactive)
+ (when langtool-buffer-process
+ (delete-process langtool-buffer-process))
+ (kill-local-variable 'langtool-buffer-process)
+ (kill-local-variable 'langtool-mode-line-message)
+ (kill-local-variable 'langtool-local-disabled-rules)
+ (langtool--clear-buffer-overlays)
+ (message "Cleaned up LanguageTool."))
+
+;;;###autoload
+(defalias 'langtool-check 'langtool-check-buffer)
+
+;;;###autoload
+(defun langtool-check-buffer (&optional lang)
+ "Check context current buffer and light up errors.
+Optional \\[universal-argument] read LANG name.
+
+You can change the `langtool-default-language' to apply all session.
+Restrict to selection when region is activated.
+"
+ (interactive
+ (when current-prefix-arg
+ (list (langtool-read-lang-name))))
+ (langtool--check-command)
+ ;; probablly ok...
+ (when (listp mode-line-process)
+ (add-to-list 'mode-line-process '(t langtool-mode-line-message)))
+ (let* ((file (buffer-file-name))
+ (region-p (langtool-region-active-p))
+ (begin (and region-p (region-beginning)))
+ (finish (and region-p (region-end))))
+ (when region-p
+ (deactivate-mark))
+ (unless langtool-temp-file
+ (setq langtool-temp-file (make-temp-file "langtool-")))
+ (when (or (null file) (buffer-modified-p) region-p)
+ (save-restriction
+ (widen)
+ (let ((coding-system-for-write buffer-file-coding-system))
+ (write-region begin finish langtool-temp-file nil 'no-msg))
+ (setq file langtool-temp-file)))
+ ;; clear previous check
+ (langtool--clear-buffer-overlays)
+ (let ((command langtool-java-bin)
+ args)
+ (setq args (list "-c" (langtool--java-coding-system buffer-file-coding-system)
+ "-l" (or lang langtool-default-language)
+ "-d" (langtool--disabled-rules)))
+ (if langtool-java-classpath
+ (setq args (append (list "-cp" langtool-java-classpath
+ "org.languagetool.commandline.Main")
+ args))
+ (setq args (append
+ (list "-jar" (expand-file-name langtool-language-tool-jar))
+ args)))
+ (when langtool-mother-tongue
+ (setq args (append args (list "-m" langtool-mother-tongue))))
+ (setq args (append args (list file)))
+ (langtool--debug "Command" "%s: %s" command args)
+ (let* ((buffer (langtool--process-create-buffer))
+ (proc (apply 'start-process "LanguageTool" buffer command args)))
+ (set-process-filter proc 'langtool--process-filter)
+ (set-process-sentinel proc 'langtool--process-sentinel)
+ (process-put proc 'langtool-source-buffer (current-buffer))
+ (process-put proc 'langtool-region-begin begin)
+ (process-put proc 'langtool-region-finish finish)
+ (setq langtool-buffer-process proc)
+ (setq langtool-mode-line-message
+ (list " LanguageTool"
+ (propertize ":run" 'face compilation-info-face)))))))
+
+;;;###autoload
+(defun langtool-switch-default-language (lang)
+ "Switch `langtool-read-lang-name' to LANG"
+ (interactive (list (langtool-read-lang-name)))
+ (setq langtool-default-language lang)
+ (message "Now default language is `%s'" lang))
+
+(defun langtool-correct-buffer ()
+ "Execute interactive correction after `langtool-check'"
+ (interactive)
+ (let ((ovs (langtool--overlays-region (point-min) (point-max))))
+ (if (null ovs)
+ (message "No error found. %s"
+ (substitute-command-keys
+ (concat
+ "Type \\[langtool-check-done] to finish checking "
+ "or type \\[langtool-check] to re-check buffer")))
+ (barf-if-buffer-read-only)
+ (langtool--correction ovs))))
+
+(defvar langtool--debug nil)
+(defun langtool-toggle-debug ()
+ "Toggle LanguageTool debugging."
+ (interactive)
+ (setq langtool--debug (not langtool--debug))
+ (if langtool--debug
+ (message "LanguageTool debug ON.")
+ (message "LanguageTool debug off.")))
+
+(defun langtool--debug (key fmt &rest args)
+ (when langtool--debug
+ (let ((buf (get-buffer-create "*LanguageTool Debug*")))
+ (with-current-buffer buf
+ (goto-char (point-max))
+ (insert "---------- [" key "] ----------\n")
+ (insert (apply 'format fmt args) "\n")))))
+
+(defun langtool--goto-error (overlays predicate)
+ (catch 'done
+ (mapc
+ (lambda (ov)
+ (when (funcall predicate ov)
+ (goto-char (overlay-start ov))
+ (throw 'done t)))
+ overlays)
+ nil))
+
+(defun langtool-read-lang-name ()
+ (completing-read "Lang: "
+ (or (mapcar 'list (langtool--available-languages))
+ locale-language-names)))
+
+(defun langtool--create-overlay (tuple)
+ (let ((line (nth 0 tuple))
+ (col (nth 1 tuple))
+ (len (nth 2 tuple))
+ (sugs (nth 3 tuple))
+ (msg (nth 4 tuple))
+ (message (nth 5 tuple))
+ (rule-id (nth 6 tuple))
+ (context (nth 7 tuple)))
+ (goto-char (point-min))
+ (forward-line (1- line))
+ (let ((start (line-beginning-position))
+ (end (line-end-position)))
+ ;; 1. sketchy move to column that is indicated by LanguageTool.
+ ;; 2. fuzzy match to reported sentence which indicated by ^^^ like string.
+ (move-to-column col)
+ (cl-destructuring-bind (start . end)
+ (langtool--fuzzy-search context len)
+ (let ((ov (make-overlay start end)))
+ (overlay-put ov 'langtool-simple-message msg)
+ (overlay-put ov 'langtool-message message)
+ (overlay-put ov 'langtool-suggestions sugs)
+ (overlay-put ov 'langtool-rule-id rule-id)
+ (overlay-put ov 'priority 1)
+ (overlay-put ov 'face 'langtool-errline))))))
+
+;;FIXME
+;;http://sourceforge.net/tracker/?func=detail&aid=3054895&group_id=110216&atid=655717
+(defun langtool--fuzzy-search (context-regexp length)
+ (let* ((regexp (concat ".*?" context-regexp))
+ (default (cons (point) (+ (point) length))))
+ (or (and (null regexp)
+ (cons (point) (+ (point) length)))
+ (and (looking-at regexp)
+ (cons (match-beginning 1) (match-end 1)))
+ (let ((beg (min (line-beginning-position) (- (point) 20))))
+ (cl-loop while (and (not (bobp))
+ (<= beg (point)))
+ ;; backward just sentence length to search sentence after point
+ do (condition-case nil
+ (backward-char length)
+ (beginning-of-buffer nil))
+ if (looking-at regexp)
+ return (cons (match-beginning 1) (match-end 1))))
+ default)))
+
+(defun langtool--current-error-messages ()
+ (remove nil
+ (mapcar
+ (lambda (ov)
+ (overlay-get ov 'langtool-message))
+ (overlays-at (point)))))
+
+(defun langtool--clear-buffer-overlays ()
+ (mapc
+ (lambda (ov)
+ (delete-overlay ov))
+ (langtool--overlays-region (point-min) (point-max))))
+
+(defun langtool--overlays-region (start end)
+ (sort
+ (remove
+ nil
+ (mapcar
+ (lambda (ov)
+ (when (overlay-get ov 'langtool-message)
+ ov))
+ (overlays-in start end)))
+ (lambda (ov1 ov2)
+ (< (overlay-start ov1) (overlay-start ov2)))))
+
+(defun langtool--check-command ()
+ (when (or (null langtool-java-bin)
+ (not (executable-find langtool-java-bin)))
+ (error "java command is not found"))
+ (unless langtool-java-classpath
+ (when (or (null langtool-language-tool-jar)
+ (not (file-readable-p langtool-language-tool-jar)))
+ (error "langtool jar file is not found")))
+ (when langtool-buffer-process
+ (error "Another process is running")))
+
+(defun langtool--disabled-rules ()
+ (let ((custom langtool-disabled-rules)
+ (locals langtool-local-disabled-rules))
+ (cond
+ ((stringp custom)
+ (mapconcat 'identity
+ (cons custom locals)
+ ","))
+ (t
+ (mapconcat 'identity
+ (append custom locals)
+ ",")))))
+
+(defun langtool--process-create-buffer ()
+ (generate-new-buffer " *LanguageTool* "))
+
+(defun langtool--process-filter (proc event)
+ (langtool--debug "Filter" "%s" event)
+ (with-current-buffer (process-buffer proc)
+ (goto-char (point-max))
+ (insert event)
+ (let ((min (or (process-get proc 'langtool-process-done)
+ (point-min)))
+ (buffer (process-get proc 'langtool-source-buffer))
+ (begin (process-get proc 'langtool-region-begin))
+ (finish (process-get proc 'langtool-region-finish))
+ n-tuple)
+ (goto-char min)
+ (while (re-search-forward langtool-output-regexp nil t)
+ (let* ((line (string-to-number (match-string 1)))
+ (column (1- (string-to-number (match-string 2))))
+ (rule-id (match-string 3))
+ (suggest (match-string 5))
+ (msg1 (match-string 4))
+ ;; rest of line. Point the raw message.
+ (msg2 (match-string 6))
+ (message
+ (concat "Rule ID: " rule-id "\n"
+ msg1 "\n\n"
+ msg2))
+ (suggestions (and suggest (split-string suggest "; ")))
+ (context (langtool--pointed-context-regexp msg2))
+ (len (langtool--pointed-length msg2)))
+ (setq n-tuple (cons
+ (list line column len suggestions
+ msg1 message rule-id context)
+ n-tuple))))
+ (process-put proc 'langtool-process-done (point))
+ (when (buffer-live-p buffer)
+ (with-current-buffer buffer
+ (save-excursion
+ (save-restriction
+ (when (and begin finish)
+ (narrow-to-region begin finish))
+ (mapc
+ (lambda (tuple)
+ (langtool--create-overlay tuple))
+ (nreverse n-tuple)))))))))
+
+;;FIXME sometimes LanguageTool says wrong column.
+(defun langtool--pointed-context-regexp (message)
+ (when (string-match "\\(.*\\)\n\\( *\\)\\(\\^+\\)" message)
+ (let* ((msg1 (match-string 1 message))
+ (pre (length (match-string 2 message)))
+ (len (length (match-string 3 message)))
+ (end (+ pre len))
+ (sentence (substring msg1 pre end))
+ (regexp (cond
+ ((string-match "^[[:space:]]+$" sentence)
+ ;; invalid sentence only have whitespace,
+ ;; search with around sentence.
+ (concat
+ "\\("
+ (let* ((count (length sentence))
+ (spaces (format "[[:space:]\n]\\{%d\\}" count)))
+ spaces)
+ "\\)"
+ ;; considered truncated spaces that is caused by
+ ;; `langtool--sentence-to-fuzzy'
+ "[[:space:]]*?"
+ ;; to match the correct block
+ ;; suffix of invalid spaces.
+ (langtool--sentence-to-fuzzy
+ (let ((from (min end (length msg1))))
+ ;;TODO magic number.
+ (substring msg1 from (min (length msg1) (+ from 20)))))))
+ (t
+ (concat "\\("
+ (langtool--sentence-to-fuzzy sentence)
+ "\\)")))))
+ regexp)))
+
+(defun langtool--sentence-to-fuzzy (sentence)
+ (mapconcat 'regexp-quote
+ ;; this sentence is reported by LanguageTool
+ (split-string sentence " +")
+ ;; LanguageTool interpreted newline as space.
+ "[[:space:]\n]+?"))
+
+(defun langtool--pointed-length (message)
+ (or
+ (and (string-match "\n\\( *\\)\\(\\^+\\)" message)
+ (length (match-string 2 message)))
+ ;; never through here, but if return nil from this function make stop everything.
+ 1))
+
+(defun langtool--process-sentinel (proc event)
+ (when (memq (process-status proc) '(exit signal))
+ (let ((source (process-get proc 'langtool-source-buffer))
+ (code (process-exit-status proc))
+ (pbuf (process-buffer proc))
+ dead marks msg face)
+ (when (/= code 0)
+ (setq face compilation-error-face))
+ (cond
+ ((buffer-live-p source)
+ (with-current-buffer source
+ (setq marks (langtool--overlays-region (point-min) (point-max)))
+ (setq face (if marks compilation-info-face compilation-warning-face))
+ (setq langtool-buffer-process nil)
+ (setq langtool-mode-line-message
+ (list " LanguageTool"
+ (propertize ":exit" 'face face)))))
+ (t (setq dead t)))
+ (cond
+ (dead)
+ ((/= code 0)
+ (let ((msg
+ (if (buffer-live-p pbuf)
+ ;; Get first line of output.
+ (with-current-buffer pbuf
+ (goto-char (point-min))
+ (buffer-substring (point) (point-at-eol)))
+ "Buffer was dead")))
+ (message "LanguageTool exited abnormally with code %d (%s)"
+ code msg)))
+ (marks
+ (message "%s"
+ (substitute-command-keys
+ "Type \\[langtool-correct-buffer] to correct buffer.")))
+ (t
+ (message "LanguageTool successfully finished with no error.")))
+ (when (buffer-live-p pbuf)
+ (kill-buffer pbuf)))))
+
+(defun langtool--available-languages ()
+ (when (stringp langtool-language-tool-jar)
+ (let ((dir (expand-file-name "rules" (file-name-directory langtool-language-tool-jar))))
+ (when (file-directory-p dir)
+ (remove nil
+ (mapcar
+ (lambda (f)
+ (when (file-directory-p f)
+ (file-name-nondirectory f)))
+ (directory-files dir t "^[^.].$")))))))
+
+;;FIXME
+;; http://java.sun.com/j2se/1.5.0/ja/docs/ja/guide/intl/encoding.doc.html
+(defun langtool--java-coding-system (coding-system)
+ (let* ((cs (coding-system-base coding-system))
+ (csname (symbol-name cs))
+ (aliases (langtool--coding-system-aliases cs))
+ (names (mapcar 'symbol-name aliases))
+ tmp)
+ (cond
+ ((string-match "utf-8" csname)
+ "utf8")
+ ((or (string-match "euc.*jp" csname)
+ (string-match "japanese-iso-.*8bit" csname))
+ "eucjp")
+ ((string-match "shift.jis" csname)
+ "sjis")
+ ((string-match "iso.*2022.*jp" csname)
+ "iso2022jp")
+ ((setq tmp
+ (cl-loop for x in names
+ if (string-match "iso-8859-\\([0-9]+\\)" x)
+ return x))
+ (concat "ISO8859_" (match-string 1 tmp)))
+ ((memq cs '(us-ascii raw-text undecided no-conversion))
+ "ascii")
+ ((memq cs '(cyrillic-koi8))
+ "koi8-r")
+ ((setq tmp
+ (cl-loop for x in names
+ if (string-match "^windows-[0-9]+$" x)
+ return x))
+ tmp)
+ (t
+ ;; default guessed as ascii
+ "ascii"))))
+
+(defun langtool--coding-system-aliases (coding-system)
+ (if (fboundp 'coding-system-aliases)
+ ;; deceive elint
+ (funcall 'coding-system-aliases coding-system)
+ (coding-system-get coding-system 'alias-coding-systems)))
+
+(defun langtool--correction (overlays)
+ (let ((conf (current-window-configuration)))
+ (unwind-protect
+ (let ((next (car overlays)))
+ (while (setq next (langtool--correction-loop next overlays))))
+ (langtool--expire-buffer-overlays)
+ (set-window-configuration conf)
+ (kill-buffer (langtool--correction-buffer)))))
+
+(defun langtool--correction-loop (ov overlays)
+ (let* ((suggests (overlay-get ov 'langtool-suggestions))
+ (msg (overlay-get ov 'langtool-simple-message))
+ (alist (langtool--correction-popup msg suggests)))
+ (catch 'next
+ (while (progn
+ (goto-char (overlay-start ov))
+ (let (message-log-max)
+ (message (concat "C-h or ? for more options; "
+ "SPC to leave unchanged, "
+ "Digit to replace word")))
+ (let* ((echo-keystrokes) ; suppress echoing
+ (c (downcase (read-char)))
+ (pair (assq c alist)))
+ (cond
+ (pair
+ (let ((sug (nth 1 pair)))
+ ;;TODO when region contains newline.
+ ;; -> insert newline after suggestion.
+ (delete-region (overlay-start ov) (overlay-end ov))
+ (insert sug)
+ (langtool--erase-overlay ov))
+ nil)
+ ((memq c '(?q))
+ (keyboard-quit))
+ ((memq c '(?c))
+ (langtool--erase-overlay ov)
+ nil)
+ ((memq c '(?e))
+ (message (substitute-command-keys
+ "Type \\[exit-recursive-edit] to finish the edit."))
+ (recursive-edit)
+ ;; stay current cursor and wait next user command.
+ (throw 'next ov))
+ ((memq c '(?i))
+ (let ((rule (overlay-get ov 'langtool-rule-id)))
+ (unless (member rule langtool-local-disabled-rules)
+ (setq langtool-local-disabled-rules
+ (cons rule langtool-local-disabled-rules)))
+ (langtool--ignore-rule rule overlays))
+ nil)
+ ((memq c '(?\C-h ?\?))
+ (langtool--correction-help)
+ t)
+ ((memq c '(?\d))
+ (throw 'next (langtool--prev-overlay ov overlays)))
+ ((memq c '(?\s)) nil)
+ (t (ding) t)))))
+ ;; next item
+ (langtool--next-overlay ov overlays))))
+
+(defun langtool--expire-buffer-overlays ()
+ (mapc
+ (lambda (o)
+ (unless (overlay-get o 'face)
+ (delete-overlay o)))
+ (langtool--overlays-region (point-min) (point-max))))
+
+(defun langtool--ignore-rule (rule overlays)
+ (cl-loop for ov in overlays
+ do (let ((r (overlay-get ov 'langtool-rule-id)))
+ (when (equal r rule)
+ (langtool--erase-overlay ov)))))
+
+(defun langtool--erase-overlay (ov)
+ (overlay-put ov 'face nil))
+
+(defun langtool--next-overlay (current overlays)
+ (cl-loop for o in (cdr (memq current overlays))
+ if (overlay-get o 'face)
+ return o))
+
+(defun langtool--prev-overlay (current overlays)
+ (cl-loop for o in (cdr (memq current (reverse overlays)))
+ if (overlay-get o 'face)
+ return o))
+
+(defvar langtool--correction-keys
+ [?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9])
+
+(defface langtool-correction-face
+ '((((class mono)) (:inverse-video t :bold t :underline t))
+ (t (:background "red1" :foreground "yellow" :bold t)))
+ "Face used to visualize correction."
+ :group 'langtool)
+
+(defun langtool--correction-popup (msg suggests)
+ (let ((buf (langtool--correction-buffer)))
+ (delete-other-windows)
+ (let ((win (split-window)))
+ (set-window-buffer win buf))
+ (with-current-buffer buf
+ (let ((inhibit-read-only t))
+ (erase-buffer)
+ (insert msg "\n\n")
+ (cl-loop for s in suggests
+ for c across langtool--correction-keys
+ do (progn
+ (insert "(" c ") ")
+ (let ((start (point)))
+ (insert s)
+ ;; colorize suggestion.
+ ;; suggestion may contains whitespace.
+ (let ((ov (make-overlay start (point))))
+ (overlay-put ov 'face 'langtool-correction-face)))
+ (insert "\n"))
+ collect (list c s))))))
+
+(defun langtool--correction-help ()
+ (let ((help-1 "[q/Q]uit correction; [c/C]lear the colorized text; ")
+ (help-2 "[i/I]gnore the rule over current session.")
+ (help-3 "[e/E]dit the buffer manually")
+ (help-4 "SPC skip; DEL move backward;")
+ )
+ (save-window-excursion
+ (unwind-protect
+ (let ((resize-mini-windows 'grow-only))
+ (select-window (minibuffer-window))
+ (erase-buffer)
+ (message nil)
+ ;;(set-minibuffer-window (selected-window))
+ (enlarge-window 2)
+ (insert (concat help-1 "\n" help-2 "\n" help-3 "\n" help-4))
+ (sit-for 5))
+ (erase-buffer)))))
+
+(defun langtool--correction-buffer ()
+ (get-buffer-create "*Langtool Correction*"))
+
+;; initialize custom variables guessed from environment.
+(let ((env (or (getenv "LANG")
+ (getenv "LC_ALL")))
+ lang mt)
+ (and env
+ (string-match "^\\(..\\)_" env)
+ (setq lang (downcase (match-string 1 env)))
+ (member lang (langtool--available-languages))
+ (setq mt lang))
+ (unless langtool-mother-tongue
+ (setq langtool-mother-tongue mt))
+ (unless langtool-default-language
+ (setq langtool-default-language (or mt "en"))))
+
+(provide 'langtool)
+
+;;; langtool.el ends here
--- /dev/null
+Good signature from 474F05837FBDEF9B GNU ELPA Signing Agent <elpasignNOSPAMelpa.gnu.org> (trust undefined) created at 2015-06-12T11:05:02+0200 using DSA
\ No newline at end of file
--- /dev/null
+;;; let-alist-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+(add-to-list 'load-path (or (file-name-directory #$) (car load-path)))
+\f
+;;;### (autoloads nil "let-alist" "let-alist.el" (21908 10928 0 0))
+;;; Generated autoloads from let-alist.el
+
+(autoload 'let-alist "let-alist" "\
+Let-bind dotted symbols to their cdrs in ALIST and execute BODY.
+Dotted symbol is any symbol starting with a `.'. Only those present
+in BODY are let-bound and this search is done at compile time.
+
+For instance, the following code
+
+ (let-alist alist
+ (if (and .title .body)
+ .body
+ .site
+ .site.contents))
+
+essentially expands to
+
+ (let ((.title (cdr (assq 'title alist)))
+ (.body (cdr (assq 'body alist)))
+ (.site (cdr (assq 'site alist)))
+ (.site.contents (cdr (assq 'contents (cdr (assq 'site alist))))))
+ (if (and .title .body)
+ .body
+ .site
+ .site.contents))
+
+If you nest `let-alist' invocations, the inner one can't access
+the variables of the outer one. You can, however, access alists
+inside the original alist by using dots inside the symbol, as
+displayed in the example above.
+
+\(fn ALIST &rest BODY)" nil t)
+
+(put 'let-alist 'lisp-indent-function '1)
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+;;; let-alist-autoloads.el ends here
--- /dev/null
+(define-package "let-alist" "1.0.4" "Easily let-bind values of an assoc-list by their names" 'nil :url "http://elpa.gnu.org/packages/let-alist.html" :keywords '("extensions" "lisp"))
--- /dev/null
+;;; let-alist.el --- Easily let-bind values of an assoc-list by their names -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014-2015 Free Software Foundation, Inc.
+
+;; Author: Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+;; Maintainer: Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+;; Version: 1.0.4
+;; Keywords: extensions lisp
+;; Prefix: let-alist
+;; Separator: -
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This package offers a single macro, `let-alist'. This macro takes a
+;; first argument (whose value must be an alist) and a body.
+;;
+;; The macro expands to a let form containing body, where each dotted
+;; symbol inside body is let-bound to their cdrs in the alist. Dotted
+;; symbol is any symbol starting with a `.'. Only those present in
+;; the body are let-bound and this search is done at compile time.
+;;
+;; For instance, the following code
+;;
+;; (let-alist alist
+;; (if (and .title .body)
+;; .body
+;; .site
+;; .site.contents))
+;;
+;; essentially expands to
+;;
+;; (let ((.title (cdr (assq 'title alist)))
+;; (.body (cdr (assq 'body alist)))
+;; (.site (cdr (assq 'site alist)))
+;; (.site.contents (cdr (assq 'contents (cdr (assq 'site alist))))))
+;; (if (and .title .body)
+;; .body
+;; .site
+;; .site.contents))
+;;
+;; If you nest `let-alist' invocations, the inner one can't access
+;; the variables of the outer one. You can, however, access alists
+;; inside the original alist by using dots inside the symbol, as
+;; displayed in the example above by the `.site.contents'.
+;;
+;;; Code:
+\f
+
+(defun let-alist--deep-dot-search (data)
+ "Return alist of symbols inside DATA that start with a `.'.
+Perform a deep search and return an alist where each car is the
+symbol, and each cdr is the same symbol without the `.'."
+ (cond
+ ((symbolp data)
+ (let ((name (symbol-name data)))
+ (when (string-match "\\`\\." name)
+ ;; Return the cons cell inside a list, so it can be appended
+ ;; with other results in the clause below.
+ (list (cons data (intern (replace-match "" nil nil name)))))))
+ ((not (consp data)) nil)
+ (t (append (let-alist--deep-dot-search (car data))
+ (let-alist--deep-dot-search (cdr data))))))
+
+(defun let-alist--access-sexp (symbol variable)
+ "Return a sexp used to access SYMBOL inside VARIABLE."
+ (let* ((clean (let-alist--remove-dot symbol))
+ (name (symbol-name clean)))
+ (if (string-match "\\`\\." name)
+ clean
+ (let-alist--list-to-sexp
+ (mapcar #'intern (nreverse (split-string name "\\.")))
+ variable))))
+
+(defun let-alist--list-to-sexp (list var)
+ "Turn symbols LIST into recursive calls to `cdr' `assq' on VAR."
+ `(cdr (assq ',(car list)
+ ,(if (cdr list) (let-alist--list-to-sexp (cdr list) var)
+ var))))
+
+(defun let-alist--remove-dot (symbol)
+ "Return SYMBOL, sans an initial dot."
+ (let ((name (symbol-name symbol)))
+ (if (string-match "\\`\\." name)
+ (intern (replace-match "" nil nil name))
+ symbol)))
+
+\f
+;;; The actual macro.
+;;;###autoload
+(defmacro let-alist (alist &rest body)
+ "Let-bind dotted symbols to their cdrs in ALIST and execute BODY.
+Dotted symbol is any symbol starting with a `.'. Only those present
+in BODY are let-bound and this search is done at compile time.
+
+For instance, the following code
+
+ (let-alist alist
+ (if (and .title .body)
+ .body
+ .site
+ .site.contents))
+
+essentially expands to
+
+ (let ((.title (cdr (assq 'title alist)))
+ (.body (cdr (assq 'body alist)))
+ (.site (cdr (assq 'site alist)))
+ (.site.contents (cdr (assq 'contents (cdr (assq 'site alist))))))
+ (if (and .title .body)
+ .body
+ .site
+ .site.contents))
+
+If you nest `let-alist' invocations, the inner one can't access
+the variables of the outer one. You can, however, access alists
+inside the original alist by using dots inside the symbol, as
+displayed in the example above."
+ (declare (indent 1) (debug t))
+ (let ((var (make-symbol "alist")))
+ `(let ((,var ,alist))
+ (let ,(mapcar (lambda (x) `(,(car x) ,(let-alist--access-sexp (car x) var)))
+ (delete-dups (let-alist--deep-dot-search body)))
+ ,@body))))
+
+;;;; ChangeLog:
+
+;; 2015-06-11 Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+;;
+;; * let-alist (let-alist--deep-dot-search): Fix cons
+;;
+;; 2015-03-07 Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+;;
+;; let-alist: Update copyright
+;;
+;; 2014-12-22 Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+;;
+;; packages/let-alist: Use `make-symbol' instead of `gensym'.
+;;
+;; 2014-12-20 Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+;;
+;; packages/let-alist: Enable access to deeper alists
+;;
+;; 2014-12-14 Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+;;
+;; let-alist.el: Add lexical binding. Version bump.
+;;
+;; 2014-12-11 Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+;;
+;; let-alist: New package
+;;
+
+
+(provide 'let-alist)
+
+;;; let-alist.el ends here
--- /dev/null
+;;; markdown-mode-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+(add-to-list 'load-path (or (file-name-directory #$) (car load-path)))
+\f
+;;;### (autoloads nil "markdown-mode" "markdown-mode.el" (21913 4078
+;;;;;; 0 0))
+;;; Generated autoloads from markdown-mode.el
+
+(autoload 'markdown-mode "markdown-mode" "\
+Major mode for editing Markdown files.
+
+\(fn)" t nil)
+
+(add-to-list 'auto-mode-alist '("\\.markdown\\'" . markdown-mode))
+
+(add-to-list 'auto-mode-alist '("\\.text\\'" . markdown-mode))
+
+(add-to-list 'auto-mode-alist '("\\.md\\'" . markdown-mode))
+
+(autoload 'gfm-mode "markdown-mode" "\
+Major mode for editing GitHub Flavored Markdown files.
+
+\(fn)" t nil)
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+;;; markdown-mode-autoloads.el ends here
--- /dev/null
+(define-package "markdown-mode" "20150703.1903" "Emacs Major mode for Markdown-formatted text files" 'nil :url "http://jblevins.org/projects/markdown-mode/" :keywords '("markdown" "github flavored markdown" "itex"))
--- /dev/null
+;;; markdown-mode.el --- Emacs Major mode for Markdown-formatted text files
+
+;; Copyright (C) 2007-2015 Jason R. Blevins <jrblevinNOSPAMsdf.org>
+;; Copyright (C) 2007, 2009 Edward O'Connor <tedNOSPAMoconnor.cx>
+;; Copyright (C) 2007 Conal Elliott <conalNOSPAMconal.net>
+;; Copyright (C) 2008 Greg Bognar <greg_bognarNOSPAMhms.harvard.edu>
+;; Copyright (C) 2008 Dmitry Dzhus <mailNOSPAMsphinx.net.ru>
+;; Copyright (C) 2008 Bryan Kyle <bryan.kyleNOSPAMgmail.com>
+;; Copyright (C) 2008 Ben Voui <intrigeriNOSPAMboum.org>
+;; Copyright (C) 2009 Ankit Solanki <ankit.solankiNOSPAMgmail.com>
+;; Copyright (C) 2009 Hilko Bengen <bengenNOSPAMdebian.org>
+;; Copyright (C) 2009 Peter Williams <pezraNOSPAMbarelyenough.org>
+;; Copyright (C) 2010 George Ogata <george.ogataNOSPAMgmail.com>
+;; Copyright (C) 2011 Eric Merritt <ericbmerrittNOSPAMgmail.com>
+;; Copyright (C) 2011 Philippe Ivaldi <pivaldiNOSPAMsfr.fr>
+;; Copyright (C) 2011 Jeremiah Dodds <jeremiah.doddsNOSPAMgmail.com>
+;; Copyright (C) 2011 Christopher J. Madsen <cjmNOSPAMcjmweb.net>
+;; Copyright (C) 2011 Shigeru Fukaya <shigeru.fukayaNOSPAMgmail.com>
+;; Copyright (C) 2011 Joost Kremers <joostkremersNOSPAMfastmail.fm>
+;; Copyright (C) 2011-2012 Donald Ephraim Curtis <dcurtisNOSPAMmilkbox.net>
+;; Copyright (C) 2012 Akinori Musha <knuNOSPAMidaemons.org>
+;; Copyright (C) 2012 Zhenlei Jia <zhenlei.jiaNOSPAMgmail.com>
+;; Copyright (C) 2012 Peter Jones <pjonesNOSPAMpmade.com>
+;; Copyright (C) 2013 Matus Goljer <dota.keysNOSPAMgmail.com>
+;; Copyright (C) 2015 Google, Inc. (Contributor: Samuel Freilich <sfreilichNOSPAMgoogle.com>)
+;; Copyright (C) 2015 Antonis Kanouras <antonisNOSPAMmetadosis.gr>
+
+;; Author: Jason R. Blevins <jrblevinNOSPAMsdf.org>
+;; Maintainer: Jason R. Blevins <jrblevinNOSPAMsdf.org>
+;; Created: May 24, 2007
+;; Version: 2.0
+;; Package-Version: 20150703.1903
+;; Keywords: Markdown, GitHub Flavored Markdown, itex
+;; URL: http://jblevins.org/projects/markdown-mode/
+
+;; This file is not part of GNU Emacs.
+
+;; 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, 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., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
+
+;;; Commentary:
+
+;; markdown-mode is a major mode for editing [Markdown][]-formatted
+;; text files in GNU Emacs. markdown-mode is free software, licensed
+;; under the GNU GPL.
+;;
+;; [Markdown]: http://daringfireball.net/projects/markdown/
+;;
+;; The latest stable version is markdown-mode 2.0, released on March 24, 2013:
+;;
+;; * [markdown-mode.el][]
+;; * [Screenshot][][^theme]
+;; * [Release notes][]
+;;
+;; [markdown-mode.el]: http://jblevins.org/projects/markdown-mode/markdown-mode.el
+;; [Screenshot]: http://jblevins.org/projects/markdown-mode/screenshots/20130131-002.png
+;; [Release notes]: http://jblevins.org/projects/markdown-mode/rev-2-0
+;;
+;; [^theme]: The theme used in the screenshot is
+;; [color-theme-twilight](https://github.com/crafterm/twilight-emacs).
+;;
+;; markdown-mode is also available in several package managers, including:
+;;
+;; * Debian Linux: [emacs-goodies-el][]
+;; * Ubuntu Linux: [emacs-goodies-el][emacs-goodies-el-ubuntu]
+;; * RedHat and Fedora Linux: [emacs-goodies][]
+;; * NetBSD: [textproc/markdown-mode][]
+;; * Arch Linux (AUR): [emacs-markdown-mode-git][]
+;; * MacPorts: [markdown-mode.el][macports-package] ([pending][macports-ticket])
+;; * FreeBSD: [textproc/markdown-mode.el][freebsd-port]
+;;
+;; [emacs-goodies-el]: http://packages.debian.org/emacs-goodies-el
+;; [emacs-goodies-el-ubuntu]: http://packages.ubuntu.com/search?keywords=emacs-goodies-el
+;; [emacs-goodies]: https://apps.fedoraproject.org/packages/emacs-goodies
+;; [textproc/markdown-mode]: http://pkgsrc.se/textproc/markdown-mode
+;; [emacs-markdown-mode-git]: https://aur.archlinux.org/packages/emacs-goodies-el/
+;; [macports-package]: https://trac.macports.org/browser/trunk/dports/editors/markdown-mode.el/Portfile
+;; [macports-ticket]: http://trac.macports.org/ticket/35716
+;; [freebsd-port]: http://svnweb.freebsd.org/ports/head/textproc/markdown-mode.el
+;;
+;; The latest development version can be downloaded directly
+;; ([markdown-mode.el][devel.el]) or it can be obtained from the
+;; (browsable and clonable) Git repository at
+;; <http://jblevins.org/git/markdown-mode.git>. The entire repository,
+;; including the full project history, can be cloned via the Git protocol
+;; by running
+;;
+;; git clone git://jblevins.org/git/markdown-mode.git
+;;
+;; [devel.el]: http://jblevins.org/git/markdown-mode.git/plain/markdown-mode.el
+
+;;; Installation:
+
+;; Make sure to place `markdown-mode.el` somewhere in the load-path and add
+;; the following lines to your `.emacs` file to associate markdown-mode
+;; with `.text`, `.markdown`, and `.md` files:
+;;
+;; (autoload 'markdown-mode "markdown-mode"
+;; "Major mode for editing Markdown files" t)
+;; (add-to-list 'auto-mode-alist '("\\.text\\'" . markdown-mode))
+;; (add-to-list 'auto-mode-alist '("\\.markdown\\'" . markdown-mode))
+;; (add-to-list 'auto-mode-alist '("\\.md\\'" . markdown-mode))
+;;
+;; There is no official Markdown file extension, nor is there even a
+;; _de facto_ standard, so you can easily add, change, or remove any
+;; of the file extensions above as needed.
+
+;;; Usage:
+
+;; Keybindings are grouped by prefixes based on their function. For
+;; example, the commands for inserting links are grouped under `C-c
+;; C-a`, where the `C-a` is a mnemonic for the HTML `<a>` tag. In
+;; other cases, the connection to HTML is not direct. For example,
+;; commands dealing with headings begin with `C-c C-t` (mnemonic:
+;; titling). The primary commands in each group will are described
+;; below. You can obtain a list of all keybindings by pressing `C-c
+;; C-h`. Movement and shifting commands tend to be associated with
+;; paired delimiters such as `M-{` and `M-}` or `C-c <` and `C-c >`.
+;; Outline navigation keybindings the same as in `org-mode'. Finally,
+;; commands for running Markdown or doing maintenance on an open file
+;; are grouped under the `C-c C-c` prefix. The most commonly used
+;; commands are described below. You can obtain a list of all
+;; keybindings by pressing `C-c C-h`.
+;;
+;; * Hyperlinks: `C-c C-a`
+;;
+;; In this group, `C-c C-a l` inserts an inline link of the form
+;; `[text](url)`. The link text is determined as follows. First,
+;; if there is an active region (i.e., when transient mark mode is
+;; on and the mark is active), use it as the link text. Second,
+;; if the point is at a word, use that word as the link text. In
+;; these two cases, the original text will be replaced with the
+;; link and point will be left at the position for inserting a
+;; URL. Otherwise, insert empty link markup and place the point
+;; for inserting the link text.
+;;
+;; `C-c C-a L` inserts a reference link of the form `[text][label]`
+;; and, optionally, a corresponding reference label definition.
+;; The link text is determined in the same way as with an inline
+;; link (using the region, when active, or the word at the point),
+;; but instead of inserting empty markup as a last resort, the
+;; link text will be read from the minibuffer. The reference
+;; label will be read from the minibuffer in both cases, with
+;; completion from the set of currently defined references. To
+;; create an implicit reference link, press `RET` to accept the
+;; default, an empty label. If the entered referenced label is
+;; not defined, additionally prompt for the URL and (optional)
+;; title. If a URL is provided, a reference definition will be
+;; inserted in accordance with `markdown-reference-location'.
+;; If a title is given, it will be added to the end of the
+;; reference definition and will be used to populate the title
+;; attribute when converted to XHTML.
+;;
+;; `C-c C-a u` inserts a bare url, delimited by angle brackets. When
+;; there is an active region, the text in the region is used as the
+;; URL. If the point is at a URL, that url is used. Otherwise,
+;; insert angle brackets and position the point in between them
+;; for inserting the URL.
+;;
+;; `C-c C-a f` inserts a footnote marker at the point, inserts a
+;; footnote definition below, and positions the point for
+;; inserting the footnote text. Note that footnotes are an
+;; extension to Markdown and are not supported by all processors.
+;;
+;; `C-c C-a w` behaves much like the inline link insertion command
+;; and inserts a wiki link of the form `[[WikiLink]]`. If there
+;; is an active region, use the region as the link text. If the
+;; point is at a word, use the word as the link text. If there is
+;; no active region and the point is not at word, simply insert
+;; link markup. Note that wiki links are an extension to Markdown
+;; and are not supported by all processors.
+;;
+;; * Images: `C-c C-i`
+;;
+;; `C-c C-i i` inserts markup for an inline image, using the
+;; active region or the word at point, if any, as the alt text.
+;; `C-c C-i I` behaves similarly and inserts a reference-style
+;; image.
+;;
+;; * Styles: `C-c C-s`
+;;
+;; `C-c C-s e` inserts markup to make a region or word italic (`e`
+;; for `<em>` or emphasis). If there is an active region, make
+;; the region italic. If the point is at a non-italic word, make
+;; the word italic. If the point is at an italic word or phrase,
+;; remove the italic markup. Otherwise, simply insert italic
+;; delimiters and place the cursor in between them. Similarly,
+;; use `C-c C-s s` for bold (`<strong>`) and `C-c C-s c` for
+;; inline code (`<code>`).
+;;
+;; `C-c C-s b` inserts a blockquote using the active region, if any,
+;; or starts a new blockquote. `C-c C-s C-b` is a variation which
+;; always operates on the region, regardless of whether it is
+;; active or not. The appropriate amount of indentation, if any,
+;; is calculated automatically given the surrounding context, but
+;; may be adjusted later using the region indentation commands.
+;;
+;; `C-c C-s p` behaves similarly for inserting preformatted code
+;; blocks, with `C-c C-s C-p` being the region-only counterpart.
+;;
+;; * Headings: `C-c C-t`
+;;
+;; All heading insertion commands use the text in the active
+;; region, if any, as the heading text. Otherwise, if the current
+;; line is not blank, they use the text on the current line.
+;; Finally, the setext commands will prompt for heading text if
+;; there is no active region and the current line is blank.
+;;
+;; `C-c C-t h` inserts a heading with automatically chosen type and
+;; level (both determined by the previous heading). `C-c C-t H`
+;; behaves similarly, but uses setext (underlined) headings when
+;; possible, still calculating the level automatically.
+;; In cases where the automatically-determined level is not what
+;; you intended, the level can be quickly promoted or demoted
+;; (as described below). Alternatively, a `C-u` prefix can be
+;; given to insert a heading promoted by one level or a `C-u C-u`
+;; prefix can be given to insert a heading demoted by one level.
+;;
+;; To insert a heading of a specific level and type, use `C-c C-t 1`
+;; through `C-c C-t 6` for atx (hash mark) headings and `C-c C-t !` or
+;; `C-c C-t @` for setext headings of level one or two, respectively.
+;; Note that `!` is `S-1` and `@` is `S-2`.
+;;
+;; If the point is at a heading, these commands will replace the
+;; existing markup in order to update the level and/or type of the
+;; heading. To remove the markup of the heading at the point,
+;; press `C-c C-k` to kill the heading and press `C-y` to yank the
+;; heading text back into the buffer.
+;;
+;; * Horizontal Rules: `C-c -`
+;;
+;; `C-c -` inserts a horizontal rule. By default, insert the
+;; first string in the list `markdown-hr-strings' (the most
+;; prominent rule). With a `C-u` prefix, insert the last string.
+;; With a numeric prefix `N`, insert the string in position `N`
+;; (counting from 1).
+;;
+;; * Markdown and Maintenance Commands: `C-c C-c`
+;;
+;; *Compile:* `C-c C-c m` will run Markdown on the current buffer
+;; and show the output in another buffer. *Preview*: `C-c C-c p`
+;; runs Markdown on the current buffer and previews, stores the
+;; output in a temporary file, and displays the file in a browser.
+;; *Export:* `C-c C-c e` will run Markdown on the current buffer
+;; and save the result in the file `basename.html`, where
+;; `basename` is the name of the Markdown file with the extension
+;; removed. *Export and View:* press `C-c C-c v` to export the
+;; file and view it in a browser. **For both export commands, the
+;; output file will be overwritten without notice.**
+;; *Open:* `C-c C-c o` will open the Markdown source file directly
+;; using `markdown-open-command'.
+;;
+;; To summarize:
+;;
+;; - `C-c C-c m`: `markdown-command' > `*markdown-output*` buffer.
+;; - `C-c C-c p`: `markdown-command' > temporary file > browser.
+;; - `C-c C-c e`: `markdown-command' > `basename.html`.
+;; - `C-c C-c v`: `markdown-command' > `basename.html` > browser.
+;; - `C-c C-c w`: `markdown-command' > kill ring.
+;; - `C-c C-c o`: `markdown-open-command'.
+;;
+;; `C-c C-c c` will check for undefined references. If there are
+;; any, a small buffer will open with a list of undefined
+;; references and the line numbers on which they appear. In Emacs
+;; 22 and greater, selecting a reference from this list and
+;; pressing `RET` will insert an empty reference definition at the
+;; end of the buffer. Similarly, selecting the line number will
+;; jump to the corresponding line.
+;;
+;; `C-c C-c n` renumbers any ordered lists in the buffer that are
+;; out of sequence.
+;;
+;; `C-c C-c ]` completes all headings and normalizes all horizontal
+;; rules in the buffer.
+;;
+;; * Following Links: `C-c C-o`
+;;
+;; Press `C-c C-o` when the point is on an inline or reference
+;; link to open the URL in a browser. When the point is at a
+;; wiki link, open it in another buffer (in the current window,
+;; or in the other window with the `C-u` prefix). Use `M-p` and
+;; `M-n` to quickly jump to the previous or next link of any type.
+;;
+;; * Jumping: `C-c C-j`
+;;
+;; Use `C-c C-j` to jump from the object at point to its counterpart
+;; elsewhere in the text, when possible. Jumps between reference
+;; links and definitions; between footnote markers and footnote
+;; text. If more than one link uses the same reference name, a
+;; new buffer will be created containing clickable buttons for jumping
+;; to each link. You may press `TAB` or `S-TAB` to jump between
+;; buttons in this window.
+;;
+;; * Promotion and Demotion: `C-c C--` and `C-c C-=`
+;;
+;; Headings, horizontal rules, and list items can be promoted and
+;; demoted, as well as bold and italic text. For headings,
+;; "promotion" means *decreasing* the level (i.e., moving from
+;; `<h2>` to `<h1>`) while "demotion" means *increasing* the
+;; level. For horizontal rules, promotion and demotion means
+;; moving backward or forward through the list of rule strings in
+;; `markdown-hr-strings'. For bold and italic text, promotion and
+;; demotion means changing the markup from underscores to asterisks.
+;; Press `C-c C--` or `M-LEFT` to promote the element at the point
+;; if possible.
+;;
+;; To remember these commands, note that `-` is for decreasing the
+;; level (promoting), and `=` (on the same key as `+`) is for
+;; increasing the level (demoting). Similarly, the left and right
+;; arrow keys indicate the direction that the atx heading markup
+;; is moving in when promoting or demoting.
+;;
+;; * Completion: `C-c C-]`
+;;
+;; Complete markup is in normalized form, which means, for
+;; example, that the underline portion of a setext header is the
+;; same length as the heading text, or that the number of leading
+;; and trailing hash marks of an atx header are equal and that
+;; there is no extra whitespace in the header text. `C-c C-]`
+;; completes the markup at the point, if it is determined to be
+;; incomplete.
+;;
+;; * Editing Lists: `M-RET`, `M-UP`, `M-DOWN`, `M-LEFT`, and `M-RIGHT`
+;;
+;; New list items can be inserted with `M-RET`. This command
+;; determines the appropriate marker (one of the possible
+;; unordered list markers or the next number in sequence for an
+;; ordered list) and indentation level by examining nearby list
+;; items. If there is no list before or after the point, start a
+;; new list. Prefix this command by `C-u` to decrease the
+;; indentation by one level. Prefix this command by `C-u C-u` to
+;; increase the indentation by one level.
+;;
+;; Existing list items can be moved up or down with `M-UP` or
+;; `M-DOWN` and indented or exdented with `M-RIGHT` or `M-LEFT`.
+;;
+;; * Shifting the Region: `C-c <` and `C-c >`
+;;
+;; Text in the region can be indented or exdented as a group using
+;; `C-c >` to indent to the next indentation point (calculated in
+;; the current context), and `C-c <` to exdent to the previous
+;; indentation point. These keybindings are the same as those for
+;; similar commands in `python-mode'.
+;;
+;; * Killing Elements: `C-c C-k`
+;;
+;; Press `C-c C-k` to kill the thing at point and add important
+;; text, without markup, to the kill ring. Possible things to
+;; kill include (roughly in order of precedece): inline code,
+;; headings, horizonal rules, links (add link text to kill ring),
+;; images (add alt text to kill ring), angle URIs, email
+;; addresses, bold, italics, reference definitions (add URI to
+;; kill ring), footnote markers and text (kill both marker and
+;; text, add text to kill ring), and list items.
+;;
+;; * Outline Navigation: `C-c C-n`, `C-c C-p`, `C-c C-f`, `C-c C-b`, and `C-c C-u`
+;;
+;; Navigation between headings is possible using `outline-mode'.
+;; Use `C-c C-n` and `C-c C-p` to move between the next and previous
+;; visible headings. Similarly, `C-c C-f` and `C-c C-b` move to the
+;; next and previous visible headings at the same level as the one
+;; at the point. Finally, `C-c C-u` will move up to a lower-level
+;; (higher precedence) visible heading.
+;;
+;; * Movement by Paragraph or Block: `M-{` and `M-}`
+;;
+;; The definition of a "paragraph" is slightly different in
+;; markdown-mode than, say, text-mode, because markdown-mode
+;; supports filling for list items and respects hard line breaks,
+;; both of which break paragraphs. So, markdown-mode overrides
+;; the usual paragraph navigation commands `M-{` and `M-}` so that
+;; with a `C-u` prefix, these commands jump to the beginning or
+;; end of an entire block of text, respectively, where "blocks"
+;; are separated by one or more lines.
+;;
+;; * Movement by Defun: `C-M-a`, `C-M-e`, and `C-M-h`
+;;
+;; The usual Emacs commands can be used to move by defuns
+;; (top-level major definitions). In markdown-mode, a defun is a
+;; section. As usual, `C-M-a` will move the point to the
+;; beginning of the current or preceding defun, `C-M-e` will move
+;; to the end of the current or following defun, and `C-M-h` will
+;; put the region around the entire defun.
+;;
+;; As noted, many of the commands above behave differently depending
+;; on whether Transient Mark mode is enabled or not. When it makes
+;; sense, if Transient Mark mode is on and the region is active, the
+;; command applies to the text in the region (e.g., `C-c C-s s` makes the
+;; region bold). For users who prefer to work outside of Transient
+;; Mark mode, since Emacs 22 it can be enabled temporarily by pressing
+;; `C-SPC C-SPC`. When this is not the case, many commands then
+;; proceed to look work with the word or line at the point.
+;;
+;; When applicable, commands that specifically act on the region even
+;; outside of Transient Mark mode have the same keybinding as their
+;; standard counterpart, but the letter is uppercase. For example,
+;; `markdown-insert-blockquote' is bound to `C-c C-s b` and only acts on
+;; the region in Transient Mark mode while `markdown-blockquote-region'
+;; is bound to `C-c C-s B` and always applies to the region (when nonempty).
+;;
+;; Note that these region-specific functions are useful in many
+;; cases where it may not be obvious. For example, yanking text from
+;; the kill ring sets the mark at the beginning of the yanked text
+;; and moves the point to the end. Therefore, the (inactive) region
+;; contains the yanked text. So, `C-y` followed by `C-c C-s C-b` will
+;; yank text and turn it into a blockquote.
+;;
+;; markdown-mode attempts to be flexible in how it handles
+;; indentation. When you press `TAB` repeatedly, the point will cycle
+;; through several possible indentation levels corresponding to things
+;; you might have in mind when you press `RET` at the end of a line or
+;; `TAB`. For example, you may want to start a new list item,
+;; continue a list item with hanging indentation, indent for a nested
+;; pre block, and so on. Exdention is handled similarly when backspace
+;; is pressed at the beginning of the non-whitespace portion of a line.
+;;
+;; markdown-mode supports outline-minor-mode as well as org-mode-style
+;; visibility cycling for atx- or hash-style headings. There are two
+;; types of visibility cycling: Pressing `S-TAB` cycles globally between
+;; the table of contents view (headings only), outline view (top-level
+;; headings only), and the full document view. Pressing `TAB` while the
+;; point is at a heading will cycle through levels of visibility for the
+;; subtree: completely folded, visible children, and fully visible.
+;; Note that mixing hash and underline style headings will give undesired
+;; results.
+
+;;; Customization:
+
+;; Although no configuration is *necessary* there are a few things
+;; that can be customized. The `M-x customize-mode` command
+;; provides an interface to all of the possible customizations:
+;;
+;; * `markdown-command' - the command used to run Markdown (default:
+;; `markdown`). This variable may be customized to pass
+;; command-line options to your Markdown processor of choice.
+;;
+;; * `markdown-command-needs-filename' - set to `t' if
+;; `markdown-command' does not accept standard input (default:
+;; `nil'). When `nil', `markdown-mode' will pass the Markdown
+;; content to `markdown-command' using standard input (`stdin`).
+;; When set to `t', `markdown-mode' will pass the name of the file
+;; as the final command-line argument to `markdown-command'. Note
+;; that in the latter case, you will only be able to run
+;; `markdown-command' from buffers which are visiting a file.
+;;
+;; * `markdown-open-command' - the command used for calling a standalone
+;; Markdown previewer which is capable of opening Markdown source files
+;; directly (default: `nil'). This command will be called
+;; with a single argument, the filename of the current buffer.
+;; A representative program is the Mac app [Marked 2][], a
+;; live-updating Markdown previewer which can be [called from a
+;; simple shell script](http://jblevins.org/log/marked-2-command).
+;;
+;; * `markdown-hr-strings' - list of strings to use when inserting
+;; horizontal rules. Different strings will not be distinguished
+;; when converted to HTML--they will all be converted to
+;; `<hr/>`--but they may add visual distinction and style to plain
+;; text documents. To maintain some notion of promotion and
+;; demotion, keep these sorted from largest to smallest.
+;;
+;; * `markdown-bold-underscore' - set to a non-nil value to use two
+;; underscores for bold instead of two asterisks (default: `nil').
+;;
+;; * `markdown-italic-underscore' - set to a non-nil value to use
+;; underscores for italic instead of asterisks (default: `nil').
+;;
+;; * `markdown-indent-function' - the function to use for automatic
+;; indentation (default: `markdown-indent-line').
+;;
+;; * `markdown-indent-on-enter' - set to a non-nil value to
+;; automatically indent new lines when the enter key is pressed
+;; (default: `t')
+;;
+;; * `markdown-wiki-link-alias-first' - set to a non-nil value to
+;; treat aliased wiki links like `[[link text|PageName]]`
+;; (default: `t'). When set to nil, they will be treated as
+;; `[[PageName|link text]]'.
+;;
+;; * `markdown-uri-types' - a list of protocol schemes (e.g., "http")
+;; for URIs that `markdown-mode' should highlight.
+;;
+;; * `markdown-enable-math' - syntax highlighting for LaTeX
+;; fragments (default: `nil'). Set this to `t' to turn on math
+;; support by default. Math support can be toggled later using
+;; the function `markdown-enable-math'."
+;;
+;; * `markdown-css-paths' - CSS files to link to in XHTML output
+;; (default: `nil`).
+;;
+;; * `markdown-content-type' - when set to a nonempty string, an
+;; `http-equiv` attribute will be included in the XHTML `<head>`
+;; block (default: `""`). If needed, the suggested values are
+;; `application/xhtml+xml` or `text/html`. See also:
+;; `markdown-coding-system'.
+;;
+;; * `markdown-coding-system' - used for specifying the character
+;; set identifier in the `http-equiv` attribute when included
+;; (default: `nil'). See `markdown-content-type', which must
+;; be set before this variable has any effect. When set to `nil',
+;; `buffer-file-coding-system' will be used to automatically
+;; determine the coding system string (falling back to
+;; `iso-8859-1' when unavailable). Common settings are `utf-8'
+;; and `iso-latin-1'.
+;;
+;; * `markdown-xhtml-header-content' - additional content to include
+;; in the XHTML `<head>` block (default: `""`).
+;;
+;; * `markdown-xhtml-standalone-regexp' - a regular expression which
+;; `markdown-mode' uses to determine whether the output of
+;; `markdown-command' is a standalone XHTML document or an XHTML
+;; fragment (default: `"^\\(<\\?xml\\|<!DOCTYPE\\|<html\\)"`). If
+;; this regular expression not matched in the first five lines of
+;; output, `markdown-mode' assumes the output is a fragment and
+;; adds a header and footer.
+;;
+;; * `markdown-link-space-sub-char' - a character to replace spaces
+;; when mapping wiki links to filenames (default: `"_"`).
+;; For example, use an underscore for compatibility with the
+;; Python Markdown WikiLinks extension. In `gfm-mode', this is
+;; set to `"-"` to conform with GitHub wiki links.
+;;
+;; * `markdown-reference-location' - where to insert reference
+;; definitions (default: `header`). The possible locations are
+;; the end of the document (`end`), after the current block
+;; (`immediately`), before the next header (`header`).
+;;
+;; * `markdown-footnote-location' - where to insert footnote text
+;; (default: `end`). The set of location options is the same as
+;; for `markdown-reference-location'.
+;;
+;; * `comment-auto-fill-only-comments' - variable is made
+;; buffer-local and set to `nil' by default. In programming
+;; language modes, when this variable is non-nil, only comments
+;; will be filled by auto-fill-mode. However, comments in
+;; Markdown documents are rare and the most users probably intend
+;; for the actual content of the document to be filled. Making
+;; this variable buffer-local allows `markdown-mode' to override
+;; the default behavior induced when the global variable is non-nil.
+;;
+;; Additionally, the faces used for syntax highlighting can be modified to
+;; your liking by issuing `M-x customize-group RET markdown-faces`
+;; or by using the "Markdown Faces" link at the bottom of the mode
+;; customization screen.
+;;
+;; [Marked 2]: https://itunes.apple.com/us/app/marked-2/id890031187?mt=12&uo=4&at=11l5Vs&ct=mm
+
+;;; Extensions:
+
+;; Besides supporting the basic Markdown syntax, markdown-mode also
+;; includes syntax highlighting for `[[Wiki Links]]` by default. Wiki
+;; links may be followed by pressing `C-c C-o` when the point
+;; is at a wiki link. Use `M-p` and `M-n` to quickly jump to the
+;; previous and next links (including links of other types).
+;; Aliased or piped wiki links of the form `[[link text|PageName]]`
+;; are also supported. Since some wikis reverse these components, set
+;; `markdown-wiki-link-alias-first' to nil to treat them as
+;; `[[PageName|link text]]`.
+;;
+;; [SmartyPants][] support is possible by customizing `markdown-command'.
+;; If you install `SmartyPants.pl` at, say, `/usr/local/bin/smartypants`,
+;; then you can set `markdown-command' to `"markdown | smartypants"`.
+;; You can do this either by using `M-x customize-group markdown`
+;; or by placing the following in your `.emacs` file:
+;;
+;; (defun markdown-custom ()
+;; "markdown-mode-hook"
+;; (setq markdown-command "markdown | smartypants"))
+;; (add-hook 'markdown-mode-hook '(lambda() (markdown-custom)))
+;;
+;; [SmartyPants]: http://daringfireball.net/projects/smartypants/
+;;
+;; Syntax highlighting for mathematical expressions written
+;; in LaTeX (only expressions denoted by `$..$`, `$$..$$`, or `\[..\]`)
+;; can be enabled by setting `markdown-enable-math' to a non-nil value,
+;; either via customize or by placing `(setq markdown-enable-math t)`
+;; in `.emacs`, and then restarting Emacs or calling
+;; `markdown-reload-extensions'.
+
+;;; GitHub Flavored Markdown:
+
+;; A [GitHub Flavored Markdown][GFM] (GFM) mode, `gfm-mode', is also
+;; available. The GitHub implementation of differs slightly from
+;; standard Markdown. The most important differences are that
+;; newlines are significant, triggering hard line breaks, and that
+;; underscores inside of words (e.g., variable names) need not be
+;; escaped. As such, `gfm-mode' turns off `auto-fill-mode' and turns
+;; on `visual-line-mode' (or `longlines-mode' if `visual-line-mode' is
+;; not available). Underscores inside of words (such as
+;; test_variable) will not trigger emphasis.
+;;
+;; Wiki links in this mode will be treated as on GitHub, with hyphens
+;; replacing spaces in filenames and where the first letter of the
+;; filename capitalized. For example, `[[wiki link]]' will map to a
+;; file named `Wiki-link` with the same extension as the current file.
+;;
+;; GFM code blocks, with optional programming language keywords, will
+;; be highlighted. They can be inserted with `C-c C-s P`. If there
+;; is an active region, the text in the region will be placed inside
+;; the code block. You will be prompted for the name of the language,
+;; but may press enter to continue without naming a language.
+;;
+;; Similarly, strike through text is supoorted in GFM mode and can be
+;; inserted (and toggled) using `C-c C-s d`. Following the mnemonics
+;; for the other style keybindings, the letter `d` coincides with the
+;; HTML tag `<del>`.
+;;
+;; For GFM preview can be powered by setting `markdown-command' to
+;; use [Docter][]. This may also be configured to work with [Marked 2][]
+;; for `markdown-open-command'.
+;;
+;; [GFM]: http://github.github.com/github-flavored-markdown/
+;; [Docter]: https://github.com/alampros/Docter
+
+;;; Acknowledgments:
+
+;; markdown-mode has benefited greatly from the efforts of the
+;; following people:
+;;
+;; * Cyril Brulebois <cyril.bruleboisNOSPAMenst-bretagne.fr> for Debian packaging.
+;; * Conal Elliott <conalNOSPAMconal.net> for a font-lock regexp patch.
+;; * Edward O'Connor <hober0NOSPAMgmail.com> for a font-lock regexp fix and
+;; GitHub Flavored Markdown mode (`gfm-mode').
+;; * Greg Bognar <greg_bognarNOSPAMhms.harvard.edu> for menus and running
+;; `markdown' with an active region.
+;; * Daniel Burrows <dburrowsNOSPAMdebian.org> for filing Debian bug #456592.
+;; * Peter S. Galbraith <psgNOSPAMdebian.org> for maintaining `emacs-goodies-el`.
+;; * Dmitry Dzhus <mailNOSPAMsphinx.net.ru> for undefined reference checking.
+;; * Carsten Dominik <carstenNOSPAMorgmode.org> for `org-mode', from which the
+;; visibility cycling functionality was derived, and for a bug fix
+;; related to `orgtbl-mode'.
+;; * Bryan Kyle <bryan.kyleNOSPAMgmail.com> for indentation code.
+;; * Ben Voui <intrigeriNOSPAMboum.org> for font-lock face customizations.
+;; * Ankit Solanki <ankit.solankiNOSPAMgmail.com> for `longlines.el`
+;; compatibility and custom CSS.
+;; * Hilko Bengen <bengenNOSPAMdebian.org> for proper XHTML output.
+;; * Jose A. Ortega Ruiz <jaoNOSPAMgnu.org> for Emacs 23 fixes.
+;; * Nelson Minar <nelsonNOSPAMsantafe.edu> for `html-helper-mode', from which
+;; comment matching functions were derived.
+;; * Alec Resnick <alecNOSPAMsproutward.org> for bug reports.
+;; * Joost Kremers <joostkremersNOSPAMfastmail.fm> for footnote-handling
+;; functions, bug reports regarding indentation, and
+;; fixes for byte-compilation warnings.
+;; * Peter Williams <pezraNOSPAMbarelyenough.org> for `fill-paragraph'
+;; enhancements.
+;; * George Ogata <george.ogataNOSPAMgmail.com> for fixing several
+;; byte-compilation warnings.
+;; * Eric Merritt <ericbmerrittNOSPAMgmail.com> for wiki link features.
+;; * Philippe Ivaldi <pivaldiNOSPAMsfr.fr> for XHTML preview
+;; customizations and XHTML export.
+;; * Jeremiah Dodds <jeremiah.doddsNOSPAMgmail.com> for supporting
+;; Markdown processors which do not accept input from stdin.
+;; * Werner Dittmann <werner.dittmannNOSPAMt-online.de> for bug reports
+;; regarding the `cl` dependency and `auto-fill-mode' and indentation.
+;; * Scott Pfister <scott.pfisterNOSPAMgmail.com> for generalizing the space
+;; substitution character for mapping wiki links to filenames.
+;; * Marcin Kasperski <marcin.kasperskiNOSPAMmekk.waw.pl> for a patch to
+;; escape shell commands.
+;; * Christopher J. Madsen <cjmNOSPAMcjmweb.net> for patches to fix a match
+;; data bug and to prefer `visual-line-mode' in `gfm-mode'.
+;; * Shigeru Fukaya <shigeru.fukayaNOSPAMgmail.com> for better adherence to
+;; Emacs Lisp coding conventions.
+;; * Donald Ephraim Curtis <dcurtisNOSPAMmilkbox.net> for fixing the `fill-paragraph'
+;; regexp, refactoring the compilation and preview functions,
+;; heading font-lock generalizations, list renumbering,
+;; and kill ring save.
+;; * Kevin Porter <kportertxNOSPAMgmail.com> for wiki link handling in `gfm-mode'.
+;; * Max Penet <max.penetNOSPAMgmail.com> and Peter Eisentraut <peter_eNOSPAMgmx.net>
+;; for an autoload token for `gfm-mode'.
+;; * Ian Yang <meNOSPAMiany.me> for improving the reference definition regex.
+;; * Akinori Musha <knuNOSPAMidaemons.org> for an imenu index function.
+;; * Michael Sperber <sperberNOSPAMdeinprogramm.de> for XEmacs fixes.
+;; * Francois Gannaz <francois.gannazNOSPAMfree.fr> for suggesting charset
+;; declaration in XHTML output.
+;; * Zhenlei Jia <zhenlei.jiaNOSPAMgmail.com> for smart exdention function.
+;; * Matus Goljer <dota.keysNOSPAMgmail.com> for improved wiki link following
+;; and GFM code block insertion.
+;; * Peter Jones <pjonesNOSPAMpmade.com> for link following functions.
+;; * Bryan Fink <bryan.finkNOSPAMgmail.com> for a bug report regarding
+;; externally modified files.
+;; * Vegard Vesterheim <vegard.vesterheim@uninett.no> for a bug fix
+;; related to `orgtbl-mode'.
+;; * Makoto Motohashi <mkt.motohashiNOSPAMgmail.com> for before- and after-
+;; export hooks, unit test improvements, and updates to support
+;; wide characters.
+;; * Michael Dwyer <mdwyerNOSPAMehtech.in> for `gfm-mode' underscore regexp.
+;; * Chris Lott <chrisNOSPAMchrislott.org> for suggesting reference label
+;; completion.
+;; * Gunnar Franke <Gunnar.FrankeNOSPAMgmx.de> for a completion bug report.
+;; * David Glasser <glasserNOSPAMmeteor.com> for a `paragraph-separate' fix.
+;; * Daniel Brotsky <devNOSPAMbrotsky.com> for better auto-fill defaults.
+;; * Samuel Freilich <sfreilichNOSPAMgoogle.com> for improved filling
+;; behavior regarding list items, footnotes, and reference
+;; definitions, improved killing of footnotes, and numerous other
+;; tests and bug fixes.
+;; * Antonis Kanouras <antonisNOSPAMmetadosis.gr> for strike through support.
+;; * Tim Visher <tim.visherNOSPAMgmail.com> for multiple CSS files and other
+;; general improvements.
+;; * Matt McClure <matthewlmcclureNOSPAMgmail.com> for a patch to prevent
+;; overwriting source files with .html extensions upon export.
+;; * Roger Bolsius <roger.bolsiusNOSPAMgmail.com> for ordered list improvements.
+;; * Google's Open Source Programs Office for recognizing the project with
+;; a monetary contribution in June 2015.
+
+;;; Bugs:
+
+;; Although markdown-mode is developed and tested primarily using
+;; GNU Emacs 24, compatibility with earlier Emacsen is also a
+;; priority.
+;;
+;; If you find any bugs in markdown-mode, please construct a test case
+;; or a patch and email me at <jrblevinNOSPAMsdf.org>.
+
+;;; History:
+
+;; markdown-mode was written and is maintained by Jason Blevins. The
+;; first version was released on May 24, 2007.
+;;
+;; * 2007-05-24: Version 1.1
+;; * 2007-05-25: Version 1.2
+;; * 2007-06-05: [Version 1.3][]
+;; * 2007-06-29: Version 1.4
+;; * 2007-10-11: [Version 1.5][]
+;; * 2008-06-04: [Version 1.6][]
+;; * 2009-10-01: [Version 1.7][]
+;; * 2011-08-12: [Version 1.8][]
+;; * 2011-08-15: [Version 1.8.1][]
+;; * 2013-01-25: [Version 1.9][]
+;; * 2013-03-24: [Version 2.0][]
+;;
+;; [Version 1.3]: http://jblevins.org/projects/markdown-mode/rev-1-3
+;; [Version 1.5]: http://jblevins.org/projects/markdown-mode/rev-1-5
+;; [Version 1.6]: http://jblevins.org/projects/markdown-mode/rev-1-6
+;; [Version 1.7]: http://jblevins.org/projects/markdown-mode/rev-1-7
+;; [Version 1.8]: http://jblevins.org/projects/markdown-mode/rev-1-8
+;; [Version 1.8.1]: http://jblevins.org/projects/markdown-mode/rev-1-8-1
+;; [Version 1.9]: http://jblevins.org/projects/markdown-mode/rev-1-9
+;; [Version 2.0]: http://jblevins.org/projects/markdown-mode/rev-2-0
+
+\f
+;;; Code:
+
+(require 'easymenu)
+(require 'outline)
+(require 'thingatpt)
+(eval-when-compile (require 'cl))
+
+\f
+;;; Constants =================================================================
+
+(defconst markdown-mode-version "2.0"
+ "Markdown mode version number.")
+
+(defconst markdown-output-buffer-name "*markdown-output*"
+ "Name of temporary buffer for markdown command output.")
+
+\f
+;;; Global Variables ==========================================================
+
+(defvar markdown-reference-label-history nil
+ "History of used reference labels.")
+
+\f
+;;; Customizable Variables ====================================================
+
+(defvar markdown-mode-hook nil
+ "Hook run when entering Markdown mode.")
+
+(defvar markdown-before-export-hook nil
+ "Hook run before running Markdown to export XHTML output.
+The hook may modify the buffer, which will be restored to it's
+original state after exporting is complete.")
+
+(defvar markdown-after-export-hook nil
+ "Hook run after XHTML output has been saved.
+Any changes to the output buffer made by this hook will be saved.")
+
+(defgroup markdown nil
+ "Major mode for editing text files in Markdown format."
+ :prefix "markdown-"
+ :group 'wp
+ :link '(url-link "http://jblevins.org/projects/markdown-mode/"))
+
+(defcustom markdown-command "markdown"
+ "Command to run markdown."
+ :group 'markdown
+ :type 'string)
+
+(defcustom markdown-command-needs-filename nil
+ "Set to non-nil if `markdown-command' does not accept input from stdin.
+Instead, it will be passed a filename as the final command line
+option. As a result, you will only be able to run Markdown from
+buffers which are visiting a file."
+ :group 'markdown
+ :type 'boolean)
+
+(defcustom markdown-open-command nil
+ "Command used for opening Markdown files directly.
+For example, a standalone Markdown previewer. This command will
+be called with a single argument: the filename of the current
+buffer."
+ :group 'markdown
+ :type 'string)
+
+(defcustom markdown-hr-strings
+ '("-------------------------------------------------------------------------------"
+ "* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *"
+ "---------------------------------------"
+ "* * * * * * * * * * * * * * * * * * * *"
+ "---------"
+ "* * * * *")
+ "Strings to use when inserting horizontal rules.
+The first string in the list will be the default when inserting a
+horizontal rule. Strings should be listed in decreasing order of
+prominence (as in headings from level one to six) for use with
+promotion and demotion functions."
+ :group 'markdown
+ :type 'list)
+
+(defcustom markdown-bold-underscore nil
+ "Use two underscores for bold instead of two asterisks."
+ :group 'markdown
+ :type 'boolean)
+
+(defcustom markdown-italic-underscore nil
+ "Use underscores for italic instead of asterisks."
+ :group 'markdown
+ :type 'boolean)
+
+(defcustom markdown-indent-function 'markdown-indent-line
+ "Function to use to indent."
+ :group 'markdown
+ :type 'function)
+
+(defcustom markdown-indent-on-enter t
+ "Automatically indent new lines when enter key is pressed.
+When this variable is set to t, pressing RET will call
+`newline-and-indent'. When set to nil, define RET to call
+`newline' as usual. In the latter case, you can still use
+auto-indentation by pressing \\[newline-and-indent]."
+ :group 'markdown
+ :type 'boolean)
+
+(defcustom markdown-wiki-link-alias-first t
+ "When non-nil, treat aliased wiki links like [[alias text|PageName]].
+Otherwise, they will be treated as [[PageName|alias text]]."
+ :group 'markdown
+ :type 'boolean)
+
+(defcustom markdown-uri-types
+ '("acap" "cid" "data" "dav" "fax" "file" "ftp" "gopher" "http" "https"
+ "imap" "ldap" "mailto" "mid" "modem" "news" "nfs" "nntp" "pop" "prospero"
+ "rtsp" "service" "sip" "tel" "telnet" "tip" "urn" "vemmi" "wais")
+ "Link types for syntax highlighting of URIs."
+ :group 'markdown
+ :type 'list)
+
+(defcustom markdown-enable-math nil
+ "Syntax highlighting for inline LaTeX and itex expressions.
+Set this to a non-nil value to turn on math support by default.
+Math support can be toggled later using `markdown-enable-math'
+or \\[markdown-enable-math]."
+ :group 'markdown
+ :type 'boolean
+ :safe 'booleanp)
+
+(defcustom markdown-css-paths nil
+ "URL of CSS file to link to in the output XHTML."
+ :group 'markdown
+ :type 'list)
+
+(defcustom markdown-content-type ""
+ "Content type string for the http-equiv header in XHTML output.
+When set to a non-empty string, insert the http-equiv attribute.
+Otherwise, this attribute is omitted."
+ :group 'markdown
+ :type 'string)
+
+(defcustom markdown-coding-system nil
+ "Character set string for the http-equiv header in XHTML output.
+Defaults to `buffer-file-coding-system' (and falling back to
+`iso-8859-1' when not available). Common settings are `utf-8'
+and `iso-latin-1'. Use `list-coding-systems' for more choices."
+ :group 'markdown
+ :type 'coding-system)
+
+(defcustom markdown-xhtml-header-content ""
+ "Additional content to include in the XHTML <head> block."
+ :group 'markdown
+ :type 'string)
+
+(defcustom markdown-xhtml-standalone-regexp
+ "^\\(<\\?xml\\|<!DOCTYPE\\|<html\\)"
+ "Regexp indicating whether `markdown-command' output is standalone XHTML."
+ :group 'markdown
+ :type 'regexp)
+
+(defcustom markdown-link-space-sub-char "_"
+ "Character to use instead of spaces when mapping wiki links to filenames."
+ :group 'markdown
+ :type 'string)
+
+(defcustom markdown-reference-location 'header
+ "Position where new reference definitions are inserted in the document."
+ :group 'markdown
+ :type '(choice (const :tag "At the end of the document" end)
+ (const :tag "Immediately after the current block" immediately)
+ (const :tag "Before next header" header)))
+
+(defcustom markdown-footnote-location 'end
+ "Position where new footnotes are inserted in the document."
+ :group 'markdown
+ :type '(choice (const :tag "At the end of the document" end)
+ (const :tag "Immediately after the current block" immediately)
+ (const :tag "Before next header" header)))
+
+(defcustom markdown-unordered-list-item-prefix " * "
+ "String inserted before unordered list items."
+ :group 'markdown
+ :type 'string)
+
+\f
+;;; Font Lock =================================================================
+
+(require 'font-lock)
+
+(defvar markdown-italic-face 'markdown-italic-face
+ "Face name to use for italic text.")
+
+(defvar markdown-bold-face 'markdown-bold-face
+ "Face name to use for bold text.")
+
+(defvar markdown-strike-through-face 'markdown-strike-through-face
+ "Face name to use for strike-through text.")
+
+(defvar markdown-header-delimiter-face 'markdown-header-delimiter-face
+ "Face name to use as a base for header delimiters.")
+
+(defvar markdown-header-rule-face 'markdown-header-rule-face
+ "Face name to use as a base for header rules.")
+
+(defvar markdown-header-face 'markdown-header-face
+ "Face name to use as a base for headers.")
+
+(defvar markdown-header-face-1 'markdown-header-face-1
+ "Face name to use for level-1 headers.")
+
+(defvar markdown-header-face-2 'markdown-header-face-2
+ "Face name to use for level-2 headers.")
+
+(defvar markdown-header-face-3 'markdown-header-face-3
+ "Face name to use for level-3 headers.")
+
+(defvar markdown-header-face-4 'markdown-header-face-4
+ "Face name to use for level-4 headers.")
+
+(defvar markdown-header-face-5 'markdown-header-face-5
+ "Face name to use for level-5 headers.")
+
+(defvar markdown-header-face-6 'markdown-header-face-6
+ "Face name to use for level-6 headers.")
+
+(defvar markdown-inline-code-face 'markdown-inline-code-face
+ "Face name to use for inline code.")
+
+(defvar markdown-list-face 'markdown-list-face
+ "Face name to use for list markers.")
+
+(defvar markdown-blockquote-face 'markdown-blockquote-face
+ "Face name to use for blockquote.")
+
+(defvar markdown-pre-face 'markdown-pre-face
+ "Face name to use for preformatted text.")
+
+(defvar markdown-language-keyword-face 'markdown-language-keyword-face
+ "Face name to use for programming language identifiers.")
+
+(defvar markdown-link-face 'markdown-link-face
+ "Face name to use for links.")
+
+(defvar markdown-missing-link-face 'markdown-missing-link-face
+ "Face name to use for links where the linked file does not exist.")
+
+(defvar markdown-reference-face 'markdown-reference-face
+ "Face name to use for reference.")
+
+(defvar markdown-footnote-face 'markdown-footnote-face
+ "Face name to use for footnote identifiers.")
+
+(defvar markdown-url-face 'markdown-url-face
+ "Face name to use for URLs.")
+
+(defvar markdown-link-title-face 'markdown-link-title-face
+ "Face name to use for reference link titles.")
+
+(defvar markdown-line-break-face 'markdown-line-break-face
+ "Face name to use for hard line breaks.")
+
+(defvar markdown-comment-face 'markdown-comment-face
+ "Face name to use for HTML comments.")
+
+(defvar markdown-math-face 'markdown-math-face
+ "Face name to use for LaTeX expressions.")
+
+(defvar markdown-metadata-key-face 'markdown-metadata-key-face
+ "Face name to use for metadata keys.")
+
+(defvar markdown-metadata-value-face 'markdown-metadata-value-face
+ "Face name to use for metadata values.")
+
+(defgroup markdown-faces nil
+ "Faces used in Markdown Mode"
+ :group 'markdown
+ :group 'faces)
+
+(defface markdown-italic-face
+ '((t (:inherit font-lock-variable-name-face :slant italic)))
+ "Face for italic text."
+ :group 'markdown-faces)
+
+(defface markdown-bold-face
+ '((t (:inherit font-lock-variable-name-face :weight bold)))
+ "Face for bold text."
+ :group 'markdown-faces)
+
+(defface markdown-strike-through-face
+ '((t (:inherit font-lock-variable-name-face :strike-through t)))
+ "Face for strike-through text."
+ :group 'markdown-faces)
+
+(defface markdown-header-rule-face
+ '((t (:inherit font-lock-function-name-face :weight bold)))
+ "Base face for headers rules."
+ :group 'markdown-faces)
+
+(defface markdown-header-delimiter-face
+ '((t (:inherit font-lock-function-name-face :weight bold)))
+ "Base face for headers hash delimiter."
+ :group 'markdown-faces)
+
+(defface markdown-header-face
+ '((t (:inherit font-lock-function-name-face :weight bold)))
+ "Base face for headers."
+ :group 'markdown-faces)
+
+(defface markdown-header-face-1
+ '((t (:inherit markdown-header-face)))
+ "Face for level-1 headers."
+ :group 'markdown-faces)
+
+(defface markdown-header-face-2
+ '((t (:inherit markdown-header-face)))
+ "Face for level-2 headers."
+ :group 'markdown-faces)
+
+(defface markdown-header-face-3
+ '((t (:inherit markdown-header-face)))
+ "Face for level-3 headers."
+ :group 'markdown-faces)
+
+(defface markdown-header-face-4
+ '((t (:inherit markdown-header-face)))
+ "Face for level-4 headers."
+ :group 'markdown-faces)
+
+(defface markdown-header-face-5
+ '((t (:inherit markdown-header-face)))
+ "Face for level-5 headers."
+ :group 'markdown-faces)
+
+(defface markdown-header-face-6
+ '((t (:inherit markdown-header-face)))
+ "Face for level-6 headers."
+ :group 'markdown-faces)
+
+(defface markdown-inline-code-face
+ '((t (:inherit font-lock-constant-face)))
+ "Face for inline code."
+ :group 'markdown-faces)
+
+(defface markdown-list-face
+ '((t (:inherit font-lock-builtin-face)))
+ "Face for list item markers."
+ :group 'markdown-faces)
+
+(defface markdown-blockquote-face
+ '((t (:inherit font-lock-doc-face)))
+ "Face for blockquote sections."
+ :group 'markdown-faces)
+
+(defface markdown-pre-face
+ '((t (:inherit font-lock-constant-face)))
+ "Face for preformatted text."
+ :group 'markdown-faces)
+
+(defface markdown-language-keyword-face
+ '((t (:inherit font-lock-type-face)))
+ "Face for programming language identifiers."
+ :group 'markdown-faces)
+
+(defface markdown-link-face
+ '((t (:inherit font-lock-keyword-face)))
+ "Face for links."
+ :group 'markdown-faces)
+
+(defface markdown-missing-link-face
+ '((t (:inherit font-lock-warning-face)))
+ "Face for missing links."
+ :group 'markdown-faces)
+
+(defface markdown-reference-face
+ '((t (:inherit font-lock-type-face)))
+ "Face for link references."
+ :group 'markdown-faces)
+
+(defface markdown-footnote-face
+ '((t (:inherit font-lock-keyword-face)))
+ "Face for footnote markers."
+ :group 'markdown-faces)
+
+(defface markdown-url-face
+ '((t (:inherit font-lock-string-face)))
+ "Face for URLs."
+ :group 'markdown-faces)
+
+(defface markdown-link-title-face
+ '((t (:inherit font-lock-comment-face)))
+ "Face for reference link titles."
+ :group 'markdown-faces)
+
+(defface markdown-line-break-face
+ '((t (:inherit font-lock-constant-face :underline t)))
+ "Face for hard line breaks."
+ :group 'markdown-faces)
+
+(defface markdown-comment-face
+ '((t (:inherit font-lock-comment-face)))
+ "Face for HTML comments."
+ :group 'markdown-faces)
+
+(defface markdown-math-face
+ '((t (:inherit font-lock-string-face)))
+ "Face for LaTeX expressions."
+ :group 'markdown-faces)
+
+(defface markdown-metadata-key-face
+ '((t (:inherit font-lock-variable-name-face)))
+ "Face for metadata keys."
+ :group 'markdown-faces)
+
+(defface markdown-metadata-value-face
+ '((t (:inherit font-lock-string-face)))
+ "Face for metadata values."
+ :group 'markdown-faces)
+
+(defconst markdown-regex-link-inline
+ "\\(!\\)?\\(\\[\\([^]^][^]]*\\|\\)\\]\\)\\((\\([^)]*?\\)\\(?:\\s-+\\(\"[^\"]*\"\\)\\)?)\\)"
+ "Regular expression for a [text](file) or an image link .
+Group 1 matches the leading exclamation point, if any.
+Group 2 matchs the entire square bracket term, including the text.
+Group 3 matches the text inside the square brackets.
+Group 4 matches the entire parenthesis term, including the URL and title.
+Group 5 matches the URL.
+Group 6 matches (optional) title.")
+
+(defconst markdown-regex-link-reference
+ "\\(!\\)?\\(\\[\\([^]^][^]]*\\|\\)\\]\\)[ ]?\\(\\[\\([^]]*?\\)\\]\\)"
+ "Regular expression for a reference link [text][id].
+Group 1 matches the leading exclamation point, if any.
+Group 2 matchs the entire first square bracket term, including the text.
+Group 3 matches the text inside the square brackets.
+Group 4 matches the entire second square bracket term.
+Group 5 matches the reference label.")
+
+(defconst markdown-regex-reference-definition
+ "^ \\{0,3\\}\\(\\[[^\n]+?\\]\\):\\s *\\(.*?\\)\\s *\\( \"[^\"]*\"$\\|$\\)"
+ "Regular expression for a link definition [id]: ...")
+
+(defconst markdown-regex-footnote
+ "\\(\\[\\^.+?\\]\\)"
+ "Regular expression for a footnote marker [^fn].")
+
+(defconst markdown-regex-header
+ "^\\(?:\\(.+\\)\n\\(=+\\)\\|\\(.+\\)\n\\(-+\\)\\|\\(#+\\)\\s-*\\(.*?\\)\\s-*?\\(#*\\)\\)$"
+ "Regexp identifying Markdown headers.")
+
+(defconst markdown-regex-header-1-atx
+ "^\\(#\\)[ \t]*\\(.+?\\)[ \t]*\\(#*\\)$"
+ "Regular expression for level 1 atx-style (hash mark) headers.")
+
+(defconst markdown-regex-header-2-atx
+ "^\\(##\\)[ \t]*\\(.+?\\)[ \t]*\\(#*\\)$"
+ "Regular expression for level 2 atx-style (hash mark) headers.")
+
+(defconst markdown-regex-header-3-atx
+ "^\\(###\\)[ \t]*\\(.+?\\)[ \t]*\\(#*\\)$"
+ "Regular expression for level 3 atx-style (hash mark) headers.")
+
+(defconst markdown-regex-header-4-atx
+ "^\\(####\\)[ \t]*\\(.+?\\)[ \t]*\\(#*\\)$"
+ "Regular expression for level 4 atx-style (hash mark) headers.")
+
+(defconst markdown-regex-header-5-atx
+ "^\\(#####\\)[ \t]*\\(.+?\\)[ \t]*\\(#*\\)$"
+ "Regular expression for level 5 atx-style (hash mark) headers.")
+
+(defconst markdown-regex-header-6-atx
+ "^\\(######\\)[ \t]*\\(.+?\\)[ \t]*\\(#*\\)$"
+ "Regular expression for level 6 atx-style (hash mark) headers.")
+
+(defconst markdown-regex-header-1-setext
+ "^\\(.*\\)\n\\(=+\\)$"
+ "Regular expression for level 1 setext-style (underline) headers.")
+
+(defconst markdown-regex-header-2-setext
+ "^\\(.*\\)\n\\(-+\\)$"
+ "Regular expression for level 2 setext-style (underline) headers.")
+
+(defconst markdown-regex-header-setext
+ "^\\(.+\\)\n\\(\\(?:=\\|-\\)+\\)$"
+ "Regular expression for generic setext-style (underline) headers.")
+
+(defconst markdown-regex-header-atx
+ "^\\(#+\\)[ \t]*\\(.*?\\)[ \t]*\\(#*\\)$"
+ "Regular expression for generic atx-style (hash mark) headers.")
+
+(defconst markdown-regex-hr
+ "^\\(\\*[ ]?\\*[ ]?\\*[ ]?[\\* ]*\\|-[ ]?-[ ]?-[--- ]*\\)$"
+ "Regular expression for matching Markdown horizontal rules.")
+
+(defconst markdown-regex-code
+ "\\(\\`\\|[^\\]\\)\\(\\(`+\\)\\(\\(.\\|\n[^\n]\\)*?[^`]\\)\\3\\)\\([^`]\\|\\'\\)"
+ "Regular expression for matching inline code fragments.
+
+The first group ensures that the leading backquote character
+is not escaped. The group \\(.\\|\n[^\n]\\) matches any
+character, including newlines, but not two newlines in a row.
+The final group requires that the character following the code
+fragment is not a backquote.")
+
+(defconst markdown-regex-pre
+ "^\\( \\|\t\\).*$"
+ "Regular expression for matching preformatted text sections.")
+
+(defconst markdown-regex-list
+ "^\\([ \t]*\\)\\([0-9]+\\.\\|[\\*\\+-]\\)\\([ \t]+\\)"
+ "Regular expression for matching list items.")
+
+(defconst markdown-regex-bold
+ "\\(^\\|[^\\]\\)\\(\\([*_]\\{2\\}\\)\\([^ \n\t\\]\\|[^ \n\t]\\(?:.\\|\n[^\n]\\)*?[^\\ ]\\)\\(\\3\\)\\)"
+ "Regular expression for matching bold text.
+Group 1 matches the character before the opening asterisk or
+underscore, if any, ensuring that it is not a backslash escape.
+Group 2 matches the entire expression, including delimiters.
+Groups 3 and 5 matches the opening and closing delimiters.
+Group 4 matches the text inside the delimiters.")
+
+(defconst markdown-regex-italic
+ "\\(^\\|[^\\]\\)\\(\\([*_]\\)\\([^ \n\t\\]\\|[^ \n\t]\\(?:.\\|\n[^\n]\\)*?[^\\ ]\\)\\(\\3\\)\\)"
+ "Regular expression for matching italic text.
+Group 1 matches the character before the opening asterisk or
+underscore, if any, ensuring that it is not a backslash escape.
+Group 2 matches the entire expression, including delimiters.
+Groups 3 and 5 matches the opening and closing delimiters.
+Group 4 matches the text inside the delimiters.")
+
+(defconst markdown-regex-strike-through
+ "\\(^\\|[^\\]\\)\\(\\(~~\\)\\([^ \n\t\\]\\|[^ \n\t]\\(?:.\\|\n[^\n]\\)*?[^\\ ]\\)\\(~~\\)\\)"
+ "Regular expression for matching strike-through text.
+Group 1 matches the character before the opening tilde, if any,
+ensuring that it is not a backslash escape.
+Group 2 matches the entire expression, including delimiters.
+Groups 3 and 5 matches the opening and closing delimiters.
+Group 4 matches the text inside the delimiters.")
+
+(defconst markdown-regex-gfm-italic
+ "\\(^\\|\\s-\\)\\(\\([*_]\\)\\([^ \\]\\3\\|[^ ]\\(.\\|\n[^\n]\\)*?[^\\ ]\\3\\)\\)"
+ "Regular expression for matching italic text in GitHub Flavored Markdown.
+Underscores in words are not treated as special.")
+
+(defconst markdown-regex-blockquote
+ "^[ \t]*\\(>\\).*$"
+ "Regular expression for matching blockquote lines.")
+
+(defconst markdown-regex-line-break
+ "[^ \n\t][ \t]*\\( \\)$"
+ "Regular expression for matching line breaks.")
+
+(defconst markdown-regex-wiki-link
+ "\\(?:^\\|[^\\]\\)\\(\\[\\[\\([^]|]+\\)\\(|\\([^]]+\\)\\)?\\]\\]\\)"
+ "Regular expression for matching wiki links.
+This matches typical bracketed [[WikiLinks]] as well as 'aliased'
+wiki links of the form [[PageName|link text]]. In this regular
+expression, group 1 matches the entire link, including square
+brackets, group 2 matches the first component of the wiki link
+and group 4 matches the second component, after the pipe, when
+present. The meanings of the first and second components depend
+on the value of `markdown-wiki-link-alias-first'.")
+
+(defconst markdown-regex-uri
+ (concat (regexp-opt markdown-uri-types) ":[^]\t\n\r<>,;() ]+")
+ "Regular expression for matching inline URIs.")
+
+(defconst markdown-regex-angle-uri
+ (concat "\\(<\\)\\(" (regexp-opt markdown-uri-types) ":[^]\t\n\r<>,;()]+\\)\\(>\\)")
+ "Regular expression for matching inline URIs in angle brackets.")
+
+(defconst markdown-regex-email
+ "<\\(\\(\\sw\\|\\s_\\|\\s.\\)+@\\(\\sw\\|\\s_\\|\\s.\\)+\\)>"
+ "Regular expression for matching inline email addresses.")
+
+(defconst markdown-regex-link-generic
+ (concat "\\(?:" markdown-regex-wiki-link
+ "\\|" markdown-regex-link-inline
+ "\\|" markdown-regex-link-reference
+ "\\|" markdown-regex-angle-uri "\\)")
+ "Regular expression for matching any recognized link.")
+
+(defconst markdown-regex-block-separator
+ "\\(\\`\\|\\(\n[ \t]*\n\\)[^\n \t]\\)"
+ "Regular expression for matching block boundaries.")
+
+(defconst markdown-regex-math-inline
+ "\\(^\\|[^\\]\\)\\(\\$\\($\\([^\\$]\\|\\\\.\\)*\\$\\|\\([^\\$]\\|\\\\.\\)*\\)\\$\\)"
+ "Regular expression for itex $..$ or $$..$$ math mode expressions.")
+
+(defconst markdown-regex-math-display
+ "^\\\\\\[\\(.\\|\n\\)*?\\\\\\]$"
+ "Regular expression for itex \[..\] display mode expressions.")
+
+(defconst markdown-regex-multimarkdown-metadata
+ "^\\([[:alpha:]][[:alpha:] _-]*?\\):[ \t]*\\(.*\\)$"
+ "Regular expression for matching MultiMarkdown metadata.")
+
+(defconst markdown-regex-pandoc-metadata
+ "^\\(%\\)[ \t]*\\(.*\\)$"
+ "Regular expression for matching Pandoc metadata.")
+
+(defvar markdown-mode-font-lock-keywords-basic
+ (list
+ (cons 'markdown-match-gfm-code-blocks '((1 markdown-pre-face)
+ (2 markdown-language-keyword-face t t)
+ (3 markdown-pre-face)
+ (4 markdown-pre-face)))
+ (cons 'markdown-match-fenced-code-blocks '((0 markdown-pre-face)))
+ (cons 'markdown-match-pre-blocks '((0 markdown-pre-face)))
+ (cons markdown-regex-blockquote 'markdown-blockquote-face)
+ (cons markdown-regex-header-1-setext '((1 markdown-header-face-1)
+ (2 markdown-header-rule-face)))
+ (cons markdown-regex-header-2-setext '((1 markdown-header-face-2)
+ (2 markdown-header-rule-face)))
+ (cons markdown-regex-header-6-atx '((1 markdown-header-delimiter-face)
+ (2 markdown-header-face-6)
+ (3 markdown-header-delimiter-face)))
+ (cons markdown-regex-header-5-atx '((1 markdown-header-delimiter-face)
+ (2 markdown-header-face-5)
+ (3 markdown-header-delimiter-face)))
+ (cons markdown-regex-header-4-atx '((1 markdown-header-delimiter-face)
+ (2 markdown-header-face-4)
+ (3 markdown-header-delimiter-face)))
+ (cons markdown-regex-header-3-atx '((1 markdown-header-delimiter-face)
+ (2 markdown-header-face-3)
+ (3 markdown-header-delimiter-face)))
+ (cons markdown-regex-header-2-atx '((1 markdown-header-delimiter-face)
+ (2 markdown-header-face-2)
+ (3 markdown-header-delimiter-face)))
+ (cons markdown-regex-header-1-atx '((1 markdown-header-delimiter-face)
+ (2 markdown-header-face-1)
+ (3 markdown-header-delimiter-face)))
+ (cons 'markdown-match-multimarkdown-metadata '((1 markdown-metadata-key-face)
+ (2 markdown-metadata-value-face)))
+ (cons 'markdown-match-pandoc-metadata '((1 markdown-comment-face)
+ (2 markdown-metadata-value-face)))
+ (cons markdown-regex-hr 'markdown-header-face)
+ (cons 'markdown-match-comments '((0 markdown-comment-face)))
+ (cons 'markdown-match-code '((0 markdown-inline-code-face)))
+ (cons markdown-regex-angle-uri 'markdown-link-face)
+ (cons markdown-regex-uri 'markdown-link-face)
+ (cons markdown-regex-email 'markdown-link-face)
+ (cons markdown-regex-list '(2 markdown-list-face))
+ (cons markdown-regex-footnote 'markdown-footnote-face)
+ (cons markdown-regex-link-inline '((1 markdown-link-face t t)
+ (2 markdown-link-face t)
+ (4 markdown-url-face t)
+ (6 markdown-link-title-face t t)))
+ (cons markdown-regex-link-reference '((1 markdown-link-face t t)
+ (2 markdown-link-face t)
+ (4 markdown-reference-face t)))
+ (cons markdown-regex-reference-definition '((1 markdown-reference-face t)
+ (2 markdown-url-face t)
+ (3 markdown-link-title-face t)))
+ (cons markdown-regex-bold '(2 markdown-bold-face))
+ (cons markdown-regex-line-break '(1 markdown-line-break-face prepend))
+ )
+ "Syntax highlighting for Markdown files.")
+
+(defvar markdown-mode-font-lock-keywords-core
+ (list
+ (cons markdown-regex-italic '(2 markdown-italic-face))
+ )
+ "Additional syntax highlighting for Markdown files.
+Includes features which are overridden by some variants.")
+
+(defconst markdown-mode-font-lock-keywords-math
+ (list
+ ;; Math mode $..$ or $$..$$
+ (cons markdown-regex-math-inline '(2 markdown-math-face))
+ ;; Display mode equations with brackets: \[ \]
+ (cons markdown-regex-math-display 'markdown-math-face)
+ ;; Equation reference (eq:foo)
+ (cons "(eq:[[:alnum:]:_]+)" 'markdown-reference-face)
+ ;; Equation reference \eqref{foo}
+ (cons "\\\\eqref{[[:alnum:]:_]+}" 'markdown-reference-face))
+ "Syntax highlighting for LaTeX and itex fragments.")
+
+(defvar markdown-mode-font-lock-keywords nil
+ "Default highlighting expressions for Markdown mode.
+This variable is defined as a buffer-local variable for dynamic
+extension support.")
+
+;; Footnotes
+(defvar markdown-footnote-counter 0
+ "Counter for footnote numbers.")
+(make-variable-buffer-local 'markdown-footnote-counter)
+
+(defconst markdown-footnote-chars
+ "[[:alnum:]-]"
+ "Regular expression maching any character that is allowed in a footnote identifier.")
+
+(defconst markdown-regex-footnote-definition
+ (concat "^\\[\\(\\^" markdown-footnote-chars "*?\\)\\]:\\(?:[ \t]+\\|$\\)")
+ "Regular expression matching a footnote definition, capturing the label.")
+
+\f
+;;; Compatibility =============================================================
+
+(defun markdown-replace-regexp-in-string (regexp rep string)
+ "Replace ocurrences of REGEXP with REP in STRING.
+This is a compatibility wrapper to provide `replace-regexp-in-string'
+in XEmacs 21."
+ (if (featurep 'xemacs)
+ (replace-in-string string regexp rep)
+ (replace-regexp-in-string regexp rep string)))
+
+;; `markdown-use-region-p' is a compatibility function which checks
+;; for an active region, with fallbacks for older Emacsen and XEmacs.
+(eval-and-compile
+ (cond
+ ;; Emacs 23 and newer
+ ((fboundp 'use-region-p)
+ (defalias 'markdown-use-region-p 'use-region-p))
+ ;; Older Emacsen
+ ((and (boundp 'transient-mark-mode) (boundp 'mark-active))
+ (defun markdown-use-region-p ()
+ "Compatibility wrapper to provide `use-region-p'."
+ (and transient-mark-mode mark-active)))
+ ;; XEmacs
+ ((fboundp 'region-active-p)
+ (defalias 'markdown-use-region-p 'region-active-p))))
+
+(defun markdown-use-buttons-p ()
+ "Determine whether this Emacs supports buttons."
+ (or (featurep 'button) (locate-library "button")))
+
+\f
+;;; Markdown Parsing Functions ================================================
+
+(defun markdown-cur-line-blank-p ()
+ "Return t if the current line is blank and nil otherwise."
+ (save-excursion
+ (beginning-of-line)
+ (re-search-forward "^\\s *$" (line-end-position) t)))
+
+(defun markdown-prev-line-blank-p ()
+ "Return t if the previous line is blank and nil otherwise.
+If we are at the first line, then consider the previous line to be blank."
+ (or (= (line-beginning-position) (point-min))
+ (save-excursion
+ (forward-line -1)
+ (markdown-cur-line-blank-p))))
+
+(defun markdown-next-line-blank-p ()
+ "Return t if the next line is blank and nil otherwise.
+If we are at the last line, then consider the next line to be blank."
+ (or (= (line-end-position) (point-max))
+ (save-excursion
+ (forward-line 1)
+ (markdown-cur-line-blank-p))))
+
+(defun markdown-prev-line-indent-p ()
+ "Return t if the previous line is indented and nil otherwise."
+ (save-excursion
+ (if (= (line-beginning-position) (point-min))
+ nil
+ (forward-line -1)
+ (goto-char (line-beginning-position))
+ (if (re-search-forward "^\\s " (line-end-position) t) t))))
+
+(defun markdown-cur-line-indent ()
+ "Return the number of leading whitespace characters in the current line."
+ (save-match-data
+ (save-excursion
+ (goto-char (line-beginning-position))
+ (re-search-forward "^[ \t]+" (line-end-position) t)
+ (current-column))))
+
+(defun markdown-prev-line-indent ()
+ "Return the number of leading whitespace characters in the previous line.
+Return 0 if the current line is the first line in the buffer."
+ (save-excursion
+ (if (= (line-beginning-position) (point-min))
+ 0
+ (forward-line -1)
+ (markdown-cur-line-indent))))
+
+(defun markdown-next-line-indent ()
+ "Return the number of leading whitespace characters in the next line.
+Return 0 if line is the last line in the buffer."
+ (save-excursion
+ (if (= (line-end-position) (point-max))
+ 0
+ (forward-line 1)
+ (markdown-cur-line-indent))))
+
+(defun markdown-cur-non-list-indent ()
+ "Return beginning position of list item text (not including the list marker).
+Return nil if the current line is not the beginning of a list item."
+ (save-match-data
+ (save-excursion
+ (beginning-of-line)
+ (when (re-search-forward markdown-regex-list (line-end-position) t)
+ (current-column)))))
+
+(defun markdown-prev-non-list-indent ()
+ "Return position of the first non-list-marker on the previous line."
+ (save-excursion
+ (forward-line -1)
+ (markdown-cur-non-list-indent)))
+
+(defun markdown-new-baseline-p ()
+ "Determine if the current line begins a new baseline level."
+ (save-excursion
+ (beginning-of-line)
+ (save-match-data
+ (or (looking-at markdown-regex-header)
+ (looking-at markdown-regex-hr)
+ (and (null (markdown-cur-non-list-indent))
+ (= (markdown-cur-line-indent) 0)
+ (markdown-prev-line-blank-p))))))
+
+(defun markdown-search-backward-baseline ()
+ "Search backward baseline point with no indentation and not a list item."
+ (end-of-line)
+ (let (stop)
+ (while (not (or stop (bobp)))
+ (re-search-backward markdown-regex-block-separator nil t)
+ (when (match-end 2)
+ (goto-char (match-end 2))
+ (cond
+ ((markdown-new-baseline-p)
+ (setq stop t))
+ ((looking-at markdown-regex-list)
+ (setq stop nil))
+ (t (setq stop t)))))))
+
+(defun markdown-update-list-levels (marker indent levels)
+ "Update list levels given list MARKER, block INDENT, and current LEVELS.
+Here, MARKER is a string representing the type of list, INDENT is an integer
+giving the indentation, in spaces, of the current block, and LEVELS is a
+list of the indentation levels of parent list items. When LEVELS is nil,
+it means we are at baseline (not inside of a nested list)."
+ (cond
+ ;; New list item at baseline.
+ ((and marker (null levels))
+ (setq levels (list indent)))
+ ;; List item with greater indentation (four or more spaces).
+ ;; Increase list level.
+ ((and marker (>= indent (+ (car levels) 4)))
+ (setq levels (cons indent levels)))
+ ;; List item with greater or equal indentation (less than four spaces).
+ ;; Do not increase list level.
+ ((and marker (>= indent (car levels)))
+ levels)
+ ;; Lesser indentation level.
+ ;; Pop appropriate number of elements off LEVELS list (e.g., lesser
+ ;; indentation could move back more than one list level). Note
+ ;; that this block need not be the beginning of list item.
+ ((< indent (car levels))
+ (while (and (> (length levels) 1)
+ (< indent (+ (cadr levels) 4)))
+ (setq levels (cdr levels)))
+ levels)
+ ;; Otherwise, do nothing.
+ (t levels)))
+
+(defun markdown-calculate-list-levels ()
+ "Calculate list levels at point.
+Return a list of the form (n1 n2 n3 ...) where n1 is the
+indentation of the deepest nested list item in the branch of
+the list at the point, n2 is the indentation of the parent
+list item, and so on. The depth of the list item is therefore
+the length of the returned list. If the point is not at or
+immediately after a list item, return nil."
+ (save-excursion
+ (let ((first (point)) levels indent pre-regexp)
+ ;; Find a baseline point with zero list indentation
+ (markdown-search-backward-baseline)
+ ;; Search for all list items between baseline and LOC
+ (while (and (< (point) first)
+ (re-search-forward markdown-regex-list first t))
+ (setq pre-regexp (format "^\\( \\|\t\\)\\{%d\\}" (1+ (length levels))))
+ (beginning-of-line)
+ (cond
+ ;; Make sure this is not a header or hr
+ ((markdown-new-baseline-p) (setq levels nil))
+ ;; Make sure this is not a line from a pre block
+ ((looking-at pre-regexp))
+ ;; If not, then update levels
+ (t
+ (setq indent (markdown-cur-line-indent))
+ (setq levels (markdown-update-list-levels (match-string 2)
+ indent levels))))
+ (end-of-line))
+ levels)))
+
+(defun markdown-prev-list-item (level)
+ "Search backward from point for a list item with indentation LEVEL.
+Set point to the beginning of the item, and return point, or nil
+upon failure."
+ (let (bounds indent prev)
+ (setq prev (point))
+ (forward-line -1)
+ (setq indent (markdown-cur-line-indent))
+ (while
+ (cond
+ ;; Stop at beginning of buffer
+ ((bobp) (setq prev nil))
+ ;; Continue if current line is blank
+ ((markdown-cur-line-blank-p) t)
+ ;; List item
+ ((and (looking-at markdown-regex-list)
+ (setq bounds (markdown-cur-list-item-bounds)))
+ (cond
+ ;; Continue at item with greater indentation
+ ((> (nth 3 bounds) level) t)
+ ;; Stop and return point at item of equal indentation
+ ((= (nth 3 bounds) level)
+ (setq prev (point))
+ nil)
+ ;; Stop and return nil at item with lesser indentation
+ ((< (nth 3 bounds) level)
+ (setq prev nil)
+ nil)))
+ ;; Continue while indentation is the same or greater
+ ((>= indent level) t)
+ ;; Stop if current indentation is less than list item
+ ;; and the next is blank
+ ((and (< indent level)
+ (markdown-next-line-blank-p))
+ (setq prev nil))
+ ;; Stop at a header
+ ((looking-at markdown-regex-header) (setq prev nil))
+ ;; Stop at a horizontal rule
+ ((looking-at markdown-regex-hr) (setq prev nil))
+ ;; Otherwise, continue.
+ (t t))
+ (forward-line -1)
+ (setq indent (markdown-cur-line-indent)))
+ prev))
+
+(defun markdown-next-list-item (level)
+ "Search forward from point for the next list item with indentation LEVEL.
+Set point to the beginning of the item, and return point, or nil
+upon failure."
+ (let (bounds indent prev next)
+ (setq next (point))
+ (forward-line)
+ (setq indent (markdown-cur-line-indent))
+ (while
+ (cond
+ ;; Stop at end of the buffer.
+ ((eobp) (setq prev nil))
+ ;; Continue if the current line is blank
+ ((markdown-cur-line-blank-p) t)
+ ;; List item
+ ((and (looking-at markdown-regex-list)
+ (setq bounds (markdown-cur-list-item-bounds)))
+ (cond
+ ;; Continue at item with greater indentation
+ ((> (nth 3 bounds) level) t)
+ ;; Stop and return point at item of equal indentation
+ ((= (nth 3 bounds) level)
+ (setq next (point))
+ nil)
+ ;; Stop and return nil at item with lesser indentation
+ ((< (nth 3 bounds) level)
+ (setq next nil)
+ nil)))
+ ;; Continue while indentation is the same or greater
+ ((>= indent level) t)
+ ;; Stop if current indentation is less than list item
+ ;; and the previous line was blank.
+ ((and (< indent level)
+ (markdown-prev-line-blank-p))
+ (setq next nil))
+ ;; Stop at a header
+ ((looking-at markdown-regex-header) (setq next nil))
+ ;; Stop at a horizontal rule
+ ((looking-at markdown-regex-hr) (setq next nil))
+ ;; Otherwise, continue.
+ (t t))
+ (forward-line)
+ (setq indent (markdown-cur-line-indent)))
+ next))
+
+(defun markdown-cur-list-item-end (level)
+ "Move to the end of the current list item with nonlist indentation LEVEL.
+If the point is not in a list item, do nothing."
+ (let (indent)
+ (forward-line)
+ (setq indent (markdown-cur-line-indent))
+ (while
+ (cond
+ ;; Stop at end of the buffer.
+ ((eobp) nil)
+ ;; Continue if the current line is blank
+ ((markdown-cur-line-blank-p) t)
+ ;; Continue while indentation is the same or greater
+ ((>= indent level) t)
+ ;; Stop if current indentation is less than list item
+ ;; and the previous line was blank.
+ ((and (< indent level)
+ (markdown-prev-line-blank-p))
+ nil)
+ ;; Stop at a new list item of the same or lesser indentation
+ ((looking-at markdown-regex-list) nil)
+ ;; Stop at a header
+ ((looking-at markdown-regex-header) nil)
+ ;; Stop at a horizontal rule
+ ((looking-at markdown-regex-hr) nil)
+ ;; Otherwise, continue.
+ (t t))
+ (forward-line)
+ (setq indent (markdown-cur-line-indent)))
+ ;; Don't skip over whitespace for empty list items (marker and
+ ;; whitespace only), just move to end of whitespace.
+ (if (looking-back (concat markdown-regex-list "\\s-*"))
+ (goto-char (match-end 3))
+ (skip-syntax-backward "-"))))
+
+(defun markdown-cur-list-item-bounds ()
+ "Return bounds and indentation of the current list item.
+Return a list of the form (begin end indent nonlist-indent marker).
+If the point is not inside a list item, return nil.
+Leave match data intact for `markdown-regex-list'."
+ (let (cur prev-begin prev-end indent nonlist-indent marker)
+ ;; Store current location
+ (setq cur (point))
+ ;; Verify that cur is between beginning and end of item
+ (save-excursion
+ (end-of-line)
+ (when (re-search-backward markdown-regex-list nil t)
+ (setq prev-begin (match-beginning 0))
+ (setq indent (length (match-string 1)))
+ (setq nonlist-indent (length (match-string 0)))
+ (setq marker (concat (match-string 2) (match-string 3)))
+ (save-match-data
+ (markdown-cur-list-item-end nonlist-indent)
+ (setq prev-end (point)))
+ (when (and (>= cur prev-begin)
+ (<= cur prev-end)
+ nonlist-indent)
+ (list prev-begin prev-end indent nonlist-indent marker))))))
+
+(defun markdown-bounds-of-thing-at-point (thing)
+ "Call `bounds-of-thing-at-point' for THING with slight modifications.
+Does not include trailing newlines when THING is 'line. Handles the
+end of buffer case by setting both endpoints equal to the value of
+`point-max', since an empty region will trigger empty markup insertion.
+Return bounds of form (beg . end) if THING is found, or nil otherwise."
+ (let* ((bounds (bounds-of-thing-at-point thing))
+ (a (car bounds))
+ (b (cdr bounds)))
+ (when bounds
+ (when (eq thing 'line)
+ (cond ((and (eobp) (markdown-cur-line-blank-p))
+ (setq a b))
+ ((char-equal (char-before b) ?\^J)
+ (setq b (1- b)))))
+ (cons a b))))
+
+(defun markdown-reference-definition (reference)
+ "Find out whether Markdown REFERENCE is defined.
+REFERENCE should include the square brackets, like [this].
+When REFERENCE is defined, return a list of the form (text start end)
+containing the definition text itself followed by the start and end
+locations of the text. Otherwise, return nil.
+Leave match data for `markdown-regex-reference-definition'
+intact additional processing."
+ (let ((reference (downcase reference)))
+ (save-excursion
+ (goto-char (point-min))
+ (catch 'found
+ (while (re-search-forward markdown-regex-reference-definition nil t)
+ (when (string= reference (downcase (match-string-no-properties 1)))
+ (throw 'found
+ (list (match-string-no-properties 2)
+ (match-beginning 2) (match-end 2)))))))))
+
+(defun markdown-get-defined-references ()
+ "Return a list of all defined reference labels (including square brackets)."
+ (save-excursion
+ (goto-char (point-min))
+ (let (refs)
+ (while (re-search-forward markdown-regex-reference-definition nil t)
+ (let ((target (match-string-no-properties 1)))
+ (add-to-list 'refs target t)))
+ refs)))
+
+(defun markdown-code-at-point-p ()
+ "Return non-nil if the point is at an inline code fragment.
+Return nil otherwise. Set match data according to
+`markdown-match-code' upon success.
+This function searches the block for a code fragment that
+contains the point using `markdown-match-code'. We do this
+because `thing-at-point-looking-at' does not work reliably with
+`markdown-regex-code'."
+ (interactive)
+ (save-excursion
+ (let ((old-point (point))
+ (end-of-block (progn (markdown-end-of-block) (point)))
+ found)
+ (markdown-beginning-of-block)
+ (while (and (markdown-match-code end-of-block)
+ (setq found t)
+ (< (match-end 0) old-point)))
+ (and found ; matched something
+ (<= (match-beginning 0) old-point) ; match contains old-point
+ (>= (match-end 0) old-point)))))
+
+\f
+;;; Markdown Font Lock Matching Functions =====================================
+
+(defun markdown-match-comments (last)
+ "Match HTML comments from the point to LAST."
+ (cond ((search-forward "<!--" last t)
+ (backward-char 4)
+ (let ((beg (point)))
+ (cond ((search-forward-regexp "--[ \t]*>" last t)
+ (set-match-data (list beg (point)))
+ t)
+ (t nil))))
+ (t nil)))
+
+(defun markdown-match-code (last)
+ "Match inline code from the point to LAST."
+ (unless (bobp)
+ (backward-char 1))
+ (cond ((re-search-forward markdown-regex-code last t)
+ (set-match-data (list (match-beginning 2) (match-end 2)
+ (match-beginning 4) (match-end 4)))
+ (goto-char (match-end 0))
+ t)
+ (t (forward-char 2) nil)))
+
+(defun markdown-match-pre-blocks (last)
+ "Match Markdown pre blocks from point to LAST."
+ (let ((levels (markdown-calculate-list-levels))
+ indent pre-regexp end-regexp begin end stop)
+ (while (and (< (point) last) (not end))
+ ;; Search for a region with sufficient indentation
+ (if (null levels)
+ (setq indent 1)
+ (setq indent (1+ (length levels))))
+ (setq pre-regexp (format "^\\( \\|\t\\)\\{%d\\}" indent))
+ (setq end-regexp (format "^\\( \\|\t\\)\\{0,%d\\}\\([^ \t]\\)" (1- indent)))
+
+ (cond
+ ;; If not at the beginning of a line, move forward
+ ((not (bolp)) (forward-line))
+ ;; Move past blank lines
+ ((markdown-cur-line-blank-p) (forward-line))
+ ;; At headers and horizontal rules, reset levels
+ ((markdown-new-baseline-p) (forward-line) (setq levels nil))
+ ;; If the current line has sufficient indentation, mark out pre block
+ ((looking-at pre-regexp)
+ (setq begin (match-beginning 0))
+ (while (and (or (looking-at pre-regexp) (markdown-cur-line-blank-p))
+ (not (eobp)))
+ (forward-line))
+ (setq end (point)))
+ ;; If current line has a list marker, update levels, move to end of block
+ ((looking-at markdown-regex-list)
+ (setq levels (markdown-update-list-levels
+ (match-string 2) (markdown-cur-line-indent) levels))
+ (markdown-end-of-block-element))
+ ;; If this is the end of the indentation level, adjust levels accordingly.
+ ;; Only match end of indentation level if levels is not the empty list.
+ ((and (car levels) (looking-at end-regexp))
+ (setq levels (markdown-update-list-levels
+ nil (markdown-cur-line-indent) levels))
+ (markdown-end-of-block-element))
+ (t (markdown-end-of-block-element))))
+
+ (if (not (and begin end))
+ ;; Return nil if no pre block was found
+ nil
+ ;; Set match data and return t upon success
+ (set-match-data (list begin end))
+ t)))
+
+(defun markdown-match-fenced-code-blocks (last)
+ "Match fenced code blocks from the point to LAST."
+ (cond ((search-forward-regexp "^\\([~]\\{3,\\}\\)" last t)
+ (beginning-of-line)
+ (let ((beg (point)))
+ (forward-line)
+ (cond ((search-forward-regexp
+ (concat "^" (match-string 1) "~*") last t)
+ (set-match-data (list beg (point)))
+ t)
+ (t nil))))
+ (t nil)))
+
+(defun markdown-match-gfm-code-blocks (last)
+ "Match GFM quoted code blocks from point to LAST."
+ (let (open lang body close all)
+ (cond ((search-forward-regexp
+ "^\\(```\\)\\([^[:space:]]+[[:space:]]*\\)?$" last t)
+ (beginning-of-line)
+ (setq open (list (match-beginning 1) (match-end 1))
+ lang (list (match-beginning 2) (match-end 2)))
+ (forward-line)
+ (setq body (list (point)))
+ (cond ((search-forward-regexp "^```$" last t)
+ (setq body (reverse (cons (1- (match-beginning 0)) body))
+ close (list (match-beginning 0) (match-end 0))
+ all (list (car open) (match-end 0)))
+ (set-match-data (append all open lang body close))
+ t)
+ (t nil)))
+ (t nil))))
+
+(defun markdown-match-generic-metadata (regexp last)
+ "Match generic metadata specified by REGEXP from the point to LAST."
+ (let ((header-end (save-excursion
+ (goto-char (point-min))
+ (if (re-search-forward "\n\n" (point-max) t)
+ (match-beginning 0)
+ (point-max)))))
+ (cond ((>= (point) header-end)
+ ;; Don't match anything outside of the header.
+ nil)
+ ((re-search-forward regexp (min last header-end) t)
+ ;; If a metadata item is found, it may span several lines.
+ (let ((key-beginning (match-beginning 1))
+ (key-end (match-end 1))
+ (value-beginning (match-beginning 2)))
+ (while (and (not (looking-at regexp))
+ (not (> (point) (min last header-end)))
+ (not (eobp)))
+ (forward-line))
+ (unless (eobp)
+ (forward-line -1)
+ (end-of-line))
+ (set-match-data (list key-beginning (point) ; complete metadata
+ key-beginning key-end ; key
+ value-beginning (point))) ; value
+ t))
+ (t nil))))
+
+(defun markdown-match-multimarkdown-metadata (last)
+ "Match MultiMarkdown metadata from the point to LAST."
+ (markdown-match-generic-metadata markdown-regex-multimarkdown-metadata last))
+
+(defun markdown-match-pandoc-metadata (last)
+ "Match Pandoc metadata from the point to LAST."
+ (markdown-match-generic-metadata markdown-regex-pandoc-metadata last))
+
+(defun markdown-font-lock-extend-region ()
+ "Extend the search region to include an entire block of text.
+This helps improve font locking for block constructs such as pre blocks."
+ ;; Avoid compiler warnings about these global variables from font-lock.el.
+ ;; See the documentation for variable `font-lock-extend-region-functions'.
+ (eval-when-compile (defvar font-lock-beg) (defvar font-lock-end))
+ (save-excursion
+ (goto-char font-lock-beg)
+ (unless (looking-back "\n\n")
+ (let ((found (or (re-search-backward "\n\n" nil t) (point-min))))
+ (goto-char font-lock-end)
+ (when (re-search-forward "\n\n" nil t)
+ (setq font-lock-end (match-beginning 0))
+ (setq font-lock-beg found))))))
+
+\f
+;;; Syntax Table ==============================================================
+
+(defvar markdown-mode-syntax-table
+ (let ((tab (make-syntax-table text-mode-syntax-table)))
+ (modify-syntax-entry ?\" "." tab)
+ tab)
+ "Syntax table for `markdown-mode'.")
+
+\f
+;;; Element Insertion =========================================================
+
+(defun markdown-ensure-blank-line-before ()
+ "If previous line is not already blank, insert a blank line before point."
+ (unless (bolp) (insert "\n"))
+ (unless (or (bobp) (looking-back "\n\\s-*\n")) (insert "\n")))
+
+(defun markdown-ensure-blank-line-after ()
+ "If following line is not already blank, insert a blank line after point.
+Return the point where it was originally."
+ (save-excursion
+ (unless (eolp) (insert "\n"))
+ (unless (or (eobp) (looking-at "\n\\s-*\n")) (insert "\n"))))
+
+(defun markdown-wrap-or-insert (s1 s2 &optional thing beg end)
+ "Insert the strings S1 and S2, wrapping around region or THING.
+If a region is specified by the optional BEG and END arguments,
+wrap the strings S1 and S2 around that region.
+If there is an active region, wrap the strings S1 and S2 around
+the region. If there is not an active region but the point is at
+THING, wrap that thing (which defaults to word). Otherwise, just
+insert S1 and S2 and place the cursor in between. Return the
+bounds of the entire wrapped string, or nil if nothing was wrapped
+and S1 and S2 were only inserted."
+ (let (a b bounds new-point)
+ (cond
+ ;; Given region
+ ((and beg end)
+ (setq a beg
+ b end
+ new-point (+ (point) (length s1))))
+ ;; Active region
+ ((markdown-use-region-p)
+ (setq a (region-beginning)
+ b (region-end)
+ new-point (+ (point) (length s1))))
+ ;; Thing (word) at point
+ ((setq bounds (markdown-bounds-of-thing-at-point (or thing 'word)))
+ (setq a (car bounds)
+ b (cdr bounds)
+ new-point (+ (point) (length s1))))
+ ;; No active region and no word
+ (t
+ (setq a (point)
+ b (point))))
+ (goto-char b)
+ (insert s2)
+ (goto-char a)
+ (insert s1)
+ (when new-point (goto-char new-point))
+ (if (= a b)
+ nil
+ (setq b (+ b (length s1) (length s2)))
+ (cons a b))))
+
+(defun markdown-point-after-unwrap (cur prefix suffix)
+ "Return desired position of point after an unwrapping operation.
+CUR gives the position of the point before the operation.
+Additionally, two cons cells must be provided. PREFIX gives the
+bounds of the prefix string and SUFFIX gives the bounds of the
+suffix string."
+ (cond ((< cur (cdr prefix)) (car prefix))
+ ((< cur (car suffix)) (- cur (- (cdr prefix) (car prefix))))
+ ((<= cur (cdr suffix))
+ (- cur (+ (- (cdr prefix) (car prefix))
+ (- cur (car suffix)))))
+ (t cur)))
+
+(defun markdown-unwrap-thing-at-point (regexp all text)
+ "Remove prefix and suffix of thing at point and reposition the point.
+When the thing at point matches REGEXP, replace the subexpression
+ALL with the string in subexpression TEXT. Reposition the point
+in an appropriate location accounting for the removal of prefix
+and suffix strings. Return new bounds of string from group TEXT.
+When REGEXP is nil, assumes match data is already set."
+ (when (or (null regexp)
+ (thing-at-point-looking-at regexp))
+ (let ((cur (point))
+ (prefix (cons (match-beginning all) (match-beginning text)))
+ (suffix (cons (match-end text) (match-end all)))
+ (bounds (cons (match-beginning text) (match-end text))))
+ ;; Replace the thing at point
+ (replace-match (match-string text) t t nil all)
+ ;; Reposition the point
+ (goto-char (markdown-point-after-unwrap cur prefix suffix))
+ ;; Adjust bounds
+ (setq bounds (cons (car prefix)
+ (- (cdr bounds) (- (cdr prefix) (car prefix))))))))
+
+(defun markdown-unwrap-things-in-region (beg end regexp all text)
+ "Remove prefix and suffix of all things in region from BEG to END.
+When a thing in the region matches REGEXP, replace the
+subexpression ALL with the string in subexpression TEXT.
+Return a cons cell containing updated bounds for the region."
+ (save-excursion
+ (goto-char beg)
+ (let ((removed 0) len-all len-text)
+ (while (re-search-forward regexp (- end removed) t)
+ (setq len-all (length (match-string-no-properties all)))
+ (setq len-text (length (match-string-no-properties text)))
+ (setq removed (+ removed (- len-all len-text)))
+ (replace-match (match-string text) t t nil all))
+ (cons beg (- end removed)))))
+
+(defun markdown-insert-hr (arg)
+ "Insert or replace a horizonal rule.
+By default, use the first element of `markdown-hr-strings'. When
+ARG is non-nil, as when given a prefix, select a different
+element as follows. When prefixed with \\[universal-argument],
+use the last element of `markdown-hr-strings' instead. When
+prefixed with an integer from 1 to the length of
+`markdown-hr-strings', use the element in that position instead."
+ (interactive "*P")
+ (when (thing-at-point-looking-at markdown-regex-hr)
+ (delete-region (match-beginning 0) (match-end 0)))
+ (markdown-ensure-blank-line-before)
+ (cond ((equal arg '(4))
+ (insert (car (reverse markdown-hr-strings))))
+ ((and (integerp arg) (> arg 0)
+ (<= arg (length markdown-hr-strings)))
+ (insert (nth (1- arg) markdown-hr-strings)))
+ (t
+ (insert (car markdown-hr-strings))))
+ (markdown-ensure-blank-line-after))
+
+(defun markdown-insert-bold ()
+ "Insert markup to make a region or word bold.
+If there is an active region, make the region bold. If the point
+is at a non-bold word, make the word bold. If the point is at a
+bold word or phrase, remove the bold markup. Otherwise, simply
+insert bold delimiters and place the cursor in between them."
+ (interactive)
+ (let ((delim (if markdown-bold-underscore "__" "**")))
+ (if (markdown-use-region-p)
+ ;; Active region
+ (let ((bounds (markdown-unwrap-things-in-region
+ (region-beginning) (region-end)
+ markdown-regex-bold 2 4)))
+ (markdown-wrap-or-insert delim delim nil (car bounds) (cdr bounds)))
+ ;; Bold markup removal, bold word at point, or empty markup insertion
+ (if (thing-at-point-looking-at markdown-regex-bold)
+ (markdown-unwrap-thing-at-point nil 2 4)
+ (markdown-wrap-or-insert delim delim 'word nil nil)))))
+
+(defun markdown-insert-italic ()
+ "Insert markup to make a region or word italic.
+If there is an active region, make the region italic. If the point
+is at a non-italic word, make the word italic. If the point is at an
+italic word or phrase, remove the italic markup. Otherwise, simply
+insert italic delimiters and place the cursor in between them."
+ (interactive)
+ (let ((delim (if markdown-italic-underscore "_" "*")))
+ (if (markdown-use-region-p)
+ ;; Active region
+ (let ((bounds (markdown-unwrap-things-in-region
+ (region-beginning) (region-end)
+ markdown-regex-italic 2 4)))
+ (markdown-wrap-or-insert delim delim nil (car bounds) (cdr bounds)))
+ ;; Italic markup removal, italic word at point, or empty markup insertion
+ (if (thing-at-point-looking-at markdown-regex-italic)
+ (markdown-unwrap-thing-at-point nil 2 4)
+ (markdown-wrap-or-insert delim delim 'word nil nil)))))
+
+(defun markdown-insert-strike-through ()
+ "Insert markup to make a region or word strike-through.
+If there is an active region, make the region strike-through. If the point
+is at a non-bold word, make the word strike-through. If the point is at a
+strike-through word or phrase, remove the strike-through markup. Otherwise,
+simply insert bold delimiters and place the cursor in between them."
+ (interactive)
+ (let ((delim "~~"))
+ (if (markdown-use-region-p)
+ ;; Active region
+ (let ((bounds (markdown-unwrap-things-in-region
+ (region-beginning) (region-end)
+ markdown-regex-strike-through 2 4)))
+ (markdown-wrap-or-insert delim delim nil (car bounds) (cdr bounds)))
+ ;; Strike-through markup removal, strike-through word at point, or empty markup insertion
+ (if (thing-at-point-looking-at markdown-regex-strike-through)
+ (markdown-unwrap-thing-at-point nil 2 4)
+ (markdown-wrap-or-insert delim delim 'word nil nil)))))
+
+(defun markdown-insert-code ()
+ "Insert markup to make a region or word an inline code fragment.
+If there is an active region, make the region an inline code
+fragment. If the point is at a word, make the word an inline
+code fragment. Otherwise, simply insert code delimiters and
+place the cursor in between them."
+ (interactive)
+ (if (markdown-use-region-p)
+ ;; Active region
+ (let ((bounds (markdown-unwrap-things-in-region
+ (region-beginning) (region-end)
+ markdown-regex-code 2 4)))
+ (markdown-wrap-or-insert "`" "`" nil (car bounds) (cdr bounds)))
+ ;; Code markup removal, code markup for word, or empty markup insertion
+ (if (markdown-code-at-point-p)
+ (markdown-unwrap-thing-at-point nil 0 1)
+ (markdown-wrap-or-insert "`" "`" 'word nil nil))))
+
+(defun markdown-insert-link ()
+ "Insert an inline link, using region or word as link text if possible.
+If there is an active region, use the region as the link text. If the
+point is at a word, use the word as the link text. In these cases, the
+point will be left at the position for inserting a URL. If there is no
+active region and the point is not at word, simply insert link markup and
+place the point in the position to enter link text."
+ (interactive)
+ (let ((bounds (markdown-wrap-or-insert "[" "]()")))
+ (when bounds
+ (goto-char (- (cdr bounds) 1)))))
+
+(defun markdown-insert-reference-link (text label &optional url title)
+ "Insert a reference link and, optionally, a reference definition.
+The link TEXT will be inserted followed by the optional LABEL.
+If a URL is given, also insert a definition for the reference
+LABEL according to `markdown-reference-location'. If a TITLE is
+given, it will be added to the end of the reference definition
+and will be used to populate the title attribute when converted
+to XHTML. If URL is nil, insert only the link portion (for
+example, when a reference label is already defined)."
+ (insert (concat "[" text "][" label "]"))
+ (when url
+ (let ((end (point))
+ (label (if (> (length label) 0) label text)))
+ (cond
+ ((eq markdown-reference-location 'end) (goto-char (point-max)))
+ ((eq markdown-reference-location 'immediately) (markdown-end-of-block))
+ ((eq markdown-reference-location 'header) (markdown-end-of-defun)))
+ (unless (markdown-cur-line-blank-p) (insert "\n"))
+ (insert "\n[" label "]: " url)
+ (unless (> (length url) 0)
+ (setq end (point)))
+ (when (> (length title) 0)
+ (insert " \"" title "\""))
+ (insert "\n")
+ (unless (or (eobp) (looking-at "\n"))
+ (insert "\n"))
+ (goto-char end)
+ (when (> (length url) 0)
+ (message
+ (substitute-command-keys
+ "Defined reference [%s], press \\[markdown-jump] to jump there")
+ (or label text))))))
+
+(defun markdown-insert-reference-link-dwim ()
+ "Insert a reference link of the form [text][label] at point.
+If there is an active region, the text in the region will be used
+as the link text. If the point is at an inline link, it will be
+converted to a reference link. If the point is at a word, it will
+be used as the link text. Otherwise, the link text will be read from
+the minibuffer. The link label will be read from the minibuffer in
+both cases, with completion from the set of currently defined
+references. To create an implicit reference link, press RET to
+accept the default, an empty label. If the entered referenced
+label is not defined, additionally prompt for the URL
+and (optional) title. The reference definition is placed at the
+location determined by `markdown-reference-location'."
+ (interactive)
+ (let* ((defined-labels (mapcar (lambda (x) (substring x 1 -1))
+ (markdown-get-defined-references)))
+ (switch (thing-at-point-looking-at markdown-regex-link-inline))
+ (bounds (cond ((markdown-use-region-p)
+ (cons (region-beginning) (region-end)))
+ (switch
+ (cons (match-beginning 0) (match-end 0)))
+ (t
+ (markdown-bounds-of-thing-at-point 'word))))
+ (text (cond (switch (match-string 3))
+ (bounds (buffer-substring (car bounds) (cdr bounds)))
+ (t (read-string "Link Text: "))))
+ (label (completing-read
+ "Link Label (default: none): " defined-labels
+ nil nil nil 'markdown-reference-label-history nil))
+ (ref (save-match-data
+ (markdown-reference-definition
+ (concat "[" (if (> (length label) 0) label text) "]"))))
+ (url (cond (ref nil)
+ (switch (match-string 5))
+ (t (read-string "Link URL: "))))
+ (title (cond
+ ((= (length url) 0) nil)
+ (switch (if (> (length (match-string 6)) 2)
+ (substring (match-string 6) 1 -1)
+ nil))
+ (t (read-string "Link Title (optional): ")))))
+ (when bounds (delete-region (car bounds) (cdr bounds)))
+ (markdown-insert-reference-link text label url title)))
+
+(defun markdown-insert-uri ()
+ "Insert markup for an inline URI.
+If there is an active region, use it as the URI. If the point is
+at a URI, wrap it with angle brackets. If the point is at an
+inline URI, remove the angle brackets. Otherwise, simply insert
+angle brackets place the point between them."
+ (interactive)
+ (if (markdown-use-region-p)
+ ;; Active region
+ (let ((bounds (markdown-unwrap-things-in-region
+ (region-beginning) (region-end)
+ markdown-regex-angle-uri 0 2)))
+ (markdown-wrap-or-insert "<" ">" nil (car bounds) (cdr bounds)))
+ ;; Markup removal, URI at point, or empty markup insertion
+ (if (thing-at-point-looking-at markdown-regex-angle-uri)
+ (markdown-unwrap-thing-at-point nil 0 2)
+ (markdown-wrap-or-insert "<" ">" 'url nil nil))))
+
+(defun markdown-insert-wiki-link ()
+ "Insert a wiki link of the form [[WikiLink]].
+If there is an active region, use the region as the link text.
+If the point is at a word, use the word as the link text. If
+there is no active region and the point is not at word, simply
+insert link markup."
+ (interactive)
+ (if (markdown-use-region-p)
+ ;; Active region
+ (markdown-wrap-or-insert "[[" "]]" nil (region-beginning) (region-end))
+ ;; Markup removal, wiki link at at point, or empty markup insertion
+ (if (thing-at-point-looking-at markdown-regex-wiki-link)
+ (if (or markdown-wiki-link-alias-first
+ (null (match-string 4)))
+ (markdown-unwrap-thing-at-point nil 1 2)
+ (markdown-unwrap-thing-at-point nil 1 4))
+ (markdown-wrap-or-insert "[[" "]]"))))
+
+(defun markdown-insert-image (&optional arg)
+ "Insert image markup using region or word as alt text if possible.
+If there is an active region, use the region as the alt text. If the
+point is at a word, use the word as the alt text. In these cases, the
+point will be left at the position for inserting a URL. If there is no
+active region and the point is not at word, simply insert image markup and
+place the point in the position to enter alt text. If ARG is nil, insert
+inline image markup. Otherwise, insert reference image markup."
+ (interactive "*P")
+ (let ((bounds (if arg
+ (markdown-wrap-or-insert "![" "][]")
+ (markdown-wrap-or-insert "![" "]()"))))
+ (when bounds
+ (goto-char (- (cdr bounds) 1)))))
+
+(defun markdown-insert-reference-image ()
+ "Insert reference-style image markup using region or word as alt text.
+Calls `markdown-insert-image' with prefix argument."
+ (interactive)
+ (markdown-insert-image t))
+
+(defun markdown-remove-header ()
+ "Remove header markup if point is at a header.
+Return bounds of remaining header text if a header was removed
+and nil otherwise."
+ (interactive "*")
+ (or (markdown-unwrap-thing-at-point markdown-regex-header-atx 0 2)
+ (markdown-unwrap-thing-at-point markdown-regex-header-setext 0 1)))
+
+(defun markdown-insert-header (&optional level text setext)
+ "Insert or replace header markup.
+The level of the header is specified by LEVEL and header text is
+given by TEXT. LEVEL must be an integer from 1 and 6, and the
+default value is 1.
+When TEXT is nil, the header text is obtained as follows.
+If there is an active region, it is used as the header text.
+Otherwise, the current line will be used as the header text.
+If there is not an active region and the point is at a header,
+remove the header markup and replace with level N header.
+Otherwise, insert empty header markup and place the cursor in
+between.
+The style of the header will be atx (hash marks) unless
+SETEXT is non-nil, in which case a setext-style (underlined)
+header will be inserted."
+ (interactive "p\nsHeader text: ")
+ (setq level (min (max (or level 1) 1) (if setext 2 6)))
+ ;; Determine header text if not given
+ (when (null text)
+ (if (markdown-use-region-p)
+ ;; Active region
+ (setq text (delete-and-extract-region (region-beginning) (region-end)))
+ ;; No active region
+ (markdown-remove-header)
+ (setq text (delete-and-extract-region
+ (line-beginning-position) (line-end-position)))
+ (when (and setext (string-match "^[ \t]*$" text))
+ (setq text (read-string "Header text: "))))
+ (setq text (markdown-compress-whitespace-string text)))
+ ;; Insertion with given text
+ (markdown-ensure-blank-line-before)
+ (let (hdr)
+ (cond (setext
+ (setq hdr (make-string (string-width text) (if (= level 2) ?- ?=)))
+ (insert text "\n" hdr))
+ (t
+ (setq hdr (make-string level ?#))
+ (insert hdr " " text " " hdr))))
+ (markdown-ensure-blank-line-after)
+ ;; Leave point at end of text
+ (if setext
+ (backward-char (1+ (string-width text)))
+ (backward-char (1+ level))))
+
+(defun markdown-insert-header-dwim (&optional arg setext)
+ "Insert or replace header markup.
+The level and type of the header are determined automatically by
+the type and level of the previous header, unless a prefix
+argument is given via ARG.
+With a numeric prefix valued 1 to 6, insert a header of the given
+level, with the type being determined automatically (note that
+only level 1 or 2 setext headers are possible).
+
+With a \\[universal-argument] prefix (i.e., when ARG is (4)),
+promote the heading by one level.
+With two \\[universal-argument] prefixes (i.e., when ARG is (16)),
+demote the heading by one level.
+When SETEXT is non-nil, prefer setext-style headers when
+possible (levels one and two).
+
+When there is an active region, use it for the header text. When
+the point is at an existing header, change the type and level
+according to the rules above.
+Otherwise, if the line is not empty, create a header using the
+text on the current line as the header text.
+Finally, if the point is on a blank line, insert empty header
+markup (atx) or prompt for text (setext).
+See `markdown-insert-header' for more details about how the
+header text is determined."
+ (interactive "*P")
+ (let (level)
+ (save-excursion
+ (when (re-search-backward markdown-regex-header nil t)
+ ;; level of previous header
+ (setq level (markdown-outline-level))
+ ;; match groups 1 and 2 indicate setext headers
+ (setq setext (or setext (match-end 1) (match-end 3)))))
+ ;; check prefix argument
+ (cond
+ ((and (equal arg '(4)) (> level 1)) ;; C-u
+ (decf level))
+ ((and (equal arg '(16)) (< level 6)) ;; C-u C-u
+ (incf level))
+ (arg ;; numeric prefix
+ (setq level (prefix-numeric-value arg))))
+ ;; setext headers must be level one or two
+ (and level (setq setext (and setext (<= level 2))))
+ ;; insert the heading
+ (markdown-insert-header level nil setext)))
+
+(defun markdown-insert-header-setext-dwim (&optional arg)
+ "Insert or replace header markup, with preference for setext.
+See `markdown-insert-header-dwim' for details, including how ARG is handled."
+ (interactive "*P")
+ (markdown-insert-header-dwim arg t))
+
+(defun markdown-insert-header-atx-1 ()
+ "Insert a first level atx-style (hash mark) header.
+See `markdown-insert-header'."
+ (interactive "*")
+ (markdown-insert-header 1 nil nil))
+
+(defun markdown-insert-header-atx-2 ()
+ "Insert a level two atx-style (hash mark) header.
+See `markdown-insert-header'."
+ (interactive "*")
+ (markdown-insert-header 2 nil nil))
+
+(defun markdown-insert-header-atx-3 ()
+ "Insert a level three atx-style (hash mark) header.
+See `markdown-insert-header'."
+ (interactive "*")
+ (markdown-insert-header 3 nil nil))
+
+(defun markdown-insert-header-atx-4 ()
+ "Insert a level four atx-style (hash mark) header.
+See `markdown-insert-header'."
+ (interactive "*")
+ (markdown-insert-header 4 nil nil))
+
+(defun markdown-insert-header-atx-5 ()
+ "Insert a level five atx-style (hash mark) header.
+See `markdown-insert-header'."
+ (interactive "*")
+ (markdown-insert-header 5 nil nil))
+
+(defun markdown-insert-header-atx-6 ()
+ "Insert a sixth level atx-style (hash mark) header.
+See `markdown-insert-header'."
+ (interactive "*")
+ (markdown-insert-header 6 nil nil))
+
+(defun markdown-insert-header-setext-1 ()
+ "Insert a setext-style (underlined) first-level header.
+See `markdown-insert-header'."
+ (interactive "*")
+ (markdown-insert-header 1 nil t))
+
+(defun markdown-insert-header-setext-2 ()
+ "Insert a setext-style (underlined) second-level header.
+See `markdown-insert-header'."
+ (interactive "*")
+ (markdown-insert-header 2 nil t))
+
+(defun markdown-blockquote-indentation (loc)
+ "Return string containing necessary indentation for a blockquote at LOC.
+Also see `markdown-pre-indentation'."
+ (save-excursion
+ (goto-char loc)
+ (let* ((list-level (length (markdown-calculate-list-levels)))
+ (indent ""))
+ (dotimes (count list-level indent)
+ (setq indent (concat indent " "))))))
+
+(defun markdown-insert-blockquote ()
+ "Start a blockquote section (or blockquote the region).
+If Transient Mark mode is on and a region is active, it is used as
+the blockquote text."
+ (interactive)
+ (if (markdown-use-region-p)
+ (markdown-blockquote-region (region-beginning) (region-end))
+ (markdown-ensure-blank-line-before)
+ (insert (markdown-blockquote-indentation (point)) "> ")
+ (markdown-ensure-blank-line-after)))
+
+(defun markdown-block-region (beg end prefix)
+ "Format the region using a block prefix.
+Arguments BEG and END specify the beginning and end of the
+region. The characters PREFIX will appear at the beginning
+of each line."
+ (save-excursion
+ (let* ((end-marker (make-marker))
+ (beg-marker (make-marker)))
+ ;; Ensure blank line after and remove extra whitespace
+ (goto-char end)
+ (skip-syntax-backward "-")
+ (set-marker end-marker (point))
+ (delete-horizontal-space)
+ (markdown-ensure-blank-line-after)
+ ;; Ensure blank line before and remove extra whitespace
+ (goto-char beg)
+ (skip-syntax-forward "-")
+ (delete-horizontal-space)
+ (markdown-ensure-blank-line-before)
+ (set-marker beg-marker (point))
+ ;; Insert PREFIX before each line
+ (goto-char beg-marker)
+ (while (and (< (line-beginning-position) end-marker)
+ (not (eobp)))
+ (insert prefix)
+ (forward-line)))))
+
+(defun markdown-blockquote-region (beg end)
+ "Blockquote the region.
+Arguments BEG and END specify the beginning and end of the region."
+ (interactive "*r")
+ (markdown-block-region
+ beg end (concat (markdown-blockquote-indentation
+ (max (point-min) (1- beg))) "> ")))
+
+(defun markdown-pre-indentation (loc)
+ "Return string containing necessary whitespace for a pre block at LOC.
+Also see `markdown-blockquote-indentation'."
+ (save-excursion
+ (goto-char loc)
+ (let* ((list-level (length (markdown-calculate-list-levels)))
+ indent)
+ (dotimes (count (1+ list-level) indent)
+ (setq indent (concat indent " "))))))
+
+(defun markdown-insert-pre ()
+ "Start a preformatted section (or apply to the region).
+If Transient Mark mode is on and a region is active, it is marked
+as preformatted text."
+ (interactive)
+ (if (markdown-use-region-p)
+ (markdown-pre-region (region-beginning) (region-end))
+ (markdown-ensure-blank-line-before)
+ (insert (markdown-pre-indentation (point)))
+ (markdown-ensure-blank-line-after)))
+
+(defun markdown-pre-region (beg end)
+ "Format the region as preformatted text.
+Arguments BEG and END specify the beginning and end of the region."
+ (interactive "*r")
+ (let ((indent (markdown-pre-indentation (max (point-min) (1- beg)))))
+ (markdown-block-region beg end indent)))
+
+(defun markdown-insert-gfm-code-block (&optional lang)
+ "Insert GFM code block for language LANG.
+If LANG is nil, the language will be queried from user. If a
+region is active, wrap this region with the markup instead. If
+the region boundaries are not on empty lines, these are added
+automatically in order to have the correct markup."
+ (interactive "sProgramming language [none]: ")
+ (if (markdown-use-region-p)
+ (let ((b (region-beginning)) (e (region-end)))
+ (goto-char e)
+ ;; if we're on a blank line, don't newline, otherwise the ```
+ ;; should go on its own line
+ (unless (looking-back "\n")
+ (newline))
+ (insert "```")
+ (markdown-ensure-blank-line-after)
+ (goto-char b)
+ ;; if we're on a blank line, insert the quotes here, otherwise
+ ;; add a new line first
+ (unless (looking-at "\n")
+ (newline)
+ (forward-line -1))
+ (markdown-ensure-blank-line-before)
+ (insert "```" lang))
+ (markdown-ensure-blank-line-before)
+ (insert "```" lang)
+ (newline 2)
+ (insert "```")
+ (markdown-ensure-blank-line-after)
+ (forward-line -1)))
+
+\f
+;;; Footnotes ======================================================================
+
+(defun markdown-footnote-counter-inc ()
+ "Increment `markdown-footnote-counter' and return the new value."
+ (when (= markdown-footnote-counter 0) ; hasn't been updated in this buffer yet.
+ (save-excursion
+ (goto-char (point-min))
+ (while (re-search-forward (concat "^\\[\\^\\(" markdown-footnote-chars "*?\\)\\]:")
+ (point-max) t)
+ (let ((fn (string-to-number (match-string 1))))
+ (when (> fn markdown-footnote-counter)
+ (setq markdown-footnote-counter fn))))))
+ (incf markdown-footnote-counter))
+
+(defun markdown-insert-footnote ()
+ "Insert footnote with a new number and move point to footnote definition."
+ (interactive)
+ (let ((fn (markdown-footnote-counter-inc)))
+ (insert (format "[^%d]" fn))
+ (markdown-footnote-text-find-new-location)
+ (markdown-ensure-blank-line-before)
+ (unless (markdown-cur-line-blank-p)
+ (insert "\n"))
+ (insert (format "[^%d]: " fn))
+ (markdown-ensure-blank-line-after)))
+
+(defun markdown-footnote-text-find-new-location ()
+ "Position the cursor at the proper location for a new footnote text."
+ (cond
+ ((eq markdown-footnote-location 'end) (goto-char (point-max)))
+ ((eq markdown-footnote-location 'immediately) (markdown-end-of-block))
+ ((eq markdown-footnote-location 'header) (markdown-end-of-defun))))
+
+(defun markdown-footnote-kill ()
+ "Kill the footnote at point.
+The footnote text is killed (and added to the kill ring), the
+footnote marker is deleted. Point has to be either at the
+footnote marker or in the footnote text."
+ (interactive)
+ (let ((marker-pos nil)
+ (skip-deleting-marker nil)
+ (starting-footnote-text-positions
+ (markdown-footnote-text-positions)))
+ (when starting-footnote-text-positions
+ ;; We're starting in footnote text, so mark our return position and jump
+ ;; to the marker if possible.
+ (let ((marker-pos (markdown-footnote-find-marker
+ (first starting-footnote-text-positions))))
+ (if marker-pos
+ (goto-char (1- marker-pos))
+ ;; If there isn't a marker, we still want to kill the text.
+ (setq skip-deleting-marker t))))
+ ;; Either we didn't start in the text, or we started in the text and jumped
+ ;; to the marker. We want to assume we're at the marker now and error if
+ ;; we're not.
+ (unless skip-deleting-marker
+ (let ((marker (markdown-footnote-delete-marker)))
+ (unless marker
+ (error "Not at a footnote"))
+ ;; Even if we knew the text position before, it changed when we deleted
+ ;; the label.
+ (setq marker-pos (second marker))
+ (let ((new-text-pos (markdown-footnote-find-text (first marker))))
+ (unless new-text-pos
+ (error "No text for footnote `%s'" (first marker)))
+ (goto-char new-text-pos))))
+ (let ((pos (markdown-footnote-kill-text)))
+ (goto-char (if starting-footnote-text-positions
+ pos
+ marker-pos)))))
+
+(defun markdown-footnote-delete-marker ()
+ "Delete a footnote marker at point.
+Returns a list (ID START) containing the footnote ID and the
+start position of the marker before deletion. If no footnote
+marker was deleted, this function returns NIL."
+ (let ((marker (markdown-footnote-marker-positions)))
+ (when marker
+ (delete-region (second marker) (third marker))
+ (butlast marker))))
+
+(defun markdown-footnote-kill-text ()
+ "Kill footnote text at point.
+Returns the start position of the footnote text before deletion,
+or NIL if point was not inside a footnote text.
+
+The killed text is placed in the kill ring (without the footnote
+number)."
+ (let ((fn (markdown-footnote-text-positions)))
+ (when fn
+ (let ((text (delete-and-extract-region (second fn) (third fn))))
+ (string-match (concat "\\[\\" (first fn) "\\]:[[:space:]]*\\(\\(.*\n?\\)*\\)") text)
+ (kill-new (match-string 1 text))
+ (when (and (markdown-cur-line-blank-p)
+ (markdown-prev-line-blank-p)
+ (not (bobp)))
+ (delete-region (1- (point)) (point)))
+ (second fn)))))
+
+(defun markdown-footnote-goto-text ()
+ "Jump to the text of the footnote at point."
+ (interactive)
+ (let ((fn (car (markdown-footnote-marker-positions))))
+ (unless fn
+ (error "Not at a footnote marker"))
+ (let ((new-pos (markdown-footnote-find-text fn)))
+ (unless new-pos
+ (error "No definition found for footnote `%s'" fn))
+ (goto-char new-pos))))
+
+(defun markdown-footnote-return ()
+ "Return from a footnote to its footnote number in the main text."
+ (interactive)
+ (let ((fn (save-excursion
+ (car (markdown-footnote-text-positions)))))
+ (unless fn
+ (error "Not in a footnote"))
+ (let ((new-pos (markdown-footnote-find-marker fn)))
+ (unless new-pos
+ (error "Footnote marker `%s' not found" fn))
+ (goto-char new-pos))))
+
+(defun markdown-footnote-find-marker (id)
+ "Find the location of the footnote marker with ID.
+The actual buffer position returned is the position directly
+following the marker's closing bracket. If no marker is found,
+NIL is returned."
+ (save-excursion
+ (goto-char (point-min))
+ (when (re-search-forward (concat "\\[" id "\\]\\([^:]\\|\\'\\)") nil t)
+ (skip-chars-backward "^]")
+ (point))))
+
+(defun markdown-footnote-find-text (id)
+ "Find the location of the text of footnote ID.
+The actual buffer position returned is the position of the first
+character of the text, after the footnote's identifier. If no
+footnote text is found, NIL is returned."
+ (save-excursion
+ (goto-char (point-min))
+ (when (re-search-forward (concat "^\\[" id "\\]:") nil t)
+ (skip-chars-forward "[ \t]")
+ (point))))
+
+(defun markdown-footnote-marker-positions ()
+ "Return the position and ID of the footnote marker point is on.
+The return value is a list (ID START END). If point is not on a
+footnote, NIL is returned."
+ ;; first make sure we're at a footnote marker
+ (if (or (looking-back (concat "\\[\\^" markdown-footnote-chars "*\\]?") (line-beginning-position))
+ (looking-at (concat "\\[?\\^" markdown-footnote-chars "*?\\]")))
+ (save-excursion
+ ;; move point between [ and ^:
+ (if (looking-at "\\[")
+ (forward-char 1)
+ (skip-chars-backward "^["))
+ (looking-at (concat "\\(\\^" markdown-footnote-chars "*?\\)\\]"))
+ (list (match-string 1) (1- (match-beginning 1)) (1+ (match-end 1))))))
+
+(defun markdown-footnote-text-positions ()
+ "Return the start and end positions of the footnote text point is in.
+The exact return value is a list of three elements: (ID START END).
+The start position is the position of the opening bracket
+of the footnote id. The end position is directly after the
+newline that ends the footnote. If point is not in a footnote,
+NIL is returned instead."
+ (save-excursion
+ (let (result)
+ (move-beginning-of-line 1)
+ ;; Try to find the label. If we haven't found the label and we're at a blank
+ ;; or indented line, back up if possible.
+ (while (and
+ (not (and (looking-at markdown-regex-footnote-definition)
+ (setq result (list (match-string 1) (point)))))
+ (and (not (bobp))
+ (or (markdown-cur-line-blank-p)
+ (>= (markdown-cur-line-indent) 4))))
+ (forward-line -1))
+ (when result
+ ; Advance if there is a next line that is either blank or indented.
+ ; (Need to check if we're on the last line, because
+ ; markdown-next-line-blank-p returns true for last line in buffer.)
+ (while (and (/= (line-end-position) (point-max))
+ (or (markdown-next-line-blank-p)
+ (>= (markdown-next-line-indent) 4)))
+ (forward-line))
+ ; Move back while the current line is blank.
+ (while (markdown-cur-line-blank-p)
+ (forward-line -1))
+ ; Advance to capture this line and a single trailing newline (if there
+ ; is one).
+ (forward-line)
+ (append result (list (point)))))))
+
+\f
+;;; Element Removal ===========================================================
+
+(defun markdown-kill-thing-at-point ()
+ "Kill thing at point and add important text, without markup, to kill ring.
+Possible things to kill include (roughly in order of precedence):
+inline code, headers, horizonal rules, links (add link text to
+kill ring), images (add alt text to kill ring), angle uri, email
+addresses, bold, italics, reference definition (add URI to kill
+ring), footnote markers and text (kill both marker and text, add
+text to kill ring), and list items."
+ (interactive "*")
+ (let (val tmp)
+ (cond
+ ;; Inline code
+ ((markdown-code-at-point-p)
+ (kill-new (match-string 1))
+ (delete-region (match-beginning 0) (match-end 0)))
+ ;; ATX header
+ ((thing-at-point-looking-at markdown-regex-header-atx)
+ (kill-new (match-string 2))
+ (delete-region (match-beginning 0) (match-end 0)))
+ ;; Setext header
+ ((thing-at-point-looking-at markdown-regex-header-setext)
+ (kill-new (match-string 1))
+ (delete-region (match-beginning 0) (match-end 0)))
+ ;; Horizonal rule
+ ((thing-at-point-looking-at markdown-regex-hr)
+ (kill-new (match-string 0))
+ (delete-region (match-beginning 0) (match-end 0)))
+ ;; Inline link or image (add link or alt text to kill ring)
+ ((thing-at-point-looking-at markdown-regex-link-inline)
+ (kill-new (match-string 3))
+ (delete-region (match-beginning 0) (match-end 0)))
+ ;; Reference link or image (add link or alt text to kill ring)
+ ((thing-at-point-looking-at markdown-regex-link-reference)
+ (kill-new (match-string 3))
+ (delete-region (match-beginning 0) (match-end 0)))
+ ;; Angle URI (add URL to kill ring)
+ ((thing-at-point-looking-at markdown-regex-angle-uri)
+ (kill-new (match-string 2))
+ (delete-region (match-beginning 0) (match-end 0)))
+ ;; Email address in angle brackets (add email address to kill ring)
+ ((thing-at-point-looking-at markdown-regex-email)
+ (kill-new (match-string 1))
+ (delete-region (match-beginning 0) (match-end 0)))
+ ;; Wiki link (add alias text to kill ring)
+ ((thing-at-point-looking-at markdown-regex-wiki-link)
+ (kill-new (markdown-wiki-link-alias))
+ (delete-region (match-beginning 1) (match-end 1)))
+ ;; Bold
+ ((thing-at-point-looking-at markdown-regex-bold)
+ (kill-new (match-string 4))
+ (delete-region (match-beginning 2) (match-end 2)))
+ ;; Italics
+ ((thing-at-point-looking-at markdown-regex-italic)
+ (kill-new (match-string 4))
+ (delete-region (match-beginning 2) (match-end 2)))
+ ;; Strike-through
+ ((thing-at-point-looking-at markdown-regex-strike-through)
+ (kill-new (match-string 4))
+ (delete-region (match-beginning 2) (match-end 2)))
+ ;; Footnote marker (add footnote text to kill ring)
+ ((thing-at-point-looking-at markdown-regex-footnote)
+ (markdown-footnote-kill))
+ ;; Footnote text (add footnote text to kill ring)
+ ((setq val (markdown-footnote-text-positions))
+ (markdown-footnote-kill))
+ ;; Reference definition (add URL to kill ring)
+ ((thing-at-point-looking-at markdown-regex-reference-definition)
+ (kill-new (match-string 2))
+ (delete-region (match-beginning 0) (match-end 0)))
+ ;; List item
+ ((setq val (markdown-cur-list-item-bounds))
+ (kill-new (delete-and-extract-region (first val) (second val))))
+ (t
+ (error "Nothing found at point to kill")))))
+
+\f
+;;; Indentation ====================================================================
+
+(defun markdown-indent-find-next-position (cur-pos positions)
+ "Return the position after the index of CUR-POS in POSITIONS.
+Positions are calculated by `markdown-calc-indents'."
+ (while (and positions
+ (not (equal cur-pos (car positions))))
+ (setq positions (cdr positions)))
+ (or (cadr positions) 0))
+
+(defun markdown-exdent-find-next-position (cur-pos positions)
+ "Return the maximal element that precedes CUR-POS from POSITIONS.
+Positions are calculated by `markdown-calc-indents'."
+ (let ((result 0))
+ (dolist (i positions)
+ (when (< i cur-pos)
+ (setq result (max result i))))
+ result))
+
+(defun markdown-indent-line ()
+ "Indent the current line using some heuristics.
+If the _previous_ command was either `markdown-enter-key' or
+`markdown-cycle', then we should cycle to the next
+reasonable indentation position. Otherwise, we could have been
+called directly by `markdown-enter-key', by an initial call of
+`markdown-cycle', or indirectly by `auto-fill-mode'. In
+these cases, indent to the default position.
+Positions are calculated by `markdown-calc-indents'."
+ (interactive)
+ (let ((positions (markdown-calc-indents))
+ (cur-pos (current-column)))
+ (if (not (equal this-command 'markdown-cycle))
+ (indent-line-to (car positions))
+ (setq positions (sort (delete-dups positions) '<))
+ (indent-line-to
+ (markdown-indent-find-next-position cur-pos positions)))))
+
+(defun markdown-calc-indents ()
+ "Return a list of indentation columns to cycle through.
+The first element in the returned list should be considered the
+default indentation level. This function does not worry about
+duplicate positions, which are handled up by calling functions."
+ (let (pos prev-line-pos positions)
+
+ ;; Indentation of previous line
+ (setq prev-line-pos (markdown-prev-line-indent))
+ (setq positions (cons prev-line-pos positions))
+
+ ;; Indentation of previous non-list-marker text
+ (when (setq pos (markdown-prev-non-list-indent))
+ (setq positions (cons pos positions)))
+
+ ;; Indentation required for a pre block in current context
+ (setq pos (length (markdown-pre-indentation (point))))
+ (setq positions (cons pos positions))
+
+ ;; Indentation of the previous line + tab-width
+ (if prev-line-pos
+ (setq positions (cons (+ prev-line-pos tab-width) positions))
+ (setq positions (cons tab-width positions)))
+
+ ;; Indentation of the previous line - tab-width
+ (if (and prev-line-pos (> prev-line-pos tab-width))
+ (setq positions (cons (- prev-line-pos tab-width) positions)))
+
+ ;; Indentation of all preceeding list markers (when in a list)
+ (when (setq pos (markdown-calculate-list-levels))
+ (setq positions (append pos positions)))
+
+ ;; First column
+ (setq positions (cons 0 positions))
+
+ ;; Return reversed list
+ (reverse positions)))
+
+(defun markdown-enter-key ()
+ "Handle RET according to to the value of `markdown-indent-on-enter'."
+ (interactive)
+ (if markdown-indent-on-enter
+ (newline-and-indent)
+ (newline)))
+
+(defun markdown-exdent-or-delete (arg)
+ "Handle BACKSPACE by cycling through indentation points.
+When BACKSPACE is pressed, if there is only whitespace
+before the current point, then exdent the line one level.
+Otherwise, do normal delete by repeating
+`backward-delete-char-untabify' ARG times."
+ (interactive "*p")
+ (let ((cur-pos (current-column))
+ (start-of-indention (save-excursion
+ (back-to-indentation)
+ (current-column)))
+ (positions (markdown-calc-indents)))
+ (if (and (> cur-pos 0) (= cur-pos start-of-indention))
+ (indent-line-to (markdown-exdent-find-next-position cur-pos positions))
+ (backward-delete-char-untabify arg))))
+
+(defun markdown-find-leftmost-column (beg end)
+ "Find the leftmost column in the region from BEG to END."
+ (let ((mincol 1000))
+ (save-excursion
+ (goto-char beg)
+ (while (< (point) end)
+ (back-to-indentation)
+ (unless (looking-at "[ \t]*$")
+ (setq mincol (min mincol (current-column))))
+ (forward-line 1)
+ ))
+ mincol))
+
+(defun markdown-indent-region (beg end arg)
+ "Indent the region from BEG to END using some heuristics.
+When ARG is non-nil, exdent the region instead.
+See `markdown-indent-line' and `markdown-indent-line'."
+ (interactive "*r\nP")
+ (let* ((positions (sort (delete-dups (markdown-calc-indents)) '<))
+ (leftmostcol (markdown-find-leftmost-column beg end))
+ (next-pos (if arg
+ (markdown-exdent-find-next-position leftmostcol positions)
+ (markdown-indent-find-next-position leftmostcol positions))))
+ (indent-rigidly beg end (- next-pos leftmostcol))
+ (setq deactivate-mark nil)))
+
+(defun markdown-exdent-region (beg end)
+ "Call `markdown-indent-region' on region from BEG to END with prefix."
+ (interactive "*r")
+ (markdown-indent-region (region-beginning) (region-end) t))
+
+\f
+;;; Markup Completion =========================================================
+
+(defconst markdown-complete-alist
+ '((markdown-regex-header-atx . markdown-complete-atx)
+ (markdown-regex-header-setext . markdown-complete-setext)
+ (markdown-regex-hr . markdown-complete-hr))
+ "Association list of form (regexp . function) for markup completion.")
+
+(defun markdown-incomplete-atx-p ()
+ "Return t if ATX header markup is incomplete and nil otherwise.
+Assumes match data is available for `markdown-regex-header-atx'.
+Checks that the number of trailing hash marks equals the number of leading
+hash marks, that there is only a single space before and after the text,
+and that there is no extraneous whitespace in the text."
+ (save-match-data
+ (or
+ ;; Number of starting and ending hash marks differs
+ (not (= (length (match-string 1)) (length (match-string 3))))
+ ;; When the header text is not empty...
+ (and (> (length (match-string 2)) 0)
+ ;; ...if there are extra leading, trailing, or interior spaces
+ (or (not (= (match-beginning 2) (1+ (match-end 1))))
+ (not (= (match-beginning 3) (1+ (match-end 2))))
+ (string-match "[ \t\n]\\{2\\}" (match-string 2))))
+ ;; When the header text is empty...
+ (and (= (length (match-string 2)) 0)
+ ;; ...if there are too many or too few spaces
+ (not (= (match-beginning 3) (+ (match-end 1) 2)))))))
+
+(defun markdown-complete-atx ()
+ "Complete and normalize ATX headers.
+Add or remove hash marks to the end of the header to match the
+beginning. Ensure that there is only a single space between hash
+marks and header text. Removes extraneous whitespace from header text.
+Assumes match data is available for `markdown-regex-header-atx'.
+Return nil if markup was complete and non-nil if markup was completed."
+ (when (markdown-incomplete-atx-p)
+ (let* ((new-marker (make-marker))
+ (new-marker (set-marker new-marker (match-end 2))))
+ ;; Hash marks and spacing at end
+ (goto-char (match-end 2))
+ (delete-region (match-end 2) (match-end 3))
+ (insert " " (match-string 1))
+ ;; Remove extraneous whitespace from title
+ (replace-match (markdown-compress-whitespace-string (match-string 2))
+ t t nil 2)
+ ;; Spacing at beginning
+ (goto-char (match-end 1))
+ (delete-region (match-end 1) (match-beginning 2))
+ (insert " ")
+ ;; Leave point at end of text
+ (goto-char new-marker))))
+
+(defun markdown-incomplete-setext-p ()
+ "Return t if setext header markup is incomplete and nil otherwise.
+Assumes match data is available for `markdown-regex-header-setext'.
+Checks that length of underline matches text and that there is no
+extraneous whitespace in the text."
+ (save-match-data
+ (or (not (= (length (match-string 1)) (length (match-string 2))))
+ (string-match "[ \t\n]\\{2\\}" (match-string 1)))))
+
+(defun markdown-complete-setext ()
+ "Complete and normalize setext headers.
+Add or remove underline characters to match length of header
+text. Removes extraneous whitespace from header text. Assumes
+match data is available for `markdown-regex-header-setext'.
+Return nil if markup was complete and non-nil if markup was completed."
+ (when (markdown-incomplete-setext-p)
+ (let* ((text (markdown-compress-whitespace-string (match-string 1)))
+ (char (char-after (match-beginning 2)))
+ (level (if (char-equal char ?-) 2 1)))
+ (goto-char (match-beginning 0))
+ (delete-region (match-beginning 0) (match-end 0))
+ (markdown-insert-header level text t)
+ t)))
+
+(defun markdown-incomplete-hr-p ()
+ "Return non-nil if hr is not in `markdown-hr-strings' and nil otherwise.
+Assumes match data is available for `markdown-regex-hr'."
+ (not (member (match-string 0) markdown-hr-strings)))
+
+(defun markdown-complete-hr ()
+ "Complete horizontal rules.
+If horizontal rule string is a member of `markdown-hr-strings',
+do nothing. Otherwise, replace with the car of
+`markdown-hr-strings'.
+Assumes match data is available for `markdown-regex-hr'.
+Return nil if markup was complete and non-nil if markup was completed."
+ (when (markdown-incomplete-hr-p)
+ (replace-match (car markdown-hr-strings))
+ t))
+
+(defun markdown-complete ()
+ "Complete markup of object near point or in region when active.
+Handle all objects in `markdown-complete-alist', in order.
+See `markdown-complete-at-point' and `markdown-complete-region'."
+ (interactive "*")
+ (if (markdown-use-region-p)
+ (markdown-complete-region (region-beginning) (region-end))
+ (markdown-complete-at-point)))
+
+(defun markdown-complete-at-point ()
+ "Complete markup of object near point.
+Handle all elements of `markdown-complete-alist' in order."
+ (interactive "*")
+ (let ((list markdown-complete-alist) found changed)
+ (while list
+ (let ((regexp (eval (caar list)))
+ (function (cdar list)))
+ (setq list (cdr list))
+ (when (thing-at-point-looking-at regexp)
+ (setq found t)
+ (setq changed (funcall function))
+ (setq list nil))))
+ (if found
+ (or changed (error "Markup at point is complete"))
+ (error "Nothing to complete at point"))))
+
+(defun markdown-complete-region (beg end)
+ "Complete markup of objects in region from BEG to END.
+Handle all objects in `markdown-complete-alist', in order. Each
+match is checked to ensure that a previous regexp does not also
+match."
+ (interactive "*r")
+ (let ((end-marker (set-marker (make-marker) end))
+ previous)
+ (dolist (element markdown-complete-alist)
+ (let ((regexp (eval (car element)))
+ (function (cdr element)))
+ (goto-char beg)
+ (while (re-search-forward regexp end-marker 'limit)
+ (when (match-string 0)
+ ;; Make sure this is not a match for any of the preceding regexps.
+ ;; This prevents mistaking an HR for a Setext subheading.
+ (let (match)
+ (save-match-data
+ (dolist (prev-regexp previous)
+ (or match (setq match (looking-back prev-regexp)))))
+ (unless match
+ (save-excursion (funcall function))))))
+ (add-to-list 'previous regexp)))))
+
+(defun markdown-complete-buffer ()
+ "Complete markup for all objects in the current buffer."
+ (interactive "*")
+ (markdown-complete-region (point-min) (point-max)))
+
+\f
+;;; Markup Cycling ============================================================
+
+(defun markdown-cycle-atx (arg &optional remove)
+ "Cycle ATX header markup.
+Promote header (decrease level) when ARG is 1 and demote
+header (increase level) if arg is -1. When REMOVE is non-nil,
+remove the header when the level reaches zero and stop cycling
+when it reaches six. Otherwise, perform a proper cycling through
+levels one through six. Assumes match data is available for
+`markdown-regex-header-atx'."
+ (let* ((old-level (length (match-string 1)))
+ (new-level (+ old-level arg))
+ (text (match-string 2)))
+ (when (not remove)
+ (setq new-level (% new-level 6))
+ (setq new-level (cond ((= new-level 0) 6)
+ ((< new-level 0) (+ new-level 6))
+ (t new-level))))
+ (cond
+ ((= new-level 0)
+ (markdown-unwrap-thing-at-point nil 0 2))
+ ((<= new-level 6)
+ (goto-char (match-beginning 0))
+ (delete-region (match-beginning 0) (match-end 0))
+ (markdown-insert-header new-level text nil)))))
+
+(defun markdown-cycle-setext (arg &optional remove)
+ "Cycle setext header markup.
+Promote header (increase level) when ARG is 1 and demote
+header (decrease level or remove) if arg is -1. When demoting a
+level-two setext header, replace with a level-three atx header.
+When REMOVE is non-nil, remove the header when the level reaches
+zero. Otherwise, cycle back to a level six atx header. Assumes
+match data is available for `markdown-regex-header-setext'."
+ (let* ((char (char-after (match-beginning 2)))
+ (old-level (if (char-equal char ?=) 1 2))
+ (new-level (+ old-level arg))
+ (text (match-string 1)))
+ (when (and (not remove) (= new-level 0))
+ (setq new-level 6))
+ (cond
+ ((= new-level 0)
+ (markdown-unwrap-thing-at-point nil 0 1))
+ ((<= new-level 2)
+ (markdown-insert-header new-level nil t))
+ ((<= new-level 6)
+ (markdown-insert-header new-level nil nil)))))
+
+(defun markdown-cycle-hr (arg &optional remove)
+ "Cycle string used for horizontal rule from `markdown-hr-strings'.
+When ARG is 1, cycle forward (demote), and when ARG is -1, cycle
+backwards (promote). When REMOVE is non-nil, remove the hr instead
+of cycling when the end of the list is reached.
+Assumes match data is available for `markdown-regex-hr'."
+ (let* ((strings (if (= arg -1)
+ (reverse markdown-hr-strings)
+ markdown-hr-strings))
+ (tail (member (match-string 0) strings))
+ (new (or (cadr tail)
+ (if remove
+ (if (= arg 1)
+ ""
+ (car tail))
+ (car strings)))))
+ (replace-match new)))
+
+(defun markdown-cycle-bold ()
+ "Cycle bold markup between underscores and asterisks.
+Assumes match data is available for `markdown-regex-bold'."
+ (save-excursion
+ (let* ((old-delim (match-string 3))
+ (new-delim (if (string-equal old-delim "**") "__" "**")))
+ (replace-match new-delim t t nil 3)
+ (replace-match new-delim t t nil 5))))
+
+(defun markdown-cycle-italic ()
+ "Cycle italic markup between underscores and asterisks.
+Assumes match data is available for `markdown-regex-italic'."
+ (save-excursion
+ (let* ((old-delim (match-string 3))
+ (new-delim (if (string-equal old-delim "*") "_" "*")))
+ (replace-match new-delim t t nil 3)
+ (replace-match new-delim t t nil 5))))
+
+\f
+;;; Keymap ====================================================================
+
+(defvar markdown-mode-map
+ (let ((map (make-keymap)))
+ ;; Element insertion
+ (define-key map "\C-c\C-al" 'markdown-insert-link)
+ (define-key map "\C-c\C-aL" 'markdown-insert-reference-link-dwim)
+ (define-key map "\C-c\C-au" 'markdown-insert-uri)
+ (define-key map "\C-c\C-af" 'markdown-insert-footnote)
+ (define-key map "\C-c\C-aw" 'markdown-insert-wiki-link)
+ (define-key map "\C-c\C-ii" 'markdown-insert-image)
+ (define-key map "\C-c\C-iI" 'markdown-insert-reference-image)
+ (define-key map "\C-c\C-th" 'markdown-insert-header-dwim)
+ (define-key map "\C-c\C-tH" 'markdown-insert-header-setext-dwim)
+ (define-key map "\C-c\C-t1" 'markdown-insert-header-atx-1)
+ (define-key map "\C-c\C-t2" 'markdown-insert-header-atx-2)
+ (define-key map "\C-c\C-t3" 'markdown-insert-header-atx-3)
+ (define-key map "\C-c\C-t4" 'markdown-insert-header-atx-4)
+ (define-key map "\C-c\C-t5" 'markdown-insert-header-atx-5)
+ (define-key map "\C-c\C-t6" 'markdown-insert-header-atx-6)
+ (define-key map "\C-c\C-t!" 'markdown-insert-header-setext-1)
+ (define-key map "\C-c\C-t@" 'markdown-insert-header-setext-2)
+ (define-key map "\C-c\C-ss" 'markdown-insert-bold)
+ (define-key map "\C-c\C-se" 'markdown-insert-italic)
+ (define-key map "\C-c\C-sc" 'markdown-insert-code)
+ (define-key map "\C-c\C-sb" 'markdown-insert-blockquote)
+ (define-key map "\C-c\C-s\C-b" 'markdown-blockquote-region)
+ (define-key map "\C-c\C-sp" 'markdown-insert-pre)
+ (define-key map "\C-c\C-s\C-p" 'markdown-pre-region)
+ (define-key map "\C-c-" 'markdown-insert-hr)
+ ;; Element insertion (deprecated)
+ (define-key map "\C-c\C-ar" 'markdown-insert-reference-link-dwim)
+ (define-key map "\C-c\C-tt" 'markdown-insert-header-setext-1)
+ (define-key map "\C-c\C-ts" 'markdown-insert-header-setext-2)
+ ;; Element removal
+ (define-key map (kbd "C-c C-k") 'markdown-kill-thing-at-point)
+ ;; Promotion, Demotion, Completion, and Cycling
+ (define-key map (kbd "C-c C--") 'markdown-promote)
+ (define-key map (kbd "C-c C-=") 'markdown-demote)
+ (define-key map (kbd "C-c C-]") 'markdown-complete)
+ ;; Following and Jumping
+ (define-key map (kbd "C-c C-o") 'markdown-follow-thing-at-point)
+ (define-key map (kbd "C-c C-j") 'markdown-jump)
+ ;; Indentation
+ (define-key map (kbd "C-m") 'markdown-enter-key)
+ (define-key map (kbd "<backspace>") 'markdown-exdent-or-delete)
+ (define-key map (kbd "C-c >") 'markdown-indent-region)
+ (define-key map (kbd "C-c <") 'markdown-exdent-region)
+ ;; Visibility cycling
+ (define-key map (kbd "<tab>") 'markdown-cycle)
+ (define-key map (kbd "<S-iso-lefttab>") 'markdown-shifttab)
+ (define-key map (kbd "<S-tab>") 'markdown-shifttab)
+ (define-key map (kbd "<backtab>") 'markdown-shifttab)
+ ;; Header navigation
+ (define-key map (kbd "C-c C-n") 'outline-next-visible-heading)
+ (define-key map (kbd "C-c C-p") 'outline-previous-visible-heading)
+ (define-key map (kbd "C-c C-f") 'outline-forward-same-level)
+ (define-key map (kbd "C-c C-b") 'outline-backward-same-level)
+ (define-key map (kbd "C-c C-u") 'outline-up-heading)
+ ;; Buffer-wide commands
+ (define-key map (kbd "C-c C-c m") 'markdown-other-window)
+ (define-key map (kbd "C-c C-c p") 'markdown-preview)
+ (define-key map (kbd "C-c C-c e") 'markdown-export)
+ (define-key map (kbd "C-c C-c v") 'markdown-export-and-preview)
+ (define-key map (kbd "C-c C-c o") 'markdown-open)
+ (define-key map (kbd "C-c C-c w") 'markdown-kill-ring-save)
+ (define-key map (kbd "C-c C-c c") 'markdown-check-refs)
+ (define-key map (kbd "C-c C-c n") 'markdown-cleanup-list-numbers)
+ (define-key map (kbd "C-c C-c ]") 'markdown-complete-buffer)
+ ;; List editing
+ (define-key map (kbd "M-<up>") 'markdown-move-up)
+ (define-key map (kbd "M-<down>") 'markdown-move-down)
+ (define-key map (kbd "M-<left>") 'markdown-promote)
+ (define-key map (kbd "M-<right>") 'markdown-demote)
+ (define-key map (kbd "M-<return>") 'markdown-insert-list-item)
+ ;; Movement
+ (define-key map (kbd "M-{") 'markdown-backward-paragraph)
+ (define-key map (kbd "M-}") 'markdown-forward-paragraph)
+ (define-key map (kbd "M-n") 'markdown-next-link)
+ (define-key map (kbd "M-p") 'markdown-previous-link)
+ ;; Alternative keys (in case of problems with the arrow keys)
+ (define-key map (kbd "C-c C-x u") 'markdown-move-up)
+ (define-key map (kbd "C-c C-x d") 'markdown-move-down)
+ (define-key map (kbd "C-c C-x l") 'markdown-promote)
+ (define-key map (kbd "C-c C-x r") 'markdown-demote)
+ (define-key map (kbd "C-c C-x m") 'markdown-insert-list-item)
+ map)
+ "Keymap for Markdown major mode.")
+
+(defvar gfm-mode-map
+ (let ((map (make-sparse-keymap)))
+ (set-keymap-parent map markdown-mode-map)
+ (define-key map (kbd "C-c C-s P") 'markdown-insert-gfm-code-block)
+ (define-key map (kbd "C-c C-s d") 'markdown-insert-strike-through)
+ map)
+ "Keymap for `gfm-mode'.
+See also `markdown-mode-map'.")
+
+\f
+;;; Menu ==================================================================
+
+(easy-menu-define markdown-mode-menu markdown-mode-map
+ "Menu for Markdown mode"
+ '("Markdown"
+ ("Show/Hide"
+ ["Cycle visibility" markdown-cycle (outline-on-heading-p)]
+ ["Cycle global visibility" markdown-shifttab])
+ "---"
+ ["Compile" markdown-other-window]
+ ["Preview" markdown-preview]
+ ["Export" markdown-export]
+ ["Export & View" markdown-export-and-preview]
+ ["Open" markdown-open]
+ ["Kill ring save" markdown-kill-ring-save]
+ "---"
+ ("Headings"
+ ["Automatic" markdown-insert-header-dwim]
+ ["Automatic (prefer setext)" markdown-insert-header-setext-dwim]
+ "---"
+ ["First level setext" markdown-insert-header-setext-1]
+ ["Second level setext" markdown-insert-header-setext-2]
+ "---"
+ ["First level atx" markdown-insert-header-atx-1]
+ ["Second level atx" markdown-insert-header-atx-2]
+ ["Third level atx" markdown-insert-header-atx-3]
+ ["Fourth level atx" markdown-insert-header-atx-4]
+ ["Fifth level atx" markdown-insert-header-atx-5]
+ ["Sixth level atx" markdown-insert-header-atx-6])
+ "---"
+ ["Bold" markdown-insert-bold]
+ ["Italic" markdown-insert-italic]
+ ["Strike-through" markdown-insert-strike-through]
+ ["Blockquote" markdown-insert-blockquote]
+ ["Preformatted" markdown-insert-pre]
+ ["Code" markdown-insert-code]
+ "---"
+ ["Insert inline link" markdown-insert-link]
+ ["Insert reference link" markdown-insert-reference-link-dwim]
+ ["Insert URL" markdown-insert-uri]
+ ["Insert inline image" markdown-insert-image]
+ ["Insert reference image" markdown-insert-reference-image]
+ ["Insert list item" markdown-insert-list-item]
+ ["Insert horizontal rule" markdown-insert-hr]
+ ["Insert footnote" markdown-insert-footnote]
+ ["Kill element" markdown-kill-thing-at-point]
+ "---"
+ ["Jump" markdown-jump]
+ ["Follow link" markdown-follow-thing-at-point]
+ ("Outline"
+ ["Next visible heading" outline-next-visible-heading]
+ ["Previous visible heading" outline-previous-visible-heading]
+ ["Forward same level" outline-forward-same-level]
+ ["Backward same level" outline-backward-same-level]
+ ["Up to parent heading" outline-up-heading])
+ "---"
+ ("Completion and Cycling"
+ ["Complete" markdown-complete]
+ ["Promote" markdown-promote]
+ ["Demote" markdown-demote])
+ ("List editing"
+ ["Indent list item" markdown-demote]
+ ["Exdent list item" markdown-promote])
+ ("Region shifting"
+ ["Indent region" markdown-indent-region]
+ ["Exdent region" markdown-exdent-region])
+ "---"
+ ["Check references" markdown-check-refs]
+ ["Clean up list numbering" markdown-cleanup-list-numbers]
+ ["Complete markup" markdown-complete-buffer]
+ "---"
+ ["Version" markdown-show-version]
+ ))
+
+\f
+;;; imenu =====================================================================
+
+(defun markdown-imenu-create-index ()
+ "Create and return an imenu index alist for the current buffer.
+See `imenu-create-index-function' and `imenu--index-alist' for details."
+ (let* ((root '(nil . nil))
+ cur-alist
+ (cur-level 0)
+ (empty-heading "-")
+ (self-heading ".")
+ hashes pos level heading)
+ (save-excursion
+ (goto-char (point-min))
+ (while (re-search-forward markdown-regex-header (point-max) t)
+ (cond
+ ((setq heading (match-string-no-properties 1))
+ (setq pos (match-beginning 1)
+ level 1))
+ ((setq heading (match-string-no-properties 3))
+ (setq pos (match-beginning 3)
+ level 2))
+ ((setq hashes (match-string-no-properties 5))
+ (setq heading (match-string-no-properties 6)
+ pos (match-beginning 5)
+ level (length hashes))))
+ (let ((alist (list (cons heading pos))))
+ (cond
+ ((= cur-level level) ; new sibling
+ (setcdr cur-alist alist)
+ (setq cur-alist alist))
+ ((< cur-level level) ; first child
+ (dotimes (i (- level cur-level 1))
+ (setq alist (list (cons empty-heading alist))))
+ (if cur-alist
+ (let* ((parent (car cur-alist))
+ (self-pos (cdr parent)))
+ (setcdr parent (cons (cons self-heading self-pos) alist)))
+ (setcdr root alist)) ; primogenitor
+ (setq cur-alist alist)
+ (setq cur-level level))
+ (t ; new sibling of an ancestor
+ (let ((sibling-alist (last (cdr root))))
+ (dotimes (i (1- level))
+ (setq sibling-alist (last (cdar sibling-alist))))
+ (setcdr sibling-alist alist)
+ (setq cur-alist alist))
+ (setq cur-level level)))))
+ (cdr root))))
+
+\f
+;;; References ================================================================
+
+(defun markdown-insert-reference-definition (ref &optional buffer)
+ "Add blank REF definition to the end of BUFFER.
+REF is a Markdown reference in square brackets, like \"[lisp-history]\"."
+ (or buffer (setq buffer (current-buffer)))
+ (with-current-buffer buffer
+ (goto-char (point-max))
+ (indent-new-comment-line)
+ (insert (concat ref ": "))))
+
+(defun markdown-reference-goto-definition ()
+ "Jump to the definition of the reference at point or create it."
+ (interactive)
+ (when (thing-at-point-looking-at markdown-regex-link-reference)
+ (let* ((text (match-string-no-properties 2))
+ (reference (match-string-no-properties 4))
+ (target (downcase (if (string= reference "[]") text reference)))
+ (loc (cadr (markdown-reference-definition target))))
+ (if loc
+ (goto-char loc)
+ (markdown-insert-reference-definition target (current-buffer))))))
+
+(defun markdown-reference-find-links (reference)
+ "Return a list of all links for REFERENCE.
+REFERENCE should include the surrounding square brackets like [this].
+Elements of the list have the form (text start line), where
+text is the link text, start is the location at the beginning of
+the link, and line is the line number on which the link appears."
+ (let* ((ref-quote (regexp-quote (substring reference 1 -1)))
+ (regexp (format "!?\\(?:\\[\\(%s\\)\\][ ]?\\[\\]\\|\\[\\([^]]+?\\)\\][ ]?\\[%s\\]\\)"
+ ref-quote ref-quote))
+ links)
+ (save-excursion
+ (goto-char (point-min))
+ (while (re-search-forward regexp nil t)
+ (let* ((text (or (match-string-no-properties 1)
+ (match-string-no-properties 2)))
+ (start (match-beginning 0))
+ (line (markdown-line-number-at-pos)))
+ (add-to-list 'links (list text start line)))))
+ links))
+
+(defun markdown-get-undefined-refs ()
+ "Return a list of undefined Markdown references.
+Result is an alist of pairs (reference . occurrences), where
+occurrences is itself another alist of pairs (label . line-number).
+For example, an alist corresponding to [Nice editor][Emacs] at line 12,
+\[GNU Emacs][Emacs] at line 45 and [manual][elisp] at line 127 is
+\((\"[emacs]\" (\"[Nice editor]\" . 12) (\"[GNU Emacs]\" . 45)) (\"[elisp]\" (\"[manual]\" . 127)))."
+ (let ((missing))
+ (save-excursion
+ (goto-char (point-min))
+ (while
+ (re-search-forward markdown-regex-link-reference nil t)
+ (let* ((text (match-string-no-properties 2))
+ (reference (match-string-no-properties 4))
+ (target (downcase (if (string= reference "[]") text reference))))
+ (unless (markdown-reference-definition target)
+ (let ((entry (assoc target missing)))
+ (if (not entry)
+ (add-to-list 'missing (cons target
+ (list (cons text (markdown-line-number-at-pos)))) t)
+ (setcdr entry
+ (append (cdr entry) (list (cons text (markdown-line-number-at-pos))))))))))
+ missing)))
+
+(defconst markdown-reference-check-buffer
+ "*Undefined references for %buffer%*"
+ "Pattern for name of buffer for listing undefined references.
+The string %buffer% will be replaced by the corresponding
+`markdown-mode' buffer name.")
+
+(defun markdown-reference-check-buffer (&optional buffer-name)
+ "Name and return buffer for reference checking.
+BUFFER-NAME is the name of the main buffer being visited."
+ (or buffer-name (setq buffer-name (buffer-name)))
+ (let ((refbuf (get-buffer-create (markdown-replace-regexp-in-string
+ "%buffer%" buffer-name
+ markdown-reference-check-buffer))))
+ (with-current-buffer refbuf
+ (when view-mode
+ (View-exit-and-edit))
+ (use-local-map button-buffer-map)
+ (erase-buffer))
+ refbuf))
+
+(defconst markdown-reference-links-buffer
+ "*Reference links for %buffer%*"
+ "Pattern for name of buffer for listing references.
+The string %buffer% will be replaced by the corresponding buffer name.")
+
+(defun markdown-reference-links-buffer (&optional buffer-name)
+ "Name, setup, and return a buffer for listing links.
+BUFFER-NAME is the name of the main buffer being visited."
+ (or buffer-name (setq buffer-name (buffer-name)))
+ (let ((linkbuf (get-buffer-create (markdown-replace-regexp-in-string
+ "%buffer%" buffer-name
+ markdown-reference-links-buffer))))
+ (with-current-buffer linkbuf
+ (when view-mode
+ (View-exit-and-edit))
+ (use-local-map button-buffer-map)
+ (erase-buffer))
+ linkbuf))
+
+(when (markdown-use-buttons-p)
+ ;; Add an empty Markdown reference definition to the end of buffer
+ ;; specified in the 'target-buffer property. The reference name is
+ ;; the button's label.
+ (define-button-type 'markdown-undefined-reference-button
+ 'help-echo "mouse-1, RET: create definition for undefined reference"
+ 'follow-link t
+ 'face 'bold
+ 'action (lambda (b)
+ (let ((buffer (button-get b 'target-buffer)))
+ (markdown-insert-reference-definition (button-label b) buffer)
+ (switch-to-buffer-other-window buffer)
+ (goto-char (point-max))
+ (markdown-check-refs t))))
+
+ ;; Jump to line in buffer specified by 'target-buffer property.
+ ;; Line number is button's 'line property.
+ (define-button-type 'markdown-goto-line-button
+ 'help-echo "mouse-1, RET: go to line"
+ 'follow-link t
+ 'face 'italic
+ 'action (lambda (b)
+ (message (button-get b 'buffer))
+ (switch-to-buffer-other-window (button-get b 'target-buffer))
+ ;; use call-interactively to silence compiler
+ (let ((current-prefix-arg (button-get b 'target-line)))
+ (call-interactively 'goto-line))))
+
+ ;; Jumps to a particular link at location given by 'target-char
+ ;; property in buffer given by 'target-buffer property.
+ (define-button-type 'markdown-link-button
+ 'help-echo "mouse-1, RET: jump to location of link"
+ 'follow-link t
+ 'face 'bold
+ 'action (lambda (b)
+ (let ((target (button-get b 'target-buffer))
+ (loc (button-get b 'target-char)))
+ (kill-buffer-and-window)
+ (switch-to-buffer target)
+ (goto-char loc)))))
+
+(defun markdown-insert-undefined-reference-button (reference oldbuf)
+ "Insert a button for creating REFERENCE in buffer OLDBUF.
+REFERENCE should be a list of the form (reference . occurrences),
+as by `markdown-get-undefined-refs'."
+ (let ((label (car reference)))
+ (if (markdown-use-buttons-p)
+ ;; Create a reference button in Emacs 22
+ (insert-button label
+ :type 'markdown-undefined-reference-button
+ 'target-buffer oldbuf)
+ ;; Insert reference as text in Emacs < 22
+ (insert label))
+ (insert " (")
+ (dolist (occurrence (cdr reference))
+ (let ((line (cdr occurrence)))
+ (if (markdown-use-buttons-p)
+ ;; Create a line number button in Emacs 22
+ (insert-button (number-to-string line)
+ :type 'markdown-goto-line-button
+ 'target-buffer oldbuf
+ 'target-line line)
+ ;; Insert line number as text in Emacs < 22
+ (insert (number-to-string line)))
+ (insert " ")))
+ (delete-char -1)
+ (insert ")")
+ (newline)))
+
+(defun markdown-insert-link-button (link oldbuf)
+ "Insert a button for jumping to LINK in buffer OLDBUF.
+LINK should be a list of the form (text char line) containing
+the link text, location, and line number."
+ (let ((label (first link))
+ (char (second link))
+ (line (third link)))
+ (if (markdown-use-buttons-p)
+ ;; Create a reference button in Emacs 22
+ (insert-button label
+ :type 'markdown-link-button
+ 'target-buffer oldbuf
+ 'target-char char)
+ ;; Insert reference as text in Emacs < 22
+ (insert label))
+ (insert (format " (line %d)\n" line))))
+
+(defun markdown-reference-goto-link (&optional reference)
+ "Jump to the location of the first use of REFERENCE."
+ (interactive)
+ (unless reference
+ (if (thing-at-point-looking-at markdown-regex-reference-definition)
+ (setq reference (match-string-no-properties 1))
+ (error "No reference definition at point")))
+ (let ((links (markdown-reference-find-links reference)))
+ (cond ((= (length links) 1)
+ (goto-char (cadr (car links))))
+ ((> (length links) 1)
+ (let ((oldbuf (current-buffer))
+ (linkbuf (markdown-reference-links-buffer)))
+ (with-current-buffer linkbuf
+ (insert "Links using reference " reference ":\n\n")
+ (dolist (link (reverse links))
+ (markdown-insert-link-button link oldbuf)))
+ (view-buffer-other-window linkbuf)
+ (goto-char (point-min))
+ (forward-line 2)))
+ (t
+ (error "No links for reference %s" reference)))))
+
+(defun markdown-check-refs (&optional silent)
+ "Show all undefined Markdown references in current `markdown-mode' buffer.
+If SILENT is non-nil, do not message anything when no undefined
+references found.
+Links which have empty reference definitions are considered to be
+defined."
+ (interactive "P")
+ (when (not (eq major-mode 'markdown-mode))
+ (error "Not available in current mode"))
+ (let ((oldbuf (current-buffer))
+ (refs (markdown-get-undefined-refs))
+ (refbuf (markdown-reference-check-buffer)))
+ (if (null refs)
+ (progn
+ (when (not silent)
+ (message "No undefined references found"))
+ (kill-buffer refbuf))
+ (with-current-buffer refbuf
+ (insert "The following references are undefined:\n\n")
+ (dolist (ref refs)
+ (markdown-insert-undefined-reference-button ref oldbuf))
+ (view-buffer-other-window refbuf)
+ (goto-char (point-min))
+ (forward-line 2)))))
+
+\f
+;;; Lists =====================================================================
+
+(defun markdown-insert-list-item (&optional arg)
+ "Insert a new list item.
+If the point is inside unordered list, insert a bullet mark. If
+the point is inside ordered list, insert the next number followed
+by a period. Use the previous list item to determine the amount
+of whitespace to place before and after list markers.
+
+With a \\[universal-argument] prefix (i.e., when ARG is (4)),
+decrease the indentation by one level.
+
+With two \\[universal-argument] prefixes (i.e., when ARG is (16)),
+increase the indentation by one level."
+ (interactive "p")
+ (let (bounds item-indent marker indent new-indent new-loc)
+ (save-match-data
+ ;; Look for a list item on current or previous non-blank line
+ (save-excursion
+ (while (and (not (setq bounds (markdown-cur-list-item-bounds)))
+ (not (bobp))
+ (markdown-cur-line-blank-p))
+ (forward-line -1)))
+ (when bounds
+ (cond ((save-excursion
+ (skip-chars-backward " \t")
+ (looking-at markdown-regex-list))
+ (beginning-of-line)
+ (insert "\n")
+ (forward-line -1))
+ ((not (markdown-cur-line-blank-p))
+ (newline)))
+ (setq new-loc (point)))
+ ;; Look ahead for a list item on next non-blank line
+ (unless bounds
+ (save-excursion
+ (while (and (null bounds)
+ (not (eobp))
+ (markdown-cur-line-blank-p))
+ (forward-line)
+ (setq bounds (markdown-cur-list-item-bounds))))
+ (when bounds
+ (setq new-loc (point))
+ (unless (markdown-cur-line-blank-p)
+ (newline))))
+ (if (not bounds)
+ ;; When not in a list, start a new unordered one
+ (progn
+ (unless (markdown-cur-line-blank-p)
+ (insert "\n"))
+ (insert markdown-unordered-list-item-prefix))
+ ;; Compute indentation for a new list item
+ (setq item-indent (nth 2 bounds))
+ (setq marker (nth 4 bounds))
+ (setq indent (cond
+ ((= arg 4) (max (- item-indent 4) 0))
+ ((= arg 16) (+ item-indent 4))
+ (t item-indent)))
+ (setq new-indent (make-string indent 32))
+ (goto-char new-loc)
+ (cond
+ ;; Ordered list
+ ((string-match "[0-9]" marker)
+ (if (= arg 16) ;; starting a new column indented one more level
+ (insert (concat new-indent "1. "))
+ ;; travel up to the last item and pick the correct number. If
+ ;; the argument was nil, "new-indent = item-indent" is the same,
+ ;; so we don't need special treatment. Neat.
+ (save-excursion
+ (while (and (not (looking-at (concat new-indent "\\([0-9]+\\)\\(\\.[ \t]*\\)")))
+ (>= (forward-line -1) 0))))
+ (let* ((old-prefix (match-string 1))
+ (old-spacing (match-string 2))
+ (new-prefix (if old-prefix
+ (int-to-string (1+ (string-to-number old-prefix)))
+ "1"))
+ (space-adjust (- (length old-prefix) (length new-prefix)))
+ (new-spacing (if (and (match-string 2)
+ (not (string-match "\t" old-spacing))
+ (< space-adjust 0)
+ (> space-adjust (- 1 (length (match-string 2)))))
+ (substring (match-string 2) 0 space-adjust)
+ (or old-spacing ". "))))
+ (insert (concat new-indent new-prefix new-spacing)))))
+ ;; Unordered list
+ ((string-match "[\\*\\+-]" marker)
+ (insert new-indent marker)))))))
+
+(defun markdown-move-list-item-up ()
+ "Move the current list item up in the list when possible."
+ (interactive)
+ (let (cur prev old)
+ (when (setq cur (markdown-cur-list-item-bounds))
+ (setq old (point))
+ (goto-char (nth 0 cur))
+ (if (markdown-prev-list-item (nth 3 cur))
+ (progn
+ (setq prev (markdown-cur-list-item-bounds))
+ (condition-case nil
+ (progn
+ (transpose-regions (nth 0 prev) (nth 1 prev)
+ (nth 0 cur) (nth 1 cur) t)
+ (goto-char (+ (nth 0 prev) (- old (nth 0 cur)))))
+ ;; Catch error in case regions overlap.
+ (error (goto-char old))))
+ (goto-char old)))))
+
+(defun markdown-move-list-item-down ()
+ "Move the current list item down in the list when possible."
+ (interactive)
+ (let (cur next old)
+ (when (setq cur (markdown-cur-list-item-bounds))
+ (setq old (point))
+ (if (markdown-next-list-item (nth 3 cur))
+ (progn
+ (setq next (markdown-cur-list-item-bounds))
+ (condition-case nil
+ (progn
+ (transpose-regions (nth 0 cur) (nth 1 cur)
+ (nth 0 next) (nth 1 next) nil)
+ (goto-char (+ old (- (nth 1 next) (nth 1 cur)))))
+ ;; Catch error in case regions overlap.
+ (error (goto-char old))))
+ (goto-char old)))))
+
+(defun markdown-demote-list-item (&optional bounds)
+ "Indent (or demote) the current list item.
+Optionally, BOUNDS of the current list item may be provided if available."
+ (interactive)
+ (when (or bounds (setq bounds (markdown-cur-list-item-bounds)))
+ (save-excursion
+ (save-match-data
+ (let* ((end-marker (make-marker))
+ (end-marker (set-marker end-marker (nth 1 bounds))))
+ (goto-char (nth 0 bounds))
+ (while (< (point) end-marker)
+ (unless (markdown-cur-line-blank-p)
+ (insert " "))
+ (forward-line)))))))
+
+(defun markdown-promote-list-item (&optional bounds)
+ "Unindent (or promote) the current list item.
+Optionally, BOUNDS of the current list item may be provided if available."
+ (interactive)
+ (when (or bounds (setq bounds (markdown-cur-list-item-bounds)))
+ (save-excursion
+ (save-match-data
+ (let* ((end-marker (make-marker))
+ (end-marker (set-marker end-marker (nth 1 bounds)))
+ num regexp)
+ (goto-char (nth 0 bounds))
+ (when (looking-at "^[ ]\\{1,4\\}")
+ (setq num (- (match-end 0) (match-beginning 0)))
+ (setq regexp (format "^[ ]\\{1,%d\\}" num))
+ (while (and (< (point) end-marker)
+ (re-search-forward regexp end-marker t))
+ (replace-match "" nil nil)
+ (forward-line))))))))
+
+(defun markdown-cleanup-list-numbers-level (&optional pfx)
+ "Update the numbering for level PFX (as a string of spaces).
+
+Assume that the previously found match was for a numbered item in
+a list."
+ (let ((cpfx pfx)
+ (idx 0)
+ (continue t)
+ (step t)
+ (sep nil))
+ (while (and continue (not (eobp)))
+ (setq step t)
+ (cond
+ ((looking-at "^\\([\s-]*\\)[0-9]+\\. ")
+ (setq cpfx (match-string-no-properties 1))
+ (cond
+ ((string= cpfx pfx)
+ (replace-match
+ (concat pfx (number-to-string (setq idx (1+ idx))) ". "))
+ (setq sep nil))
+ ;; indented a level
+ ((string< pfx cpfx)
+ (setq sep (markdown-cleanup-list-numbers-level cpfx))
+ (setq step nil))
+ ;; exit the loop
+ (t
+ (setq step nil)
+ (setq continue nil))))
+
+ ((looking-at "^\\([\s-]*\\)[^ \t\n\r].*$")
+ (setq cpfx (match-string-no-properties 1))
+ (cond
+ ;; reset if separated before
+ ((string= cpfx pfx) (when sep (setq idx 0)))
+ ((string< cpfx pfx)
+ (setq step nil)
+ (setq continue nil))))
+ (t (setq sep t)))
+
+ (when step
+ (beginning-of-line)
+ (setq continue (= (forward-line) 0))))
+ sep))
+
+(defun markdown-cleanup-list-numbers ()
+ "Update the numbering of ordered lists."
+ (interactive)
+ (save-excursion
+ (goto-char (point-min))
+ (markdown-cleanup-list-numbers-level "")))
+
+\f
+;;; Outline ===================================================================
+
+(defvar markdown-cycle-global-status 1)
+(defvar markdown-cycle-subtree-status nil)
+
+(defun markdown-end-of-subtree (&optional invisible-OK)
+ "Move to the end of the current subtree.
+Only visible heading lines are considered, unless INVISIBLE-OK is
+non-nil.
+Derived from `org-end-of-subtree'."
+ (outline-back-to-heading invisible-OK)
+ (let ((first t)
+ (level (funcall outline-level)))
+ (while (and (not (eobp))
+ (or first (> (funcall outline-level) level)))
+ (setq first nil)
+ (outline-next-heading))
+ (if (memq (preceding-char) '(?\n ?\^M))
+ (progn
+ ;; Go to end of line before heading
+ (forward-char -1)
+ (if (memq (preceding-char) '(?\n ?\^M))
+ ;; leave blank line before heading
+ (forward-char -1)))))
+ (point))
+
+(defun markdown-cycle (&optional arg)
+ "Visibility cycling for Markdown mode.
+If ARG is t, perform global visibility cycling. If the point is
+at an atx-style header, cycle visibility of the corresponding
+subtree. Otherwise, insert a tab using `indent-relative'.
+Derived from `org-cycle'."
+ (interactive "P")
+ (cond
+ ((eq arg t) ;; Global cycling
+ (cond
+ ((and (eq last-command this-command)
+ (eq markdown-cycle-global-status 2))
+ ;; Move from overview to contents
+ (hide-sublevels 1)
+ (message "CONTENTS")
+ (setq markdown-cycle-global-status 3))
+
+ ((and (eq last-command this-command)
+ (eq markdown-cycle-global-status 3))
+ ;; Move from contents to all
+ (show-all)
+ (message "SHOW ALL")
+ (setq markdown-cycle-global-status 1))
+
+ (t
+ ;; Defaults to overview
+ (hide-body)
+ (message "OVERVIEW")
+ (setq markdown-cycle-global-status 2))))
+
+ ((save-excursion (beginning-of-line 1) (looking-at outline-regexp))
+ ;; At a heading: rotate between three different views
+ (outline-back-to-heading)
+ (let ((goal-column 0) eoh eol eos)
+ ;; Determine boundaries
+ (save-excursion
+ (outline-back-to-heading)
+ (save-excursion
+ (beginning-of-line 2)
+ (while (and (not (eobp)) ;; this is like `next-line'
+ (get-char-property (1- (point)) 'invisible))
+ (beginning-of-line 2)) (setq eol (point)))
+ (outline-end-of-heading) (setq eoh (point))
+ (markdown-end-of-subtree t)
+ (skip-chars-forward " \t\n")
+ (beginning-of-line 1) ; in case this is an item
+ (setq eos (1- (point))))
+ ;; Find out what to do next and set `this-command'
+ (cond
+ ((= eos eoh)
+ ;; Nothing is hidden behind this heading
+ (message "EMPTY ENTRY")
+ (setq markdown-cycle-subtree-status nil))
+ ((>= eol eos)
+ ;; Entire subtree is hidden in one line: open it
+ (show-entry)
+ (show-children)
+ (message "CHILDREN")
+ (setq markdown-cycle-subtree-status 'children))
+ ((and (eq last-command this-command)
+ (eq markdown-cycle-subtree-status 'children))
+ ;; We just showed the children, now show everything.
+ (show-subtree)
+ (message "SUBTREE")
+ (setq markdown-cycle-subtree-status 'subtree))
+ (t
+ ;; Default action: hide the subtree.
+ (hide-subtree)
+ (message "FOLDED")
+ (setq markdown-cycle-subtree-status 'folded)))))
+
+ (t
+ (indent-for-tab-command))))
+
+(defun markdown-shifttab ()
+ "Global visibility cycling.
+Calls `markdown-cycle' with argument t."
+ (interactive)
+ (markdown-cycle t))
+
+(defun markdown-outline-level ()
+ "Return the depth to which a statement is nested in the outline."
+ (cond
+ ((match-end 1) 1)
+ ((match-end 3) 2)
+ ((- (match-end 5) (match-beginning 5)))))
+
+\f
+;;; Movement ==================================================================
+
+(defun markdown-beginning-of-defun (&optional arg)
+ "`beginning-of-defun-function' for Markdown.
+Move backward to the beginning of the current or previous section.
+When ARG is non-nil, repeat that many times. When ARG is negative,
+move forward to the ARG-th following section."
+ (interactive "P")
+ (or arg (setq arg 1))
+ (or (re-search-backward markdown-regex-header nil t arg)
+ (goto-char (point-min))))
+
+(defun markdown-end-of-defun (&optional arg)
+ "`end-of-defun-function' for Markdown.
+Move forward to the end of the current or following section.
+When ARG is non-nil, repeat that many times. When ARG is negative,
+move back to the ARG-th preceding section."
+ (interactive "P")
+ (or arg (setq arg 1))
+ (when (looking-at markdown-regex-header)
+ (goto-char (match-beginning 0))
+ (forward-char 1))
+ (if (re-search-forward markdown-regex-header nil t arg)
+ (goto-char (match-beginning 0))
+ (goto-char (point-max)))
+ (skip-syntax-backward "-"))
+
+(defun markdown-beginning-of-block ()
+ "Move the point to the start of the previous text block."
+ (interactive)
+ (if (re-search-backward markdown-regex-block-separator nil t)
+ (goto-char (or (match-end 2) (match-end 0)))
+ (goto-char (point-min))))
+
+(defun markdown-end-of-block ()
+ "Move the point to the start of the next text block."
+ (interactive)
+ (beginning-of-line)
+ (skip-syntax-forward "-")
+ (when (= (point) (point-min))
+ (forward-char))
+ (if (re-search-forward markdown-regex-block-separator nil t)
+ (goto-char (or (match-end 2) (match-end 0)))
+ (goto-char (point-max)))
+ (skip-syntax-backward "-")
+ (forward-line))
+
+(defun markdown-forward-paragraph (arg)
+ "Move forward one or more paragraphs or by one block.
+When ARG is nil or a numeric prefix, call `forward-paragraph'
+with ARG. When called with \\[universal-argument], call
+`markdown-end-of-block' instead."
+ (interactive "P")
+ (or arg (setq arg 1))
+ (cond ((integerp arg)
+ (forward-paragraph arg))
+ ((equal arg '(4))
+ (markdown-end-of-block))))
+
+(defun markdown-backward-paragraph (arg)
+ "Move backward one or more paragraphs or by one block.
+When ARG is nil or a numeric prefix, call `backward-paragraph'
+with ARG. When called with \\[universal-argument], call
+`markdown-beginning-of-block' instead."
+ (interactive "P")
+ (or arg (setq arg 1))
+ (cond ((integerp arg)
+ (backward-paragraph arg))
+ ((equal arg '(4))
+ (markdown-beginning-of-block))))
+
+(defun markdown-end-of-block-element ()
+ "Move the point to the start of the next block unit.
+Stops at blank lines, list items, headers, and horizontal rules."
+ (interactive)
+ (forward-line)
+ (while (and (or (not (markdown-prev-line-blank-p))
+ (markdown-cur-line-blank-p))
+ (not (or (looking-at markdown-regex-list)
+ (looking-at markdown-regex-header)
+ (looking-at markdown-regex-hr)))
+ (not (eobp)))
+ (forward-line)))
+
+(defun markdown-next-link ()
+ "Jump to next inline, reference, or wiki link.
+If successful, return point. Otherwise, return nil.
+See `markdown-wiki-link-p' and `markdown-previous-wiki-link'."
+ (interactive)
+ (let ((opoint (point)))
+ (when (or (markdown-link-p) (markdown-wiki-link-p))
+ ;; At a link already, move past it.
+ (goto-char (+ (match-end 0) 1)))
+ ;; Search for the next wiki link and move to the beginning.
+ (if (re-search-forward markdown-regex-link-generic nil t)
+ ;; Group 1 will move past non-escape character in wiki link regexp.
+ ;; Go to beginning of group zero for all other link types.
+ (goto-char (or (match-beginning 1) (match-beginning 0)))
+ (goto-char opoint)
+ nil)))
+
+(defun markdown-previous-link ()
+ "Jump to previous wiki link.
+If successful, return point. Otherwise, return nil.
+See `markdown-wiki-link-p' and `markdown-next-wiki-link'."
+ (interactive)
+ (if (re-search-backward markdown-regex-link-generic nil t)
+ (goto-char (or (match-beginning 1) (match-beginning 0)))
+ nil))
+
+\f
+;;; Generic Structure Editing, Completion, and Cycling Commands ===============
+
+(defun markdown-move-up ()
+ "Move list item up.
+Calls `markdown-move-list-item-up'."
+ (interactive)
+ (markdown-move-list-item-up))
+
+(defun markdown-move-down ()
+ "Move list item down.
+Calls `markdown-move-list-item-down'."
+ (interactive)
+ (markdown-move-list-item-down))
+
+(defun markdown-promote ()
+ "Either promote header or list item at point or cycle markup.
+See `markdown-cycle-atx', `markdown-cycle-setext', and
+`markdown-promote-list-item'."
+ (interactive)
+ (let (bounds)
+ (cond
+ ;; Promote atx header
+ ((thing-at-point-looking-at markdown-regex-header-atx)
+ (markdown-cycle-atx -1))
+ ;; Promote setext header
+ ((thing-at-point-looking-at markdown-regex-header-setext)
+ (markdown-cycle-setext -1))
+ ;; Promote horizonal rule
+ ((thing-at-point-looking-at markdown-regex-hr)
+ (markdown-cycle-hr -1))
+ ;; Promote list item
+ ((setq bounds (markdown-cur-list-item-bounds))
+ (markdown-promote-list-item))
+ ;; Promote bold
+ ((thing-at-point-looking-at markdown-regex-bold)
+ (markdown-cycle-bold))
+ ;; Promote italic
+ ((thing-at-point-looking-at markdown-regex-italic)
+ (markdown-cycle-italic))
+ (t
+ (error "Nothing to promote at point")))))
+
+(defun markdown-demote ()
+ "Either demote header or list item at point or cycle or remove markup.
+See `markdown-cycle-atx', `markdown-cycle-setext', and
+`markdown-demote-list-item'."
+ (interactive)
+ (let (bounds)
+ (cond
+ ;; Demote atx header
+ ((thing-at-point-looking-at markdown-regex-header-atx)
+ (markdown-cycle-atx 1))
+ ;; Demote setext header
+ ((thing-at-point-looking-at markdown-regex-header-setext)
+ (markdown-cycle-setext 1))
+ ;; Demote horizonal rule
+ ((thing-at-point-looking-at markdown-regex-hr)
+ (markdown-cycle-hr 1))
+ ;; Demote list item
+ ((setq bounds (markdown-cur-list-item-bounds))
+ (markdown-demote-list-item))
+ ;; Demote bold
+ ((thing-at-point-looking-at markdown-regex-bold)
+ (markdown-cycle-bold))
+ ;; Demote italic
+ ((thing-at-point-looking-at markdown-regex-italic)
+ (markdown-cycle-italic))
+ (t
+ (error "Nothing to demote at point")))))
+
+\f
+;;; Commands ==================================================================
+
+(defun markdown (&optional output-buffer-name)
+ "Run `markdown-command' on buffer, sending output to OUTPUT-BUFFER-NAME.
+The output buffer name defaults to `markdown-output-buffer-name'.
+Return the name of the output buffer used."
+ (interactive)
+ (save-window-excursion
+ (let ((begin-region)
+ (end-region))
+ (if (markdown-use-region-p)
+ (setq begin-region (region-beginning)
+ end-region (region-end))
+ (setq begin-region (point-min)
+ end-region (point-max)))
+
+ (unless output-buffer-name
+ (setq output-buffer-name markdown-output-buffer-name))
+
+ (cond
+ ;; Handle case when `markdown-command' does not read from stdin
+ (markdown-command-needs-filename
+ (if (not buffer-file-name)
+ (error "Must be visiting a file")
+ (shell-command (concat markdown-command " "
+ (shell-quote-argument buffer-file-name))
+ output-buffer-name)))
+ ;; Pass region to `markdown-command' via stdin
+ (t
+ (shell-command-on-region begin-region end-region markdown-command
+ output-buffer-name))))
+ output-buffer-name))
+
+(defun markdown-standalone (&optional output-buffer-name)
+ "Special function to provide standalone HTML output.
+Insert the output in the buffer named OUTPUT-BUFFER-NAME."
+ (interactive)
+ (setq output-buffer-name (markdown output-buffer-name))
+ (with-current-buffer output-buffer-name
+ (set-buffer output-buffer-name)
+ (unless (markdown-output-standalone-p)
+ (markdown-add-xhtml-header-and-footer output-buffer-name))
+ (goto-char (point-min))
+ (html-mode))
+ output-buffer-name)
+
+(defun markdown-other-window (&optional output-buffer-name)
+ "Run `markdown-command' on current buffer and display in other window.
+When OUTPUT-BUFFER-NAME is given, insert the output in the buffer with
+that name."
+ (interactive)
+ (display-buffer (markdown-standalone output-buffer-name)))
+
+(defun markdown-output-standalone-p ()
+ "Determine whether `markdown-command' output is standalone XHTML.
+Standalone XHTML output is identified by an occurrence of
+`markdown-xhtml-standalone-regexp' in the first five lines of output."
+ (save-excursion
+ (goto-char (point-min))
+ (re-search-forward
+ markdown-xhtml-standalone-regexp
+ (save-excursion (goto-char (point-min)) (forward-line 4) (point))
+ t)))
+
+(defun markdown-stylesheet-link-string (stylesheet-path)
+ (concat "<link rel=\"stylesheet\" type=\"text/css\" media=\"all\" href=\""
+ stylesheet-path
+ "\" />"))
+
+(defun markdown-add-xhtml-header-and-footer (title)
+ "Wrap XHTML header and footer with given TITLE around current buffer."
+ (insert "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n"
+ "\t\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n\n"
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n\n"
+ "<head>\n<title>")
+ (insert title)
+ (insert "</title>\n")
+ (when (> (length markdown-content-type) 0)
+ (insert
+ (format
+ "<meta http-equiv=\"Content-Type\" content=\"%s;charset=%s\"/>\n"
+ markdown-content-type
+ (or (and markdown-coding-system
+ (fboundp 'coding-system-get)
+ (coding-system-get markdown-coding-system
+ 'mime-charset))
+ (and (fboundp 'coding-system-get)
+ (coding-system-get buffer-file-coding-system
+ 'mime-charset))
+ "iso-8859-1"))))
+ (if (> (length markdown-css-paths) 0)
+ (insert (mapconcat 'markdown-stylesheet-link-string markdown-css-paths "\n")))
+ (when (> (length markdown-xhtml-header-content) 0)
+ (insert markdown-xhtml-header-content))
+ (insert "\n</head>\n\n"
+ "<body>\n\n")
+ (goto-char (point-max))
+ (insert "\n"
+ "</body>\n"
+ "</html>\n"))
+
+(defun markdown-preview (&optional output-buffer-name)
+ "Run `markdown-command' on the current buffer and view output in browser.
+When OUTPUT-BUFFER-NAME is given, insert the output in the buffer with
+that name."
+ (interactive)
+ (browse-url-of-buffer (markdown-standalone markdown-output-buffer-name)))
+
+(defun markdown-export-file-name (&optional extension)
+ "Attempt to generate a filename for Markdown output.
+The file extension will be EXTENSION if given, or .html by default.
+If the current buffer is visiting a file, we construct a new
+output filename based on that filename. Otherwise, return nil."
+ (when (buffer-file-name)
+ (unless extension
+ (setq extension ".html"))
+ (let ((candidate
+ (concat
+ (cond
+ ((buffer-file-name)
+ (file-name-sans-extension (buffer-file-name)))
+ (t (buffer-name)))
+ extension)))
+ (cond
+ ((equal candidate (buffer-file-name))
+ (concat candidate extension))
+ (t
+ candidate)))))
+
+(defun markdown-export (&optional output-file)
+ "Run Markdown on the current buffer, save to file, and return the filename.
+If OUTPUT-FILE is given, use that as the filename. Otherwise, use the filename
+generated by `markdown-export-file-name', which will be constructed using the
+current filename, but with the extension removed and replaced with .html."
+ (interactive)
+ (unless output-file
+ (setq output-file (markdown-export-file-name ".html")))
+ (when output-file
+ (let* ((init-buf (current-buffer))
+ (init-point (point))
+ (init-buf-string (buffer-string))
+ (output-buffer (find-file-noselect output-file))
+ (output-buffer-name (buffer-name output-buffer)))
+ (run-hooks 'markdown-before-export-hook)
+ (markdown-standalone output-buffer-name)
+ (with-current-buffer output-buffer
+ (run-hooks 'markdown-after-export-hook)
+ (save-buffer))
+ ;; if modified, restore initial buffer
+ (when (buffer-modified-p init-buf)
+ (erase-buffer)
+ (insert init-buf-string)
+ (save-buffer)
+ (goto-char init-point))
+ output-file)))
+
+(defun markdown-export-and-preview ()
+ "Export to XHTML using `markdown-export' and browse the resulting file."
+ (interactive)
+ (browse-url-of-file (markdown-export)))
+
+(defun markdown-open ()
+ "Open file for the current buffer with `markdown-open-command'."
+ (interactive)
+ (if (not markdown-open-command)
+ (error "Variable `markdown-open-command' must be set")
+ (if (not buffer-file-name)
+ (error "Must be visiting a file")
+ (call-process markdown-open-command
+ nil nil nil buffer-file-name))))
+
+(defun markdown-kill-ring-save ()
+ "Run Markdown on file and store output in the kill ring."
+ (interactive)
+ (save-window-excursion
+ (markdown)
+ (with-current-buffer markdown-output-buffer-name
+ (kill-ring-save (point-min) (point-max)))))
+
+\f
+;;; Links =====================================================================
+
+(defun markdown-link-p ()
+ "Return non-nil when `point' is at a non-wiki link.
+See `markdown-wiki-link-p' for more information."
+ (let ((case-fold-search nil))
+ (and (not (markdown-wiki-link-p))
+ (or (thing-at-point-looking-at markdown-regex-link-inline)
+ (thing-at-point-looking-at markdown-regex-link-reference)
+ (thing-at-point-looking-at markdown-regex-uri)
+ (thing-at-point-looking-at markdown-regex-angle-uri)))))
+
+(defun markdown-link-link ()
+ "Return the link part of the regular (non-wiki) link at point.
+Works with both inline and reference style links. If point is
+not at a link or the link reference is not defined returns nil."
+ (cond
+ ((thing-at-point-looking-at markdown-regex-link-inline)
+ (match-string-no-properties 5))
+ ((thing-at-point-looking-at markdown-regex-link-reference)
+ (let* ((text (match-string-no-properties 2))
+ (reference (match-string-no-properties 4))
+ (target (downcase (if (string= reference "[]") text reference))))
+ (car (markdown-reference-definition target))))
+ ((thing-at-point-looking-at markdown-regex-uri)
+ (match-string-no-properties 0))
+ ((thing-at-point-looking-at markdown-regex-angle-uri)
+ (match-string-no-properties 2))
+ (t nil)))
+
+(defun markdown-follow-link-at-point ()
+ "Open the current non-wiki link in a browser."
+ (interactive)
+ (if (markdown-link-p) (browse-url (markdown-link-link))
+ (error "Point is not at a Markdown link or URI")))
+
+\f
+;;; WikiLink Following/Markup =================================================
+
+(defun markdown-wiki-link-p ()
+ "Return non-nil when `point' is at a true wiki link.
+A true wiki link name matches `markdown-regex-wiki-link' but does not
+match the current file name after conversion. This modifies the data
+returned by `match-data'. Note that the potential wiki link name must
+be available via `match-string'."
+ (let ((case-fold-search nil))
+ (and (thing-at-point-looking-at markdown-regex-wiki-link)
+ (or (not buffer-file-name)
+ (not (string-equal (buffer-file-name)
+ (markdown-convert-wiki-link-to-filename
+ (markdown-wiki-link-link)))))
+ (not (save-match-data
+ (save-excursion))))))
+
+(defun markdown-wiki-link-link ()
+ "Return the link part of the wiki link using current match data.
+The location of the link component depends on the value of
+`markdown-wiki-link-alias-first'."
+ (if markdown-wiki-link-alias-first
+ (or (match-string-no-properties 4) (match-string-no-properties 2))
+ (match-string-no-properties 2)))
+
+(defun markdown-wiki-link-alias ()
+ "Return the alias or text part of the wiki link using current match data.
+The location of the alias component depends on the value of
+`markdown-wiki-link-alias-first'."
+ (if markdown-wiki-link-alias-first
+ (match-string-no-properties 2)
+ (or (match-string-no-properties 4) (match-string-no-properties 2))))
+
+(defun markdown-convert-wiki-link-to-filename (name)
+ "Generate a filename from the wiki link NAME.
+Spaces in NAME are replaced with `markdown-link-space-sub-char'.
+When in `gfm-mode', follow GitHub's conventions where [[Test Test]]
+and [[test test]] both map to Test-test.ext."
+ (let ((basename (markdown-replace-regexp-in-string
+ "[[:space:]\n]" markdown-link-space-sub-char name)))
+ (when (eq major-mode 'gfm-mode)
+ (setq basename (concat (upcase (substring basename 0 1))
+ (downcase (substring basename 1 nil)))))
+ (concat basename
+ (if (buffer-file-name)
+ (concat "."
+ (file-name-extension (buffer-file-name)))))))
+
+(defun markdown-follow-wiki-link (name &optional other)
+ "Follow the wiki link NAME.
+Convert the name to a file name and call `find-file'. Ensure that
+the new buffer remains in `markdown-mode'. Open the link in another
+window when OTHER is non-nil."
+ (let ((filename (markdown-convert-wiki-link-to-filename name))
+ (wp (file-name-directory buffer-file-name)))
+ (when other (other-window 1))
+ (find-file (concat wp filename)))
+ (when (not (eq major-mode 'markdown-mode))
+ (markdown-mode)))
+
+(defun markdown-follow-wiki-link-at-point (&optional arg)
+ "Find Wiki Link at point.
+With prefix argument ARG, open the file in other window.
+See `markdown-wiki-link-p' and `markdown-follow-wiki-link'."
+ (interactive "P")
+ (if (markdown-wiki-link-p)
+ (markdown-follow-wiki-link (markdown-wiki-link-link) arg)
+ (error "Point is not at a Wiki Link")))
+
+(defun markdown-highlight-wiki-link (from to face)
+ "Highlight the wiki link in the region between FROM and TO using FACE."
+ (put-text-property from to 'font-lock-face face))
+
+(defun markdown-unfontify-region-wiki-links (from to)
+ "Remove wiki link faces from the region specified by FROM and TO."
+ (interactive "nfrom: \nnto: ")
+ (remove-text-properties from to '(font-lock-face markdown-link-face))
+ (remove-text-properties from to '(font-lock-face markdown-missing-link-face)))
+
+(defun markdown-fontify-region-wiki-links (from to)
+ "Search region given by FROM and TO for wiki links and fontify them.
+If a wiki link is found check to see if the backing file exists
+and highlight accordingly."
+ (goto-char from)
+ (save-match-data
+ (while (re-search-forward markdown-regex-wiki-link to t)
+ (let ((highlight-beginning (match-beginning 1))
+ (highlight-end (match-end 1))
+ (file-name
+ (markdown-convert-wiki-link-to-filename
+ (markdown-wiki-link-link))))
+ (if (file-exists-p file-name)
+ (markdown-highlight-wiki-link
+ highlight-beginning highlight-end markdown-link-face)
+ (markdown-highlight-wiki-link
+ highlight-beginning highlight-end markdown-link-face)
+ (markdown-highlight-wiki-link
+ highlight-beginning highlight-end markdown-missing-link-face))))))
+
+(defun markdown-extend-changed-region (from to)
+ "Extend region given by FROM and TO so that we can fontify all links.
+The region is extended to the first newline before and the first
+newline after."
+ ;; start looking for the first new line before 'from
+ (goto-char from)
+ (re-search-backward "\n" nil t)
+ (let ((new-from (point-min))
+ (new-to (point-max)))
+ (if (not (= (point) from))
+ (setq new-from (point)))
+ ;; do the same thing for the first new line after 'to
+ (goto-char to)
+ (re-search-forward "\n" nil t)
+ (if (not (= (point) to))
+ (setq new-to (point)))
+ (values new-from new-to)))
+
+(defun markdown-check-change-for-wiki-link (from to change)
+ "Check region between FROM and TO for wiki links and re-fontfy as needed.
+Designed to be used with the `after-change-functions' hook.
+CHANGE is the number of bytes of pre-change text replaced by the
+given range."
+ (interactive "nfrom: \nnto: \nnchange: ")
+ (let* ((modified (buffer-modified-p))
+ (buffer-undo-list t)
+ (inhibit-read-only t)
+ (inhibit-point-motion-hooks t)
+ deactivate-mark
+ buffer-file-truename)
+ (unwind-protect
+ (save-excursion
+ (save-match-data
+ (save-restriction
+ ;; Extend the region to fontify so that it starts
+ ;; and ends at safe places.
+ (multiple-value-bind (new-from new-to)
+ (markdown-extend-changed-region from to)
+ ;; Unfontify existing fontification (start from scratch)
+ (markdown-unfontify-region-wiki-links new-from new-to)
+ ;; Now do the fontification.
+ (markdown-fontify-region-wiki-links new-from new-to)))))
+ (and (not modified)
+ (buffer-modified-p)
+ (set-buffer-modified-p nil)))))
+
+(defun markdown-fontify-buffer-wiki-links ()
+ "Refontify all wiki links in the buffer."
+ (interactive)
+ (markdown-check-change-for-wiki-link (point-min) (point-max) 0))
+
+\f
+;;; Following and Jumping =====================================================
+
+(defun markdown-follow-thing-at-point (arg)
+ "Follow thing at point if possible, such as a reference link or wiki link.
+Opens inline and reference links in a browser. Opens wiki links
+to other files in the current window, or the another window if
+ARG is non-nil.
+See `markdown-follow-link-at-point' and
+`markdown-follow-wiki-link-at-point'."
+ (interactive "P")
+ (cond ((markdown-link-p)
+ (markdown-follow-link-at-point))
+ ((markdown-wiki-link-p)
+ (markdown-follow-wiki-link-at-point arg))
+ (t
+ (error "Nothing to follow at point"))))
+
+(defun markdown-jump ()
+ "Jump to another location based on context at point.
+Jumps between reference links and definitions; between footnote
+markers and footnote text."
+ (interactive)
+ (cond ((markdown-footnote-text-positions)
+ (markdown-footnote-return))
+ ((markdown-footnote-marker-positions)
+ (markdown-footnote-goto-text))
+ ((thing-at-point-looking-at markdown-regex-link-reference)
+ (markdown-reference-goto-definition))
+ ((thing-at-point-looking-at markdown-regex-reference-definition)
+ (markdown-reference-goto-link (match-string-no-properties 1)))
+ (t
+ (error "Nothing to jump to from context at point"))))
+
+\f
+;;; Miscellaneous =============================================================
+
+(defun markdown-compress-whitespace-string (str)
+ "Compress whitespace in STR and return result.
+Leading and trailing whitespace is removed. Sequences of multiple
+spaces, tabs, and newlines are replaced with single spaces."
+ (replace-regexp-in-string "\\(^[ \t\n]+\\|[ \t\n]+$\\)" ""
+ (replace-regexp-in-string "[ \t\n]+" " " str)))
+
+(defun markdown-line-number-at-pos (&optional pos)
+ "Return (narrowed) buffer line number at position POS.
+If POS is nil, use current buffer location.
+This is an exact copy of `line-number-at-pos' for use in emacs21."
+ (let ((opoint (or pos (point))) start)
+ (save-excursion
+ (goto-char (point-min))
+ (setq start (point))
+ (goto-char opoint)
+ (forward-line 0)
+ (1+ (count-lines start (point))))))
+
+(defun markdown-inside-link-text-p ()
+ "Return nil if not currently within link anchor text."
+ (looking-back "\\[[^]]*"))
+
+(defun markdown-line-is-reference-definition-p ()
+ "Return whether the current line is a (non-footnote) reference defition."
+ (save-excursion
+ (move-beginning-of-line 1)
+ (and (looking-at-p markdown-regex-reference-definition)
+ (not (looking-at-p "[ \t]*\\[^")))))
+
+(defun markdown-adaptive-fill-function ()
+ "Return prefix for filling paragraph or nil if not determined."
+ (cond
+ ;; List item inside blockquote
+ ((looking-at "^[ \t]*>[ \t]*\\([0-9]+\\.\\|[*+-]\\)[ \t]+")
+ (replace-regexp-in-string
+ "[0-9\\.*+-]" " " (match-string-no-properties 0)))
+ ;; Blockquote
+ ((looking-at "^[ \t]*>[ \t]*")
+ (match-string-no-properties 0))
+ ;; List items
+ ((looking-at markdown-regex-list)
+ (match-string-no-properties 0))
+ ((looking-at markdown-regex-footnote-definition)
+ " ") ; four spaces
+ ;; No match
+ (t nil)))
+
+(defun markdown-fill-forward-paragraph-function (&optional arg)
+ (let* ((arg (or arg 1))
+ (paragraphs-remaining (forward-paragraph arg))
+ (start (point)))
+ (when (< arg 0)
+ (while (and (not (eobp))
+ (progn (move-to-left-margin) (not (eobp)))
+ (looking-at paragraph-separate))
+ (forward-line 1))
+ (if (looking-at markdown-regex-list)
+ (forward-char (length (match-string 0)))
+ (goto-char start)))
+ paragraphs-remaining))
+
+\f
+;;; Extensions ================================================================
+
+(defun markdown-reload-extensions ()
+ "Check settings, update font-lock keywords, and re-fontify buffer."
+ (interactive)
+ (when (eq major-mode 'markdown-mode)
+ (setq markdown-mode-font-lock-keywords
+ (append
+ (when markdown-enable-math
+ markdown-mode-font-lock-keywords-math)
+ markdown-mode-font-lock-keywords-basic
+ markdown-mode-font-lock-keywords-core))
+ (setq font-lock-defaults '(markdown-mode-font-lock-keywords))
+ (when (fboundp 'font-lock-refresh-defaults) (font-lock-refresh-defaults))))
+
+(defun markdown-enable-math (&optional arg)
+ "Toggle support for inline and display LaTeX math expressions.
+With a prefix argument ARG, enable math mode if ARG is positive,
+and disable it otherwise. If called from Lisp, enable the mode
+if ARG is omitted or nil."
+ (interactive (list (or current-prefix-arg 'toggle)))
+ (setq markdown-enable-math
+ (if (eq arg 'toggle)
+ (not markdown-enable-math)
+ (> (prefix-numeric-value arg) 0)))
+ (if markdown-enable-math
+ (message "markdown-mode math support enabled")
+ (message "markdown-mode math support disabled"))
+ (markdown-reload-extensions))
+
+\f
+;;; Mode Definition ==========================================================
+
+(defun markdown-show-version ()
+ "Show the version number in the minibuffer."
+ (interactive)
+ (message "markdown-mode, version %s" markdown-mode-version))
+
+;;;###autoload
+(define-derived-mode markdown-mode text-mode "Markdown"
+ "Major mode for editing Markdown files."
+ ;; Natural Markdown tab width
+ (setq tab-width 4)
+ ;; Comments
+ (make-local-variable 'comment-start)
+ (setq comment-start "<!-- ")
+ (make-local-variable 'comment-end)
+ (setq comment-end " -->")
+ (make-local-variable 'comment-start-skip)
+ (setq comment-start-skip "<!--[ \t]*")
+ (make-local-variable 'comment-column)
+ (setq comment-column 0)
+ (set (make-local-variable 'comment-auto-fill-only-comments) nil)
+ ;; Font lock.
+ (set (make-local-variable 'markdown-mode-font-lock-keywords) nil)
+ (set (make-local-variable 'font-lock-defaults) nil)
+ (set (make-local-variable 'font-lock-multiline) t)
+ (markdown-reload-extensions)
+ ;; Extensions
+ (make-local-variable 'markdown-enable-math)
+ (add-hook 'hack-local-variables-hook 'markdown-reload-extensions)
+ ;; For imenu support
+ (setq imenu-create-index-function 'markdown-imenu-create-index)
+ ;; For menu support in XEmacs
+ (easy-menu-add markdown-mode-menu markdown-mode-map)
+ ;; Defun movement
+ (set (make-local-variable 'beginning-of-defun-function)
+ 'markdown-beginning-of-defun)
+ (set (make-local-variable 'end-of-defun-function)
+ 'markdown-end-of-defun)
+ ;; Paragraph filling
+ (set
+ ; Should match start of lines that start or seaprate paragraphs
+ (make-local-variable 'paragraph-start)
+ (mapconcat 'identity
+ '(
+ "\f" ; starts with a literal line-feed
+ "[ \t\f]*$" ; space-only line
+ "[ \t]*[*+-][ \t]+" ; unordered list item
+ "[ \t]*[0-9]+\\.[ \t]+" ; ordered list item
+ "[ \t]*\\[\\S-*\\]:[ \t]+" ; link ref def
+ )
+ "\\|"))
+ (set
+ ; Should match lines that separate paragraphs without being
+ ; part of any paragraph:
+ (make-local-variable 'paragraph-separate)
+ (mapconcat 'identity
+ '("[ \t\f]*$" ; space-only line
+ ; The following is not ideal, but the Fill customization
+ ; options really only handle paragraph-starting prefixes,
+ ; not paragraph-ending suffixes:
+ ".* $" ; line ending in two spaces
+ "[ \t]*\\[\\^\\S-*\\]:[ \t]*$") ; just the start of a footnote def
+ "\\|"))
+ (set (make-local-variable 'adaptive-fill-first-line-regexp)
+ "\\`[ \t]*>[ \t]*?\\'")
+ (set (make-local-variable 'adaptive-fill-function)
+ 'markdown-adaptive-fill-function)
+ (set (make-local-variable 'fill-forward-paragraph-function)
+ 'markdown-fill-forward-paragraph-function)
+ ;; Outline mode
+ (make-local-variable 'outline-regexp)
+ (setq outline-regexp markdown-regex-header)
+ (make-local-variable 'outline-level)
+ (setq outline-level 'markdown-outline-level)
+ ;; Cause use of ellipses for invisible text.
+ (add-to-invisibility-spec '(outline . t))
+
+ ;; Inhibiting line-breaking:
+ ;; Separating out each condition into a separate function so that users can
+ ;; override if desired (with remove-hook)
+ (add-hook 'fill-nobreak-predicate
+ 'markdown-inside-link-text-p nil t)
+ (add-hook 'fill-nobreak-predicate
+ 'markdown-line-is-reference-definition-p nil t)
+
+ ;; Indentation
+ (setq indent-line-function markdown-indent-function)
+
+ ;; Backwards compatibility with markdown-css-path
+ (when (boundp 'markdown-css-path)
+ (warn "markdown-css-path is deprecated, see markdown-css-paths.")
+ (add-to-list 'markdown-css-paths markdown-css-path))
+
+ ;; Prepare hooks for XEmacs compatibility
+ (when (featurep 'xemacs)
+ (make-local-hook 'after-change-functions)
+ (make-local-hook 'font-lock-extend-region-functions)
+ (make-local-hook 'window-configuration-change-hook))
+
+ ;; Multiline font lock
+ (add-hook 'font-lock-extend-region-functions
+ 'markdown-font-lock-extend-region)
+
+ ;; Anytime text changes make sure it gets fontified correctly
+ (add-hook 'after-change-functions 'markdown-check-change-for-wiki-link t t)
+
+ ;; If we left the buffer there is a really good chance we were
+ ;; creating one of the wiki link documents. Make sure we get
+ ;; refontified when we come back.
+ (add-hook 'window-configuration-change-hook
+ 'markdown-fontify-buffer-wiki-links t t)
+
+ ;; do the initial link fontification
+ (markdown-fontify-buffer-wiki-links))
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.markdown\\'" . markdown-mode))
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.text\\'" . markdown-mode))
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.md\\'" . markdown-mode))
+
+\f
+;;; GitHub Flavored Markdown Mode ============================================
+
+(defvar gfm-font-lock-keywords
+ (append
+ ;; GFM features to match first
+ (list
+ (cons markdown-regex-strike-through '(2 markdown-strike-through-face)))
+ ;; Basic Markdown features (excluding possibly overridden ones)
+ markdown-mode-font-lock-keywords-basic
+ ;; GFM features to match last
+ (list
+ (cons markdown-regex-gfm-italic '(2 markdown-italic-face))))
+ "Default highlighting expressions for GitHub Flavored Markdown mode.")
+
+;;;###autoload
+(define-derived-mode gfm-mode markdown-mode "GFM"
+ "Major mode for editing GitHub Flavored Markdown files."
+ (setq markdown-link-space-sub-char "-")
+ (set (make-local-variable 'font-lock-defaults)
+ '(gfm-font-lock-keywords))
+ (auto-fill-mode 0)
+ ;; Use visual-line-mode if available, fall back to longlines-mode:
+ (cond ((fboundp 'visual-line-mode)
+ (visual-line-mode 1))
+ ((fboundp 'longlines-mode)
+ (longlines-mode 1)))
+ ;; do the initial link fontification
+ (markdown-fontify-buffer-wiki-links))
+
+\f
+(provide 'markdown-mode)
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
+;;; markdown-mode.el ends here
--- /dev/null
+;;; mustache-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+
+\f
+;;;### (autoloads nil nil ("mustache-lex.el" "mustache-parse.el"
+;;;;;; "mustache-pkg.el" "mustache-render.el" "mustache.el") (21490
+;;;;;; 25514 540076 0))
+
+;;;***
+\f
+(provide 'mustache-autoloads)
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; coding: utf-8
+;; End:
+;;; mustache-autoloads.el ends here
--- /dev/null
+(eval-when-compile (require 'cl)) ;; first, second, destructuring-bind, loop
+(require 's)
+
+(defun mst--lex (template)
+ "Iterate through TEMPLATE, splitting {{ tags }} and bare strings.
+We return a list of lists: ((:text \"foo\") (:tag \"variable-name\"))"
+ ;; convert {{{foo}}} to {{& foo}}
+ (setq template (replace-regexp-in-string "{{{\\(.*?\\)}}}" "{{& \\1}}" template))
+
+ (let ((open-delimeter "{{")
+ (close-delimeter "}}")
+ (lexemes nil))
+ (while (not (s-equals? template ""))
+ (let* ((open-index (s-index-of open-delimeter template))
+ (close-index (s-index-of close-delimeter template)))
+ ;; todo: check open-index < close-index
+ ;; todo: error if we have an open and no close
+ (if (and open-index close-index)
+ ;; we have a tag
+ (let ((between-delimeters
+ (substring template (+ open-index (length open-delimeter)) close-index))
+ (continue-from-index (+ close-index (length close-delimeter))))
+ ;; save the string before the tag
+ (when (> open-index 0)
+ (push (list :text (substring template 0 open-index)) lexemes))
+
+ ;; if this is a tag that changes delimeters e.g. {{=<< >>=}}
+ ;; then set the new open/close delimeter string
+ (if (s-matches-p "^=.+ .+=$" between-delimeters)
+ (let* (;; strip leading/trailing =
+ (delimeter-spec (substring between-delimeters 1 -1))
+ (spec-parts (s-split " " delimeter-spec)))
+ (setq open-delimeter (first spec-parts))
+ (setq close-delimeter (second spec-parts)))
+
+ ;; otherwise it's a normal tag, so save it
+ (push (list :tag (s-trim between-delimeters)) lexemes))
+
+ ;; iterate on the remaining template
+ (setq template
+ (substring template continue-from-index)))
+ ;; else only plain text left
+ (progn
+ (push (list :text template) lexemes)
+ (setq template "")))))
+ (nreverse lexemes)))
+
+(defun mst--clean-whitespace (lexemes)
+ "Given a list of LEXEMES, remove whitespace around sections and
+comments if they're on their own on a line. Modifies the original
+list."
+ ;; iterate over all lexemes:
+ (loop for i from 0 to (- (length lexemes) 3)
+ collect
+ ;; find sections that have plain text before and after
+ (let ((first (elt lexemes i))
+ (second (elt lexemes (+ i 1)))
+ (third (elt lexemes (+ i 2))))
+ (when (and (mst--text-p first)
+ (or
+ (mst--section-tag-p second)
+ (mst--comment-tag-p second))
+ (mst--text-p third)
+ ;; check the section is on its own line
+ (string-match-p "\n *$" (mst--tag-text first))
+ (string-match-p "^\n" (mst--tag-text third)))
+ ;; then we cleanup whitespace
+ (setf (elt lexemes i) (mst--no-trailing-newline first)))))
+ lexemes)
+
+(defalias 'mst--tag-text 'second
+ "Returns the text context of a tag.")
+
+(defun mst--no-trailing-newline (lexeme)
+ "Replace \"\n\" or \"\n \" at the end of a plain text lexeme."
+ (list
+ :text
+ (replace-regexp-in-string "\n *$" "" (mst--tag-text lexeme))))
+
+(defun mst--tag-p (lexeme)
+ "Is LEXEME a tag?"
+ (equal (car lexeme) :tag))
+
+(defun mst--section-tag-p (lexeme)
+ "Is LEXEME a section tag?"
+ (or
+ (mst--open-section-tag-p lexeme)
+ (mst--inverted-section-tag-p lexeme)
+ (mst--close-section-tag-p lexeme)))
+
+(defun mst--open-section-tag-p (lexeme)
+ "Is LEXEME an open section tag?
+See also `mst--inverted-section-tag-p'."
+ (and (mst--tag-p lexeme)
+ (s-starts-with-p "#" (second lexeme))))
+
+(defun mst--inverted-section-tag-p (lexeme)
+ "Is LEXEME an inverted section tag?"
+ (and (mst--tag-p lexeme)
+ (s-starts-with-p "^" (second lexeme))))
+
+(defun mst--close-section-tag-p (lexeme)
+ "Is LEXEME a close section tag?"
+ (and (mst--tag-p lexeme)
+ (s-starts-with-p "/" (second lexeme))))
+
+(defun mst--comment-tag-p (lexeme)
+ "Is LEXEME a comment tag?"
+ (and (mst--tag-p lexeme)
+ (s-starts-with-p "!" (second lexeme))))
+
+(defun mst--unescaped-tag-p (lexeme)
+ "Is LEXEME an unescaped variable tag?
+Note that the lexer converts {{{foo}}} to {{& foo}}."
+ (and (mst--tag-p lexeme)
+ (s-starts-with-p "&" (second lexeme))))
+
+(defun mst--partial-tag-p (lexeme)
+ "Is LEXEME a partial tag?"
+ (and (mst--tag-p lexeme)
+ (s-starts-with-p ">" (second lexeme))))
+
+(defun mst--section-p (lexeme)
+ "Is LEXEME a nested section?"
+ (not (atom (car lexeme))))
+
+(defun mst--text-p (lexeme)
+ "Is LEXEME plain text?"
+ (equal (car lexeme) :text))
+
+;; fixme: assumes the delimeters haven't changed
+;; fixme: mst--lex doens't preserve whitespace
+(defun mst--unlex (lexemes)
+ "Given a lexed (and optionally parsed) list of lexemes,
+return the original input string."
+ (if lexemes
+ (let ((lexeme (first lexemes))
+ (rest (cdr lexemes)))
+ (cond
+ ;; recurse on this section, then the rest
+ ((mst--section-p lexeme)
+ (concat (mst--unlex lexeme) (mst--unlex rest)))
+ ((mst--tag-p lexeme)
+ ;; restore the delimeters, then unlex the rest
+ (let ((tag-name (second lexeme)))
+ (concat "{{" tag-name "}}" (mst--unlex rest))))
+ ;; otherwise, it's just raw text
+ (t
+ (let ((text (second lexeme)))
+ (concat text (mst--unlex rest))))))
+ ""))
--- /dev/null
+(require 's)
+(eval-when-compile (require 'cl)) ;; loop, return
+
+(defvar mst--remaining-lexemes nil
+ "Since `mst--parse-inner' recursively calls itself, we need a shared value to mutate.")
+
+(defun mst--parse (lexemes)
+ "Given a list LEXEMES, return a list of lexemes nested according to #tags or ^tags."
+ (setq mst--remaining-lexemes lexemes)
+ (mst--parse-inner))
+
+(defun mst--parse-inner (&optional section-name)
+ "Parse `mst--remaining-lexemes', and return a list of lexemes nested according to #tags or ^tags."
+ (let (parsed-lexemes
+ lexeme)
+ (loop while mst--remaining-lexemes do
+ (setq lexeme (pop mst--remaining-lexemes))
+ (cond
+ ((mst--open-section-p lexeme)
+ ;; recurse on this nested section
+ (push (cons lexeme (mst--parse-inner (mst--section-name lexeme))) parsed-lexemes))
+ ((mst--close-section-p lexeme)
+ ;; this is the last tag in this section
+ (unless (equal section-name (mst--section-name lexeme))
+ (error "Mismatched brackets: You closed a section with %s, but it wasn't open" section-name))
+ (push lexeme parsed-lexemes)
+ (return))
+ (t
+ ;; this is just a tag in the current section
+ (push lexeme parsed-lexemes))))
+
+ ;; ensure we aren't inside an unclosed section
+ (when (and section-name (not (mst--close-section-p lexeme)))
+ (error "Unclosed section: You haven't closed %s" section-name))
+
+ (nreverse parsed-lexemes)))
+
+(defun mst--open-section-p (lexeme)
+ "Is LEXEME a #tag or ^tag ?"
+ (destructuring-bind (type value) lexeme
+ (and (equal type :tag)
+ (or
+ (s-starts-with-p "#" value)
+ (s-starts-with-p "^" value)))))
+
+(defun mst--close-section-p (lexeme)
+ "Is LEXEME a /tag ?"
+ (destructuring-bind (type value) lexeme
+ (and (equal type :tag)
+ (s-starts-with-p "/" value))))
+
+(defun mst--section-name (lexeme)
+ "Get the name of the section from LEXEME, a two part list returned by `mst--lex'.
+The leading character (the #, ^ or /) is stripped."
+ (s-chop-prefixes '("#" "^" "/") (cadr lexeme)))
--- /dev/null
+(define-package "mustache" "20131117.1407" "a mustache templating library in emacs lisp"
+ '((ht "0.9")
+ (s "1.3.0")
+ (dash "1.2.0")))
--- /dev/null
+(require 'ht)
+(require 's)
+(require 'dash)
+
+(eval-when-compile '(require 'cl)) ;; return, dolist
+
+(load "mustache-lex.el")
+(load "mustache-parse.el")
+
+(defun mst--render (template context)
+ "Render a mustache TEMPLATE with hash table CONTEXT."
+ (-> template
+ mst--lex
+ mst--clean-whitespace
+ mst--parse
+ (mst--render-section-list context)))
+
+(defun mst--mapconcat (function sequence)
+ "Apply FUNCTION to every element in SEQUENCE, and concat the results as strings."
+ (mapconcat function sequence ""))
+
+(defmacro mst--amapconcat (form sequence)
+ "Anaphoric version of `mst--mapconcat'."
+ `(mst--mapconcat (lambda (it) ,form) ,sequence))
+
+;; todo: set flag to set tolerance of missing templates
+(defun mst--get-partial (name)
+ "Get the first partial whose file name is NAME.mustache, or nil otherwise.
+Partials are searched for in `mustache-partial-paths'."
+ (unless (listp mustache-partial-paths)
+ (error "`mustache-partial-paths' must be a list of paths"))
+ (let ((partial-name (format "%s.mustache" name)))
+ (dolist (path mustache-partial-paths)
+ (-when-let*
+ ((partials (directory-files path nil "\\.mustache$"))
+ (matching-partial (--first
+ (string-match-p (regexp-quote partial-name) it)
+ partials)))
+ (return
+ (with-temp-buffer
+ (insert-file-contents-literally (expand-file-name matching-partial path))
+ (buffer-substring-no-properties (point-min) (point-max))))))))
+
+(defun mst--render-section-list (sections context)
+ "Render a parsed list SECTIONS in CONTEXT."
+ (mst--amapconcat (mst--render-section it context) sections))
+
+(defun mst--context-get (context variable-name &optional default)
+ "Lookup VARIABLE-NAME in CONTEXT, returning DEFAULT if not present."
+ (when (eq mustache-key-type 'keyword)
+ (setq variable-name (intern (concat ":" variable-name))))
+ (ht-get context variable-name default))
+
+(defun mst--render-tag (parsed-tag context)
+ "Given PARSED-TAG, render it in hash table CONTEXT."
+ (let ((inner-text (mst--tag-text parsed-tag)))
+ (cond
+ ((mst--comment-tag-p parsed-tag)
+ "")
+ ((mst--unescaped-tag-p parsed-tag)
+ (let ((variable-value (mst--context-get context (s-trim (substring inner-text 1)) "")))
+ (when (numberp variable-value)
+ (setq variable-value (number-to-string variable-value)))
+ variable-value))
+ ((mst--partial-tag-p parsed-tag)
+ (let ((partial (mst--get-partial (s-trim (substring inner-text 1)))))
+ (if partial
+ (mst--render partial context)
+ "")))
+ (t ;; normal variable
+ (let ((variable-value (mst--context-get context inner-text "")))
+ (when (numberp variable-value)
+ (setq variable-value (number-to-string variable-value)))
+ (mst--escape-html variable-value))))))
+
+(defun mst--context-add (table from-table)
+ "Return a copy of TABLE where all the key-value pairs in FROM-TABLE have been set."
+ (let ((new-table (ht-copy table)))
+ (ht-update new-table from-table)
+ new-table))
+
+(defun mst--listp (object)
+ "Return t if OBJECT is a list.
+Unlike `listp', does not return t if OBJECT is a function."
+ (and (not (functionp object)) (listp object)))
+
+(defun mst--section-name (section-tag)
+ "Get the name of this SECCTION-TAG.
+E.g. from {{#foo}} to \"foo\"."
+ (-> section-tag ;; e.g (:tag "#foo")
+ mst--tag-text ;; to "#foo"
+ (substring 1) ;; to "foo"
+ s-trim))
+
+(defun mst--render-section (parsed-lexeme context)
+ "Given PARSED-LEXEME -- a lexed tag, plain text, or a nested list,
+render it in CONTEXT."
+ (cond ((mst--section-p parsed-lexeme)
+ ;; nested section
+ (let* ((section-tag (first parsed-lexeme))
+ (section-name (mst--section-name section-tag))
+ (context-value (mst--context-get context section-name))
+ ;; strip section open and close
+ (section-contents (-slice parsed-lexeme 1 -1)))
+ (cond
+ ((mst--open-section-tag-p section-tag)
+ (cond
+ ;; if the context is a list of hash tables, render repeatedly
+ ((or (mst--listp context-value) (vectorp context-value))
+ (mst--amapconcat (mst--render-section-list section-contents (mst--context-add context it)) context-value))
+ ;; if the context is a hash table, render in that context
+ ((hash-table-p context-value)
+ (mst--render-section-list section-contents (mst--context-add context context-value)))
+ ;; if the context is a function, call it
+ ((functionp context-value)
+ (funcall context-value (mst--unlex section-contents) context))
+ ;; if it's a truthy value, render in the current context
+ (context-value
+ (mst--render-section-list section-contents context))
+ ;; otherwise, don't render anything
+ (t "")))
+ ;; render ^tags
+ ((mst--inverted-section-tag-p section-tag)
+ (if context-value
+ ""
+ (mst--render-section-list section-contents context))))))
+ ((mst--tag-p parsed-lexeme)
+ (mst--render-tag parsed-lexeme context))
+ ;; plain text
+ (t
+ (second parsed-lexeme))))
+
+(defun mst--escape-html (string)
+ "Escape HTML in STRING."
+ (->> string
+ (s-replace "&" "&")
+ (s-replace "<" "<")
+ (s-replace ">" ">")
+ (s-replace "'" "'")
+ (s-replace "\"" """)))
--- /dev/null
+;;; mustache.el --- a mustache templating library in emacs lisp
+
+;; Copyright (C) 2013 Wilfred Hughes
+
+;; Author: Wilfred Hughes <meNOSPAMwilfred.me.uk>
+;; Version: 0.23
+;; Keywords: mustache, template
+;; Package-Requires: ((ht "0.9") (s "1.3.0") (dash "1.2.0"))
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; See documentation at https://github.com/Wilfred/mustache.el
+
+;; Note on terminology: We treat mustache templates as a sequence of
+;; strings (plain text), and tags (anything wrapped in delimeters:
+;; {{foo}}). A section is a special tag that requires closing
+;; (e.g. {{#foo}}{{/foo}}).
+
+;; We treat mustache templates as if they conform to a rough grammar:
+
+;; TEMPLATE = plaintext | TAG | SECTION | TEMPLATE TEMPLATE
+;; SECTION = OPEN-TAG TEMPLATE CLOSE-TAG
+;; TAG = "{{" text "}}"
+
+;; Public functions are of the form `mustache-FOO`, private
+;; functions/variables are of the form `mst--FOO`.
+
+;;; Code:
+
+(load "mustache-render.el")
+
+;; todo: add flag to set tolerance of missing variables
+(defun mustache-render (template context)
+ "Render a mustache TEMPLATE with hash table CONTEXT."
+ (mst--render template context))
+
+(defvar mustache-partial-paths nil
+ "A list of paths to be searched for mustache partial templates (files ending .mustache).")
+
+(defvar mustache-key-type 'string
+ "What type of key we expect in contexts.
+Can take the value 'string or 'keyword.
+
+For 'string we expect contexts of the form:
+#s\(hash-table data \(\"name\" \"J. Random user\"\)\)
+
+for 'keyword we expect contexts of the form:
+#s\(hash-table data \(:name \"J. Random user\"\)\)
+")
+
+(provide 'mustache)
+;;; mustache.el ends here
--- /dev/null
+;;; mustache-mode-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+(add-to-list 'load-path (or (file-name-directory #$) (car load-path)))
+\f
+;;;### (autoloads nil "mustache-mode" "mustache-mode.el" (21908 14210
+;;;;;; 0 0))
+;;; Generated autoloads from mustache-mode.el
+
+(autoload 'mustache-mode "mustache-mode" "\
+
+
+\(fn)" t nil)
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+;;; mustache-mode-autoloads.el ends here
--- /dev/null
+(define-package "mustache-mode" "20141024.732" "A major mode for editing Mustache files." 'nil)
--- /dev/null
+;;; mustache-mode.el --- A major mode for editing Mustache files.
+
+;; Author: Tony Gentilcore
+;; Chris Wanstrath
+;; Daniel Hackney
+
+;; Version: 1.3
+;; Package-Version: 20141024.732
+
+;; This file is not part of Emacs
+
+;; This file 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.
+
+;; This file 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., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;; 1) Copy this file somewhere in your Emacs `load-path'. To see what
+;; your `load-path' is, run inside emacs: C-h v load-path<RET>
+;;
+;; 2) Add the following to your .emacs file:
+;;
+;; (require 'mustache-mode)
+
+;; While the Mustache language can be used for any types of text,
+;; this mode is intended for using Mustache to write HTML.
+
+;;; Known Bugs:
+
+;; The indentation still has minor bugs due to the fact that
+;; templates do not require valid HTML.
+
+;; It would be nice to be able to highlight attributes of HTML tags,
+;; however this is difficult due to the presence of CTemplate symbols
+;; embedded within attributes.
+
+(eval-when-compile
+ (require 'font-lock))
+
+(defvar mustache-mode-version "1.3"
+ "Version of `mustache-mode.el'.")
+
+(defvar mustache-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map "\C-m" 'reindent-then-newline-and-indent)
+ (define-key map "\C-ct" 'mustache-insert-tag)
+ (define-key map "\C-cv" 'mustache-insert-variable)
+ (define-key map "\C-cs" 'mustache-insert-section)
+ map)
+ "Keymap for mustache-mode major mode")
+
+(defvar mustache-mode-syntax-table
+ (let ((st (make-syntax-table)))
+ (modify-syntax-entry ?< "(> " st)
+ (modify-syntax-entry ?> ")< " st)
+ (modify-syntax-entry ?\" ". " st)
+ (modify-syntax-entry ?\\ ". " st)
+ (modify-syntax-entry ?' "w " st)
+ st)
+ "Syntax table in use in mustache-mode buffers.")
+
+(defvar mustache-basic-offset 2
+ "The basic indentation offset.")
+
+;; Constant regular expressions to identify template elements.
+(defconst mustache-mode-mustache-token "[a-zA-Z_.][a-zA-Z0-9_:=\?!.-]*?")
+(defconst mustache-mode-section (concat "\\({{[#^/]\s*"
+ mustache-mode-mustache-token
+ "\s*}}\\)"))
+(defconst mustache-mode-open-section (concat "\\({{[#^]\s*"
+ mustache-mode-mustache-token
+ "\s*}}\\)"))
+(defconst mustache-mode-close-section (concat "{{/\\(\s*"
+ mustache-mode-mustache-token
+ "\s*\\)}}"))
+(defconst mustache-mode-comment "\\({{!\\(?:.\\|\n\\)*?}}\\)")
+(defconst mustache-mode-include (concat "\\({{[><]\s*"
+ mustache-mode-mustache-token
+ "\s*}}\\)"))
+(defconst mustache-mode-variable (concat "\\({{\s*"
+ mustache-mode-mustache-token
+ "\s*}}\\)"))
+(defconst mustache-mode-variable (concat "\\({{{?\s*"
+ mustache-mode-mustache-token
+ "\s*}}}?\\)"))
+(defconst mustache-mode-builtins
+ (concat
+ "\\({{\\<\s*"
+ (regexp-opt
+ '("BI_NEWLINE" "BI_SPACE")
+ t)
+ "\s*\\>}}\\)"))
+(defconst mustache-mode-close-section-at-start (concat "^[ \t]*?"
+ mustache-mode-close-section))
+
+;; Constant regular expressions to identify html tags.
+;; Taken from HTML 4.01 / XHTML 1.0 Reference found at:
+;; http://www.w3schools.com/tags/default.asp.
+(defconst mustache-mode-html-constant "\\(&#?[a-z0-9]\\{2,5\\};\\)")
+(defconst mustache-mode-pair-tag
+ (concat
+ "\\<"
+ (regexp-opt
+ '("a" "abbr" "acronym" "address" "applet" "area" "b" "bdo"
+ "big" "blockquote" "body" "button" "caption" "center" "cite"
+ "code" "col" "colgroup" "dd" "del" "dfn" "dif" "div" "dl"
+ "dt" "em" "fieldset" "font" "form" "frame" "frameset" "h1"
+ "header" "nav" "footer" "section"
+ "h2" "h3" "h4" "h5" "h6" "head" "html" "i" "iframe" "ins"
+ "kbd" "label" "legend" "li" "link" "map" "menu" "noframes"
+ "noscript" "object" "ol" "optgroup" "option" "p" "pre" "q"
+ "s" "samp" "script" "select" "small" "span" "strike"
+ "strong" "style" "sub" "sup" "table" "tbody" "td" "textarea"
+ "tfoot" "th" "thead" "title" "tr" "tt" "u" "ul" "var")
+ t)
+ "\\>"))
+(defconst mustache-mode-standalone-tag
+ (concat
+ "\\<"
+ (regexp-opt
+ '("base" "br" "hr" "img" "input" "meta" "param")
+ t)
+ "\\>"))
+(defconst mustache-mode-open-tag (concat "<\\("
+ mustache-mode-pair-tag
+ "\\)"))
+(defconst mustache-mode-close-tag (concat "</\\("
+ mustache-mode-pair-tag
+ "\\)>"))
+(defconst mustache-mode-close-tag-at-start (concat "^[ \t]*?"
+ mustache-mode-close-tag))
+
+(defconst mustache-mode-blank-line "^[ \t]*?$")
+(defconst mustache-mode-dangling-open (concat "\\("
+ mustache-mode-open-section
+ "\\)\\|\\("
+ mustache-mode-open-tag
+ "\\)[^/]*$"))
+
+(defun mustache-insert-tag (tag)
+ "Inserts an HTML tag."
+ (interactive "sTag: ")
+ (mustache-indent)
+ (insert (concat "<" tag ">"))
+ (insert "\n\n")
+ (insert (concat "</" tag ">"))
+ (mustache-indent)
+ (forward-line -1)
+ (mustache-indent))
+
+(defun mustache-insert-variable (variable)
+ "Inserts a tpl variable."
+ (interactive "sVariable: ")
+ (insert (concat "{{" variable "}}")))
+
+(defun mustache-insert-section (section)
+ "Inserts a tpl section."
+ (interactive "sSection: ")
+ (mustache-indent)
+ (insert (concat "{{#" section "}}\n"))
+ (insert "\n")
+ (insert (concat "{{/" section "}}"))
+ (mustache-indent)
+ (forward-line -1)
+ (mustache-indent))
+
+(defun mustache-indent ()
+ "Indent current line"
+ ;; Set the point to beginning of line.
+ (beginning-of-line)
+ ;; If we are at the beginning of the file, indent to 0.
+ (if (bobp)
+ (indent-line-to 0)
+ (let ((tag-stack 1) (close-tag "") (cur-indent 0) (old-pnt (point-marker))
+ (close-at-start) (open-token) (dangling-open))
+ (progn
+ ;; Determine if this is a template line or an html line.
+ (if (looking-at "^[ \t]*?{{")
+ (setq close-at-start mustache-mode-close-section-at-start
+ open-token "{{#")
+ (setq close-at-start mustache-mode-close-tag-at-start
+ open-token "<"))
+ ;; If there is a closing tag at the start of the line, search back
+ ;; for its opener and indent to that level.
+ (if (looking-at close-at-start)
+ (progn
+ (save-excursion
+ (setq close-tag (match-string 1))
+ ;; Keep searching for a match for the close tag until
+ ;; the tag-stack is 0.
+ (while (and (not (bobp))
+ (> tag-stack 0)
+ (re-search-backward (concat open-token
+ "\\(/?\\)"
+ close-tag) nil t))
+ (if (string-equal (match-string 1) "/")
+ ;; We found another close tag, so increment tag-stack.
+ (setq tag-stack (+ tag-stack 1))
+ ;; We found an open tag, so decrement tag-stack.
+ (setq tag-stack (- tag-stack 1)))
+ (setq cur-indent (current-indentation))))
+ (if (> tag-stack 0)
+ (save-excursion
+ (forward-line -1)
+ (setq cur-indent (current-indentation)))))
+ ;; This was not a closing tag, so we check if the previous line
+ ;; was an opening tag.
+ (save-excursion
+ ;; Keep moving back until we find a line that is not blank
+ (while (progn
+ (forward-line -1)
+ (and (not (bobp)) (looking-at mustache-mode-blank-line))))
+ (setq cur-indent (current-indentation))
+ (if (re-search-forward mustache-mode-dangling-open old-pnt t)
+ (setq cur-indent (+ cur-indent mustache-basic-offset)))))
+ ;; Finally, we execute the actual indentation.
+ (if (> cur-indent 0)
+ (indent-line-to cur-indent)
+ (indent-line-to 0))))))
+
+(defconst mustache-mode-font-lock-keywords
+ `((,mustache-mode-section (1 font-lock-keyword-face))
+ (,mustache-mode-comment (1 font-lock-comment-face))
+ (,mustache-mode-include (1 font-lock-function-name-face))
+ (,mustache-mode-builtins (1 font-lock-variable-name-face))
+ (,mustache-mode-variable (1 font-lock-reference-face))
+ (,(concat "</?\\(" mustache-mode-pair-tag "\\)") (1 font-lock-function-name-face))
+ (,(concat "<\\(" mustache-mode-standalone-tag "\\)") (1 font-lock-function-name-face))
+ (,mustache-mode-html-constant (1 font-lock-variable-name-face))))
+
+;;;###autoload
+(define-derived-mode mustache-mode fundamental-mode "Mustache"
+ (set (make-local-variable 'indent-line-function) 'mustache-indent)
+ (set (make-local-variable 'indent-tabs-mode) nil)
+ (set (make-local-variable 'font-lock-defaults) '(mustache-mode-font-lock-keywords)))
+
+(add-to-list 'auto-mode-alist '("\\.mustache$" . mustache-mode))
+
+(provide 'mustache-mode)
+
+;;; mustache-mode.el ends here
--- /dev/null
+;;; paradox-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+(add-to-list 'load-path (or (file-name-directory #$) (car load-path)))
+\f
+;;;### (autoloads nil "paradox" "paradox.el" (21908 14207 0 0))
+;;; Generated autoloads from paradox.el
+
+(autoload 'paradox-list-packages "paradox" "\
+Improved version of `package-list-packages'. The heart of Paradox.
+Function is equivalent to `package-list-packages' (including the
+prefix NO-FETCH), but the resulting Package Menu is improved in
+several ways.
+
+Among them:
+
+1. Uses `paradox-menu-mode', which has more functionality and
+keybinds than `package-menu-mode'.
+
+2. Uses some font-locking to improve readability.
+
+3. Optionally shows the number GitHub stars and Melpa downloads
+for packages.
+
+4. Adds useful information in the mode-line.
+
+\(fn NO-FETCH)" t nil)
+
+(autoload 'paradox-upgrade-packages "paradox" "\
+Upgrade all packages. No questions asked.
+This function is equivalent to `list-packages', followed by a
+`package-menu-mark-upgrades' and a `package-menu-execute'. Except
+the user isn't asked to confirm deletion of packages.
+
+If `paradox-execute-asynchronously' is non-nil, part of this
+operation may be performed in the background.
+
+The NO-FETCH prefix argument is passed to `list-packages'. It
+prevents re-download of information about new versions. It does
+not prevent downloading the actual packages (obviously).
+
+\(fn &optional NO-FETCH)" t nil)
+
+(autoload 'paradox-require "paradox" "\
+Like `require', but also install FEATURE if it is absent.
+FILENAME is passed to `require'.
+If NOERROR is non-nil, don't complain if the feature couldn't be
+installed, just return nil.
+
+- If FEATURE is present, `require' it and return t.
+
+- If FEATURE is not present, install PACKAGE with `package-install'.
+If PACKAGE is nil, assume FEATURE is the package name.
+After installation, `require' FEATURE.
+
+By default, the current package database is only updated if it is
+empty. Passing a non-nil REFRESH argument forces this update.
+
+\(fn FEATURE &optional FILENAME NOERROR PACKAGE REFRESH)" nil nil)
+
+;;;***
+\f
+;;;### (autoloads nil nil ("paradox-commit-list.el" "paradox-core.el"
+;;;;;; "paradox-execute.el" "paradox-github.el" "paradox-menu.el"
+;;;;;; "paradox-pkg.el") (21908 14207 607948 0))
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+;;; paradox-autoloads.el ends here
--- /dev/null
+;;; paradox-commit-list.el --- listing commits for a package's repository -*- lexical-binding:t -*-
+
+;; Copyright (C) 2014-2015 Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+
+;; Author: Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+;; Prefix: paradox
+;; Separator: -
+
+;;; License:
+;;
+;; This file is NOT part of GNU Emacs.
+;;
+;; 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.
+;;
+
+\f
+;;; Code:
+(require 'subr-x)
+(require 'cl-lib)
+(require 'package)
+
+(require 'paradox-github)
+
+(defgroup paradox-commit-list nil
+ "Buffer used by paradox to list commits for a package."
+ :prefix "paradox-"
+ :package-version '(paradox . "2.0")
+ :group 'paradox)
+
+\f
+;;; Variables
+(defcustom paradox-commit-list-query-max-pages 1
+ "Max number of pages we read from github when fetching the commit-list.
+Each page lists 100 commits, so 1 page should be more than enough
+for most repositories.
+
+Increasing this number consequently multiplies the time it takes
+to load the commit list on repos which actually use that many
+pages."
+ :type 'integer
+ :group 'paradox-commit-list
+ :package-version '(paradox . "1.2.3"))
+
+(defcustom paradox-date-format "%Y-%m-%d"
+ "Format used for the date displayed on the commit list.
+See `format-time-string' for more information.
+
+Set it to \"%x\" for a more \"human\" date format."
+ :type 'string
+ :group 'paradox-commit-list
+ :package-version '(paradox . "1.2.3"))
+
+(defface paradox-commit-tag-face
+ '((t :foreground "goldenrod4"
+ :background "LemonChiffon1"
+ :box 1))
+ "Face used for tags on the commit list."
+ :group 'paradox-commit-list)
+
+\f
+;;; Variables
+(defvar paradox--commit-message-face nil
+ "Face currently being used on commit messages.
+Gets dynamically changed to `font-lock-comment-face' on old commits.
+nil means `default'.")
+
+(defvar-local paradox--package-repo nil
+ "Repo of the package in a commit-list buffer.")
+(defvar-local paradox--package-name nil
+ "Name of the package in a commit-list buffer.")
+(defvar-local paradox--package-version nil
+ "Installed version of the package in a commit-list buffer.")
+(defvar-local paradox--package-tag-commit-alist nil
+ "Alist of (COMMIT-SHA . TAG) for this package's repo.")
+
+\f
+;;; Functions
+(defun paradox--get-tag-commit-alist (repo)
+ "Get REPO's tag list and associate them to commit hashes."
+ (require 'json)
+ (mapcar
+ (lambda (x)
+ (cons
+ (cdr (assoc 'sha (cdr (assoc 'commit x))))
+ (cdr (assoc 'name x))))
+ (let ((json-array-type 'list))
+ (paradox--github-action
+ (format "repos/%s/tags?per_page=100" repo)
+ :reader #'json-read
+ :max-pages paradox-commit-list-query-max-pages))))
+
+(defun paradox--get-installed-version (pkg)
+ "Return the installed version of PKG.
+- If PKG isn't installed, return '(0).
+- If it has a Melpa-like version (YYYYMMDD HHMM), return it as a
+ time value.
+- If it has a regular version number, return it as a string."
+ (let ((desc (cadr (assoc pkg package-alist))))
+ (if desc
+ (let ((version (package-desc-version desc)))
+ (if (> (car version) 19000000)
+ (date-to-time
+ (format "%8dT%02d:%02d"
+ (car version)
+ (/ (cadr version) 100)
+ (% (cadr version) 100)))
+ ;; Regular version numbers.
+ (mapconcat 'int-to-string version ".")))
+ '(0 0))))
+
+(defun paradox--commit-tabulated-list (repo)
+ "Return the tabulated list for REPO's commit list."
+ (require 'json)
+ (let* ((paradox--commit-message-face nil)
+ (json-array-type 'list)
+ (feed (paradox--github-action
+ (format "repos/%s/commits?per_page=100" repo)
+ :reader #'json-read
+ :max-pages paradox-commit-list-query-max-pages)))
+ (apply 'append (mapcar 'paradox--commit-print-info feed))))
+
+(defun paradox--commit-print-info (x)
+ "Parse json in X into a tabulated list entry."
+ (let* ((commit (cdr (assoc 'commit x)))
+ (date (date-to-time (cdr (assoc 'date (cdr (assoc 'committer commit))))))
+ (title (split-string (cdr (assoc 'message commit)) "[\n\r][ \t]*" t))
+ ;; (url (cdr (assoc 'html_url commit)))
+ (cc (cdr (assoc 'comment_count commit)))
+ (sha (cdr (assoc 'sha x)))
+ (tag (cdr (assoc-string sha paradox--package-tag-commit-alist))))
+ ;; Have we already crossed the installed commit, or is it not even installed?
+ (unless (or paradox--commit-message-face
+ (equal '(0) paradox--package-version))
+ ;; Is this where we cross to old commits?
+ (when (paradox--version<= date tag)
+ (setq paradox--commit-message-face 'paradox-comment-face)))
+ ;; Return the tabulated list entry.
+ (cons
+ ;; The ID
+ (list `((is-old . ,(null paradox--commit-message-face))
+ (lisp-date . ,date)
+ ,@x)
+ ;; The actual displayed data
+ (vector
+ (propertize (format-time-string paradox-date-format date)
+ 'button t
+ 'follow-link t
+ 'action 'paradox-commit-list-visit-commit
+ 'face (or paradox--commit-message-face 'link))
+ (concat (if (> cc 0)
+ (propertize (format "(%s comments) " cc)
+ 'face 'font-lock-function-name-face)
+ "")
+ (if (stringp tag)
+ (propertize tag 'face 'paradox-commit-tag-face)
+ "")
+ (if (stringp tag) " " "")
+ (propertize (or (car-safe title) "")
+ 'face paradox--commit-message-face))))
+ (mapcar (lambda (m) (list x (vector "" (propertize m 'face paradox--commit-message-face))))
+ (cdr title)))))
+
+(defun paradox--version<= (date version)
+ "Non-nil if commit at DATE tagged with VERSION is older or equal to `paradox--package-version'."
+ ;; Melpa date-like versions
+ (if (listp paradox--package-version)
+ ;; Installed date >= to commit date
+ (null (time-less-p paradox--package-version date))
+ ;; Regular version numbers.
+ (and version
+ (ignore-errors (version<= version paradox--package-version)))))
+
+(defun paradox--commit-list-update-entries ()
+ "Update entries of current commit-list."
+ (setq tabulated-list-entries
+ (paradox--commit-tabulated-list paradox--package-repo)))
+
+\f
+;;; Commands
+(defun paradox-commit-list-visit-commit (&optional _)
+ "Visit this commit on GitHub.
+IGNORE is ignored."
+ (interactive)
+ (when (derived-mode-p 'paradox-commit-list-mode)
+ (browse-url (cdr (assoc 'html_url (tabulated-list-get-id))))))
+
+(defun paradox-previous-commit (&optional n)
+ "Move to previous commit, which might not be the previous line.
+With prefix N, move to the N-th previous commit."
+ (interactive "p")
+ (paradox-next-commit (- n)))
+
+(defun paradox-next-commit (&optional n)
+ "Move to next commit, which might not be the next line.
+With prefix N, move to the N-th next commit."
+ (interactive "p")
+ (dotimes (_ (abs n))
+ (let ((d (cl-signum n)))
+ (forward-line d)
+ (while (looking-at " +")
+ (forward-line d)))))
+
+\f
+;;; Mode definition
+(define-derived-mode paradox-commit-list-mode
+ tabulated-list-mode "Paradox Commit List"
+ "Major mode for browsing a list of commits.
+Letters do not insert themselves; instead, they are commands.
+\\<paradox-commit-list-mode-map>
+\\{paradox-commit-list-mode-map}"
+ (hl-line-mode 1)
+ (setq tabulated-list-format
+ `[("Date" ,(length (format-time-string paradox-date-format (current-time))) nil)
+ ("Message" 0 nil)])
+ (setq tabulated-list-padding 1)
+ (setq tabulated-list-sort-key nil)
+ (add-hook 'tabulated-list-revert-hook 'paradox--commit-list-update-entries nil t)
+ (tabulated-list-init-header))
+
+(define-key paradox-commit-list-mode-map "\r" #'paradox-commit-list-visit-commit)
+(define-key paradox-commit-list-mode-map "p" #'paradox-previous-commit)
+(define-key paradox-commit-list-mode-map "n" #'paradox-next-commit)
+
+
+(provide 'paradox-commit-list)
+;;; paradox-commit-list.el ends here.
--- /dev/null
+;;; paradox-core.el --- common functions -*- lexical-binding:t -*-
+
+;; Copyright (C) 2014-2015 Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+
+;; Author: Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+;; Prefix: paradox
+;; Separator: -
+
+;;; License:
+;;
+;; This file is NOT part of GNU Emacs.
+;;
+;; 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.
+;;
+
+;;; Code:
+(require 'subr-x)
+
+\f
+;;; Configurations
+(defface paradox-comment-face
+ '((((background light)) :foreground "Grey30")
+ (((background dark)) :foreground "Grey60"))
+ "Face used on faded out stuff."
+ :group 'paradox-menu
+ :group 'paradox-commit-list)
+(defface paradox-highlight-face
+ '((t :weight bold :inherit font-lock-variable-name-face))
+ "Face used on highlighted stuff."
+ :group 'paradox-menu
+ :group 'paradox-commit-list)
+
+\f
+;;; Internal variables
+(defvar paradox--star-count (make-hash-table))
+(defvar paradox--download-count (make-hash-table))
+(defvar paradox--package-repo-list (make-hash-table))
+(defvar paradox--wiki-packages (make-hash-table))
+
+(defconst paradox--data-url
+ "https://raw.githubusercontent.com/Malabarba/paradox/data/"
+ "Address of Paradox's data directory.")
+
+(defconst paradox--star-count-url (concat paradox--data-url "data-hashtables")
+ "Address of the raw star-count file.")
+(make-obsolete-variable 'paradox--star-count-url 'paradox--data-url "2.1")
+
+(defconst paradox--package-count
+ '(("total" . 0) ("built-in" . 0)
+ ("obsolete" . 0) ("deleted" . 0)
+ ("available" . 0) ("new" . 0)
+ ("held" . 0) ("disabled" . 0)
+ ("dependency" . 0) ("avail-obso" . 0)
+ ("incompat" . 0) ("external" . 0)
+ ("installed" . 0) ("unsigned" . 0)))
+
+(defvar paradox--truncate-string-to-width-backup)
+
+(defmacro paradox--cas (string)
+ "Same as (cdr (assoc-string ,STRING paradox--package-count))."
+ `(cdr (assoc-string ,string paradox--package-count)))
+
+(defun paradox--truncate-string-to-width (&rest args)
+ "Like `truncate-string-to-width', but uses \"…\" on package buffer.
+All arguments STR, END-COLUMN, START-COLUMN, PADDING, and
+ELLIPSIS are passed to `truncate-string-to-width'.
+
+\(fn STR END-COLUMN &optional START-COLUMN PADDING ELLIPSIS)"
+ (when (and (eq major-mode 'paradox-menu-mode)
+ (eq t (nth 4 args)))
+ (setf (nth 4 args) (if (char-displayable-p ?…) "…" "$")))
+ (apply paradox--truncate-string-to-width-backup args))
+
+\f
+;;; Overriding definitions
+(defun paradox--core-enable ()
+ "Enable core features."
+ (paradox--override-definition 'truncate-string-to-width 'paradox--truncate-string-to-width))
+
+(defvar paradox--backups nil)
+
+(defun paradox-disable ()
+ "Disable paradox, and go back to regular package-menu."
+ (interactive)
+ (dolist (it paradox--backups)
+ (message "Restoring %s to %s" (car it) (eval (cdr it)))
+ (fset (car it) (eval (cdr it))))
+ (setq paradox--backups nil))
+
+(defun paradox--override-definition (sym newdef)
+ "Temporarily override SYM's function definition with NEWDEF.
+The original definition is saved to paradox--SYM-backup."
+ (let ((backup-name (intern (format "paradox--%s-backup" sym)))
+ (def (symbol-function sym)))
+ (unless (assoc sym paradox--backups)
+ (message "Overriding %s with %s" sym newdef)
+ (eval (list 'defvar backup-name nil))
+ (add-to-list 'paradox--backups (cons sym backup-name))
+ (set backup-name def)
+ (fset sym newdef))))
+
+\f
+;;; Pre 25.1 support
+(declare-function paradox--update-downloads-in-progress "paradox-menu")
+(if (fboundp 'package--update-downloads-in-progress)
+ (defun paradox--update-downloads-in-progress (&optional name)
+ (when name
+ (package--update-downloads-in-progress name)))
+ (defalias 'paradox--update-downloads-in-progress #'ignore))
+(define-obsolete-function-alias
+ 'paradox--pdate-downloads-in-progress
+ 'paradox--update-downloads-in-progress
+ "2.1")
+
+\f
+;;; Spinner
+(defvar paradox--spinner nil)
+
+(eval-and-compile (require 'spinner))
+(defcustom paradox-spinner-type 'horizontal-moving
+ "Holds the type of spinner to be used in the mode-line.
+Takes a value accepted by `spinner-start'."
+ :type `(choice (choice :tag "Choose a spinner by name"
+ ,@(mapcar (lambda (c) (list 'const (car c)))
+ spinner-types))
+ (const :tag "A random spinner" random)
+ (repeat :tag "A list of symbols from `spinner-types' to randomly choose from"
+ (choice :tag "Choose a spinner by name"
+ ,@(mapcar (lambda (c) (list 'const (car c)))
+ spinner-types)))
+ (vector :tag "A user defined vector"
+ (repeat :inline t string)))
+ :package-version '(paradox . "2.1")
+ :group 'paradox-execute)
+
+(defun paradox--start-spinner ()
+ (when (spinner-p paradox--spinner)
+ (spinner-stop paradox--spinner))
+ (setq paradox--spinner
+ (make-spinner paradox-spinner-type t 10))
+ (spinner-start paradox--spinner))
+
+(defun paradox--stop-spinner ()
+ (when (spinner-p paradox--spinner)
+ (spinner-stop paradox--spinner))
+ (setq paradox--spinner nil))
+
+(provide 'paradox-core)
+;;; paradox-core.el ends here.
--- /dev/null
+;;; paradox-execute.el --- executing package transactions -*- lexical-binding:t -*-
+
+;; Copyright (C) 2014-2015 Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+
+;; Author: Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+;; Prefix: paradox
+;; Separator: -
+
+;;; License:
+;;
+;; This file is NOT part of GNU Emacs.
+;;
+;; 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.
+;;
+
+;;; Commentary:
+;;
+;; Functions related to executing package-menu transactions.
+;; Everything that happens when you hit `x' is in here.
+
+\f
+;;; Code:
+(require 'cl-lib)
+(require 'seq)
+
+(require 'package)
+(require 'paradox-core)
+(require 'paradox-github)
+
+(defgroup paradox-execute nil
+ "Paradox Packages Menu configurations."
+ :prefix "paradox-"
+ :package-version '(paradox . "2.0")
+ :group 'paradox)
+
+(defvar paradox--current-filter)
+\f
+;;; Customization Variables
+(defcustom paradox-execute-asynchronously 'ask
+ "Whether the install/delete/upgrade should be asynchronous.
+Possible values are:
+ t, which means always;
+ nil, which means never;
+ ask, which means ask each time."
+ :type '(choice (const :tag "Always" t)
+ (const :tag "Never" nil)
+ (const :tag "Ask each time" ask))
+ :package-version '(paradox . "2.0")
+ :group 'paradox-execute)
+
+(defcustom paradox-async-display-buffer-function #'display-buffer
+ "Function used to display *Paradox Report* buffer after asynchronous upgrade.
+Set this to nil to avoid displaying the buffer. Or set this to a
+function like `display-buffer' or `pop-to-buffer'.
+
+This is only used if `paradox-menu-execute' was given a non-nil
+NOQUERY argument. Otherwise, only a message is displayed."
+ :type '(choice (const :tag "Don't display the buffer" nil)
+ function)
+ :package-version '(paradox . "2.0")
+ :group 'paradox-execute)
+
+\f
+;;; Execution Hook
+(defvar paradox-after-execute-functions nil
+ "List of functions run after performing package transactions.
+These are run after a set of installation, deletion, or upgrades
+has been performed. Each function in this hook must take a single
+argument. An associative list of the form
+
+ ((SYMBOL . DATA) (SYMBOL . DATA) ...)
+
+This list contains the following entries, describing what
+occurred during the execution:
+
+ SYMBOL DATA
+ `installed' List of installed packages.
+ `deleted' List of deleted packages.
+ `activated' List of activated packages.
+ `error' List of errors.
+ `async' Non-nil if transaction was performed asynchronously.
+ `noquery' The NOQUERY argument given to `paradox-menu-execute'.")
+(put 'risky-local-variable-p 'paradox-after-execute-functions t)
+(mapc (lambda (x) (add-hook 'paradox-after-execute-functions x t))
+ '(paradox--activate-if-asynchronous
+ paradox--refresh-package-buffer
+ paradox--report-buffer-print
+ paradox--report-buffer-display-if-noquery
+ paradox--report-message
+ ))
+
+(defun paradox--refresh-package-buffer (_)
+ "Refresh the *Packages* buffer, if it exists."
+ (let ((buf (get-buffer "*Packages*")))
+ (when (buffer-live-p buf)
+ (with-current-buffer buf
+ (revert-buffer)))))
+
+(defun paradox--activate-if-asynchronous (alist)
+ "Activate packages after an asynchronous operation.
+Argument ALIST describes the operation."
+ (let-alist alist
+ (when .async
+ (mapc #'package-activate-1 .activated))))
+
+(defun paradox--print-package-list (list)
+ "Print LIST at point."
+ (let* ((width (apply #'max
+ (mapcar (lambda (x) (string-width (symbol-name (package-desc-name x))))
+ list)))
+ (tabulated-list-format
+ `[("Package" ,(1+ width) nil)
+ ("Version" 0 nil)])
+ (tabulated-list-padding 2))
+ (mapc
+ (lambda (p) (tabulated-list-print-entry
+ p
+ `[,(symbol-name (package-desc-name p))
+ ,(package-version-join (package-desc-version p))]))
+ list)))
+
+(defun paradox--report-buffer-print (alist)
+ "Print a transaction report in *Package Report* buffer.
+Possibly display the buffer or message the user depending on the
+situation.
+Argument ALIST describes the operation."
+ (let-alist alist
+ (let ((buf (get-buffer-create "*Paradox Report*"))
+ (inhibit-read-only t))
+ (with-current-buffer buf
+ (goto-char (point-max))
+ ;; TODO: Write our own mode for this.
+ (special-mode)
+ (insert "\n\f\n")
+ (save-excursion
+ (insert (format-time-string "Package transaction finished. %c\n"))
+ (when .error
+ (insert "Errors:\n ")
+ (dolist (it .error)
+ (princ it (current-buffer))
+ (insert "\n"))
+ (insert "\n\n"))
+ (when .installed
+ (insert "Installed:\n")
+ (paradox--print-package-list .installed)
+ (insert "\n"))
+ (when .deleted
+ (insert "Deleted:\n")
+ (paradox--print-package-list .deleted)
+ (insert "\n")))))))
+
+(defun paradox--report-buffer-display-if-noquery (alist)
+ "Display report buffer if `paradox-execute' was called with a NOQUERY prefix.
+ALIST describes the transaction.
+`paradox-async-display-buffer-function' is used if transaction
+was asynchronous. Otherwise, `pop-to-buffer' is used."
+ (let-alist alist
+ ;; The user has never seen the packages in this transaction. So
+ ;; we display them in a buffer.
+ (when (or .noquery .error)
+ (let ((buf (get-buffer "*Paradox Report*")))
+ (when (buffer-live-p buf)
+ (cond
+ ;; If we're async, the user might be doing something else, so
+ ;; we don't steal focus.
+ ((and .async paradox-async-display-buffer-function)
+ (funcall paradox-async-display-buffer-function buf))
+ ;; If we're not async, just go ahead and pop.
+ ((or (not .async)
+ ;; If there's an error, display the buffer even if
+ ;; `paradox-async-display-buffer-function' is nil.
+ .error)
+ (pop-to-buffer buf))))))))
+
+(defun paradox--report-message (alist)
+ "Message the user about the executed transaction.
+ALIST describes the transaction."
+ (let-alist alist
+ (message "%s%s"
+ (paradox--format-message nil .installed .deleted)
+ (if (memq 'paradox--report-buffer-print paradox-after-execute-functions)
+ " See the buffer *Paradox Report* for more details." ""))
+ (when .errors
+ (message "Errors encountered during the operation: %S\n%s"
+ .errors
+ (if (memq 'paradox--report-buffer-print paradox-after-execute-functions)
+ " See the buffer *Paradox Report* for more details." "")))))
+
+\f
+;;; Execution
+(defun paradox-menu-execute (&optional noquery)
+ "Perform marked Package Menu actions.
+Packages marked for installation are downloaded and installed;
+packages marked for deletion are removed.
+
+Afterwards, if `paradox-automatically-star' is t, automatically
+star new packages, and unstar removed packages. Upgraded packages
+aren't changed.
+
+Synchronicity of the actions depends on
+`paradox-execute-asynchronously'. Optional argument NOQUERY
+non-nil means do not ask the user to confirm. If asynchronous,
+never ask anyway."
+ (interactive "P")
+ (unless (derived-mode-p 'paradox-menu-mode)
+ (error "The current buffer is not in Paradox Menu mode"))
+ (when (and (stringp paradox-github-token)
+ (eq paradox-automatically-star 'unconfigured))
+ (customize-save-variable
+ 'paradox-automatically-star
+ (y-or-n-p "When you install new packages would you like them to be automatically starred?
+\(They will be unstarred when you delete them) ")))
+ (when (and (stringp paradox--current-filter)
+ (string-match "Upgradable" paradox--current-filter))
+ (setq tabulated-list-sort-key '("Status" . nil))
+ (setq paradox--current-filter nil))
+ (paradox--menu-execute-1 noquery))
+
+(defmacro paradox--perform-package-transaction (install delete)
+ "Install all packages from INSTALL and delete those from DELETE.
+Return an alist with properties listing installed,
+deleted, and activated packages, and errors."
+ `(let (activated installed deleted errored)
+ (advice-add #'package-activate-1 :after
+ (lambda (pkg &rest _)
+ (ignore-errors (add-to-list 'activated pkg 'append)))
+ '((name . paradox--track-activated)))
+ (condition-case err
+ (progn
+ (dolist (pkg ,install)
+ ;; 2nd arg introduced in 25.
+ (if (version<= "25" emacs-version)
+ (package-install pkg 'dont-select)
+ (package-install pkg))
+ (push pkg installed))
+ (dolist (pkg ,delete)
+ (condition-case err
+ (progn (package-delete pkg)
+ (push pkg deleted))
+ (error (push err errored)))))
+ (error (push err errored)))
+ (advice-remove #'package-activate-1 'paradox--track-activated)
+ (list (cons 'installed (nreverse installed))
+ (cons 'deleted (nreverse deleted))
+ ;; Because we used 'append, this is in the right order.
+ (cons 'activated activated)
+ (cons 'error (nreverse errored)))))
+
+(defvar paradox--current-filter)
+
+(declare-function async-inject-variables "async")
+(defun paradox--menu-execute-1 (&optional noquery)
+ "Implementation used by `paradox-menu-execute'.
+NOQUERY, if non-nil, means to execute without prompting the
+user."
+ (let ((before-alist (paradox--repo-alist))
+ install-list delete-list)
+ (save-excursion
+ (goto-char (point-min))
+ (let ((p (point))
+ (inhibit-read-only t))
+ (while (not (eobp))
+ (let ((c (char-after)))
+ (if (eq c ?\s)
+ (forward-line 1)
+ (push (tabulated-list-get-id)
+ (pcase c
+ (`?D delete-list)
+ (`?I install-list)))
+ (delete-region p (point))
+ (forward-line 1)
+ (setq p (point)))))
+ (when (or delete-list install-list)
+ (delete-region p (point))
+ (ignore-errors
+ (set-window-start (selected-window) (point-min))))))
+ (if (not (or delete-list install-list))
+ (message "No operations specified.")
+ ;; Confirm with the user.
+ (when (or noquery
+ (y-or-n-p (paradox--format-message 'question install-list delete-list)))
+ ;; On Emacs 25, update the selected packages list.
+ (when (fboundp 'package--update-selected-packages)
+ (let-alist (package-menu--partition-transaction install-list delete-list)
+ (package--update-selected-packages .install .delete)))
+ ;; Background or foreground?
+ (if (or (not install-list)
+ (not (pcase paradox-execute-asynchronously
+ (`nil nil)
+ (`ask
+ (if noquery nil
+ (y-or-n-p "Execute in the background (see `paradox-execute-asynchronously')? ")))
+ (_ t))))
+ ;; Synchronous execution
+ (progn
+ (let ((alist (paradox--perform-package-transaction install-list delete-list)))
+ (run-hook-with-args 'paradox-after-execute-functions
+ `((noquery . ,noquery) (async . nil) ,@alist)))
+ (when (and (stringp paradox-github-token) paradox-automatically-star)
+ (paradox--post-execute-star-unstar before-alist (paradox--repo-alist))))
+ ;; Start spinning
+ (paradox--start-spinner)
+
+ ;; Async execution
+ (unless (require 'async nil t)
+ (error "For asynchronous execution please install the `async' package"))
+ ;; We have to do this with eval, because `async-start' is a
+ ;; macro and it might not have been defined at compile-time.
+ (eval
+ `(async-start
+ (lambda ()
+ (require 'package)
+ ,(async-inject-variables "\\`package-")
+ (setq package-menu-async nil)
+ (dolist (elt package-alist)
+ (package-activate (car elt) 'force))
+ (let ((alist ,(macroexpand
+ `(paradox--perform-package-transaction ',install-list ',delete-list))))
+ (list package-alist
+ (when (boundp 'package-selected-packages)
+ package-selected-packages)
+ package-archive-contents
+ ;; This is the alist that will be passed to the hook.
+ (cons '(noquery . ,noquery) (cons '(async . t) alist)))))
+ (lambda (x)
+ (setq package-alist (pop x)
+ package-selected-packages (pop x)
+ package-archive-contents (pop x))
+ (when (spinner-p paradox--spinner)
+ (spinner-stop paradox--spinner)
+ (setq paradox--spinner nil))
+ (setq paradox--executing nil)
+ (run-hook-with-args 'paradox-after-execute-functions (pop x))
+ (paradox--post-execute-star-unstar ',before-alist (paradox--repo-alist))))))))))
+
+\f
+;;; Aux functions
+(defun paradox--repo-alist ()
+ "List of known repos."
+ (delete-dups
+ (remove nil
+ (mapcar
+ (lambda (it) (gethash it paradox--package-repo-list))
+ package-alist))))
+
+(defun paradox--format-message (question-p install-list delete-list)
+ "Format a message regarding a transaction.
+If QUESTION-P is non-nil, format a question suitable for
+`y-or-n-p', otherwise format a report in the past sense.
+INSTALL-LIST and DELETE-LIST are a list of packages about to be
+installed and deleted, respectively."
+ (concat
+ (when install-list
+ (let ((len (length install-list)))
+ (format "Install%s %d package%s"
+ (if question-p "" "ed")
+ len
+ (if (> len 1) "s" ""))))
+ (when (and install-list (not delete-list))
+ (if question-p "? " "."))
+ (when (and install-list delete-list)
+ ", and ")
+ (when delete-list
+ (let ((len (length delete-list)))
+ (format "Delete%s %d package%s%s"
+ (if question-p "" "d")
+ len
+ (if (> len 1) "s" "")
+ (if question-p "? " "."))))))
+
+(defun paradox--post-execute-star-unstar (before after)
+ "Star repos in AFTER absent from BEFORE, unstar vice-versa."
+ (let ((repos (hash-table-keys paradox--user-starred-repos)))
+ (mapc #'paradox--star-repo
+ (seq-difference (seq-difference after before) repos))
+ (mapc #'paradox--unstar-repo
+ (seq-intersection (seq-difference before after) repos))))
+
+(provide 'paradox-execute)
+;;; paradox-execute.el ends here
--- /dev/null
+;;; paradox-github.el --- interacting with the Github API -*- lexical-binding:t -*-
+
+;; Copyright (C) 2014-2015 Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+
+;; Author: Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+;; Prefix: paradox
+;; Separator: -
+
+;;; License:
+;;
+;; This file is NOT part of GNU Emacs.
+;;
+;; 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.
+;;
+
+\f
+;;; Code:
+(require 'cl-lib)
+(require 'package)
+(require 'paradox-core)
+
+(defgroup paradox-github nil
+ "Paradox Github configurations."
+ :prefix "paradox-"
+ :package-version '(paradox . "2.0")
+ :group 'paradox)
+
+(defvar paradox--user-starred-list nil)
+(make-obsolete-variable
+ 'paradox--user-starred-list 'paradox--user-starred-repos "2.1")
+(defvar paradox--user-starred-repos (make-hash-table))
+
+\f
+;;; Github token
+(defcustom paradox-github-token nil
+ "Access token to use for github actions.
+Currently, that means (un)starring repos.
+
+To generate an access token:
+ 1. Visit the page https://github.com/settings/tokens/new and
+ login to github (if asked).
+ 2. Give the token any name you want (Paradox, for instance).
+ 3. The only permission we need is \"public_repo\", so unmark
+ all others.
+ 4. Click on \"Generate Token\", copy the generated token, and
+ save it to this variable by writing
+ (setq paradox-github-token TOKEN)
+ somewhere in your configuration and evaluating it (or just
+ restart emacs).
+
+This is similar to how erc or jabber handle authentication in
+emacs, but the following disclaimer always worth reminding.
+
+DISCLAIMER
+When you save this variable, DON'T WRITE IT ANYWHERE PUBLIC. This
+token grants (very) limited access to your account.
+END DISCLAIMER
+
+Paradox will ask you whether you want github integration the
+first time you start it. If you answer \"no\", it will remember
+your choice via `customize-save-variable'. You can do this
+manually by setting this variable to t. Setting it to nil means
+it hasn't been configured yet."
+ :type '(choice (string :tag "Token")
+ (const :tag "Disable" t)
+ (const :tag "Ask me next time" nil))
+ :group 'paradox-github
+ :package-version '(paradox . "0.2"))
+
+(defcustom paradox-automatically-star 'unconfigured
+ "When you install new packages, should they be automatically starred?
+This variable has no effect if `paradox-github-token' isn't set
+to a string.
+
+Paradox is capable of automatically starring packages when you
+install them, and unstarring when you delete them. This only
+applies to actual installation/deletion, i.e. Paradox doesn't
+auto (un)star packages that were simply upgraded.
+
+If this variable is nil, this behaviour is disabled. \\<paradox-menu-mode-map>
+
+On the Package Menu, you can always manually star packages with \\[paradox-menu-mark-star-unstar]."
+ :type '(choice (const :tag "Yes" t)
+ (const :tag "No" nil)
+ (const :tag "Ask later" unconfigured))
+ :group 'paradox-github
+ :package-version '(paradox . "0.2"))
+
+(defmacro paradox--enforce-github-token (&rest forms)
+ "If a token is defined, perform FORMS, otherwise ignore forms ask for it be defined."
+ `(if (stringp paradox-github-token)
+ (progn ,@forms)
+ (setq paradox-github-token nil)
+ (paradox--check-github-token)))
+
+(defun paradox--check-github-token ()
+ "Check that the user has either set or refused the github token.
+If neither has happened, ask the user now whether he'd like to
+configure or refuse the token."
+ (if (stringp paradox-github-token) t
+ (if paradox-github-token
+ t
+ (if (not (y-or-n-p "Would you like to set up GitHub integration?
+This will allow you to star/unstar packages from the Package Menu. "))
+ (customize-save-variable 'paradox-github-token t)
+ (describe-variable 'paradox-github-token)
+ (when (get-buffer "*Help*")
+ (switch-to-buffer "*Help*")
+ (delete-other-windows))
+ (if (y-or-n-p "Follow the instructions on the `paradox-github-token' variable.
+May I take you to the token generation page? ")
+ (browse-url "https://github.com/settings/tokens/new"))
+ (message "Once you're finished, simply call `paradox-list-packages' again.")
+ nil))))
+
+\f
+;;; Starring
+(defun paradox-star-all-installed-packages ()
+ "Star all of your currently installed packages.
+No questions asked."
+ (interactive)
+ (paradox--enforce-github-token
+ (mapc (lambda (x) (paradox--star-package-safe (car-safe x))) package-alist)))
+
+(defun paradox--starred-repo-p (repo)
+ "Non-nil if REPO is starred by the user."
+ (gethash repo paradox--user-starred-repos))
+
+(defun paradox--star-package-safe (pkg &optional delete query)
+ "Star PKG without throwing errors, unless DELETE is non-nil, then unstar.
+If QUERY is non-nil, ask the user first."
+ (let ((repo (gethash pkg paradox--package-repo-list)))
+ (when (and repo (paradox--starred-repo-p repo))
+ (paradox--star-repo repo delete query))))
+
+(defun paradox--star-repo (repo &optional delete query)
+ "Star REPO, unless DELETE is non-nil, then unstar.
+If QUERY is non-nil, ask the user first.
+
+Throws error if repo is malformed."
+ (when (or (not query)
+ (y-or-n-p (format "Really %sstar %s? "
+ (if delete "un" "") repo)))
+ (paradox--github-action-star repo delete)
+ (message "%starred %s." (if delete "Uns" "S") repo)
+ (if delete
+ (remhash repo paradox--user-starred-repos)
+ (puthash repo t paradox--user-starred-repos))))
+
+(defun paradox--unstar-repo (repo &optional delete query)
+ "Unstar REPO.
+Calls (paradox--star-repo REPO (not DELETE) QUERY)."
+ (paradox--star-repo repo (not delete) query))
+
+(defun paradox--full-name-reader ()
+ "Return all \"full_name\" properties in the buffer.
+Much faster than `json-read'."
+ (let (out)
+ (while (search-forward-regexp
+ "^ *\"full_name\" *: *\"\\(.*\\)\", *$" nil t)
+ (push (match-string-no-properties 1) out))
+ (goto-char (point-max))
+ out))
+
+(defun paradox--refresh-user-starred-list (&optional async)
+ "Fetch the user's list of starred repos."
+ (paradox--github-action
+ "user/starred?per_page=100"
+ :async (when async 'refresh)
+ :callback (lambda (res)
+ (setq paradox--user-starred-repos
+ (make-hash-table :size (length res)
+ :test #'equal))
+ (dolist (it res)
+ (puthash it t paradox--user-starred-repos)))
+ :reader #'paradox--full-name-reader))
+
+(defun paradox--github-action-star (repo &optional delete)
+ "Call `paradox--github-action' with \"user/starred/REPO\" as the action.
+DELETE and NO-RESULT are passed on."
+ (paradox--github-action (concat "user/starred/" repo)
+ :async t
+ :method (if (stringp delete) delete
+ (if delete "DELETE" "PUT"))))
+
+\f
+;;; The Base (generic) function
+(defun paradox--github-report (text)
+ "Write TEXT to the *Paradox Github* buffer."
+ (with-current-buffer (get-buffer-create "*Paradox Report*")
+ (erase-buffer)
+ (insert text)
+ (goto-char (point-min))))
+
+(defun paradox--github-error (format &rest args)
+ "Throw an error using FORMAT and ARGS.
+Also print contents of current buffer to *Paradox Github*."
+ (declare (indent 1))
+ (paradox--github-report (buffer-string))
+ (apply #'error
+ (concat format " See *Paradox Github* buffer for the full result")
+ args))
+
+(defvar paradox--github-errors-to-ignore nil
+ "List of numbers to ignore when parsing the HTML return code.
+`paradox--github-parse-response-code' normally returns nil on
+200, t on 204, and emits messages or errors on other values.
+Adding values to this list makes them be treated as a 200.")
+
+(defun paradox--github-parse-response-code ()
+ "Non-nil if this reponse buffer looks ok.
+Leave point at the return code on the first line."
+ (goto-char (point-min))
+ (unless (search-forward " " nil t)
+ (paradox--github-report (buffer-string))
+ (error "Tried contacting Github, but I can't understand the result. See *Paradox Github* buffer for the full result"))
+ (pcase (thing-at-point 'number)
+ ((pred (lambda (n) (member n paradox--github-errors-to-ignore))) nil)
+ (`204 nil) ;; OK, but no content.
+ (`200 t) ;; OK, with content.
+ ;; I'll implement redirection if anyone ever reports this.
+ ;; For now, I haven't found a place where it's used.
+ ((or `301 `302 `303 `304 `305 `306 `307)
+ (paradox--github-error
+ "Received a redirect reply, please file a bug report (M-x `paradox-bug-report')"))
+ ((or `403 `404) ;; Not found.
+ (paradox--github-report (buffer-string))
+ (message "This repo doesn't seem to exist, Github replied with: %s"
+ (substring (thing-at-point 'line) 0 -1))
+ nil)
+ ((or `400 `422) ;; Bad request.
+ (paradox--github-error
+ "Github didn't understand my request, please file a bug report (M-x `paradox-bug-report')"))
+ (`401 (paradox--github-error
+ (if (stringp paradox-github-token)
+ "Github says you're not authenticated, try creating a new Github token"
+ "Github says you're not authenticated, you need to configure `paradox-github-token'")))
+ (_ (paradox--github-error "Github returned: %S"
+ (substring (thing-at-point 'line) 0 -1)))))
+
+(defmacro paradox--with-github-buffer (method action async unwind-form
+ &rest body)
+ "Run BODY in a Github request buffer.
+UNWIND-FORM is run no matter what, and doesn't affect the return
+value."
+ (declare (indent 4)
+ (debug t))
+ (let ((call-name (make-symbol "callback")))
+ `(let ((,call-name
+ (lambda (&optional process event)
+ (cond
+ ((or (not event)
+ (string-match "\\`finished" event))
+ (with-current-buffer (if (processp process)
+ (process-buffer process)
+ (current-buffer))
+ (unwind-protect
+ (when (paradox--github-parse-response-code)
+ (let ((next-page))
+ (when (search-forward-regexp
+ "^Link: .*<\\([^>]+\\)>; rel=\"next\"" nil t)
+ (setq next-page (match-string-no-properties 1)))
+ (ignore next-page)
+ (search-forward-regexp "^\r?$")
+ (skip-chars-forward "[:blank:]\n\r")
+ (delete-region (point-min) (point))
+ ,@body))
+ ,unwind-form
+ (kill-buffer (current-buffer)))))
+ ((string-match "\\`exited abnormally" event)
+ ,unwind-form
+ (paradox--github-report (buffer-string))
+ (message "async curl command %s\n method: %s\n action: %s"
+ event ,method ,action))))))
+ (if ,async
+ (condition-case nil
+ (set-process-sentinel
+ (apply #'start-process "paradox-github"
+ (generate-new-buffer "*Paradox http*")
+ "curl" "-s" "-i" "-d" "" "-X" ,method ,action
+ (when (stringp paradox-github-token)
+ (list "-u" (concat paradox-github-token ":x-oauth-basic"))))
+ ,call-name)
+ (error ,unwind-form))
+ (with-temp-buffer
+ ;; Make the request.
+ (condition-case nil
+ (apply #'call-process
+ "curl" nil t nil "-s" "-i" "-d" "" "-X" ,method ,action
+ (when (stringp paradox-github-token)
+ (list "-u" (concat paradox-github-token ":x-oauth-basic"))))
+ (error ,unwind-form))
+ ;; Do the processing.
+ (funcall ,call-name))))))
+
+(cl-defun paradox--github-action (action &key
+ (method "GET")
+ reader
+ max-pages
+ (callback #'identity)
+ async)
+ "Contact the github api performing ACTION with METHOD.
+Default METHOD is \"GET\".
+
+Action can be anything such as \"user/starred?per_page=100\". If
+it's not a full url, it will be prepended with
+\"https://api.github.com/\". The action might not work if
+`paradox-github-token' isn't set.
+
+This function also handles the pagination used in github results,
+results of each page are appended together. Use MAX-PAGES to
+limit the number of pages that are fetched.
+
+Return value is always a list.
+- If READER is nil, the result of the action is completely
+ ignored (no pagination is performed on this case, making it
+ much faster).
+- Otherwise, READER is called as a function with point right
+ after the headers and should always return a list. As a special
+ exception, if READER is t, it is equivalent to a function that
+ returns (t).
+
+CALLBACK, if provided, is a function to be called with the read
+data as an argument. If the request succeeds with no data, it
+will be given nil as an argument. Its return value is returned by
+this function.
+
+ASYNC determines to run the command asynchronously. In this case,
+the function's return value is undefined. In particular, if ASYNC
+is the symbol refresh, it means the package-menu should be
+refreshed after the operation is done."
+ ;; Make sure the token's configured.
+ (unless (string-match "\\`https?://" action)
+ (setq action (concat "https://api.github.com/" action)))
+ (let ((do-update (when (eq async 'refresh)
+ (make-symbol "paradox-github"))))
+ (when do-update
+ (add-to-list 'package--downloads-in-progress do-update))
+ (paradox--with-github-buffer method action async
+ (paradox--update-downloads-in-progress
+ do-update)
+ (cond
+ ((not reader)
+ (funcall callback nil))
+ ((or (not next-page)
+ (and max-pages (< max-pages 2)))
+ (funcall callback
+ (unless (eobp) (funcall reader))))
+ (t
+ (let ((result (unless (eobp) (funcall reader))))
+ (paradox--github-action
+ next-page
+ :method method
+ :reader reader
+ :async async
+ :max-pages (when max-pages (1- max-pages))
+ :callback (lambda (res)
+ (funcall callback
+ (append result res))))))))))
+
+(provide 'paradox-github)
+;;; paradox-github.el ends here
--- /dev/null
+;;; paradox-menu.el --- defining the Packages menu -*- lexical-binding:t -*-
+
+;; Copyright (C) 2014-2015 Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+
+;; Author: Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+;; Prefix: paradox
+;; Separator: -
+
+;;; License:
+;;
+;; This file is NOT part of GNU Emacs.
+;;
+;; 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.
+;;
+
+;;; Change Log:
+;; \f
+;;; Code:
+(require 'cl-lib)
+(require 'package)
+(require 'subr-x)
+
+(require 'paradox-core)
+(require 'paradox-github)
+(require 'paradox-commit-list)
+(require 'paradox-execute)
+
+(defgroup paradox-menu nil
+ "Paradox Packages Menu configurations."
+ :prefix "paradox-"
+ :package-version '(paradox . "2.0")
+ :group 'paradox)
+
+\f
+;;; Customization Variables
+(defcustom paradox-column-width-package 18
+ "Width of the \"Package\" column."
+ :type 'integer
+ :group 'paradox-menu
+ :package-version '(paradox . "0.1"))
+
+(defcustom paradox-column-width-version 9
+ "Width of the \"Version\" column."
+ :type 'integer
+ :group 'paradox-menu
+ :package-version '(paradox . "0.1"))
+
+(defcustom paradox-column-width-status 10
+ "Width of the \"Status\" column."
+ :type 'integer
+ :group 'paradox-menu
+ :package-version '(paradox . "0.1"))
+
+(defcustom paradox-column-width-star 4
+ "Width of the \"Star\" column."
+ :type 'integer
+ :group 'paradox-menu
+ :package-version '(paradox . "0.1"))
+
+(defcustom paradox-column-width-download 4
+ "Width of the \"Download Count\" column."
+ :type 'integer
+ :group 'paradox-menu
+ :package-version '(paradox . "1.1"))
+
+(defcustom paradox-display-star-count t
+ "If non-nil, adds a \"Star\" column to the Package Menu."
+ :type 'boolean
+ :group 'paradox-menu
+ :package-version '(paradox . "1.1"))
+
+(defcustom paradox-display-download-count nil
+ "If non-nil, adds a \"Download\" column to the Package Menu."
+ :type 'boolean
+ :group 'paradox-menu
+ :package-version '(paradox . "1.2.3"))
+
+(defface paradox-mode-line-face
+ '((t :inherit mode-line-buffer-id :weight normal :foreground "Black"))
+ "Face used on the package's name."
+ :group 'paradox)
+(defface paradox-name-face
+ '((t :inherit link))
+ "Face used on the package's name."
+ :group 'paradox)
+(defface paradox-homepage-button-face
+ '((t :underline t :inherit font-lock-comment-face))
+ "Face used on the homepage button."
+ :group 'paradox)
+;; (defface paradox-version-face
+;; '((t :inherit default))
+;; "Face used on the version column."
+;; :group 'paradox)
+(defface paradox-archive-face
+ '((t :inherit paradox-comment-face))
+ "Face used on the archive column."
+ :group 'paradox)
+(defface paradox-star-face
+ '((t :inherit font-lock-string-face))
+ "Face used on the star column, for packages you haven't starred."
+ :group 'paradox)
+(defface paradox-starred-face
+ '((t :inherit font-lock-variable-name-face))
+ "Face used on the star column, for packages you have starred."
+ :group 'paradox)
+(defface paradox-download-face
+ '((t :inherit font-lock-keyword-face))
+ "Face used on the Downloads column."
+ :group 'paradox)
+(defface paradox-description-face
+ '((t :inherit default))
+ "Face used on the description column.
+If `paradox-lines-per-entry' > 1, the face
+`paradox-description-face-multiline' is used instead."
+ :group 'paradox)
+(defface paradox-description-face-multiline
+ '((t :inherit font-lock-doc-face))
+ "Face used on the description column when `paradox-lines-per-entry' > 1.
+If `paradox-lines-per-entry' = 1, the face
+`paradox-description-face' is used instead."
+ :group 'paradox)
+
+(defcustom paradox-status-face-alist
+ '(("built-in" . font-lock-builtin-face)
+ ("available" . default)
+ ("new" . bold)
+ ("held" . font-lock-constant-face)
+ ("disabled" . font-lock-warning-face)
+ ("avail-obso" . font-lock-comment-face)
+ ("installed" . font-lock-comment-face)
+ ("dependency" . font-lock-comment-face)
+ ("incompat" . font-lock-comment-face)
+ ("deleted" . font-lock-comment-face)
+ ("unsigned" . font-lock-warning-face))
+ "List of (\"STATUS\" . FACE) cons cells.
+When displaying the package menu, FACE will be used to paint the
+Version, Status, and Description columns of each package whose
+status is STATUS."
+ :type '(repeat (cons string face))
+ :group 'paradox-menu
+ :package-version '(paradox . "2.0"))
+
+(defcustom paradox-homepage-button-string "h"
+ "String used to for the link that takes you to a package's homepage."
+ :type 'string
+ :group 'paradox-menu
+ :package-version '(paradox . "0.10"))
+
+(defcustom paradox-use-homepage-buttons t
+ "If non-nil a button will be added after the name of each package.
+This button takes you to the package's homepage."
+ :type 'boolean
+ :group 'paradox-menu
+ :package-version '(paradox . "0.10"))
+
+(defcustom paradox-lines-per-entry 1
+ "Number of lines used to display each entry in the Package Menu.
+1 Gives you the regular package menu.
+2 Displays the description on a separate line below the entry.
+3+ Adds empty lines separating the entries."
+ :type 'integer
+ :group 'paradox-menu
+ :package-version '(paradox . "0.10"))
+
+\f
+;;; Internal
+(defvar-local paradox--current-filter nil)
+
+(defvar paradox--column-name-star
+ (if (char-displayable-p ?\x2605) "\x2605" "*"))
+
+(defvar paradox--column-name-download
+ (if (char-displayable-p ?\x2193) "\x2193" "DC"))
+
+(defvar paradox--upgradeable-packages nil)
+(defvar paradox--upgradeable-packages-number nil)
+(defvar paradox--upgradeable-packages-any? nil)
+
+(defvar paradox--column-index-star nil)
+(defvar paradox--column-index-download nil)
+
+(defvar paradox--desc-suffix nil)
+(defvar paradox--desc-prefix nil)
+
+(defvar paradox--commit-list-buffer "*Package Commit List*")
+
+\f
+;;; Building the packages buffer.
+(defun paradox-refresh-upgradeable-packages ()
+ "Refresh the list of upgradeable packages."
+ (interactive)
+ (setq paradox--upgradeable-packages (package-menu--find-upgrades))
+ (setq paradox--upgradeable-packages-number
+ (length paradox--upgradeable-packages))
+ (setq paradox--upgradeable-packages-any?
+ (> paradox--upgradeable-packages-number 0)))
+
+(defun paradox--print-info (pkg)
+ "Return a package entry suitable for `tabulated-list-entries'.
+PKG has the form (PKG-DESC . STATUS).
+Return (PKG-DESC [STAR NAME VERSION STATUS DOC])."
+ (let* ((pkg-desc (if (consp pkg) (car pkg) pkg))
+ (status (if (consp pkg) (cdr pkg) (package-desc-status pkg)))
+ (face (or (cdr (assoc-string status paradox-status-face-alist))
+ 'font-lock-warning-face))
+ (url (paradox--package-homepage pkg-desc))
+ (name (symbol-name (package-desc-name pkg-desc)))
+ (name-length (length name))
+ (button-length (length paradox-homepage-button-string)))
+ (paradox--incf status)
+ (list pkg-desc
+ `[,(concat
+ (propertize name
+ 'font-lock-face 'paradox-name-face
+ 'button t
+ 'follow-link t
+ 'help-echo (format "Package: %s" name)
+ 'package-desc pkg-desc
+ 'action 'package-menu-describe-package)
+ (if (and paradox-use-homepage-buttons url
+ (< (+ name-length button-length) paradox-column-width-package))
+ (concat
+ (make-string (- paradox-column-width-package name-length button-length) ?\s)
+ (propertize paradox-homepage-button-string
+ 'font-lock-face 'paradox-homepage-button-face
+ 'mouse-face 'custom-button-mouse
+ 'help-echo (format "Visit %s" url)
+ 'button t
+ 'follow-link t
+ 'keymap '(keymap (mouse-2 . push-button))
+ 'action #'paradox-menu-visit-homepage))
+ ""))
+ ,(propertize (package-version-join
+ (package-desc-version pkg-desc))
+ 'font-lock-face face)
+ ,(propertize status 'font-lock-face face)
+ ,@(if (cdr package-archives)
+ (list (propertize (or (package-desc-archive pkg-desc) "")
+ 'font-lock-face 'paradox-archive-face)))
+ ,@(paradox--count-print (package-desc-name pkg-desc))
+ ,(propertize
+ (concat (propertize " " 'display paradox--desc-prefix)
+ (package-desc-summary pkg-desc)
+ (propertize " " 'display paradox--desc-suffix)) ;└╰
+ 'font-lock-face
+ (if (> paradox-lines-per-entry 1)
+ 'paradox-description-face-multiline
+ 'paradox-description-face))])))
+
+(defun paradox--count-print (pkg)
+ "Return counts of PKG as a package-desc list."
+ (append
+ (when (and paradox-display-star-count (hash-table-p paradox--star-count))
+ (list (paradox--package-star-count pkg)))
+ (when (and paradox-display-download-count (hash-table-p paradox--download-count))
+ (list (paradox--package-download-count pkg)))))
+
+(defun paradox--package-download-count (pkg)
+ "Return propertized string with the download count of PKG."
+ (let ((c (gethash pkg paradox--download-count nil)))
+ (propertize
+ (if (numberp c)
+ (if (> c 999) (format "%sK" (truncate c 1000)) (format "%s" c))
+ " ")
+ 'font-lock-face 'paradox-download-face
+ 'value (or c 0))))
+
+(defun paradox--package-homepage (pkg)
+ "PKG can be the package-name symbol or a package-desc object."
+ (let* ((object (if (symbolp pkg) (cadr (assoc pkg package-archive-contents)) pkg))
+ (name (if (symbolp pkg) pkg (package-desc-name pkg)))
+ (extras (package-desc-extras object))
+ (homepage (and (listp extras) (cdr-safe (assoc :url extras)))))
+ (or homepage
+ (and (setq extras (gethash name paradox--package-repo-list))
+ (format "https://github.com/%s" extras)))))
+(defun paradox--get-or-return-package (pkg)
+ "Take a marker or package name PKG and return a package name."
+ (if (or (markerp pkg) (null pkg))
+ (if (derived-mode-p 'package-menu-mode)
+ (package-desc-name (tabulated-list-get-id))
+ (error "Not in Package Menu"))
+ pkg))
+
+(defun paradox--incf (status)
+ "Increment the count for STATUS on `paradox--package-count'.
+Also increments the count for \"total\"."
+ (paradox--inc-count status)
+ (unless (member status '("obsolete" "avail-obso" "incompat"))
+ (paradox--inc-count "total")))
+
+(defun paradox--inc-count (string)
+ "Increment the cdr of (assoc-string STRING paradox--package-count)."
+ (let ((cons (assoc-string string paradox--package-count)))
+ (setcdr cons (1+ (cdr cons)))))
+
+(defun paradox--entry-star-count (entry)
+ "Get the star count of the package in ENTRY."
+ (paradox--package-star-count
+ ;; The package symbol should be in the ID field, but that's not mandatory,
+ (or (ignore-errors (elt (car entry) 1))
+ ;; So we also try interning the package name.
+ (intern (car (elt (cadr entry) 0))))))
+
+(defun paradox--handle-failed-download (&rest _)
+ "Handle the case when Emacs fails to download Github data."
+ (paradox--update-downloads-in-progress 'paradox--data)
+ (unless (hash-table-p paradox--download-count)
+ (setq paradox--download-count (make-hash-table)))
+ (unless (hash-table-p paradox--package-repo-list)
+ (setq paradox--package-repo-list (make-hash-table)))
+ (unless (hash-table-p paradox--star-count)
+ (setq paradox--star-count (make-hash-table)))
+ (unless (hash-table-p paradox--wiki-packages)
+ (setq paradox--wiki-packages (make-hash-table)))
+ (message "[Paradox] Error downloading Github data"))
+
+(defmacro paradox--with-work-buffer (location file &rest body)
+ "Run BODY in a buffer containing the contents of FILE at LOCATION.
+This is the same as `package--with-work-buffer-async', except it
+automatically decides whether to download asynchronously based on
+`package-menu-async'."
+ (declare (indent 2) (debug t))
+ (require 'package)
+ (if (fboundp 'package--with-work-buffer-async)
+ `(package--with-work-buffer-async
+ ,location ,file
+ (when package-menu-async
+ #'paradox--handle-failed-download)
+ ,@body
+ (paradox--update-downloads-in-progress 'paradox--data))
+ `(package--with-work-buffer ,location ,file ,@body)))
+
+(defun paradox--refresh-star-count ()
+ "Download the star-count file and populate the respective variable."
+ (interactive)
+ (when (boundp 'package--downloads-in-progress)
+ (add-to-list 'package--downloads-in-progress 'paradox--data))
+ (condition-case-unless-debug nil
+ (paradox--with-work-buffer paradox--data-url "data-hashtables"
+ (setq paradox--star-count (read (current-buffer)))
+ (setq paradox--package-repo-list (read (current-buffer)))
+ (setq paradox--download-count (read (current-buffer)))
+ (setq paradox--wiki-packages (read (current-buffer))))
+ (error (paradox--handle-failed-download))))
+
+(defun paradox--package-star-count (package)
+ "Get the star count of PACKAGE."
+ (let ((count (gethash package paradox--star-count nil))
+ (repo (gethash package paradox--package-repo-list nil)))
+ (propertize
+ (format "%s" (or count ""))
+ 'font-lock-face
+ (if (and repo (paradox--starred-repo-p repo))
+ 'paradox-starred-face
+ 'paradox-star-face))))
+
+(defun paradox--star-predicate (A B)
+ "Non-nil t if star count of A is larget than B."
+ (> (string-to-number (elt (cadr A) paradox--column-index-star))
+ (string-to-number (elt (cadr B) paradox--column-index-star))))
+(defun paradox--download-predicate (A B)
+ "Non-nil t if download count of A is larget than B."
+ (> (get-text-property 0 'value (elt (cadr A) paradox--column-index-download))
+ (get-text-property 0 'value (elt (cadr B) paradox--column-index-download))))
+
+(defun paradox--generate-menu (remember-pos packages &optional keywords)
+ "Populate the Package Menu, without hacking into the header-format.
+If REMEMBER-POS is non-nil, keep point on the same entry.
+PACKAGES should be t, which means to display all known packages,
+or a list of package names (symbols) to display.
+
+With KEYWORDS given, only packages with those keywords are
+shown."
+ (paradox-menu--refresh packages keywords)
+ (setq paradox--current-filter
+ (if keywords (mapconcat 'identity keywords ",")
+ nil))
+ (let ((idx (paradox--column-index "Package")))
+ (setcar (elt tabulated-list-format idx)
+ (if keywords
+ (concat "Package[" paradox--current-filter "]")
+ "Package")))
+ (tabulated-list-print remember-pos)
+ (tabulated-list-init-header)
+ (paradox--update-mode-line))
+
+(defcustom paradox-hide-wiki-packages nil
+ "If non-nil, don't display packages from the emacswiki."
+ :type 'boolean)
+
+(defun paradox--maybe-remove-wiki-packages (pkgs)
+ "Remove wiki packages from PKGS.
+If `paradox-hide-wiki-packages' is nil, just return PKGS."
+ (if (not paradox-hide-wiki-packages)
+ pkgs
+ (remq nil
+ (mapcar
+ (lambda (entry)
+ (unless (gethash (car entry) paradox--wiki-packages)
+ (car entry)))
+ (if (or (not pkgs) (eq t pkgs))
+ package-archive-contents
+ pkgs)))))
+
+(defun paradox-menu--refresh (&optional packages keywords)
+ "Call `package-menu--refresh' retaining current filter.
+PACKAGES and KEYWORDS are passed to `package-menu--refresh'. If
+KEYWORDS is nil and `paradox--current-filter' is non-nil, it is
+used to define keywords."
+ (mapc (lambda (x) (setf (cdr x) 0)) paradox--package-count)
+ (let ((paradox--desc-prefix (if (> paradox-lines-per-entry 1) " \n " ""))
+ (paradox--desc-suffix (make-string (max 0 (- paradox-lines-per-entry 2)) ?\n)))
+ (cond
+ ((or packages keywords (not paradox--current-filter))
+ (package-menu--refresh
+ (paradox--maybe-remove-wiki-packages packages)
+ keywords)
+ (paradox-refresh-upgradeable-packages))
+ ((string= paradox--current-filter "Upgradable")
+ (paradox-refresh-upgradeable-packages)
+ (paradox-filter-upgrades))
+ ((string= paradox--current-filter "Starred")
+ (paradox-filter-stars)
+ (paradox-refresh-upgradeable-packages))
+ ((string-match "\\`Regexp:\\(.*\\)\\'" paradox--current-filter)
+ (paradox-filter-regexp (match-string 1 paradox--current-filter))
+ (paradox-refresh-upgradeable-packages))
+ (t
+ (paradox-menu--refresh
+ packages (split-string paradox--current-filter ","))))))
+
+(defun paradox--column-index (regexp)
+ "Find the index of the column that matches REGEXP."
+ (cl-position (format "\\`%s\\'" (regexp-quote regexp)) tabulated-list-format
+ :test (lambda (x y) (string-match x (or (car-safe y) "")))))
+
+(defun paradox--count-format ()
+ "List of star/download counts to be used as part of the entry."
+ (remove
+ nil
+ (list
+ (when paradox-display-star-count
+ (list paradox--column-name-star paradox-column-width-star
+ 'paradox--star-predicate :right-align t))
+ (when paradox-display-download-count
+ (list paradox--column-name-download paradox-column-width-download
+ 'paradox--download-predicate :right-align t)))))
+
+(defun paradox--archive-format ()
+ "List containing archive to be used as part of the entry."
+ (when (cdr package-archives)
+ (list (list "Archive"
+ (apply 'max (mapcar 'length (mapcar 'car package-archives)))
+ 'package-menu--archive-predicate))))
+
+(add-hook 'paradox-menu-mode-hook 'paradox-refresh-upgradeable-packages)
+
+\f
+;;; Mode Definition
+(define-derived-mode paradox-menu-mode tabulated-list-mode "Paradox Menu"
+ "Major mode for browsing a list of packages.
+Letters do not insert themselves; instead, they are commands.
+\\<paradox-menu-mode-map>
+\\{paradox-menu-mode-map}"
+ (hl-line-mode 1)
+ (when (boundp 'package--post-download-archives-hook)
+ (add-hook 'package--post-download-archives-hook
+ #'paradox--stop-spinner))
+ (if (boundp 'package--downloads-in-progress)
+ (setq mode-line-process
+ '("" (package--downloads-in-progress
+ (":Loading "
+ (paradox--spinner
+ (:eval (spinner-print paradox--spinner))
+ (:eval (paradox--start-spinner))))
+ (paradox--spinner
+ (":Executing " (:eval (spinner-print paradox--spinner)))))))
+ (setq mode-line-process
+ '(paradox--spinner
+ (":Executing " (:eval (spinner-print paradox--spinner))))))
+ (paradox--update-mode-line)
+ (setq tabulated-list-format
+ `[("Package" ,paradox-column-width-package package-menu--name-predicate)
+ ("Version" ,paradox-column-width-version nil)
+ ("Status" ,paradox-column-width-status package-menu--status-predicate)
+ ,@(paradox--archive-format)
+ ,@(paradox--count-format)
+ ("Description" 0 nil)])
+ (setq paradox--column-index-star
+ (paradox--column-index paradox--column-name-star))
+ (setq paradox--column-index-download
+ (paradox--column-index paradox--column-name-download))
+ (setq tabulated-list-padding 2)
+ (setq tabulated-list-sort-key (cons "Status" nil))
+ (add-hook 'tabulated-list-revert-hook #'paradox-menu--refresh nil t)
+ (add-hook 'tabulated-list-revert-hook #'paradox-refresh-upgradeable-packages nil t)
+ ;; (add-hook 'tabulated-list-revert-hook #'paradox--refresh-star-count nil t)
+ (add-hook 'tabulated-list-revert-hook #'paradox--update-mode-line 'append t)
+ (tabulated-list-init-header)
+ ;; We need package-menu-mode to be our parent, otherwise some
+ ;; commands throw errors. But we can't actually derive from it,
+ ;; otherwise its initialization will screw up the header-format. So
+ ;; we "patch" it like this.
+ (put 'paradox-menu-mode 'derived-mode-parent 'package-menu-mode)
+ (run-hooks 'package-menu-mode-hook))
+
+(defun paradox--define-sort (name &optional key)
+ "Define sorting by column NAME and bind it to KEY.
+Defines a function called paradox-sort-by-NAME."
+ (let ((symb (intern (format "paradox-sort-by-%s" (downcase name))))
+ (key (or key (substring name 0 1))))
+ (eval
+ `(progn
+ (defun ,symb
+ (invert)
+ ,(format "Sort Package Menu by the %s column." name)
+ (interactive "P")
+ (when invert
+ (setq tabulated-list-sort-key (cons ,name nil)))
+ (tabulated-list--sort-by-column-name ,name))
+ (define-key paradox-menu-mode-map ,(concat "S" (upcase key)) ',symb)
+ (define-key paradox-menu-mode-map ,(concat "S" (downcase key)) ',symb)))))
+
+(paradox--define-sort "Package")
+(paradox--define-sort "Status")
+(paradox--define-sort paradox--column-name-star "*")
+(declare-function paradox-sort-by-package "paradox-menu")
+
+(defalias 'paradox-filter-clear #'package-show-package-list
+ "Clear current Package filter.
+Redisplay the Packages buffer listing all packages, without
+fetching the list.")
+
+(defmacro paradox--apply-filter (name packages &optional nil-message)
+ "Apply filter called NAME (a string) listing only PACKAGES.
+PACKAGES should be a list of symbols (the names of packages to
+display) or a list of cons cells whose `car's are symbols.
+NIL-MESSAGE is the message to show if PACKAGES is nil, and
+defaults to: \"No %s packages\"."
+ (declare (debug t)
+ (indent 1))
+ (let* ((n (format "%s" name))
+ (cn (capitalize n))
+ (dn (downcase n)))
+ (macroexp-let2 macroexp-copyable-p pl packages
+ `(if (null ,pl)
+ (user-error ,(or nil-message (format "No %s packages." dn)))
+ (package-show-package-list
+ (mapcar (lambda (p) (or (car-safe p) p)) ,pl))
+ (setq paradox--current-filter ,cn)))))
+
+(defun paradox-filter-upgrades ()
+ "Show only upgradable packages."
+ (interactive)
+ (paradox--apply-filter Upgradable
+ paradox--upgradeable-packages)
+ (paradox-sort-by-package nil))
+
+(defun paradox-filter-stars ()
+ "Show only starred packages."
+ (interactive)
+ (let ((list))
+ (maphash (lambda (pkg repo)
+ (when (paradox--starred-repo-p repo)
+ (push pkg list)))
+ paradox--package-repo-list)
+ (paradox--apply-filter Starred list)))
+
+(defun paradox-filter-regexp (regexp)
+ "Show only packages matching REGEXP.
+Test match against name and summary."
+ (interactive (list (read-regexp "Enter Regular Expression: ")))
+ (paradox--apply-filter Regexp
+ (cl-remove-if-not
+ (lambda (package)
+ (or (string-match-p regexp (symbol-name (car package)))
+ (string-match-p regexp (package-desc-summary (cadr package)))))
+ package-archive-contents)
+ "No packages match this regexp.")
+ (setq paradox--current-filter (concat "Regexp:" regexp)))
+
+(set-keymap-parent paradox-menu-mode-map package-menu-mode-map)
+(defvar paradox--filter-map)
+(define-prefix-command 'paradox--filter-map)
+(define-key paradox-menu-mode-map "q" #'paradox-quit-and-close)
+(define-key paradox-menu-mode-map "p" #'paradox-previous-entry)
+(define-key paradox-menu-mode-map "n" #'paradox-next-entry)
+(define-key paradox-menu-mode-map "k" #'paradox-previous-describe)
+(define-key paradox-menu-mode-map "j" #'paradox-next-describe)
+(define-key paradox-menu-mode-map "f" 'paradox--filter-map)
+(define-key paradox-menu-mode-map "s" #'paradox-menu-mark-star-unstar)
+(define-key paradox-menu-mode-map "h" #'paradox-menu-quick-help)
+(define-key paradox-menu-mode-map "v" #'paradox-menu-visit-homepage)
+(define-key paradox-menu-mode-map "l" #'paradox-menu-view-commit-list)
+(define-key paradox-menu-mode-map "x" #'paradox-menu-execute)
+(define-key paradox-menu-mode-map "\r" #'paradox-push-button)
+(define-key paradox-menu-mode-map "F" 'package-menu-filter)
+(define-key paradox--filter-map "k" #'package-menu-filter)
+(define-key paradox--filter-map "f" #'package-menu-filter)
+(define-key paradox--filter-map "r" #'paradox-filter-regexp)
+(define-key paradox--filter-map "u" #'paradox-filter-upgrades)
+(define-key paradox--filter-map "s" #'paradox-filter-stars)
+(define-key paradox--filter-map "c" #'paradox-filter-clear)
+
+\f
+;;; Menu Mode Commands
+(defun paradox-previous-entry (&optional n)
+ "Move to previous entry, which might not be the previous line.
+With prefix N, move to the N-th previous entry."
+ (interactive "p")
+ (paradox-next-entry (- n))
+ (forward-line 0)
+ (forward-button 1))
+
+(defun paradox-next-entry (&optional n)
+ "Move to next entry, which might not be the next line.
+With prefix N, move to the N-th next entry."
+ (interactive "p")
+ (dotimes (_ (abs n))
+ (let ((d (cl-signum n)))
+ (forward-line (if (> n 0) 1 0))
+ (if (eobp) (forward-line -1))
+ (forward-button d))))
+
+(defun paradox-next-describe (&optional n)
+ "Move to the next package and describe it.
+With prefix N, move to the N-th next package instead."
+ (interactive "p")
+ (paradox-next-entry n)
+ (call-interactively 'package-menu-describe-package))
+
+(defun paradox-previous-describe (&optional n)
+ "Move to the previous package and describe it.
+With prefix N, move to the N-th previous package instead."
+ (interactive "p")
+ (paradox-previous-entry n)
+ (call-interactively 'package-menu-describe-package))
+
+(defun paradox-push-button ()
+ "Push button under point, or describe package."
+ (interactive)
+ (if (get-text-property (point) 'action)
+ (call-interactively 'push-button)
+ (call-interactively 'package-menu-describe-package)))
+
+(defvar paradox--key-descriptors
+ '(("next," "previous," "install," "delete," ("execute," . 1) "refresh," "help")
+ ("star," "visit homepage")
+ ("list commits")
+ ("filter by" "+" "upgrades" "regexp" "keyword" "starred" "clear")
+ ("Sort by" "+" "Package name" "Status" "*(star)")))
+
+(defun paradox-menu-quick-help ()
+ "Show short key binding help for `paradox-menu-mode'.
+The full list of keys can be viewed with \\[describe-mode]."
+ (interactive)
+ (message (mapconcat 'paradox--prettify-key-descriptor
+ paradox--key-descriptors "\n")))
+
+(defun paradox-quit-and-close (kill)
+ "Bury this buffer and close the window.
+With prefix KILL, kill the buffer instead of burying."
+ (interactive "P")
+ (let ((log (get-buffer-window paradox--commit-list-buffer)))
+ (when (window-live-p log)
+ (quit-window kill log))
+ (quit-window kill)))
+
+(defun paradox-menu-visit-homepage (pkg)
+ "Visit the homepage of package named PKG.
+PKG is a symbol. Interactively it is the package under point."
+ (interactive '(nil))
+ (let ((url (paradox--package-homepage
+ (paradox--get-or-return-package pkg))))
+ (if (stringp url)
+ (browse-url url)
+ (message "Package %s has no homepage."
+ (propertize (symbol-name pkg)
+ 'face 'font-lock-keyword-face)))))
+
+(defun paradox-menu-mark-star-unstar ()
+ "Star or unstar a package and move to the next line."
+ (interactive)
+ (paradox--enforce-github-token
+ (unless paradox--user-starred-repos
+ (paradox--refresh-user-starred-list))
+ ;; Get package name
+ (let* ((pkg (paradox--get-or-return-package nil))
+ (repo (gethash pkg paradox--package-repo-list))
+ will-delete)
+ (unless pkg (error "Couldn't find package-name for this entry"))
+ ;; (Un)Star repo
+ (if (not repo)
+ (message "This package is not a GitHub repo.")
+ (setq will-delete (paradox--starred-repo-p repo))
+ (paradox--star-repo repo will-delete)
+ (cl-incf (gethash pkg paradox--star-count 0)
+ (if will-delete -1 1))
+ (tabulated-list-set-col paradox--column-name-star
+ (paradox--package-star-count pkg)))))
+ (forward-line 1))
+
+(defun paradox-menu-view-commit-list (pkg)
+ "Visit the commit list of package named PKG.
+PKG is a symbol. Interactively it is the package under point."
+ (interactive '(nil))
+ (let* ((name (paradox--get-or-return-package pkg))
+ (repo (gethash name paradox--package-repo-list)))
+ (if repo
+ (with-selected-window
+ (display-buffer (get-buffer-create paradox--commit-list-buffer))
+ (paradox-commit-list-mode)
+ (setq paradox--package-repo repo)
+ (setq paradox--package-name name)
+ (setq paradox--package-version
+ (paradox--get-installed-version name))
+ (setq paradox--package-tag-commit-alist
+ (paradox--get-tag-commit-alist repo))
+ (paradox--commit-list-update-entries)
+ (tabulated-list-print))
+ (message "Package %s is not a GitHub repo." pkg))))
+
+\f
+;;; Mode-line Construction
+(defcustom paradox-local-variables
+ '(mode-line-mule-info
+ mode-line-client
+ mode-line-remote mode-line-position
+ column-number-mode size-indication-mode)
+ "Variables which will take special values on the Packages buffer.
+This is a list, where each element is either SYMBOL or (SYMBOL . VALUE).
+
+Each SYMBOL (if it is bound) will be locally set to VALUE (or
+nil) on the Packages buffer."
+ :type '(repeat (choice symbol (cons symbol sexp)))
+ :group 'paradox-menu
+ :package-version '(paradox . "0.1"))
+
+(defcustom paradox-display-buffer-name nil
+ "If nil, *Packages* buffer name won't be displayed in the mode-line."
+ :type 'boolean
+ :group 'paradox-menu
+ :package-version '(paradox . "0.2"))
+
+(defun paradox--build-buffer-id (st n)
+ "Return a list that propertizes ST and N for the mode-line."
+ `((:propertize ,st
+ face paradox-mode-line-face)
+ (:propertize ,(int-to-string n)
+ face mode-line-buffer-id)))
+
+(defun paradox--update-mode-line ()
+ "Update `mode-line-format'."
+ (mapc #'paradox--set-local-value paradox-local-variables)
+ (let ((total-lines (int-to-string (length tabulated-list-entries))))
+ (paradox--update-mode-line-front-space total-lines)
+ (paradox--update-mode-line-buffer-identification total-lines))
+ (set-face-foreground
+ 'paradox-mode-line-face
+ (let ((fg (or (face-foreground 'mode-line-buffer-id nil t)
+ (face-foreground 'default nil t))))
+ (when fg
+ (if (> (color-distance "white" fg)
+ (color-distance "black" fg))
+ "black" "white")))))
+
+(defun paradox--update-mode-line-buffer-identification (_total-lines)
+ "Update `mode-line-buffer-identification'.
+TOTAL-LINES is currently unused."
+ (require 'spinner)
+ (setq mode-line-buffer-identification
+ `((paradox-display-buffer-name
+ ,(propertized-buffer-identification
+ (format "%%%sb" (length (buffer-name)))))
+ (paradox--current-filter (:propertize ("[" paradox--current-filter "]") face paradox-mode-line-face))
+ (paradox--upgradeable-packages-any?
+ (:eval (paradox--build-buffer-id " Upgrade:" paradox--upgradeable-packages-number)))
+ (package-menu--new-package-list
+ (:eval (paradox--build-buffer-id " New:" (paradox--cas "new"))))
+ ,(paradox--build-buffer-id " Installed:" (+ (paradox--cas "installed")
+ (paradox--cas "dependency")
+ (paradox--cas "unsigned")))
+ (paradox--current-filter
+ "" ,(paradox--build-buffer-id " Total:" (length package-archive-contents)))
+ (spinner-current " Executing Transaction"))))
+
+(defvar sml/col-number)
+(defvar sml/numbers-separator)
+(defvar sml/col-number-format)
+(defvar sml/line-number-format)
+(defvar sml/position-construct)
+(declare-function sml/compile-position-construct "sml")
+(defvar sml/post-id-separator)
+(defun paradox--update-mode-line-front-space (total-lines)
+ "Update `mode-line-front-space'.
+TOTAL-LINES is the number of lines in the buffer."
+ (if (memq 'sml/post-id-separator mode-line-format)
+ (progn
+ (add-to-list (make-local-variable 'mode-line-front-space)
+ (propertize " (" 'face 'sml/col-number))
+ (setq column-number-mode line-number-mode)
+ (set (make-local-variable 'sml/numbers-separator) "")
+ (set (make-local-variable 'sml/col-number-format)
+ (format "/%s)" total-lines))
+ (set (make-local-variable 'sml/line-number-format)
+ (format "%%%sl" (length total-lines)))
+ (make-local-variable 'sml/position-construct)
+ (sml/compile-position-construct))
+ (set (make-local-variable 'mode-line-front-space)
+ `(line-number-mode
+ ("(" (:propertize ,(format "%%%sl" (length total-lines)) face mode-line-buffer-id) "/"
+ ,total-lines ")")))
+ (set (make-local-variable 'mode-line-modified) nil)))
+
+(defun paradox--set-local-value (x)
+ "Locally set value of (car X) to (cdr X)."
+ (let ((sym (or (car-safe x) x)))
+ (when (boundp sym)
+ (set (make-local-variable sym) (cdr-safe x)))))
+
+(defun paradox--prettify-key-descriptor (desc)
+ "Prettify DESC to be displayed as a help menu."
+ (if (listp desc)
+ (if (listp (cdr desc))
+ (mapconcat 'paradox--prettify-key-descriptor desc " ")
+ (let ((place (cdr desc))
+ (out (car desc)))
+ (setq out (propertize out 'face 'paradox-comment-face))
+ (add-text-properties place (1+ place) '(face paradox-highlight-face) out)
+ out))
+ (paradox--prettify-key-descriptor (cons desc 0))))
+
+(provide 'paradox-menu)
+;;; paradox-menu.el ends here
--- /dev/null
+(define-package "paradox" "20150611.645" "A modern Packages Menu. Colored, with package ratings, and customizable."
+ '((emacs "24.4")
+ (seq "1.7")
+ (cl-lib "0.5")
+ (json "1.3")
+ (let-alist "1.0.3")
+ (spinner "1.3"))
+ :url "http://github.com/Malabarba/paradox" :keywords
+ '("package" "packages"))
+;; Local Variables:
+;; no-byte-compile: t
+;; End:
--- /dev/null
+;;; paradox.el --- A modern Packages Menu. Colored, with package ratings, and customizable. -*- lexical-binding:t -*-
+
+;; Copyright (C) 2014-2015 Artur Malabarba <emacsNOSPAMendlessparentheses.com>
+
+;; Author: Artur Malabarba <emacsNOSPAMendlessparentheses.com>
+;; URL: http://github.com/Malabarba/paradox
+;; Version: 2.3.2
+;; Keywords: package packages
+;; Package-Requires: ((emacs "24.4") (seq "1.7") (cl-lib "0.5") (json "1.3") (let-alist "1.0.3") (spinner "1.3"))
+;; Prefix: paradox
+;; Separator: -
+
+;;; Commentary:
+;;
+;; Paradox can be installed from Melpa with M-x `package-install' RET
+;; paradox.
+;; It can also be installed manually in the usual way, just be mindful of
+;; the dependencies.
+;;
+;; To use it, simply call M-x `paradox-list-packages' (instead of the
+;; regular `list-packages').
+;; This will give you most features out of the box. If you want to be
+;; able to star packages as well, just configure the
+;; `paradox-github-token' variable then call `paradox-list-packages'
+;; again.
+;;
+;; If you'd like to stop using Paradox, you may call `paradox-disable'
+;; and go back to using the regular `list-packages'.
+;;
+;; ## Current Features ##
+;;
+;; ### Several Improvements ###
+;;
+;; Paradox implements many small improvements to the package menu
+;; itself. They all work out of the box and are completely customizable!
+;; *(Also, hit `h' to see all keys.)*
+;;
+;; * Visit the package's homepage with `v' (or just use the provided buttons).
+;; * Shortcuts for package filtering:
+;; * <f r> filters by regexp (`occur');
+;; * <f u> display only packages with upgrades;
+;; * <f k> filters by keyword (Emacs 24.4 only).
+;; * `hl-line-mode' enabled by default.
+;; * Display useful information on the mode-line and cleanup a bunch of
+;; useless stuff.
+;; * **Customization!** Just call M-x `paradox-customize' to see what you can
+;; do.
+;; * Customize column widths.
+;; * Customize faces (`paradox-star-face', `paradox-status-face-alist' and `paradox-archive-face').
+;; * Customize local variables.
+;;
+;; ### Package Ratings ###
+;;
+;; Paradox also integrates with
+;; **GitHub Stars**, which works as **rough** package rating system.
+;; That is, Paradox package menu will:
+;;
+;; 1. Display the number of GitHub Stars each package has (assuming it's
+;; in a github repo, of course);
+;; 2. Possibly automatically star packages you install, and unstar
+;; packages you delete (you will be asked the first time whether you
+;; want this);
+;; 3. Let you star and unstar packages by hitting the `s' key;
+;; 4. Let you star all packages you have installed with M-x `paradox-star-all-installed-packages'.
+;;
+;; Item **1.** will work out of the box, the other items obviously
+;; require a github account (Paradox will help you generate a token the
+;; first time you call `paradox-list-packages').
+;;
+;; ## How Star Displaying Works ##
+;;
+;; We generate a map of Package Name -> Repository from
+;; [Melpa](https://github.com/milkypostman/melpa.git)'s `recipe'
+;; directory, some repos may correspond to more than one package.
+;; This map is used count the stars a given package has.
+;; _This doesn't mean you need Melpa to see the star counts, the numbers
+;; will be displayed regardless of what archives you use._
+;;
+;; Currently, packages that are not hosted on GitHub are listed with a
+;; blank star count, which is clearly different from 0-star packages
+;; (which are displayed with a 0, obviously).
+;; If you know of an alternative that could be used for these packages,
+;; [open an issue](https://github.com/Bruce-Connor/paradox/issues/new)
+;; here, I'd love to hear.
+
+;;; License:
+;;
+;; This file is NOT part of GNU Emacs.
+;;
+;; 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.
+
+\f
+;;; Code:
+
+(require 'package)
+(require 'cl-lib)
+
+(require 'paradox-core)
+(require 'paradox-execute)
+(require 'paradox-menu)
+
+(defconst paradox-version "2.3.2" "Version of the paradox.el package.")
+(defun paradox-bug-report ()
+ "Opens github issues page in a web browser. Please send any bugs you find.
+Please include your Emacs and paradox versions."
+ (interactive)
+ (message "Your paradox-version is: %s, and your emacs version is: %s.\nPlease include this in your report!"
+ paradox-version emacs-version)
+ (browse-url "https://github.com/Bruce-Connor/paradox/issues/new"))
+(defun paradox-customize ()
+ "Open the customization menu in the `paradox' group."
+ (interactive)
+ (customize-group 'paradox t))
+(defgroup paradox nil
+ "Customization group for paradox."
+ :prefix "paradox-"
+ :group 'emacs
+ :package-version '(paradox . "0.1"))
+
+\f
+;;; External Commands
+;;;###autoload
+(defun paradox-list-packages (no-fetch)
+ "Improved version of `package-list-packages'. The heart of Paradox.
+Function is equivalent to `package-list-packages' (including the
+prefix NO-FETCH), but the resulting Package Menu is improved in
+several ways.
+
+Among them:
+
+1. Uses `paradox-menu-mode', which has more functionality and
+keybinds than `package-menu-mode'.
+
+2. Uses some font-locking to improve readability.
+
+3. Optionally shows the number GitHub stars and Melpa downloads
+for packages.
+
+4. Adds useful information in the mode-line."
+ (interactive "P")
+ (when (paradox--check-github-token)
+ (paradox-enable)
+ (unless no-fetch
+ (if (fboundp 'package--update-downloads-in-progress)
+ (when (boundp 'package--downloads-in-progress)
+ (add-to-list 'package--downloads-in-progress 'paradox--data))
+ (paradox--refresh-star-count)))
+ (package-list-packages no-fetch)
+ (unless no-fetch
+ (when (stringp paradox-github-token)
+ (paradox--refresh-user-starred-list
+ (bound-and-true-p package-menu-async)))
+ (when (fboundp 'package--update-downloads-in-progress)
+ (paradox--refresh-star-count)))))
+
+;;;###autoload
+(defun paradox-upgrade-packages (&optional no-fetch)
+ "Upgrade all packages. No questions asked.
+This function is equivalent to `list-packages', followed by a
+`package-menu-mark-upgrades' and a `package-menu-execute'. Except
+the user isn't asked to confirm deletion of packages.
+
+If `paradox-execute-asynchronously' is non-nil, part of this
+operation may be performed in the background.
+
+The NO-FETCH prefix argument is passed to `list-packages'. It
+prevents re-download of information about new versions. It does
+not prevent downloading the actual packages (obviously)."
+ (interactive "P")
+ (save-window-excursion
+ (paradox-list-packages no-fetch)
+ (package-menu-mark-upgrades)
+ (paradox-menu-execute 'noquery)))
+
+(defun paradox-enable ()
+ "Enable paradox, overriding the default package-menu."
+ (interactive)
+ (paradox--override-definition 'package-menu--print-info 'paradox--print-info)
+ (when (fboundp 'package-menu--print-info-simple)
+ (paradox--override-definition 'package-menu--print-info-simple 'paradox--print-info))
+ (paradox--override-definition 'package-menu--generate 'paradox--generate-menu)
+ ;; Tough it may not look like it, this is totally necessary too.
+ (paradox--override-definition 'package-menu-mode 'paradox-menu-mode)
+ (paradox--core-enable))
+
+;;;###autoload
+(defun paradox-require (feature &optional filename noerror package refresh)
+ "Like `require', but also install FEATURE if it is absent.
+FILENAME is passed to `require'.
+If NOERROR is non-nil, don't complain if the feature couldn't be
+installed, just return nil.
+
+- If FEATURE is present, `require' it and return t.
+
+- If FEATURE is not present, install PACKAGE with `package-install'.
+If PACKAGE is nil, assume FEATURE is the package name.
+After installation, `require' FEATURE.
+
+By default, the current package database is only updated if it is
+empty. Passing a non-nil REFRESH argument forces this update."
+ (or (require feature filename t)
+ (let ((package (or package
+ (if (stringp feature)
+ (intern feature)
+ feature))))
+ (require 'package)
+ (unless (and package-archive-contents (null refresh))
+ (package-refresh-contents))
+ (and (condition-case e
+ (package-install package)
+ (error (if noerror nil (error (cadr e)))))
+ (require feature filename noerror)))))
+
+(provide 'paradox)
+;;; paradox.el ends here
--- /dev/null
+Paradox can be installed from Melpa with M-x `package-install' RET
+paradox.
+It can also be installed manually in the usual way, just be mindful of
+the dependencies.
+
+To use it, simply call M-x `paradox-list-packages' (instead of the
+regular `list-packages').
+This will give you most features out of the box. If you want to be
+able to star packages as well, just configure the
+`paradox-github-token' variable then call `paradox-list-packages'
+again.
+
+If you'd like to stop using Paradox, you may call `paradox-disable'
+and go back to using the regular `list-packages'.
+
+## Current Features ##
+
+### Several Improvements ###
+
+Paradox implements many small improvements to the package menu
+itself. They all work out of the box and are completely customizable!
+*(Also, hit `h' to see all keys.)*
+
+* Visit the package's homepage with `v' (or just use the provided buttons).
+* Shortcuts for package filtering:
+ * <f r> filters by regexp (`occur');
+ * <f u> display only packages with upgrades;
+ * <f k> filters by keyword (Emacs 24.4 only).
+* `hl-line-mode' enabled by default.
+* Display useful information on the mode-line and cleanup a bunch of
+ useless stuff.
+* **Customization!** Just call M-x `paradox-customize' to see what you can
+ do.
+ * Customize column widths.
+ * Customize faces (`paradox-star-face', `paradox-status-face-alist' and `paradox-archive-face').
+ * Customize local variables.
+
+### Package Ratings ###
+
+Paradox also integrates with
+**GitHub Stars**, which works as **rough** package rating system.
+That is, Paradox package menu will:
+
+1. Display the number of GitHub Stars each package has (assuming it's
+ in a github repo, of course);
+2. Possibly automatically star packages you install, and unstar
+ packages you delete (you will be asked the first time whether you
+ want this);
+3. Let you star and unstar packages by hitting the `s' key;
+4. Let you star all packages you have installed with M-x `paradox-star-all-installed-packages'.
+
+Item **1.** will work out of the box, the other items obviously
+require a github account (Paradox will help you generate a token the
+first time you call `paradox-list-packages').
+
+## How Star Displaying Works ##
+
+We generate a map of Package Name -> Repository from
+[Melpa](https://github.com/milkypostman/melpa.git)'s `recipe'
+directory, some repos may correspond to more than one package.
+This map is used count the stars a given package has.
+_This doesn't mean you need Melpa to see the star counts, the numbers
+will be displayed regardless of what archives you use._
+
+Currently, packages that are not hosted on GitHub are listed with a
+blank star count, which is clearly different from 0-star packages
+(which are displayed with a 0, obviously).
+If you know of an alternative that could be used for these packages,
+[open an issue](https://github.com/Bruce-Connor/paradox/issues/new)
+here, I'd love to hear.
+++ /dev/null
-;;; pkg-info-autoloads.el --- automatically extracted autoloads
-;;
-;;; Code:
-
-\f
-;;;### (autoloads (pkg-info-version-info pkg-info-package-version
-;;;;;; pkg-info-defining-library-version pkg-info-defining-library-original-version
-;;;;;; pkg-info-library-version pkg-info-library-original-version)
-;;;;;; "pkg-info" "pkg-info.el" (21695 37112 0 0))
-;;; Generated autoloads from pkg-info.el
-
-(autoload 'pkg-info-library-original-version "pkg-info" "\
-Get the original version in the header of LIBRARY.
-
-The original version is stored in the X-Original-Version header.
-This header is added by the MELPA package archive to preserve
-upstream version numbers.
-
-LIBRARY is either a symbol denoting a named feature, or a library
-name as string.
-
-If SHOW is non-nil, show the version in the minibuffer.
-
-Return the version from the header of LIBRARY as list. Signal an
-error if the LIBRARY was not found or had no X-Original-Version
-header.
-
-See Info node `(elisp)Library Headers' for more information
-about library headers.
-
-\(fn LIBRARY &optional SHOW)" t nil)
-
-(autoload 'pkg-info-library-version "pkg-info" "\
-Get the version in the header of LIBRARY.
-
-LIBRARY is either a symbol denoting a named feature, or a library
-name as string.
-
-If SHOW is non-nil, show the version in the minibuffer.
-
-Return the version from the header of LIBRARY as list. Signal an
-error if the LIBRARY was not found or had no proper header.
-
-See Info node `(elisp)Library Headers' for more information
-about library headers.
-
-\(fn LIBRARY &optional SHOW)" t nil)
-
-(autoload 'pkg-info-defining-library-original-version "pkg-info" "\
-Get the original version of the library defining FUNCTION.
-
-The original version is stored in the X-Original-Version header.
-This header is added by the MELPA package archive to preserve
-upstream version numbers.
-
-If SHOW is non-nil, show the version in mini-buffer.
-
-This function is mainly intended to find the version of a major
-or minor mode, i.e.
-
- (pkg-info-defining-library-version 'flycheck-mode)
-
-Return the version of the library defining FUNCTION. Signal an
-error if FUNCTION is not a valid function, if its defining
-library was not found, or if the library had no proper version
-header.
-
-\(fn FUNCTION &optional SHOW)" t nil)
-
-(autoload 'pkg-info-defining-library-version "pkg-info" "\
-Get the version of the library defining FUNCTION.
-
-If SHOW is non-nil, show the version in mini-buffer.
-
-This function is mainly intended to find the version of a major
-or minor mode, i.e.
-
- (pkg-info-defining-library-version 'flycheck-mode)
-
-Return the version of the library defining FUNCTION. Signal an
-error if FUNCTION is not a valid function, if its defining
-library was not found, or if the library had no proper version
-header.
-
-\(fn FUNCTION &optional SHOW)" t nil)
-
-(autoload 'pkg-info-package-version "pkg-info" "\
-Get the version of an installed PACKAGE.
-
-If SHOW is non-nil, show the version in the minibuffer.
-
-Return the version as list, or nil if PACKAGE is not installed.
-
-\(fn PACKAGE &optional SHOW)" t nil)
-
-(autoload 'pkg-info-version-info "pkg-info" "\
-Obtain complete version info for LIBRARY and PACKAGE.
-
-LIBRARY is a symbol denoting a named feature, or a library name
-as string. PACKAGE is a symbol denoting an ELPA package. If
-omitted or nil, default to LIBRARY.
-
-If SHOW is non-nil, show the version in the minibuffer.
-
-When called interactively, prompt for LIBRARY. When called
-interactively with prefix argument, prompt for PACKAGE as well.
-
-Return a string with complete version information for LIBRARY.
-This version information contains the version from the headers of
-LIBRARY, and the version of the installed PACKAGE, the LIBRARY is
-part of. If PACKAGE is not installed, or if the PACKAGE version
-is the same as the LIBRARY version, do not include a package
-version.
-
-\(fn LIBRARY &optional PACKAGE SHOW)" t nil)
-
-;;;***
-\f
-;;;### (autoloads nil nil ("pkg-info-pkg.el") (21695 37112 672123
-;;;;;; 0))
-
-;;;***
-\f
-(provide 'pkg-info-autoloads)
-;; Local Variables:
-;; version-control: never
-;; no-byte-compile: t
-;; no-update-autoloads: t
-;; coding: utf-8
-;; End:
-;;; pkg-info-autoloads.el ends here
+++ /dev/null
-(define-package "pkg-info" "20140610.630" "Information about packages" (quote ((epl "0.4"))))
+++ /dev/null
-;;; pkg-info.el --- Information about packages -*- lexical-binding: t; -*-
-
-;; Copyright (C) 2013, 2014 Sebastian Wiesner
-
-;; Author: Sebastian Wiesner <swiesnerNOSPAMlunaryorn.com>
-;; URL: https://github.com/lunaryorn/pkg-info.el
-;; Keywords: convenience
-;; Version: 20140610.630
-;; X-Original-Version: 0.6-cvs
-;; Package-Requires: ((epl "0.4"))
-
-;; This file is not part of GNU Emacs.
-
-;; 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 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, see <http://www.gnu.org/licenses/>.
-
-;;; Commentary:
-
-;; This library extracts information from installed packages.
-
-;;;; Functions:
-
-;; `pkg-info-library-version' extracts the version from the header of a library.
-;;
-;; `pkg-info-defining-library-version' extracts the version from the header of a
-;; library defining a function.
-;;
-;; `pkg-info-package-version' gets the version of an installed package.
-;;
-;; `pkg-info-format-version' formats a version list as human readable string.
-;;
-;; `pkg-info-version-info' returns complete version information for a specific
-;; package.
-
-;;; Code:
-
-(require 'epl)
-
-(require 'lisp-mnt)
-(require 'find-func)
-
-\f
-;;; Version information
-(defun pkg-info-format-version (version)
- "Format VERSION as human-readable string.
-
-Return a human-readable string representing VERSION."
- ;; XXX: Find a better, more flexible way of formatting?
- (package-version-join version))
-
-(defsubst pkg-info--show-version-and-return (version show)
- "Show and return VERSION.
-
-When SHOW is non-nil, show VERSION in minibuffer.
-
-Return VERSION."
- (when show
- (message (if (listp version) (pkg-info-format-version version) version)))
- version)
-
-(defun pkg-info--read-library ()
- "Read a library from minibuffer."
- (completing-read "Load library: "
- (apply-partially 'locate-file-completion-table
- load-path
- (get-load-suffixes))))
-
-(defun pkg-info--read-function ()
- "Read a function name from minibuffer."
- (let ((input (completing-read "Function: " obarray #'boundp :require-match)))
- (if (string= input "") nil (intern input))))
-
-(defun pkg-info--read-package ()
- "Read a package name from minibuffer."
- (let* ((installed (epl-installed-packages))
- (names (sort (mapcar (lambda (pkg)
- (symbol-name (epl-package-name pkg)))
- installed)
- #'string<))
- (default (car names)))
- (completing-read "Installed package: " names nil 'require-match
- nil nil default)))
-
-(defun pkg-info-library-source (library)
- "Get the source file of LIBRARY.
-
-LIBRARY is either a symbol denoting a named feature, or a library
-name as string.
-
-Return the source file of LIBRARY as string."
- (find-library-name (if (symbolp library) (symbol-name library) library)))
-
-(defun pkg-info-defining-library (function)
- "Get the source file of the library defining FUNCTION.
-
-FUNCTION is a function symbol.
-
-Return the file name of the library as string. Signal an error
-if the library does not exist, or if the definition of FUNCTION
-was not found."
- (unless (functionp function)
- (signal 'wrong-type-argument (list 'functionp function)))
- (let ((library (symbol-file function 'defun)))
- (unless library
- (error "Can't find definition of %s" function))
- library))
-
-(defun pkg-info-x-original-version (file)
- "Read the X-Original-Version header from FILE.
-
-Return the value as version list, or return nil if FILE lacks
-this header. Signal an error, if the value of the header is not
-a valid version."
- (let ((version-str (with-temp-buffer
- (insert-file-contents file)
- (lm-header "X-Original-Version"))))
- (when version-str
- (version-to-list version-str))))
-
-;;;###autoload
-(defun pkg-info-library-original-version (library &optional show)
- "Get the original version in the header of LIBRARY.
-
-The original version is stored in the X-Original-Version header.
-This header is added by the MELPA package archive to preserve
-upstream version numbers.
-
-LIBRARY is either a symbol denoting a named feature, or a library
-name as string.
-
-If SHOW is non-nil, show the version in the minibuffer.
-
-Return the version from the header of LIBRARY as list. Signal an
-error if the LIBRARY was not found or had no X-Original-Version
-header.
-
-See Info node `(elisp)Library Headers' for more information
-about library headers."
- (interactive (list (pkg-info--read-library) t))
- (let ((version (pkg-info-x-original-version
- (pkg-info-library-source library))))
- (if version
- (pkg-info--show-version-and-return version show)
- (error "Library %s has no original version" library))))
-
-;;;###autoload
-(defun pkg-info-library-version (library &optional show)
- "Get the version in the header of LIBRARY.
-
-LIBRARY is either a symbol denoting a named feature, or a library
-name as string.
-
-If SHOW is non-nil, show the version in the minibuffer.
-
-Return the version from the header of LIBRARY as list. Signal an
-error if the LIBRARY was not found or had no proper header.
-
-See Info node `(elisp)Library Headers' for more information
-about library headers."
- (interactive (list (pkg-info--read-library) t))
- (let* ((source (pkg-info-library-source library))
- (version (epl-package-version (epl-package-from-file source))))
- (pkg-info--show-version-and-return version show)))
-
-;;;###autoload
-(defun pkg-info-defining-library-original-version (function &optional show)
- "Get the original version of the library defining FUNCTION.
-
-The original version is stored in the X-Original-Version header.
-This header is added by the MELPA package archive to preserve
-upstream version numbers.
-
-If SHOW is non-nil, show the version in mini-buffer.
-
-This function is mainly intended to find the version of a major
-or minor mode, i.e.
-
- (pkg-info-defining-library-version 'flycheck-mode)
-
-Return the version of the library defining FUNCTION. Signal an
-error if FUNCTION is not a valid function, if its defining
-library was not found, or if the library had no proper version
-header."
- (interactive (list (pkg-info--read-function) t))
- (pkg-info-library-original-version (pkg-info-defining-library function) show))
-
-;;;###autoload
-(defun pkg-info-defining-library-version (function &optional show)
- "Get the version of the library defining FUNCTION.
-
-If SHOW is non-nil, show the version in mini-buffer.
-
-This function is mainly intended to find the version of a major
-or minor mode, i.e.
-
- (pkg-info-defining-library-version 'flycheck-mode)
-
-Return the version of the library defining FUNCTION. Signal an
-error if FUNCTION is not a valid function, if its defining
-library was not found, or if the library had no proper version
-header."
- (interactive (list (pkg-info--read-function) t))
- (pkg-info-library-version (pkg-info-defining-library function) show))
-
-;;;###autoload
-(defun pkg-info-package-version (package &optional show)
- "Get the version of an installed PACKAGE.
-
-If SHOW is non-nil, show the version in the minibuffer.
-
-Return the version as list, or nil if PACKAGE is not installed."
- (interactive (list (pkg-info--read-package) t))
- (let* ((name (if (stringp package) (intern package) package))
- (package (epl-find-installed-package name)))
- (unless package
- (error "Can't find installed package %s" name))
- (pkg-info--show-version-and-return (epl-package-version package) show)))
-
-;;;###autoload
-(defun pkg-info-version-info (library &optional package show)
- "Obtain complete version info for LIBRARY and PACKAGE.
-
-LIBRARY is a symbol denoting a named feature, or a library name
-as string. PACKAGE is a symbol denoting an ELPA package. If
-omitted or nil, default to LIBRARY.
-
-If SHOW is non-nil, show the version in the minibuffer.
-
-When called interactively, prompt for LIBRARY. When called
-interactively with prefix argument, prompt for PACKAGE as well.
-
-Return a string with complete version information for LIBRARY.
-This version information contains the version from the headers of
-LIBRARY, and the version of the installed PACKAGE, the LIBRARY is
-part of. If PACKAGE is not installed, or if the PACKAGE version
-is the same as the LIBRARY version, do not include a package
-version."
- (interactive (list (pkg-info--read-library)
- (when current-prefix-arg
- (pkg-info--read-package))
- t))
- (let* ((package (or package (if (stringp library) (intern library) library)))
- (orig-version (condition-case nil
- (pkg-info-library-original-version library)
- (error nil)))
- ;; If we have X-Original-Version, we assume that MELPA replaced the
- ;; library version with its generated version, so we use the
- ;; X-Original-Version header instead, and ignore the library version
- ;; header
- (lib-version (or orig-version (pkg-info-library-version library)))
- (pkg-version (condition-case nil
- (pkg-info-package-version package)
- (error nil)))
- (version (if (and pkg-version
- (not (version-list-= lib-version pkg-version)))
- (format "%s (package: %s)"
- (pkg-info-format-version lib-version)
- (pkg-info-format-version pkg-version))
- (pkg-info-format-version lib-version))))
- (pkg-info--show-version-and-return version show)))
-
-(provide 'pkg-info)
-
-;; Local Variables:
-;; indent-tabs-mode: nil
-;; coding: utf-8
-;; End:
-
-;;; pkg-info.el ends here
--- /dev/null
+;;; pkg-info-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+(add-to-list 'load-path (or (file-name-directory #$) (car load-path)))
+\f
+;;;### (autoloads nil "pkg-info" "pkg-info.el" (21908 14204 0 0))
+;;; Generated autoloads from pkg-info.el
+
+(autoload 'pkg-info-library-original-version "pkg-info" "\
+Get the original version in the header of LIBRARY.
+
+The original version is stored in the X-Original-Version header.
+This header is added by the MELPA package archive to preserve
+upstream version numbers.
+
+LIBRARY is either a symbol denoting a named feature, or a library
+name as string.
+
+If SHOW is non-nil, show the version in the minibuffer.
+
+Return the version from the header of LIBRARY as list. Signal an
+error if the LIBRARY was not found or had no X-Original-Version
+header.
+
+See Info node `(elisp)Library Headers' for more information
+about library headers.
+
+\(fn LIBRARY &optional SHOW)" t nil)
+
+(autoload 'pkg-info-library-version "pkg-info" "\
+Get the version in the header of LIBRARY.
+
+LIBRARY is either a symbol denoting a named feature, or a library
+name as string.
+
+If SHOW is non-nil, show the version in the minibuffer.
+
+Return the version from the header of LIBRARY as list. Signal an
+error if the LIBRARY was not found or had no proper header.
+
+See Info node `(elisp)Library Headers' for more information
+about library headers.
+
+\(fn LIBRARY &optional SHOW)" t nil)
+
+(autoload 'pkg-info-defining-library-original-version "pkg-info" "\
+Get the original version of the library defining FUNCTION.
+
+The original version is stored in the X-Original-Version header.
+This header is added by the MELPA package archive to preserve
+upstream version numbers.
+
+If SHOW is non-nil, show the version in mini-buffer.
+
+This function is mainly intended to find the version of a major
+or minor mode, i.e.
+
+ (pkg-info-defining-library-version 'flycheck-mode)
+
+Return the version of the library defining FUNCTION. Signal an
+error if FUNCTION is not a valid function, if its defining
+library was not found, or if the library had no proper version
+header.
+
+\(fn FUNCTION &optional SHOW)" t nil)
+
+(autoload 'pkg-info-defining-library-version "pkg-info" "\
+Get the version of the library defining FUNCTION.
+
+If SHOW is non-nil, show the version in mini-buffer.
+
+This function is mainly intended to find the version of a major
+or minor mode, i.e.
+
+ (pkg-info-defining-library-version 'flycheck-mode)
+
+Return the version of the library defining FUNCTION. Signal an
+error if FUNCTION is not a valid function, if its defining
+library was not found, or if the library had no proper version
+header.
+
+\(fn FUNCTION &optional SHOW)" t nil)
+
+(autoload 'pkg-info-package-version "pkg-info" "\
+Get the version of an installed PACKAGE.
+
+If SHOW is non-nil, show the version in the minibuffer.
+
+Return the version as list, or nil if PACKAGE is not installed.
+
+\(fn PACKAGE &optional SHOW)" t nil)
+
+(autoload 'pkg-info-version-info "pkg-info" "\
+Obtain complete version info for LIBRARY and PACKAGE.
+
+LIBRARY is a symbol denoting a named feature, or a library name
+as string. PACKAGE is a symbol denoting an ELPA package. If
+omitted or nil, default to LIBRARY.
+
+If SHOW is non-nil, show the version in the minibuffer.
+
+When called interactively, prompt for LIBRARY. When called
+interactively with prefix argument, prompt for PACKAGE as well.
+
+Return a string with complete version information for LIBRARY.
+This version information contains the version from the headers of
+LIBRARY, and the version of the installed PACKAGE, the LIBRARY is
+part of. If PACKAGE is not installed, or if the PACKAGE version
+is the same as the LIBRARY version, do not include a package
+version.
+
+\(fn LIBRARY &optional PACKAGE SHOW)" t nil)
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+;;; pkg-info-autoloads.el ends here
--- /dev/null
+(define-package "pkg-info" "20150517.443" "Information about packages" '((epl "0.8")) :url "https://github.com/lunaryorn/pkg-info.el" :keywords '("convenience"))
--- /dev/null
+;;; pkg-info.el --- Information about packages -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2013-2015 Sebastian Wiesner <swiesner@lunaryorn.com>
+
+;; Author: Sebastian Wiesner <swiesnerNOSPAMlunaryorn.com>
+;; URL: https://github.com/lunaryorn/pkg-info.el
+;; Package-Version: 20150517.443
+;; Keywords: convenience
+;; Version: 0.7-cvs
+;; Package-Requires: ((epl "0.8"))
+
+;; This file is not part of GNU Emacs.
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This library extracts information from installed packages.
+
+;;;; Functions:
+
+;; `pkg-info-library-version' extracts the version from the header of a library.
+;;
+;; `pkg-info-defining-library-version' extracts the version from the header of a
+;; library defining a function.
+;;
+;; `pkg-info-package-version' gets the version of an installed package.
+;;
+;; `pkg-info-format-version' formats a version list as human readable string.
+;;
+;; `pkg-info-version-info' returns complete version information for a specific
+;; package.
+;;
+;; `pkg-info-get-melpa-recipe' gets the MELPA recipe for a package.
+;;
+;; `pkg-info-get-melpa-fetcher' gets the fetcher used to build a package on
+;; MELPA.
+;;
+;; `pkg-info-wiki-package-p' determines whether a package was build from
+;; EmacsWiki on MELPA.
+
+;;; Code:
+
+(require 'epl)
+
+(require 'lisp-mnt)
+(require 'find-func)
+(require 'json) ; `json-read'
+(require 'url-http) ; `url-http-parse-response'
+
+(defvar url-http-end-of-headers)
+
+\f
+;;; Version information
+(defun pkg-info-format-version (version)
+ "Format VERSION as human-readable string.
+
+Return a human-readable string representing VERSION."
+ ;; XXX: Find a better, more flexible way of formatting?
+ (package-version-join version))
+
+(defsubst pkg-info--show-version-and-return (version show)
+ "Show and return VERSION.
+
+When SHOW is non-nil, show VERSION in minibuffer.
+
+Return VERSION."
+ (when show
+ (message (if (listp version) (pkg-info-format-version version) version)))
+ version)
+
+(defun pkg-info--read-library ()
+ "Read a library from minibuffer."
+ (completing-read "Load library: "
+ (apply-partially 'locate-file-completion-table
+ load-path
+ (get-load-suffixes))))
+
+(defun pkg-info--read-function ()
+ "Read a function name from minibuffer."
+ (let ((input (completing-read "Function: " obarray #'boundp :require-match)))
+ (if (string= input "") nil (intern input))))
+
+(defun pkg-info--read-package ()
+ "Read a package name from minibuffer."
+ (let* ((installed (epl-installed-packages))
+ (names (sort (mapcar (lambda (pkg)
+ (symbol-name (epl-package-name pkg)))
+ installed)
+ #'string<))
+ (default (car names)))
+ (completing-read "Installed package: " names nil 'require-match
+ nil nil default)))
+
+(defun pkg-info-library-source (library)
+ "Get the source file of LIBRARY.
+
+LIBRARY is either a symbol denoting a named feature, or a library
+name as string.
+
+Return the source file of LIBRARY as string."
+ (find-library-name (if (symbolp library) (symbol-name library) library)))
+
+(defun pkg-info-defining-library (function)
+ "Get the source file of the library defining FUNCTION.
+
+FUNCTION is a function symbol.
+
+Return the file name of the library as string. Signal an error
+if the library does not exist, or if the definition of FUNCTION
+was not found."
+ (unless (functionp function)
+ (signal 'wrong-type-argument (list 'functionp function)))
+ (let ((library (symbol-file function 'defun)))
+ (unless library
+ (error "Can't find definition of %s" function))
+ library))
+
+(defun pkg-info-x-original-version (file)
+ "Read the X-Original-Version header from FILE.
+
+Return the value as version list, or return nil if FILE lacks
+this header. Signal an error, if the value of the header is not
+a valid version."
+ (let ((version-str (with-temp-buffer
+ (insert-file-contents file)
+ (lm-header "X-Original-Version"))))
+ (when version-str
+ (version-to-list version-str))))
+
+;;;###autoload
+(defun pkg-info-library-original-version (library &optional show)
+ "Get the original version in the header of LIBRARY.
+
+The original version is stored in the X-Original-Version header.
+This header is added by the MELPA package archive to preserve
+upstream version numbers.
+
+LIBRARY is either a symbol denoting a named feature, or a library
+name as string.
+
+If SHOW is non-nil, show the version in the minibuffer.
+
+Return the version from the header of LIBRARY as list. Signal an
+error if the LIBRARY was not found or had no X-Original-Version
+header.
+
+See Info node `(elisp)Library Headers' for more information
+about library headers."
+ (interactive (list (pkg-info--read-library) t))
+ (let ((version (pkg-info-x-original-version
+ (pkg-info-library-source library))))
+ (if version
+ (pkg-info--show-version-and-return version show)
+ (error "Library %s has no original version" library))))
+
+;;;###autoload
+(defun pkg-info-library-version (library &optional show)
+ "Get the version in the header of LIBRARY.
+
+LIBRARY is either a symbol denoting a named feature, or a library
+name as string.
+
+If SHOW is non-nil, show the version in the minibuffer.
+
+Return the version from the header of LIBRARY as list. Signal an
+error if the LIBRARY was not found or had no proper header.
+
+See Info node `(elisp)Library Headers' for more information
+about library headers."
+ (interactive (list (pkg-info--read-library) t))
+ (let* ((source (pkg-info-library-source library))
+ (version (epl-package-version (epl-package-from-file source))))
+ (pkg-info--show-version-and-return version show)))
+
+;;;###autoload
+(defun pkg-info-defining-library-original-version (function &optional show)
+ "Get the original version of the library defining FUNCTION.
+
+The original version is stored in the X-Original-Version header.
+This header is added by the MELPA package archive to preserve
+upstream version numbers.
+
+If SHOW is non-nil, show the version in mini-buffer.
+
+This function is mainly intended to find the version of a major
+or minor mode, i.e.
+
+ (pkg-info-defining-library-version 'flycheck-mode)
+
+Return the version of the library defining FUNCTION. Signal an
+error if FUNCTION is not a valid function, if its defining
+library was not found, or if the library had no proper version
+header."
+ (interactive (list (pkg-info--read-function) t))
+ (pkg-info-library-original-version (pkg-info-defining-library function) show))
+
+;;;###autoload
+(defun pkg-info-defining-library-version (function &optional show)
+ "Get the version of the library defining FUNCTION.
+
+If SHOW is non-nil, show the version in mini-buffer.
+
+This function is mainly intended to find the version of a major
+or minor mode, i.e.
+
+ (pkg-info-defining-library-version 'flycheck-mode)
+
+Return the version of the library defining FUNCTION. Signal an
+error if FUNCTION is not a valid function, if its defining
+library was not found, or if the library had no proper version
+header."
+ (interactive (list (pkg-info--read-function) t))
+ (pkg-info-library-version (pkg-info-defining-library function) show))
+
+;;;###autoload
+(defun pkg-info-package-version (package &optional show)
+ "Get the version of an installed PACKAGE.
+
+If SHOW is non-nil, show the version in the minibuffer.
+
+Return the version as list, or nil if PACKAGE is not installed."
+ (interactive (list (pkg-info--read-package) t))
+ (let* ((name (if (stringp package) (intern package) package))
+ (package (car (epl-find-installed-packages name))))
+ (unless package
+ (error "Can't find installed package %s" name))
+ (pkg-info--show-version-and-return (epl-package-version package) show)))
+
+;;;###autoload
+(defun pkg-info-version-info (library &optional package show)
+ "Obtain complete version info for LIBRARY and PACKAGE.
+
+LIBRARY is a symbol denoting a named feature, or a library name
+as string. PACKAGE is a symbol denoting an ELPA package. If
+omitted or nil, default to LIBRARY.
+
+If SHOW is non-nil, show the version in the minibuffer.
+
+When called interactively, prompt for LIBRARY. When called
+interactively with prefix argument, prompt for PACKAGE as well.
+
+Return a string with complete version information for LIBRARY.
+This version information contains the version from the headers of
+LIBRARY, and the version of the installed PACKAGE, the LIBRARY is
+part of. If PACKAGE is not installed, or if the PACKAGE version
+is the same as the LIBRARY version, do not include a package
+version."
+ (interactive (list (pkg-info--read-library)
+ (when current-prefix-arg
+ (pkg-info--read-package))
+ t))
+ (let* ((package (or package (if (stringp library) (intern library) library)))
+ (orig-version (condition-case nil
+ (pkg-info-library-original-version library)
+ (error nil)))
+ ;; If we have X-Original-Version, we assume that MELPA replaced the
+ ;; library version with its generated version, so we use the
+ ;; X-Original-Version header instead, and ignore the library version
+ ;; header
+ (lib-version (or orig-version (pkg-info-library-version library)))
+ (pkg-version (condition-case nil
+ (pkg-info-package-version package)
+ (error nil)))
+ (version (if (and pkg-version
+ (not (version-list-= lib-version pkg-version)))
+ (format "%s (package: %s)"
+ (pkg-info-format-version lib-version)
+ (pkg-info-format-version pkg-version))
+ (pkg-info-format-version lib-version))))
+ (pkg-info--show-version-and-return version show)))
+
+(defconst pkg-info-melpa-recipe-url "http://melpa.org/recipes.json"
+ "The URL from which to fetch MELPA recipes.")
+
+(defvar pkg-info-melpa-recipes nil
+ "An alist of MELPA recipes.")
+
+(defun pkg-info-retrieve-melpa-recipes ()
+ "Retrieve MELPA recipes from MELPA archive."
+ (let ((buffer (url-retrieve-synchronously pkg-info-melpa-recipe-url)))
+ (with-current-buffer buffer
+ (unwind-protect
+ (let ((response-code (url-http-parse-response)))
+ (unless (equal response-code 200)
+ (error "Failed to retrieve MELPA recipes from %s (code %s)"
+ pkg-info-melpa-recipe-url response-code))
+ (goto-char url-http-end-of-headers)
+ (json-read))
+ (when (and buffer (buffer-live-p buffer))
+ (kill-buffer buffer))))))
+
+(defun pkg-info-get-melpa-recipes ()
+ "Get MELPA recipes."
+ (setq pkg-info-melpa-recipes
+ (or pkg-info-melpa-recipes
+ (pkg-info-retrieve-melpa-recipes))))
+
+(defun pkg-info-get-melpa-recipe (package)
+ "Get the MELPA recipe for PACKAGE.
+
+Return nil if PACKAGE is not on MELPA."
+ (cdr (assq package (pkg-info-get-melpa-recipes))))
+
+(defun pkg-info-get-melpa-fetcher (package)
+ "Get the MELPA fetcher for PACKAGE."
+ (cdr (assq 'fetcher (pkg-info-get-melpa-recipe package))))
+
+(defun pkg-info-wiki-package-p (package)
+ "Determine whether PACKAGE is build from the EmacsWiki."
+ (equal (pkg-info-get-melpa-fetcher package) "wiki"))
+
+(provide 'pkg-info)
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; coding: utf-8
+;; End:
+
+;;; pkg-info.el ends here
--- /dev/null
+;;; projectile-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+(add-to-list 'load-path (or (file-name-directory #$) (car load-path)))
+\f
+;;;### (autoloads nil "projectile" "projectile.el" (21908 14196 0
+;;;;;; 0))
+;;; Generated autoloads from projectile.el
+
+(autoload 'projectile-cache-current-file "projectile" "\
+Add the currently visited file to the cache.
+
+\(fn)" t nil)
+
+(autoload 'projectile-switch-to-buffer "projectile" "\
+Switch to a project buffer.
+
+\(fn)" t nil)
+
+(autoload 'projectile-switch-to-buffer-other-window "projectile" "\
+Switch to a project buffer and show it in another window.
+
+\(fn)" t nil)
+
+(autoload 'projectile-display-buffer "projectile" "\
+Display a project buffer in another window without selecting it.
+
+\(fn)" t nil)
+
+(autoload 'projectile-project-buffers-other-buffer "projectile" "\
+Switch to the most recently selected buffer project buffer.
+Only buffers not visible in windows are returned.
+
+\(fn)" t nil)
+
+(autoload 'projectile-multi-occur "projectile" "\
+Do a `multi-occur' in the project's buffers.
+
+\(fn)" t nil)
+
+(autoload 'projectile-find-file-dwim "projectile" "\
+Jump to a project's files using completion based on context.
+
+With a prefix ARG invalidates the cache first.
+
+If point is on a filename, Projectile first tries to search for that
+file in project:
+
+- If it finds just a file, it switches to that file instantly. This works even
+if the filename is incomplete, but there's only a single file in the current project
+that matches the filename at point. For example, if there's only a single file named
+\"projectile/projectile.el\" but the current filename is \"projectile/proj\" (incomplete),
+`projectile-find-file' still switches to \"projectile/projectile.el\" immediately
+ because this is the only filename that matches.
+
+- If it finds a list of files, the list is displayed for selecting. A list of
+files is displayed when a filename appears more than one in the project or the
+filename at point is a prefix of more than two files in a project. For example,
+if `projectile-find-file' is executed on a filepath like \"projectile/\", it lists
+the content of that directory. If it is executed on a partial filename like
+ \"projectile/a\", a list of files with character 'a' in that directory is presented.
+
+- If it finds nothing, display a list of all files in project for selecting.
+
+\(fn &optional ARG)" t nil)
+
+(autoload 'projectile-find-file-dwim-other-window "projectile" "\
+Jump to a project's files using completion based on context in other window.
+
+With a prefix ARG invalidates the cache first.
+
+If point is on a filename, Projectile first tries to search for that
+file in project:
+
+- If it finds just a file, it switches to that file instantly. This works even
+if the filename is incomplete, but there's only a single file in the current project
+that matches the filename at point. For example, if there's only a single file named
+\"projectile/projectile.el\" but the current filename is \"projectile/proj\" (incomplete),
+`projectile-find-file' still switches to \"projectile/projectile.el\"
+immediately because this is the only filename that matches.
+
+- If it finds a list of files, the list is displayed for selecting. A list of
+files is displayed when a filename appears more than one in the project or the
+filename at point is a prefix of more than two files in a project. For example,
+if `projectile-find-file' is executed on a filepath like \"projectile/\", it lists
+the content of that directory. If it is executed on a partial filename
+like \"projectile/a\", a list of files with character 'a' in that directory
+is presented.
+
+- If it finds nothing, display a list of all files in project for selecting.
+
+\(fn &optional ARG)" t nil)
+
+(autoload 'projectile-find-file "projectile" "\
+Jump to a project's file using completion.
+With a prefix ARG invalidates the cache first.
+
+\(fn &optional ARG)" t nil)
+
+(autoload 'projectile-find-file-other-window "projectile" "\
+Jump to a project's file using completion and show it in another window.
+
+With a prefix ARG invalidates the cache first.
+
+\(fn &optional ARG)" t nil)
+
+(autoload 'projectile-find-implementation-or-test-other-window "projectile" "\
+Open matching implementation or test file in other window.
+
+\(fn)" t nil)
+
+(autoload 'projectile-toggle-between-implementation-and-test "projectile" "\
+Toggle between an implementation file and its test file.
+
+\(fn)" t nil)
+
+(autoload 'projectile-regenerate-tags "projectile" "\
+Regenerate the project's [e|g]tags.
+
+\(fn)" t nil)
+
+(autoload 'projectile-find-tag "projectile" "\
+Find tag in project.
+
+\(fn)" t nil)
+
+(autoload 'projectile-run-command-in-root "projectile" "\
+Invoke `execute-extended-command' in the project's root.
+
+\(fn)" t nil)
+
+(autoload 'projectile-run-shell-command-in-root "projectile" "\
+Invoke `shell-command' in the project's root.
+
+\(fn)" t nil)
+
+(autoload 'projectile-run-async-shell-command-in-root "projectile" "\
+Invoke `async-shell-command' in the project's root.
+
+\(fn)" t nil)
+
+(autoload 'projectile-kill-buffers "projectile" "\
+Kill all project buffers.
+
+\(fn)" t nil)
+
+(autoload 'projectile-save-project-buffers "projectile" "\
+Save all project buffers.
+
+\(fn)" t nil)
+
+(autoload 'projectile-dired "projectile" "\
+Open `dired' at the root of the project.
+
+\(fn)" t nil)
+
+(autoload 'projectile-vc "projectile" "\
+Open `vc-dir' at the root of the project.
+
+For git projects `magit-status-internal' is used if available.
+
+\(fn &optional PROJECT-ROOT)" t nil)
+
+(autoload 'projectile-recentf "projectile" "\
+Show a list of recently visited files in a project.
+
+\(fn)" t nil)
+
+(autoload 'projectile-find-file-in-known-projects "projectile" "\
+Jump to a file in any of the known projects.
+
+\(fn)" t nil)
+
+(autoload 'projectile-cleanup-known-projects "projectile" "\
+Remove known projects that don't exist anymore.
+
+\(fn)" t nil)
+
+(autoload 'projectile-clear-known-projects "projectile" "\
+Clear both `projectile-known-projects' and `projectile-known-projects-file'.
+
+\(fn)" t nil)
+
+(autoload 'projectile-remove-current-project-from-known-projects "projectile" "\
+Remove the current project from the list of known projects.
+
+\(fn)" t nil)
+
+(autoload 'projectile-commander "projectile" "\
+Execute a Projectile command with a single letter.
+The user is prompted for a single character indicating the action to invoke.
+The `?' character describes then
+available actions.
+
+See `def-projectile-commander-method' for defining new methods.
+
+\(fn)" t nil)
+
+(defvar projectile-mode-line '(:eval (format " Projectile[%s]" (projectile-project-name))) "\
+Mode line lighter for Projectile.
+
+The value of this variable is a mode line template as in
+`mode-line-format'. See Info Node `(elisp)Mode Line Format' for
+details about mode line templates.
+
+Customize this variable to change how Projectile displays its
+status in the mode line. The default value displays the project
+name. Set this variable to nil to disable the mode line
+entirely.")
+
+(custom-autoload 'projectile-mode-line "projectile" t)
+
+(autoload 'projectile-mode "projectile" "\
+Minor mode to assist project management and navigation.
+
+When called interactively, toggle `projectile-mode'. With prefix
+ARG, enable `projectile-mode' if ARG is positive, otherwise disable
+it.
+
+When called from Lisp, enable `projectile-mode' if ARG is omitted,
+nil or positive. If ARG is `toggle', toggle `projectile-mode'.
+Otherwise behave as if called interactively.
+
+\\{projectile-mode-map}
+
+\(fn &optional ARG)" t nil)
+
+(defvar projectile-global-mode nil "\
+Non-nil if Projectile-Global mode is enabled.
+See the command `projectile-global-mode' for a description of this minor mode.
+Setting this variable directly does not take effect;
+either customize it (see the info node `Easy Customization')
+or call the function `projectile-global-mode'.")
+
+(custom-autoload 'projectile-global-mode "projectile" nil)
+
+(autoload 'projectile-global-mode "projectile" "\
+Toggle Projectile mode in all buffers.
+With prefix ARG, enable Projectile-Global mode if ARG is positive;
+otherwise, disable it. If called from Lisp, enable the mode if
+ARG is omitted or nil.
+
+Projectile mode is enabled in all buffers where
+`projectile-mode' would do it.
+See `projectile-mode' for more information on Projectile mode.
+
+\(fn &optional ARG)" t nil)
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+;;; projectile-autoloads.el ends here
--- /dev/null
+(define-package "projectile" "20150701.121" "Manage and navigate projects in Emacs easily" '((dash "2.11.0") (pkg-info "0.4")) :url "https://github.com/bbatsov/projectile" :keywords '("project" "convenience"))
--- /dev/null
+;;; projectile.el --- Manage and navigate projects in Emacs easily -*- lexical-binding: t -*-
+
+;; Copyright © 2011-2015 Bozhidar Batsov <bozhidarNOSPAMbatsov.com>
+
+;; Author: Bozhidar Batsov <bozhidarNOSPAMbatsov.com>
+;; URL: https://github.com/bbatsov/projectile
+;; Package-Version: 20150701.121
+;; Keywords: project, convenience
+;; Version: 0.13.0-cvs
+;; Package-Requires: ((dash "2.11.0") (pkg-info "0.4"))
+
+;; This file is NOT part of GNU Emacs.
+
+;; 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 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.
+
+;;; Commentary:
+;;
+;; This library provides easy project management and navigation. The
+;; concept of a project is pretty basic - just a folder containing
+;; special file. Currently git, mercurial and bazaar repos are
+;; considered projects by default. If you want to mark a folder
+;; manually as a project just create an empty .projectile file in
+;; it. See the README for more details.
+;;
+;;; Code:
+
+(require 'thingatpt)
+(require 'dash)
+(require 'ibuffer)
+(require 'ibuf-ext)
+
+(eval-when-compile
+ (defvar ggtags-completion-table)
+ (defvar tags-completion-table))
+
+(declare-function ggtags-ensure-project "ggtags")
+(declare-function ggtags-update-tags "ggtags")
+(declare-function pkg-info-version-info "pkg-info")
+(declare-function tags-completion-table "etags")
+
+;;;; Compatibility
+(eval-and-compile
+ ;; Added in Emacs 24.3.
+ (unless (fboundp 'defvar-local)
+ (defmacro defvar-local (var val &optional docstring)
+ "Define VAR as a buffer-local variable with default value VAL.
+Like `defvar' but additionally marks the variable as being automatically
+buffer-local wherever it is set."
+ (declare (debug defvar) (doc-string 3))
+ `(progn
+ (defvar ,var ,val ,docstring)
+ (make-variable-buffer-local ',var))))
+
+ ;; Added in Emacs 24.4
+ (unless (fboundp 'string-suffix-p)
+ (defun string-suffix-p (suffix string &optional ignore-case)
+ "Return non-nil if SUFFIX is a suffix of STRING.
+If IGNORE-CASE is non-nil, the comparison is done without paying
+attention to case differences."
+ (let ((start-pos (- (length string) (length suffix))))
+ (and (>= start-pos 0)
+ (eq t (compare-strings suffix nil nil
+ string start-pos nil ignore-case))))))
+ )
+
+(defun projectile-trim-string (string)
+ "Remove whitespace at the beginning and end of STRING."
+ (->> string
+ (replace-regexp-in-string "\\`[ \t\n\r]+" "")
+ (replace-regexp-in-string "[ \t\n\r]+\\'" "")))
+
+\f
+;;; Customization
+(defgroup projectile nil
+ "Manage and navigate projects easily."
+ :group 'tools
+ :group 'convenience)
+
+(defcustom projectile-indexing-method (if (eq system-type 'windows-nt) 'native 'alien)
+ "Specifies the indexing method used by Projectile.
+
+There are two indexing methods - native and alien.
+
+The native method is implemented in Emacs Lisp (therefore it is
+native to Emacs). It's advantage is that is portable and will
+work everywhere that Emacs does. It's disadvantage is that is a
+bit slow (especially for large projects). Generally it's a good
+idea to pair the native indexing method with caching.
+
+The alien indexing method uses external tools (e.g. git, find,
+etc) to speed up the indexing process. The disadvantage of this
+method is that it's not well supported on Windows systems.
+
+By default alien indexing is the default on all operating
+systems, except Windows."
+ :group 'projectile
+ :type '(radio
+ (const :tag "Native" native)
+ (const :tag "Alien" alien)))
+
+(defcustom projectile-enable-caching (eq projectile-indexing-method 'native)
+ "When t enables project files caching.
+
+Project caching is automatically enabled by default if you're
+using the native indexing method."
+ :group 'projectile
+ :type 'boolean)
+
+(defcustom projectile-file-exists-local-cache-expire nil
+ "Number of seconds before file existence cache expires for a
+file on a local file system.
+
+ A value of nil disables this cache."
+
+ :group 'projectile
+ :type '(choice (const :tag "Disabled" nil)
+ (integer :tag "Seconds")))
+
+(defcustom projectile-file-exists-remote-cache-expire (* 5 60)
+ "Number of seconds before file existence cache expires for a
+file on a remote file system such as tramp.
+
+ A value of nil disables this cache."
+ :group 'projectile
+ :type '(choice (const :tag "Disabled" nil)
+ (integer :tag "Seconds")))
+
+(defcustom projectile-require-project-root t
+ "Require the presence of a project root to operate when true.
+Otherwise consider the current directory the project root."
+ :group 'projectile
+ :type 'boolean)
+
+(defcustom projectile-completion-system 'ido
+ "The completion system to be used by Projectile."
+ :group 'projectile
+ :type '(radio
+ (const :tag "Ido" ido)
+ (const :tag "Grizzl" grizzl)
+ (const :tag "Helm" helm)
+ (const :tag "Ivy" ivy)
+ (const :tag "Default" default)
+ (function :tag "Custom function")))
+
+(defcustom projectile-keymap-prefix (kbd "C-c p")
+ "Projectile keymap prefix."
+ :group 'projectile
+ :type 'string)
+
+(defcustom projectile-cache-file
+ (expand-file-name "projectile.cache" user-emacs-directory)
+ "The name of Projectile's cache file."
+ :group 'projectile
+ :type 'string)
+
+(defcustom projectile-tags-file-name "TAGS"
+ "The tags filename Projectile's going to use."
+ :group 'projectile
+ :type 'string)
+
+(defcustom projectile-tags-command "ctags -Re -f \"%s\" %s"
+ "The command Projectile's going to use to generate a TAGS file."
+ :group 'projectile
+ :type 'string)
+
+(defcustom projectile-sort-order 'default
+ "The sort order used for a project's files."
+ :group 'projectile
+ :type '(radio
+ (const :tag "default" default)
+ (const :tag "recentf" recentf)
+ (const :tag "recently active" recently-active)
+ (const :tag "access time" access-time)
+ (const :tag "modification time" modification-time)))
+
+(defcustom projectile-verbose t
+ "Echo messages that are not errors."
+ :group 'projectile
+ :type 'boolean)
+
+(defcustom projectile-buffers-filter-function nil
+ "A function used to filter the buffers in `projectile-project-buffers'.
+
+The function should accept and return a list of Emacs buffers.
+Two example filter functions are shipped by default - `projectile-buffers-with-file'
+and `projectile-buffers-with-file-or-process'."
+ :group 'projectile
+ :type 'symbol)
+
+(defcustom projectile-project-root-files
+ '("rebar.config" ; Rebar project file
+ "project.clj" ; Leiningen project file
+ "SConstruct" ; Scons project file
+ "pom.xml" ; Maven project file
+ "build.sbt" ; SBT project file
+ "build.gradle" ; Gradle project file
+ "Gemfile" ; Bundler file
+ "requirements.txt" ; Pip file
+ "setup.py" ; Setuptools file
+ "tox.ini" ; Tox file
+ "package.json" ; npm package file
+ "gulpfile.js" ; Gulp build file
+ "Gruntfile.js" ; Grunt project file
+ "bower.json" ; Bower project file
+ "composer.json" ; Composer project file
+ "Cargo.toml" ; Cargo project file
+ "mix.exs" ; Elixir mix project file
+ )
+ "A list of files considered to mark the root of a project.
+The topmost match has precedence."
+ :group 'projectile
+ :type '(repeat string))
+
+(defcustom projectile-project-root-files-bottom-up
+ '(".projectile" ; projectile project marker
+ ".git" ; Git VCS root dir
+ ".hg" ; Mercurial VCS root dir
+ ".fslckout" ; Fossil VCS root dir
+ ".bzr" ; Bazaar VCS root dir
+ "_darcs" ; Darcs VCS root dir
+ )
+ "A list of files considered to mark the root of a project.
+The bottommost (parentmost) match has precedence."
+ :group 'projectile
+ :type '(repeat string))
+
+(defcustom projectile-project-root-files-top-down-recurring
+ '(".svn" ; Svn VCS root dir
+ "CVS" ; Csv VCS root dir
+ "Makefile")
+ "A list of files considered to mark the root of a project.
+The search starts at the top and descends down till a directory
+that contains a match file but its parent does not. Thus, it's a
+bottommost match in the topmost sequence of directories
+containing a root file."
+ :group 'projectile
+ :type '(repeat string))
+
+(defcustom projectile-project-root-files-functions
+ '(projectile-root-bottom-up
+ projectile-root-top-down
+ projectile-root-top-down-recurring)
+ "A list of functions for finding project roots."
+ :group 'projectile
+ :type '(repeat function))
+
+(defcustom projectile-globally-ignored-files
+ (list projectile-tags-file-name)
+ "A list of files globally ignored by projectile."
+ :group 'projectile
+ :type '(repeat string))
+
+(defcustom projectile-globally-ignored-file-suffixes
+ nil
+ "A list of file suffixes globally ignored by projectile."
+ :group 'projectile
+ :type '(repeat string))
+
+(defcustom projectile-globally-ignored-directories
+ '(".idea"
+ ".eunit"
+ ".git"
+ ".hg"
+ ".fslckout"
+ ".bzr"
+ "_darcs"
+ ".tox"
+ ".svn")
+ "A list of directories globally ignored by projectile."
+ :group 'projectile
+ :type '(repeat string))
+
+(defcustom projectile-globally-ignored-modes
+ '("erc-mode"
+ "help-mode"
+ "completion-list-mode"
+ "Buffer-menu-mode"
+ "gnus-.*-mode"
+ "occur-mode")
+ "A list of regular expressions for major modes ignored by projectile.
+
+If a buffer is using a given major mode, projectile will ignore
+it for functions working with buffers."
+ :group 'projectile
+ :type '(repeat string))
+
+(defcustom projectile-globally-ignored-buffers nil
+ "A list of buffer-names ignored by projectile.
+
+If a buffer is in the list projectile will ignore
+it for functions working with buffers."
+ :group 'projectile
+ :type '(repeat string)
+ :package-version '(projectile . "0.12.0"))
+
+(defcustom projectile-find-file-hook nil
+ "Hooks run when a file is opened with `projectile-find-file'."
+ :group 'projectile
+ :type 'hook)
+
+(defcustom projectile-find-dir-hook nil
+ "Hooks run when a directory is opened with `projectile-find-dir'."
+ :group 'projectile
+ :type 'hook)
+
+(defcustom projectile-switch-project-action 'projectile-find-file
+ "Action invoked after switching projects with `projectile-switch-project'.
+
+Any function that does not take arguments will do."
+ :group 'projectile
+ :type 'symbol)
+
+(defcustom projectile-find-dir-includes-top-level nil
+ "If true, add top-level dir to options offered by `projectile-find-dir'."
+ :group 'projectile
+ :type 'boolean)
+
+(defcustom projectile-use-git-grep nil
+ "If true, use `vc-git-grep' in git projects."
+ :group 'projectile
+ :type 'boolean)
+\f
+;;; Idle Timer
+(defvar projectile-idle-timer nil
+ "The timer object created when `projectile-enable-idle-timer' is non-nil.")
+
+(defcustom projectile-idle-timer-seconds 30
+ "The idle period to use when `projectile-enable-idle-timer' is non-nil."
+ :group 'projectile
+ :type 'number)
+
+(defcustom projectile-idle-timer-hook '(projectile-regenerate-tags)
+ "The hook run when `projectile-enable-idle-timer' is non-nil."
+ :group 'projectile
+ :type '(repeat symbol))
+
+(defcustom projectile-enable-idle-timer nil
+ "Enables idle timer hook `projectile-idle-timer-functions'.
+
+When `projectile-enable-idle-timer' is non-nil, the hook
+`projectile-idle-timer-hook' is run each time Emacs has been idle
+for `projectile-idle-timer-seconds' seconds and we're in a
+project."
+ :group 'projectile
+ :set (lambda (symbol value)
+ (set symbol value)
+ (when projectile-idle-timer
+ (cancel-timer projectile-idle-timer))
+ (setq projectile-idle-timer nil)
+ (when projectile-enable-idle-timer
+ (setq projectile-idle-timer (run-with-idle-timer
+ projectile-idle-timer-seconds t
+ (lambda ()
+ (when (projectile-project-p)
+ (run-hooks 'projectile-idle-timer-hook)))))))
+ :type 'boolean)
+\f
+;;; Serialization
+(defun projectile-serialize (data filename)
+ "Serialize DATA to FILENAME.
+
+The saved data can be restored with `projectile-unserialize'."
+ (when (file-writable-p filename)
+ (with-temp-file filename
+ (insert (let (print-length) (prin1-to-string data))))))
+
+(defun projectile-unserialize (filename)
+ "Read data serialized by `projectile-serialize' from FILENAME."
+ (when (file-exists-p filename)
+ (with-temp-buffer
+ (insert-file-contents filename)
+ (read (buffer-string)))))
+
+(defvar projectile-projects-cache nil
+ "A hashmap used to cache project file names to speed up related operations.")
+
+(defvar projectile-project-root-cache (make-hash-table :test 'equal)
+ "Cached value of function `projectile-project-root`.")
+
+(defvar projectile-known-projects nil
+ "List of locations where we have previously seen projects.
+The list of projects is ordered by the time they have been accessed.")
+
+(defvar projectile-known-projects-on-file nil
+ "List of known projects reference point.
+
+Contains a copy of `projectile-known-projects' when it was last
+synchronized with `projectile-known-projects-file'.")
+
+(defcustom projectile-known-projects-file
+ (expand-file-name "projectile-bookmarks.eld"
+ user-emacs-directory)
+ "Name and location of the Projectile's known projects file."
+ :group 'projectile
+ :type 'string)
+
+(defcustom projectile-ignored-projects nil
+ "A list of projects not to be added to `projectile-known-projects'."
+ :group 'projectile
+ :type '(repeat :tag "Project list" directory)
+ :package-version '(projectile . "0.11.0"))
+
+(defcustom projectile-ignored-project-function nil
+ "Function to decide if a project is added to `projectile-known-projects'.
+
+Can be either nil, or a function that takes the truename of the
+project root as argument and returns non-nil if the project is to
+be ignored or nil otherwise.
+
+This function is only called if the project is not listed in
+`projectile-ignored-projects'.
+
+A suitable candidate would be `file-remote-p' to ignore remote
+projects."
+ :group 'projectile
+ :type '(choice
+ (const :tag "Nothing" nil)
+ (const :tag "Remote files" file-remote-p)
+ function)
+ :package-version '(projectile . "0.13.0"))
+
+\f
+;;; Version information
+
+(defun projectile-version (&optional show-version)
+ "Get the Projectile version as string.
+
+If called interactively or if SHOW-VERSION is non-nil, show the
+version in the echo area and the messages buffer.
+
+The returned string includes both, the version from package.el
+and the library version, if both a present and different.
+
+If the version number could not be determined, signal an error,
+if called interactively, or if SHOW-VERSION is non-nil, otherwise
+just return nil."
+ (interactive (list t))
+ (if (require 'pkg-info nil t)
+ (let ((version (pkg-info-version-info 'projectile)))
+ (when show-version
+ (message "Projectile version: %s" version))
+ version)
+ (error "Cannot determine version without package pkg-info")))
+
+\f
+;;; Caching
+(defvar projectile-file-exists-cache
+ (make-hash-table :test 'equal)
+ "Cached `projectile-file-exists-p' results.")
+
+(defvar projectile-file-exists-cache-timer nil
+ "Timer for scheduling`projectile-file-exists-cache-cleanup'.")
+
+(defun projectile-file-exists-cache-cleanup ()
+ "Removed timed out cache entries and reschedules or remove the
+timer if no more items are in the cache."
+ (let ((now (current-time)))
+ (maphash (lambda (key value)
+ (if (time-less-p (cdr value) now)
+ (remhash key projectile-file-exists-cache)))
+ projectile-file-exists-cache)
+ (setq projectile-file-exists-cache-timer
+ (if (> (hash-table-count projectile-file-exists-cache) 0)
+ (run-with-timer 10 nil 'projectile-file-exists-cache-cleanup)))))
+
+(defun projectile-file-exists-p (filename)
+ "Return t if file FILENAME exist.
+A wrapper around `file-exists-p' with additional caching support."
+ (let* ((file-remote (file-remote-p filename))
+ (expire-seconds
+ (if file-remote
+ (and projectile-file-exists-remote-cache-expire
+ (> projectile-file-exists-remote-cache-expire 0)
+ projectile-file-exists-remote-cache-expire)
+ (and projectile-file-exists-local-cache-expire
+ (> projectile-file-exists-local-cache-expire 0)
+ projectile-file-exists-local-cache-expire)))
+ (remote-file-name-inhibit-cache (if expire-seconds
+ expire-seconds
+ remote-file-name-inhibit-cache)))
+ (if (not expire-seconds)
+ (file-exists-p filename)
+ (let* ((current-time (current-time))
+ (cached (gethash filename projectile-file-exists-cache))
+ (cached-value (if cached (car cached)))
+ (cached-expire (if cached (cdr cached)))
+ (cached-expired (if cached (time-less-p cached-expire current-time) t))
+ (value (or (and (not cached-expired) cached-value)
+ (if (file-exists-p filename) 'found 'notfound))))
+ (when (or (not cached) cached-expired)
+ (puthash filename
+ (cons value (time-add current-time (seconds-to-time expire-seconds)))
+ projectile-file-exists-cache))
+ (unless projectile-file-exists-cache-timer
+ (setq projectile-file-exists-cache-timer
+ (run-with-timer 10 nil 'projectile-file-exists-cache-cleanup)))
+ (equal value 'found)))))
+
+(defun projectile-invalidate-cache (arg)
+ "Remove the current project's files from `projectile-projects-cache'.
+
+With a prefix argument ARG prompts for the name of the project whose cache
+to invalidate."
+ (interactive "P")
+ (let ((project-root
+ (if arg
+ (completing-read "Remove cache for: "
+ (projectile-hash-keys projectile-projects-cache))
+ (projectile-project-root))))
+ (setq projectile-project-root-cache (make-hash-table :test 'equal))
+ (remhash project-root projectile-projects-cache)
+ (projectile-serialize-cache)
+ (when projectile-verbose
+ (message "Invalidated Projectile cache for %s."
+ (propertize project-root 'face 'font-lock-keyword-face))))
+ (when (fboundp 'recentf-cleanup)
+ (recentf-cleanup)))
+
+(defun projectile-cache-project (project files)
+ "Cache PROJECTs FILES.
+The cache is created both in memory and on the hard drive."
+ (when projectile-enable-caching
+ (message "Empty cache. Projectile is initializing cache...")
+ (puthash project files projectile-projects-cache)
+ (projectile-serialize-cache)))
+
+(defun projectile-purge-file-from-cache (file)
+ "Purge FILE from the cache of the current project."
+ (interactive
+ (list (projectile-completing-read
+ "Remove file from cache: "
+ (projectile-current-project-files))))
+ (let* ((project-root (projectile-project-root))
+ (project-cache (gethash project-root projectile-projects-cache)))
+ (if (projectile-file-cached-p file project-root)
+ (progn
+ (puthash project-root (remove file project-cache) projectile-projects-cache)
+ (projectile-serialize-cache)
+ (when projectile-verbose
+ (message "%s removed from cache" file)))
+ (error "%s is not in the cache" file))))
+
+(defun projectile-purge-dir-from-cache (dir)
+ "Purge DIR from the cache of the current project."
+ (interactive
+ (list (projectile-completing-read
+ "Remove directory from cache: "
+ (projectile-current-project-dirs))))
+ (let* ((project-root (projectile-project-root))
+ (project-cache (gethash project-root projectile-projects-cache)))
+ (puthash project-root
+ (--filter (string-prefix-p dir it) project-cache)
+ projectile-projects-cache)))
+
+(defun projectile-file-cached-p (file project)
+ "Check if FILE is already in PROJECT cache."
+ (member file (gethash project projectile-projects-cache)))
+
+;;;###autoload
+(defun projectile-cache-current-file ()
+ "Add the currently visited file to the cache."
+ (interactive)
+ (let ((current-project (projectile-project-root)))
+ (when (gethash (projectile-project-root) projectile-projects-cache)
+ (let* ((abs-current-file (file-truename (buffer-file-name (current-buffer))))
+ (current-file (file-relative-name abs-current-file current-project)))
+ (unless (or (projectile-file-cached-p current-file current-project)
+ (projectile-ignored-directory-p (file-name-directory abs-current-file))
+ (projectile-ignored-file-p abs-current-file))
+ (puthash current-project
+ (cons current-file (gethash current-project projectile-projects-cache))
+ projectile-projects-cache)
+ (projectile-serialize-cache)
+ (message "File %s added to project %s cache."
+ (propertize current-file 'face 'font-lock-keyword-face)
+ (propertize current-project 'face 'font-lock-keyword-face)))))))
+
+;; cache opened files automatically to reduce the need for cache invalidation
+(defun projectile-cache-files-find-file-hook ()
+ "Function for caching files with `find-file-hook'."
+ (when (and projectile-enable-caching (projectile-project-p))
+ (projectile-cache-current-file)))
+
+(defun projectile-cache-projects-find-file-hook ()
+ "Function for caching projects with `find-file-hook'."
+ (when (projectile-project-p)
+ (let ((known-projects (and (sequencep projectile-known-projects)
+ (copy-sequence projectile-known-projects))))
+ (projectile-add-known-project (projectile-project-root))
+ (unless (equal known-projects projectile-known-projects)
+ (projectile-merge-known-projects)))))
+
+
+(defun projectile-maybe-invalidate-cache (force)
+ "Invalidate if FORCE or project's dirconfig newer than cache."
+ (when (or force (file-newer-than-file-p (projectile-dirconfig-file)
+ projectile-cache-file))
+ (projectile-invalidate-cache nil)))
+
+\f
+(defadvice delete-file (before purge-from-projectile-cache (filename &optional trash))
+ (if (and projectile-enable-caching (projectile-project-p))
+ (let* ((project-root (projectile-project-root))
+ (true-filename (file-truename filename))
+ (relative-filename (file-relative-name true-filename project-root)))
+ (if (projectile-file-cached-p relative-filename project-root)
+ (projectile-purge-file-from-cache relative-filename)))))
+
+\f
+;;; Project root related utilities
+(defun projectile-parent (path)
+ "Return the parent directory of PATH.
+PATH may be a file or directory and directory paths may end with a slash."
+ (directory-file-name (file-name-directory (directory-file-name (expand-file-name path)))))
+
+(defun projectile-locate-dominating-file (file name)
+ "Look up the directory hierarchy from FILE for a directory containing NAME.
+Stop at the first parent directory containing a file NAME,
+and return the directory. Return nil if not found.
+Instead of a string, NAME can also be a predicate taking one argument
+\(a directory) and returning a non-nil value if that directory is the one for
+which we're looking."
+ ;; copied from files.el (stripped comments) emacs-24 bzr branch 2014-03-28 10:20
+ (setq file (abbreviate-file-name file))
+ (let ((root nil)
+ try)
+ (while (not (or root
+ (null file)
+ (string-match locate-dominating-stop-dir-regexp file)))
+ (setq try (if (stringp name)
+ (projectile-file-exists-p (expand-file-name name file))
+ (funcall name file)))
+ (cond (try (setq root file))
+ ((equal file (setq file (file-name-directory
+ (directory-file-name file))))
+ (setq file nil))))
+ (and root (expand-file-name (file-name-as-directory root)))))
+
+(defun projectile-root-top-down (dir &optional list)
+ "Identify a project root in DIR by top-down search for files in LIST.
+If LIST is nil, use `projectile-project-root-files' instead.
+Return the first (topmost) matched directory or nil if not found."
+ (projectile-locate-dominating-file
+ dir
+ (lambda (dir)
+ (--first (projectile-file-exists-p (expand-file-name it dir))
+ (or list projectile-project-root-files)))))
+
+(defun projectile-root-bottom-up (dir &optional list)
+ "Identify a project root in DIR by bottom-up search for files in LIST.
+If LIST is nil, use `projectile-project-root-files-bottom-up' instead.
+Return the first (bottommost) matched directory or nil if not found."
+ (--some (projectile-locate-dominating-file dir it)
+ (or list projectile-project-root-files-bottom-up)))
+
+(defun projectile-root-top-down-recurring (dir &optional list)
+ "Identify a project root in DIR by recurring top-down search for files in LIST.
+If LIST is nil, use `projectile-project-root-files-top-down-recurring'
+instead. Return the last (bottommost) matched directory in the
+topmost sequence of matched directories. Nil otherwise."
+ (--some (projectile-locate-dominating-file
+ dir
+ (lambda (dir)
+ (and (projectile-file-exists-p (expand-file-name it dir))
+ (or (string-match locate-dominating-stop-dir-regexp (projectile-parent dir))
+ (not (projectile-file-exists-p (expand-file-name it (projectile-parent dir))))))))
+ (or list projectile-project-root-files-top-down-recurring)))
+
+(defun projectile-project-root ()
+ "Retrieves the root directory of a project if available.
+The current directory is assumed to be the project's root otherwise."
+ (let ((dir default-directory))
+ (or (--some (let* ((cache-key (format "%s-%s" it dir))
+ (cache-value (gethash cache-key projectile-project-root-cache)))
+ (if cache-value
+ (if (eq cache-value 'no-project-root)
+ nil
+ cache-value)
+ (let ((value (funcall it (file-truename dir))))
+ (puthash cache-key (or value 'no-project-root) projectile-project-root-cache)
+ value)))
+ projectile-project-root-files-functions)
+ (if projectile-require-project-root
+ (error "You're not in a project")
+ default-directory))))
+
+(defun projectile-file-truename (file-name)
+ "Return the truename of FILE-NAME.
+A thin wrapper around `file-truename' that handles nil."
+ (when file-name
+ (file-truename file-name)))
+
+(defun projectile-project-p ()
+ "Check if we're in a project."
+ (condition-case nil
+ (projectile-project-root)
+ (error nil)))
+
+(defun projectile-project-name ()
+ "Return project name."
+ (let ((project-root
+ (condition-case nil
+ (projectile-project-root)
+ (error nil))))
+ (if project-root
+ (file-name-nondirectory (directory-file-name project-root))
+ "-")))
+
+\f
+;;; Project indexing
+(defun projectile-get-project-directories ()
+ "Get the list of project directories that are of interest to the user."
+ (-map (lambda (subdir) (concat (projectile-project-root) subdir))
+ (or (car (projectile-parse-dirconfig-file)) '(""))))
+
+(defun projectile-dir-files (directory)
+ "List the files in DIRECTORY and in its sub-directories.
+Files are returned as relative paths to the project root."
+ ;; check for a cache hit first if caching is enabled
+ (let ((files-list (and projectile-enable-caching
+ (gethash directory projectile-projects-cache)))
+ (root (projectile-project-root)))
+ ;; cache disabled or cache miss
+ (or files-list
+ (if (eq projectile-indexing-method 'native)
+ (projectile-dir-files-native root directory)
+ ;; use external tools to get the project files
+ (projectile-remove-ignored (projectile-dir-files-external root directory))))))
+
+(defun projectile-dir-files-native (root directory)
+ "Get the files for ROOT under DIRECTORY using just Emacs Lisp."
+ (let ((progress-reporter
+ (make-progress-reporter
+ (format "Projectile is indexing %s"
+ (propertize directory 'face 'font-lock-keyword-face)))))
+ ;; we need the files with paths relative to the project root
+ (-map (lambda (file) (file-relative-name file root))
+ (projectile-index-directory directory (projectile-patterns-to-ignore)
+ progress-reporter))))
+
+(defun projectile-dir-files-external (root directory)
+ "Get the files for ROOT under DIRECTORY using external tools."
+ (let ((default-directory directory))
+ (-map (lambda (file)
+ (file-relative-name (expand-file-name file directory) root))
+ (projectile-get-repo-files))))
+
+(defcustom projectile-git-command "git ls-files -zco --exclude-standard"
+ "Command used by projectile to get the files in a git project."
+ :group 'projectile
+ :type 'string)
+
+(defcustom projectile-git-submodule-command "git submodule --quiet foreach 'echo $path' | tr '\\n' '\\0'"
+ "Command used by projectile to get the files in git submodules."
+ :group 'projectile
+ :type 'string)
+
+(defcustom projectile-hg-command "hg locate -0 -I ."
+ "Command used by projectile to get the files in a hg project."
+ :group 'projectile
+ :type 'string)
+
+(defcustom projectile-fossil-command "fossil ls"
+ "Command used by projectile to get the files in a fossil project."
+ :group 'projectile
+ :type 'string)
+
+(defcustom projectile-bzr-command "bzr ls -R --versioned -0"
+ "Command used by projectile to get the files in a bazaar project."
+ :group 'projectile
+ :type 'string)
+
+(defcustom projectile-darcs-command "darcs show files -0 . "
+ "Command used by projectile to get the files in a darcs project."
+ :group 'projectile
+ :type 'string)
+
+(defcustom projectile-svn-command "svn list -R . | grep -v '$/' | tr '\\n' '\\0'"
+ "Command used by projectile to get the files in a svn project."
+ :group 'projectile
+ :type 'string)
+
+(defcustom projectile-generic-command "find . -type f -print0"
+ "Command used by projectile to get the files in a generic project."
+ :group 'projectile
+ :type 'string)
+
+(defun projectile-get-ext-command ()
+ "Determine which external command to invoke based on the project's VCS."
+ (let ((vcs (projectile-project-vcs)))
+ (cond
+ ((eq vcs 'git) projectile-git-command)
+ ((eq vcs 'hg) projectile-hg-command)
+ ((eq vcs 'fossil) projectile-fossil-command)
+ ((eq vcs 'bzr) projectile-bzr-command)
+ ((eq vcs 'darcs) projectile-darcs-command)
+ ((eq vcs 'svn) projectile-svn-command)
+ (t projectile-generic-command))))
+
+(defun projectile-get-sub-projects-command ()
+ (let ((vcs (projectile-project-vcs)))
+ (cond
+ ((eq vcs 'git) projectile-git-submodule-command)
+ (t ""))))
+
+(defun projectile-get-all-sub-projects (project)
+ "Get all sub-projects for a given projects.
+PROJECT is base directory to start search recursively."
+ (let* ((default-directory project)
+ ;; search for sub-projects under current project `project'
+ (submodules (mapcar
+ (lambda (s)
+ (file-name-as-directory (expand-file-name s default-directory)))
+ (projectile-files-via-ext-command (projectile-get-sub-projects-command)))))
+
+ (cond
+ ((null submodules)
+ nil)
+ (t
+ (nconc submodules (-flatten
+ ;; recursively get sub-projects of each sub-project
+ (mapcar (lambda (s)
+ (projectile-get-all-sub-projects s)) submodules)))))))
+
+(defun projectile-get-sub-projects-files ()
+ "Get files from sub-projects recursively."
+ (-flatten
+ (mapcar (lambda (s)
+ (let ((default-directory s))
+ (mapcar (lambda (f)
+ (concat s f))
+ (projectile-files-via-ext-command projectile-git-command))))
+ (condition-case nil
+ (projectile-get-all-sub-projects (projectile-project-root))
+ (error nil)))))
+
+(defun projectile-get-repo-files ()
+ "Get a list of the files in the project, including sub-projects."
+ (cond
+ ((eq (projectile-project-vcs) 'git)
+ (nconc (projectile-files-via-ext-command (projectile-get-ext-command))
+ (projectile-get-sub-projects-files)))
+ (t (projectile-files-via-ext-command (projectile-get-ext-command)))))
+
+(defun projectile-files-via-ext-command (command)
+ "Get a list of relative file names in the project root by executing COMMAND."
+ (split-string (shell-command-to-string command) "\0" t))
+
+(defun projectile-index-directory (directory patterns progress-reporter)
+ "Index DIRECTORY taking into account PATTERNS.
+The function calls itself recursively until all sub-directories
+have been indexed. The PROGRESS-REPORTER is updated while the
+function is executing."
+ (--mapcat
+ (unless (or (and patterns (projectile-ignored-rel-p it directory patterns))
+ (member (file-name-nondirectory (directory-file-name it))
+ '("." ".." ".svn" ".cvs")))
+ (progress-reporter-update progress-reporter)
+ (if (file-directory-p it)
+ (unless (projectile-ignored-directory-p
+ (file-name-as-directory it))
+ (projectile-index-directory it patterns progress-reporter))
+ (unless (projectile-ignored-file-p it)
+ (list it))))
+ (directory-files directory t)))
+
+(defun projectile-remove-ignored (files)
+ "Remove ignored files and folders from FILES.
+
+Operates on filenames relative to the project root."
+ (let ((ignored (append (projectile-ignored-files-rel)
+ (projectile-ignored-directories-rel))))
+ (-remove (lambda (file)
+ (or (--any-p (string-prefix-p it file) ignored)
+ (--any-p (string-suffix-p it file) projectile-globally-ignored-file-suffixes)))
+ files)))
+
+
+(defun projectile-buffers-with-file (buffers)
+ "Return only those BUFFERS backed by files."
+ (--filter (buffer-file-name it) buffers))
+
+(defun projectile-buffers-with-file-or-process (buffers)
+ "Return only those BUFFERS backed by files or processes."
+ (--filter (or (buffer-file-name it)
+ (get-buffer-process it)) buffers))
+
+(defun projectile-project-buffers ()
+ "Get a list of project buffers."
+ (let* ((project-root (projectile-project-root))
+ (all-buffers (-filter (lambda (buffer)
+ (projectile-project-buffer-p buffer project-root))
+ (buffer-list))))
+ (if projectile-buffers-filter-function
+ (funcall projectile-buffers-filter-function all-buffers)
+ all-buffers)))
+
+(defun projectile-process-current-project-buffers (action)
+ "Process the current project's buffers using ACTION."
+ (let ((project-buffers (projectile-project-buffers)))
+ (dolist (buffer project-buffers)
+ (funcall action buffer))))
+
+(defun projectile-project-buffer-files ()
+ "Get a list of project buffer files."
+ (let ((project-root (projectile-project-root)))
+ (->> (projectile-buffers-with-file (projectile-project-buffers))
+ (-map (lambda (buffer)
+ (file-relative-name (buffer-file-name buffer) project-root))))))
+
+(defun projectile-project-buffer-p (buffer project-root)
+ "Check if BUFFER is under PROJECT-ROOT."
+ (with-current-buffer buffer
+ (and (not (string-prefix-p " " (buffer-name buffer)))
+ (not (projectile-ignored-buffer-p buffer))
+ (string-equal (file-remote-p default-directory)
+ (file-remote-p project-root))
+ (not (string-match-p "^http\\(s\\)?://" default-directory))
+ (string-prefix-p project-root (file-truename default-directory)))))
+
+(defun projectile-ignored-buffer-p (buffer)
+ "Check if BUFFER should be ignored."
+ (or
+ (member (buffer-name buffer) projectile-globally-ignored-buffers)
+ (with-current-buffer buffer
+ (--any-p (string-match-p (concat "^" it "$")
+ (symbol-name major-mode))
+ projectile-globally-ignored-modes))))
+
+(defun projectile-recently-active-files ()
+ "Get list of recently active files.
+
+Files are ordered by recently active buffers, and then recently
+opened through use of recentf."
+ (let ((project-buffer-files (projectile-project-buffer-files)))
+ (append project-buffer-files
+ (-difference (projectile-recentf-files)
+ project-buffer-files))))
+
+(defun projectile-project-buffer-names ()
+ "Get a list of project buffer names."
+ (-map #'buffer-name (projectile-project-buffers)))
+
+(defun projectile-prepend-project-name (string)
+ "Prepend the current project's name to STRING."
+ (format "[%s] %s" (projectile-project-name) string))
+
+;;;###autoload
+(defun projectile-switch-to-buffer ()
+ "Switch to a project buffer."
+ (interactive)
+ (switch-to-buffer
+ (projectile-completing-read
+ "Switch to buffer: "
+ (projectile-project-buffer-names))))
+
+;;;###autoload
+(defun projectile-switch-to-buffer-other-window ()
+ "Switch to a project buffer and show it in another window."
+ (interactive)
+ (switch-to-buffer-other-window
+ (projectile-completing-read
+ "Switch to buffer: "
+ (projectile-project-buffer-names))))
+
+;;;###autoload
+(defun projectile-display-buffer ()
+ "Display a project buffer in another window without selecting it."
+ (interactive)
+ (display-buffer
+ (projectile-completing-read
+ "Display buffer: "
+ (projectile-project-buffer-names))))
+
+;;;###autoload
+(defun projectile-project-buffers-other-buffer ()
+ "Switch to the most recently selected buffer project buffer.
+Only buffers not visible in windows are returned."
+ (interactive)
+ (switch-to-buffer (car (projectile-project-buffers-non-visible))) nil t)
+
+(defun projectile-project-buffers-non-visible ()
+ "Get a list of non visible project buffers."
+ (-filter (lambda (buffer)
+ (not (get-buffer-window buffer 'visible)))
+ (projectile-project-buffers)))
+
+;;;###autoload
+(defun projectile-multi-occur ()
+ "Do a `multi-occur' in the project's buffers."
+ (interactive)
+ (multi-occur (projectile-project-buffers)
+ (car (occur-read-primary-args))))
+
+(defun projectile-ignored-directory-p (directory)
+ "Check if DIRECTORY should be ignored."
+ (member directory (projectile-ignored-directories)))
+
+(defun projectile-ignored-file-p (file)
+ "Check if FILE should be ignored."
+ (member file (projectile-ignored-files)))
+
+(defun projectile-ignored-rel-p (file directory patterns)
+ "Check if FILE should be ignored relative to DIRECTORY according to PATTERNS."
+ (let ((default-directory directory))
+ (--any-p (or (string-suffix-p (directory-file-name it)
+ (directory-file-name file))
+ (member file (file-expand-wildcards it t)))
+ patterns)))
+
+(defun projectile-ignored-files ()
+ "Return list of ignored files."
+ (-map
+ #'projectile-expand-root
+ (append
+ projectile-globally-ignored-files
+ (projectile-project-ignored-files))))
+
+(defun projectile-ignored-directories ()
+ "Return list of ignored directories."
+ (-map
+ #'file-name-as-directory
+ (-map
+ #'projectile-expand-root
+ (append
+ projectile-globally-ignored-directories
+ (projectile-project-ignored-directories)))))
+
+(defun projectile-ignored-directories-rel ()
+ "Return list of ignored directories, relative to the root."
+ (let ((project-root (projectile-project-root)))
+ (--map (file-relative-name it project-root) (projectile-ignored-directories))))
+
+(defun projectile-ignored-files-rel ()
+ "Return list of ignored files, relative to the root."
+ (let ((project-root (projectile-project-root)))
+ (--map (file-relative-name it project-root) (projectile-ignored-files))))
+
+(defun projectile-project-ignored-files ()
+ "Return list of project ignored files."
+ (-remove 'file-directory-p (projectile-project-ignored)))
+
+(defun projectile-project-ignored-directories ()
+ "Return list of project ignored directories."
+ (-filter 'file-directory-p (projectile-project-ignored)))
+
+(defun projectile-paths-to-ignore ()
+ "Return a list of ignored project paths."
+ (-non-nil (--map (and (string-prefix-p "/" it)
+ ;; remove the leading /
+ (substring it 1))
+ (cdr (projectile-parse-dirconfig-file)))))
+
+(defun projectile-patterns-to-ignore ()
+ "Return a list of relative file patterns."
+ (--remove (string-prefix-p "/" it)
+ (cdr (projectile-parse-dirconfig-file))))
+
+(defun projectile-project-ignored ()
+ "Return list of project ignored files/directories."
+ (let ((paths (projectile-paths-to-ignore))
+ (default-directory (projectile-project-root)))
+ (-flatten (-map
+ (lambda (pattern)
+ (or (file-expand-wildcards pattern t)
+ (projectile-expand-root pattern)))
+ paths))))
+
+(defun projectile-dirconfig-file ()
+ "Return the absolute path to the project's dirconfig file."
+ (expand-file-name ".projectile" (projectile-project-root)))
+
+(defun projectile-parse-dirconfig-file ()
+ "Parse project ignore file and return directories to ignore and keep.
+
+The return value will be a cons, the car being the list of
+directories to keep, and the cdr being the list of files or
+directories to ignore.
+
+Strings starting with + will be added to the list of directories
+to keep, and strings starting with - will be added to the list of
+directories to ignore. For backward compatibility, without a
+prefix the string will be assumed to be an ignore string."
+ (let (keep ignore (dirconfig (projectile-dirconfig-file)))
+ (when (projectile-file-exists-p dirconfig)
+ (with-temp-buffer
+ (insert-file-contents-literally dirconfig)
+ (while (not (eobp))
+ (pcase (char-after)
+ (?+ (push (buffer-substring (1+ (point)) (line-end-position)) keep))
+ (?- (push (buffer-substring (1+ (point)) (line-end-position)) ignore))
+ (_ (push (buffer-substring (point) (line-end-position)) ignore)))
+ (forward-line)))
+ (cons (--map (file-name-as-directory (projectile-trim-string it))
+ (delete "" (reverse keep)))
+ (-map #'projectile-trim-string
+ (delete "" (reverse ignore)))))))
+
+(defun projectile-expand-root (name)
+ "Expand NAME to project root.
+
+Never use on many files since it's going to recalculate the
+project-root for every file."
+ (expand-file-name name (projectile-project-root)))
+
+(defun projectile-completing-read (prompt choices &optional initial-input)
+ "Present a project tailored PROMPT with CHOICES."
+ (let ((prompt (projectile-prepend-project-name prompt)))
+ (cond
+ ((eq projectile-completion-system 'ido)
+ (ido-completing-read prompt choices nil nil initial-input))
+ ((eq projectile-completion-system 'default)
+ (completing-read prompt choices nil nil initial-input))
+ ((eq projectile-completion-system 'helm)
+ (if (fboundp 'helm-comp-read)
+ (helm-comp-read prompt choices
+ :initial-input initial-input
+ :candidates-in-buffer t
+ :must-match 'confirm)
+ (user-error "Please install helm from \
+https://github.com/emacs-helm/helm")))
+ ((eq projectile-completion-system 'grizzl)
+ (if (and (fboundp 'grizzl-completing-read)
+ (fboundp 'grizzl-make-index))
+ (grizzl-completing-read prompt (grizzl-make-index choices))
+ (user-error "Please install grizzl from \
+https://github.com/d11wtq/grizzl")))
+ ((eq projectile-completion-system 'ivy)
+ (if (fboundp 'ivy-completing-read)
+ (ivy-completing-read prompt choices nil nil initial-input)
+ (user-error "Please install ivy from \
+https://github.com/abo-abo/swiper")))
+ (t (funcall projectile-completion-system prompt choices)))))
+
+(defun projectile-current-project-files ()
+ "Return a list of files for the current project."
+ (let ((files (and projectile-enable-caching
+ (gethash (projectile-project-root) projectile-projects-cache))))
+ ;; nothing is cached
+ (unless files
+ (setq files (-mapcat #'projectile-dir-files
+ (projectile-get-project-directories)))
+ ;; cache the resulting list of files
+ (when projectile-enable-caching
+ (projectile-cache-project (projectile-project-root) files)))
+ (projectile-sort-files files)))
+
+(defun projectile-process-current-project-files (action)
+ "Process the current project's files using ACTION."
+ (let ((project-files (projectile-current-project-files))
+ (default-directory (projectile-project-root)))
+ (dolist (filename project-files)
+ (funcall action filename))))
+
+(defun projectile-current-project-dirs ()
+ "Return a list of dirs for the current project."
+ (-remove #'null (-distinct
+ (-map #'file-name-directory
+ (projectile-current-project-files)))))
+
+(defun projectile-hash-keys (hash)
+ "Return a list of all HASH keys."
+ (let (allkeys)
+ (maphash (lambda (k _v) (setq allkeys (cons k allkeys))) hash)
+ allkeys))
+
+\f
+;;; Interactive commands
+(defcustom projectile-other-file-alist
+ '(;; handle C/C++ extensions
+ ("cpp" . ("h" "hpp" "ipp"))
+ ("ipp" . ("h" "hpp" "cpp"))
+ ("hpp" . ("h" "ipp" "cpp"))
+ ("cxx" . ("hxx" "ixx"))
+ ("ixx" . ("cxx" "hxx"))
+ ("hxx" . ("ixx" "cxx"))
+ ("c" . ("h"))
+ ("m" . ("h"))
+ ("mm" . ("h"))
+ ("h" . ("c" "cpp" "ipp" "hpp" "m" "mm"))
+ ("cc" . ("hh"))
+ ("hh" . ("cc"))
+
+ ;; vertex shader and fragment shader extensions in glsl
+ ("vert" . ("frag"))
+ ("frag" . ("vert"))
+
+ ;; handle files with no extension
+ (nil . ("lock" "gpg"))
+ ("lock" . (""))
+ ("gpg" . (""))
+ )
+ "Alist of extensions for switching to file with the same name, using other extensions based on the extension of current file.")
+
+(defun projectile-find-other-file (&optional flex-matching)
+ "Switch between files with the same name but different extensions.
+With FLEX-MATCHING, match any file that contains the base name of current file.
+Other file extensions can be customized with the variable `projectile-other-file-alist'."
+ (interactive "P")
+ (-if-let (other-files (projectile-get-other-files (buffer-file-name) (projectile-current-project-files) flex-matching))
+ (if (= (length other-files) 1)
+ (find-file (expand-file-name (car other-files) (projectile-project-root)))
+ (find-file (expand-file-name (projectile-completing-read "Switch to: " other-files) (projectile-project-root))))
+ (error "No other file found")))
+
+(defun projectile-find-other-file-other-window (&optional flex-matching)
+ "Switch between files with the same name but different extensions in other window.
+With FLEX-MATCHING, match any file that contains the base name of current file.
+Other file extensions can be customized with the variable `projectile-other-file-alist'."
+ (interactive "P")
+ (-if-let (other-files (projectile-get-other-files (buffer-file-name) (projectile-current-project-files) flex-matching))
+ (if (= (length other-files) 1)
+ (find-file-other-window (expand-file-name (car other-files) (projectile-project-root)))
+ (find-file-other-window (expand-file-name (projectile-completing-read "Switch to: " other-files) (projectile-project-root))))
+ (error "No other file found")))
+
+(defun projectile-get-other-files (current-file project-file-list &optional flex-matching)
+ "Narrow to files with the same names but different extensions.
+Returns a list of possible files for users to choose.
+
+With FLEX-MATCHING, match any file that contains the base name of current file"
+ (let* ((file-ext-list (cdr (assoc (file-name-extension current-file) projectile-other-file-alist)))
+ (fulldirname (if (file-name-directory current-file)
+ (file-name-directory current-file) "./"))
+ (dirname (file-name-nondirectory (directory-file-name fulldirname)))
+ (filename (file-name-base current-file))
+ (file-list (mapcar (lambda (ext)
+ (if flex-matching
+ (concat ".*" filename ".*" "\." ext "\\'")
+ (concat "^" filename
+ (unless (equal ext "")
+ (concat "\." ext))
+ "\\'")))
+ file-ext-list))
+ (candidates (-filter (lambda (project-file)
+ (string-match filename project-file))
+ project-file-list))
+ (candidates
+ (-flatten (mapcar
+ (lambda (file)
+ (-filter (lambda (project-file)
+ (string-match file
+ (concat (file-name-base project-file)
+ (unless (equal (file-name-extension project-file) nil)
+ (concat "\." (file-name-extension project-file))))))
+ candidates))
+ file-list)))
+ (candidates
+ (-sort (lambda (file _)
+ (let ((candidate-dirname (file-name-nondirectory (directory-file-name (file-name-directory file)))))
+ (unless (equal fulldirname (file-name-directory file))
+ (equal dirname candidate-dirname))))
+ candidates)))
+ candidates))
+
+(defun projectile-select-files (project-files &optional arg)
+ "Select a list of files based on filename at point.
+
+With a prefix ARG invalidates the cache first."
+ (projectile-maybe-invalidate-cache arg)
+ (let* ((file (if (region-active-p)
+ (buffer-substring (region-beginning) (region-end))
+ (or (thing-at-point 'filename) "")))
+ (file (if (string-match "\\.?\\./" file)
+ (file-relative-name (file-truename file) (projectile-project-root))
+ file))
+ (files (if file
+ (-filter (lambda (project-file)
+ (string-match file project-file))
+ project-files)
+ nil)))
+ files))
+
+;;;###autoload
+(defun projectile-find-file-dwim (&optional arg)
+ "Jump to a project's files using completion based on context.
+
+With a prefix ARG invalidates the cache first.
+
+If point is on a filename, Projectile first tries to search for that
+file in project:
+
+- If it finds just a file, it switches to that file instantly. This works even
+if the filename is incomplete, but there's only a single file in the current project
+that matches the filename at point. For example, if there's only a single file named
+\"projectile/projectile.el\" but the current filename is \"projectile/proj\" (incomplete),
+`projectile-find-file' still switches to \"projectile/projectile.el\" immediately
+ because this is the only filename that matches.
+
+- If it finds a list of files, the list is displayed for selecting. A list of
+files is displayed when a filename appears more than one in the project or the
+filename at point is a prefix of more than two files in a project. For example,
+if `projectile-find-file' is executed on a filepath like \"projectile/\", it lists
+the content of that directory. If it is executed on a partial filename like
+ \"projectile/a\", a list of files with character 'a' in that directory is presented.
+
+- If it finds nothing, display a list of all files in project for selecting."
+ (interactive "P")
+ (let* ((project-files (projectile-current-project-files))
+ (files (projectile-select-files project-files arg)))
+ (cond
+ ((= (length files) 1)
+ (find-file (expand-file-name (car files) (projectile-project-root))))
+ ((> (length files) 1)
+ (find-file (expand-file-name (projectile-completing-read "Switch to: " files) (projectile-project-root))))
+ (t (find-file (expand-file-name (projectile-completing-read "Switch to: " project-files) (projectile-project-root)))))
+ (run-hooks 'projectile-find-file-hook)))
+
+;;;###autoload
+(defun projectile-find-file-dwim-other-window (&optional arg)
+ "Jump to a project's files using completion based on context in other window.
+
+With a prefix ARG invalidates the cache first.
+
+If point is on a filename, Projectile first tries to search for that
+file in project:
+
+- If it finds just a file, it switches to that file instantly. This works even
+if the filename is incomplete, but there's only a single file in the current project
+that matches the filename at point. For example, if there's only a single file named
+\"projectile/projectile.el\" but the current filename is \"projectile/proj\" (incomplete),
+`projectile-find-file' still switches to \"projectile/projectile.el\"
+immediately because this is the only filename that matches.
+
+- If it finds a list of files, the list is displayed for selecting. A list of
+files is displayed when a filename appears more than one in the project or the
+filename at point is a prefix of more than two files in a project. For example,
+if `projectile-find-file' is executed on a filepath like \"projectile/\", it lists
+the content of that directory. If it is executed on a partial filename
+like \"projectile/a\", a list of files with character 'a' in that directory
+is presented.
+
+- If it finds nothing, display a list of all files in project for selecting."
+ (interactive "P")
+ (let* ((project-files (projectile-current-project-files))
+ (files (projectile-select-files project-files arg)))
+ (cond
+ ((= (length files) 1)
+ (find-file-other-window (expand-file-name (car files) (projectile-project-root))))
+ ((> (length files) 1)
+ (find-file-other-window (expand-file-name (projectile-completing-read "Switch to: " files) (projectile-project-root))))
+ (t (find-file-other-window (expand-file-name (projectile-completing-read "Switch to: " project-files) (projectile-project-root)))))
+ (run-hooks 'projectile-find-file-hook)))
+
+;;;###autoload
+(defun projectile-find-file (&optional arg)
+ "Jump to a project's file using completion.
+With a prefix ARG invalidates the cache first."
+ (interactive "P")
+ (projectile-maybe-invalidate-cache arg)
+ (let ((file (projectile-completing-read "Find file: "
+ (projectile-current-project-files))))
+ (find-file (expand-file-name file (projectile-project-root)))
+ (run-hooks 'projectile-find-file-hook)))
+
+;;;###autoload
+(defun projectile-find-file-other-window (&optional arg)
+ "Jump to a project's file using completion and show it in another window.
+
+With a prefix ARG invalidates the cache first."
+ (interactive "P")
+ (projectile-maybe-invalidate-cache arg)
+ (let ((file (projectile-completing-read "Find file: "
+ (projectile-current-project-files))))
+ (find-file-other-window (expand-file-name file (projectile-project-root)))
+ (run-hooks 'projectile-find-file-hook)))
+
+(defun projectile-sort-files (files)
+ "Sort FILES according to `projectile-sort-order'."
+ (pcase projectile-sort-order
+ (`default files)
+ (`recentf (projectile-sort-by-recentf-first files))
+ (`recently-active (projectile-sort-by-recently-active-first files))
+ (`modification-time (projectile-sort-by-modification-time files))
+ (`access-time (projectile-sort-by-access-time files))))
+
+(defun projectile-sort-by-recentf-first (files)
+ "Sort FILES by a recent first scheme."
+ (let ((project-recentf-files (projectile-recentf-files)))
+ (append project-recentf-files
+ (-difference files project-recentf-files))))
+
+(defun projectile-sort-by-recently-active-first (files)
+ "Sort FILES by most recently active buffers or opened files."
+ (let ((project-recently-active-files (projectile-recently-active-files)))
+ (append project-recently-active-files
+ (-difference files project-recently-active-files))))
+
+(defun projectile-sort-by-modification-time (files)
+ "Sort FILES by modification time."
+ (let ((default-directory (projectile-project-root)))
+ (-sort (lambda (file1 file2)
+ (let ((file1-mtime (nth 5 (file-attributes file1)))
+ (file2-mtime (nth 5 (file-attributes file2))))
+ (not (time-less-p file1-mtime file2-mtime))))
+ files)))
+
+(defun projectile-sort-by-access-time (files)
+ "Sort FILES by access time."
+ (let ((default-directory (projectile-project-root)))
+ (-sort (lambda (file1 file2)
+ (let ((file1-atime (nth 4 (file-attributes file1)))
+ (file2-atime (nth 4 (file-attributes file2))))
+ (not (time-less-p file1-atime file2-atime))))
+ files)))
+
+(defun projectile-find-dir (&optional arg)
+ "Jump to a project's directory using completion.
+
+With a prefix ARG invalidates the cache first."
+ (interactive "P")
+ (projectile-maybe-invalidate-cache arg)
+ (let ((dir (projectile-complete-dir)))
+ (dired (expand-file-name dir (projectile-project-root)))
+ (run-hooks 'projectile-find-dir-hook)))
+
+(defun projectile-find-dir-other-window (&optional arg)
+ "Jump to a project's directory in other window using completion.
+
+With a prefix ARG invalidates the cache first."
+ (interactive "P")
+ (when arg
+ (projectile-invalidate-cache nil))
+ (let ((dir (projectile-complete-dir)))
+ (dired-other-window (expand-file-name dir (projectile-project-root)))
+ (run-hooks 'projectile-find-dir-hook)))
+
+(defun projectile-complete-dir ()
+ (projectile-completing-read
+ "Find dir: "
+ (if projectile-find-dir-includes-top-level
+ (append '("./") (projectile-current-project-dirs))
+ (projectile-current-project-dirs))))
+
+(defun projectile-find-test-file (&optional arg)
+ "Jump to a project's test file using completion.
+
+With a prefix ARG invalidates the cache first."
+ (interactive "P")
+ (projectile-maybe-invalidate-cache arg)
+ (let ((file (projectile-completing-read "Find test file: "
+ (projectile-current-project-test-files))))
+ (find-file (expand-file-name file (projectile-project-root)))))
+
+(defun projectile-test-files (files)
+ "Return only the test FILES."
+ (-filter 'projectile-test-file-p files))
+
+(defun projectile-test-file-p (file)
+ "Check if FILE is a test file."
+ (or (--any? (string-prefix-p it (file-name-nondirectory file))
+ (-non-nil (list (funcall projectile-test-prefix-function (projectile-project-type)))))
+ (--any? (string-suffix-p it (file-name-nondirectory file))
+ (-non-nil (list (funcall projectile-test-suffix-function (projectile-project-type)))))))
+
+(defun projectile-current-project-test-files ()
+ "Return a list of test files for the current project."
+ (projectile-test-files (projectile-current-project-files)))
+
+(defvar projectile-rails-rspec '("Gemfile" "app" "lib" "db" "config" "spec"))
+(defvar projectile-rails-test '("Gemfile" "app" "lib" "db" "config" "test"))
+(defvar projectile-symfony '("composer.json" "app" "src" "vendor"))
+(defvar projectile-ruby-rspec '("Gemfile" "lib" "spec"))
+(defvar projectile-ruby-test '("Gemfile" "lib" "test"))
+(defvar projectile-django '("manage.py"))
+(defvar projectile-python-pip '("requirements.txt"))
+(defvar projectile-python-pkg '("setup.py"))
+(defvar projectile-python-tox '("tox.ini"))
+(defvar projectile-scons '("SConstruct"))
+(defvar projectile-maven '("pom.xml"))
+(defvar projectile-gradle '("build.gradle"))
+(defvar projectile-grails '("application.properties" "grails-app"))
+(defvar projectile-lein-test '("project.clj"))
+(defvar projectile-lein-midje '("project.clj" ".midje.clj"))
+(defvar projectile-rebar '("rebar"))
+(defvar projectile-sbt '("build.sbt"))
+(defvar projectile-make '("Makefile"))
+(defvar projectile-grunt '("Gruntfile.js"))
+(defvar projectile-gulp '("gulpfile.js"))
+(defvar projectile-haskell-cabal '("*.cabal"))
+(defvar projectile-rust-cargo '("Cargo.toml"))
+(defvar projectile-r '("DESCRIPTION"))
+
+(defun projectile-go ()
+ "Check if a project contains Go source files."
+ (-any? (lambda (file)
+ (string= (file-name-extension file) "go"))
+ (projectile-current-project-files)))
+
+(defcustom projectile-go-function 'projectile-go
+ "Function to determine if project's type is go."
+ :group 'projectile
+ :type 'function)
+
+(defvar-local projectile-project-type nil
+ "Buffer local var for overriding the auto-detected project type.
+Normally you'd set this from .dir-locals.el.")
+
+(defun projectile-project-type ()
+ "Determine the project's type based on its structure."
+ (cond
+ (projectile-project-type projectile-project-type)
+ ((projectile-verify-files projectile-rails-rspec) 'rails-rspec)
+ ((projectile-verify-files projectile-rails-test) 'rails-test)
+ ((projectile-verify-files projectile-ruby-rspec) 'ruby-rspec)
+ ((projectile-verify-files projectile-ruby-test) 'ruby-test)
+ ((projectile-verify-files projectile-django) 'django)
+ ((projectile-verify-files projectile-python-tox) 'python-tox)
+ ((projectile-verify-files projectile-python-pip)'python)
+ ((projectile-verify-files projectile-python-pkg) 'python)
+ ((projectile-verify-files projectile-symfony) 'symfony)
+ ((projectile-verify-files projectile-lein-midje) 'lein-midje)
+ ((projectile-verify-files projectile-lein-test) 'lein-test)
+ ((projectile-verify-files projectile-scons) 'scons)
+ ((projectile-verify-files projectile-maven) 'maven)
+ ((projectile-verify-files projectile-gradle) 'gradle)
+ ((projectile-verify-files projectile-grails) 'grails)
+ ((projectile-verify-files projectile-rebar) 'rebar)
+ ((projectile-verify-files projectile-sbt) 'sbt)
+ ((projectile-verify-files projectile-make) 'make)
+ ((projectile-verify-files projectile-gulp) 'gulp)
+ ((projectile-verify-files projectile-grunt) 'grunt)
+ ((projectile-verify-files projectile-haskell-cabal) 'haskell-cabal)
+ ((projectile-verify-files projectile-rust-cargo) 'rust-cargo)
+ ((projectile-verify-files projectile-r) 'r)
+ ((funcall projectile-go-function) 'go)
+ (t 'generic)))
+
+(defun projectile-project-info ()
+ "Display info for current project."
+ (interactive)
+ (message "Project dir: %s ## Project VCS: %s ## Project type: %s"
+ (projectile-project-root)
+ (projectile-project-vcs)
+ (projectile-project-type)))
+
+(defun projectile-verify-files (files)
+ "Check whether all FILES exist in the current project."
+ (-all? 'projectile-verify-file files))
+
+(defun projectile-verify-file (file)
+ "Check whether FILE exists in the current project.
+Expands wildcards using `file-expand-wildcards' before checking."
+ (file-expand-wildcards (projectile-expand-root file)))
+
+(defun projectile-project-vcs (&optional project-root)
+ "Determine the VCS used by the project if any.
+PROJECT-ROOT is the targeted directory. If nil, use
+`projectile-project-root'."
+ (or project-root (setq project-root (projectile-project-root)))
+ (cond
+ ((projectile-file-exists-p (expand-file-name ".git" project-root)) 'git)
+ ((projectile-file-exists-p (expand-file-name ".hg" project-root)) 'hg)
+ ((projectile-file-exists-p (expand-file-name ".fossil" project-root)) 'fossil)
+ ((projectile-file-exists-p (expand-file-name ".bzr" project-root)) 'bzr)
+ ((projectile-file-exists-p (expand-file-name "_darcs" project-root)) 'darcs)
+ ((projectile-file-exists-p (expand-file-name ".svn" project-root)) 'svn)
+ ((projectile-locate-dominating-file project-root ".git") 'git)
+ ((projectile-locate-dominating-file project-root ".hg") 'hg)
+ ((projectile-locate-dominating-file project-root ".fossil") 'fossil)
+ ((projectile-locate-dominating-file project-root ".bzr") 'bzr)
+ ((projectile-locate-dominating-file project-root "_darcs") 'darcs)
+ ((projectile-locate-dominating-file project-root ".svn") 'svn)
+ (t 'none)))
+
+(defun projectile--test-name-for-impl-name (impl-file-path)
+ (let* ((project-type (projectile-project-type))
+ (impl-file-name (file-name-sans-extension (file-name-nondirectory impl-file-path)))
+ (impl-file-ext (file-name-extension impl-file-path))
+ (test-prefix (funcall projectile-test-prefix-function project-type))
+ (test-suffix (funcall projectile-test-suffix-function project-type)))
+ (cond
+ (test-prefix (concat test-prefix impl-file-name "." impl-file-ext))
+ (test-suffix (concat impl-file-name test-suffix "." impl-file-ext))
+ (t (error "Project type not supported!")))))
+
+(defun projectile-create-test-file-for (impl-file-path)
+ (let* ((test-file (projectile--test-name-for-impl-name impl-file-path))
+ (test-dir (replace-regexp-in-string "src/" "test/" (file-name-directory impl-file-path))))
+ (unless (file-exists-p (expand-file-name test-file test-dir))
+ (progn (unless (file-exists-p test-dir)
+ (make-directory test-dir :create-parents))
+ (concat test-dir test-file)))))
+
+(defcustom projectile-create-missing-test-files nil
+ "During toggling, if non-nil enables creating test files if not found.
+
+When not-nil, every call to projectile-find-implementation-or-test-*
+creates test files if not found on the file system. Defaults to nil.
+It assumes the test/ folder is at the same level as src/."
+ :group 'projectile
+ :type 'boolean)
+
+(defun projectile-find-implementation-or-test (file-name)
+ "Given a FILE-NAME return the matching implementation or test filename."
+ (unless file-name (error "The current buffer is not visiting a file"))
+ (if (projectile-test-file-p file-name)
+ ;; find the matching impl file
+ (let ((impl-file (projectile-find-matching-file file-name)))
+ (if impl-file
+ (projectile-expand-root impl-file)
+ (error "No matching source file found")))
+ ;; find the matching test file
+ (let ((test-file (projectile-find-matching-test file-name)))
+ (if test-file
+ (projectile-expand-root test-file)
+ (if projectile-create-missing-test-files
+ (projectile-create-test-file-for file-name)
+ (error "No matching test file found"))))))
+
+;;;###autoload
+(defun projectile-find-implementation-or-test-other-window ()
+ "Open matching implementation or test file in other window."
+ (interactive)
+ (find-file-other-window
+ (projectile-find-implementation-or-test (buffer-file-name))))
+
+;;;###autoload
+(defun projectile-toggle-between-implementation-and-test ()
+ "Toggle between an implementation file and its test file."
+ (interactive)
+ (find-file
+ (projectile-find-implementation-or-test (buffer-file-name))))
+
+(defcustom projectile-test-prefix-function 'projectile-test-prefix
+ "Function to find test files prefix based on PROJECT-TYPE."
+ :group 'projectile
+ :type 'function)
+
+(defcustom projectile-test-suffix-function 'projectile-test-suffix
+ "Function to find test files suffix based on PROJECT-TYPE."
+ :group 'projectile
+ :type 'function)
+
+(defun projectile-test-affix (project-type)
+ "Find test files affix based on PROJECT-TYPE."
+ (or (funcall projectile-test-prefix-function project-type)
+ (funcall projectile-test-suffix-function project-type)
+ (error "Project type not supported!")))
+
+(defun projectile-test-prefix (project-type)
+ "Find default test files prefix based on PROJECT-TYPE."
+ (cond
+ ((member project-type '(django python)) "test_")
+ ((member project-type '(lein-midje)) "t_")))
+
+(defun projectile-test-suffix (project-type)
+ "Find default test files suffix based on PROJECT-TYPE."
+ (cond
+ ((member project-type '(rails-rspec ruby-rspec)) "_spec")
+ ((member project-type '(rails-test ruby-test lein-test go)) "_test")
+ ((member project-type '(scons)) "test")
+ ((member project-type '(maven symfony)) "Test")
+ ((member project-type '(gradle grails)) "Spec")))
+
+(defun projectile-dirname-matching-count (a b)
+ "Count matching dirnames ascending file paths."
+ (setq a (reverse (split-string (file-name-directory a) "/" t))
+ b (reverse (split-string (file-name-directory b) "/" t)))
+ (let ((common 0))
+ (while (and a b (string-equal (pop a) (pop b)))
+ (setq common (1+ common)))
+ common))
+
+(defun projectile-group-file-candidates (file candidates)
+ "Group file candidates by dirname matching count."
+ (--sort (> (car it) (car other))
+ (--group-by (projectile-dirname-matching-count file it) candidates)))
+
+(defun projectile-find-matching-test (file)
+ "Compute the name of the test matching FILE."
+ (let* ((basename (file-name-nondirectory (file-name-sans-extension file)))
+ (test-affix (projectile-test-affix (projectile-project-type)))
+ (candidates
+ (-filter (lambda (current-file)
+ (let ((name (file-name-nondirectory
+ (file-name-sans-extension current-file))))
+ (or (string-equal name (concat test-affix basename))
+ (string-equal name (concat basename test-affix)))))
+ (projectile-current-project-files))))
+ (cond
+ ((null candidates) nil)
+ ((= (length candidates) 1) (car candidates))
+ (t (let ((grouped-candidates (projectile-group-file-candidates file candidates)))
+ (if (= (length (car grouped-candidates)) 2)
+ (-last-item (car grouped-candidates))
+ (projectile-completing-read "Switch to: " (--mapcat (cdr it) grouped-candidates))))))))
+
+(defun projectile-find-matching-file (test-file)
+ "Compute the name of a file matching TEST-FILE."
+ (let* ((basename (file-name-nondirectory (file-name-sans-extension test-file)))
+ (test-affix (projectile-test-affix (projectile-project-type)))
+ (candidates
+ (-filter (lambda (current-file)
+ (let ((name (file-name-nondirectory
+ (file-name-sans-extension current-file))))
+ (or (string-equal (concat test-affix name) basename)
+ (string-equal (concat name test-affix) basename))))
+ (projectile-current-project-files))))
+ (cond
+ ((null candidates) nil)
+ ((= (length candidates) 1) (car candidates))
+ (t (let ((grouped-candidates (projectile-group-file-candidates test-file candidates)))
+ (if (= (length (car grouped-candidates)) 2)
+ (-last-item (car grouped-candidates))
+ (projectile-completing-read "Switch to: " (--mapcat (cdr it) grouped-candidates))))))))
+
+(defun projectile-grep-default-files ()
+ "Try to find a default pattern for `projectile-grep'.
+This is a subset of `grep-read-files', where either a matching entry from
+`grep-files-aliases' or file name extension pattern is returned."
+ (when buffer-file-name
+ (let* ((fn (file-name-nondirectory buffer-file-name))
+ (default-alias
+ (let ((aliases (remove (assoc "all" grep-files-aliases)
+ grep-files-aliases))
+ alias)
+ (while aliases
+ (setq alias (car aliases)
+ aliases (cdr aliases))
+ (if (string-match (mapconcat
+ #'wildcard-to-regexp
+ (split-string (cdr alias) nil t)
+ "\\|")
+ fn)
+ (setq aliases nil)
+ (setq alias nil)))
+ (cdr alias)))
+ (default-extension
+ (let ((ext (file-name-extension fn)))
+ (and ext (concat "*." ext)))))
+ (or default-alias default-extension))))
+
+(defun projectile-grep (&optional arg)
+ "Perform rgrep in the project.
+
+With a prefix ARG asks for files (globbing-aware) which to grep in.
+With prefix ARG of `-' (such as `M--'), default the files (without prompt),
+to `projectile-grep-default-files'."
+ (interactive "P")
+ (require 'grep) ;; for `rgrep'
+ (let* ((roots (projectile-get-project-directories))
+ (search-regexp (if (and transient-mark-mode mark-active)
+ (buffer-substring (region-beginning) (region-end))
+ (read-string (projectile-prepend-project-name "Grep for: ")
+ (projectile-symbol-at-point))))
+ (files (and arg (or (and (equal current-prefix-arg '-)
+ (projectile-grep-default-files))
+ (read-string (projectile-prepend-project-name "Grep in: ")
+ (projectile-grep-default-files))))))
+ (dolist (root-dir roots)
+ (require 'vc-git) ;; for `vc-git-grep'
+ ;; in git projects users have the option to use `vc-git-grep' instead of `rgrep'
+ (if (and (eq (projectile-project-vcs) 'git)
+ projectile-use-git-grep
+ (fboundp 'vc-git-grep))
+ (vc-git-grep search-regexp (or files "") root-dir)
+ ;; paths for find-grep should relative and without trailing /
+ (let ((grep-find-ignored-directories
+ (-union (--map (directory-file-name (file-relative-name it root-dir))
+ (projectile-ignored-directories))
+ grep-find-ignored-directories))
+ (grep-find-ignored-files
+ (-union (-map (lambda (file)
+ (file-relative-name file root-dir))
+ (projectile-ignored-files))
+ grep-find-ignored-files)))
+ (grep-compute-defaults)
+ (rgrep search-regexp (or files "* .*") root-dir))))))
+
+(defun projectile-ag (search-term &optional arg)
+ "Run an ag search with SEARCH-TERM in the project.
+
+With an optional prefix argument ARG SEARCH-TERM is interpreted as a
+regular expression."
+ (interactive
+ (list (read-from-minibuffer
+ (projectile-prepend-project-name (format "Ag %ssearch for: " (if current-prefix-arg "regexp " "")))
+ (projectile-symbol-at-point))
+ current-prefix-arg))
+ (if (fboundp 'ag-regexp)
+ (let ((ag-command (if arg 'ag-regexp 'ag))
+ ;; reset the prefix arg, otherwise it will affect the ag-command
+ (current-prefix-arg nil))
+ (funcall ag-command search-term (projectile-project-root)))
+ (error "Ag is not available")))
+
+(defun projectile-tags-exclude-patterns ()
+ "Return a string with exclude patterns for ctags."
+ (mapconcat (lambda (pattern) (format "--exclude=\"%s\""
+ (directory-file-name pattern)))
+ (projectile-ignored-directories-rel) " "))
+
+;;;###autoload
+(defun projectile-regenerate-tags ()
+ "Regenerate the project's [e|g]tags."
+ (interactive)
+ (if (boundp 'ggtags-mode)
+ (progn
+ (let* ((ggtags-project-root (projectile-project-root))
+ (default-directory ggtags-project-root))
+ (ggtags-ensure-project)
+ (ggtags-update-tags t)))
+ (let* ((project-root (projectile-project-root))
+ (tags-exclude (projectile-tags-exclude-patterns))
+ (default-directory project-root)
+ (tags-file (expand-file-name projectile-tags-file-name))
+ (command (format projectile-tags-command tags-file tags-exclude))
+ shell-output exit-code)
+ (with-temp-buffer
+ (setq exit-code
+ (call-process-shell-command command nil (current-buffer))
+ shell-output (projectile-trim-string
+ (buffer-substring (point-min) (point-max)))))
+ (unless (zerop exit-code)
+ (error shell-output))
+ (visit-tags-table tags-file))))
+
+(defun projectile-visit-project-tags-table ()
+ "Visit the current project's tags table."
+ (when (projectile-project-p)
+ (let ((tags-file (projectile-expand-root projectile-tags-file-name)))
+ (when (file-exists-p tags-file)
+ (with-demoted-errors "Error loading tags-file: %s"
+ (visit-tags-table tags-file t))))))
+
+;;;###autoload
+(defun projectile-find-tag ()
+ "Find tag in project."
+ (interactive)
+ (let ((find-tag-function (if (boundp 'ggtags-mode) 'ggtags-find-tag 'find-tag))
+ (tags (if (boundp 'ggtags-mode)
+ (projectile--tags (all-completions "" ggtags-completion-table))
+ (require 'etags)
+ ;; we have to manually reset the tags-completion-table every time
+ (setq tags-completion-table nil)
+ (tags-completion-table)
+ (projectile--tags tags-completion-table))))
+ (funcall find-tag-function (projectile-completing-read "Find tag: "
+ tags
+ (projectile-symbol-at-point)))))
+
+(defun projectile--tags (completion-table)
+ "Find tags using COMPLETION-TABLE."
+ (-reject #'null
+ (-map (lambda (x)
+ (unless (integerp x)
+ (prin1-to-string x t)))
+ completion-table)))
+
+(defmacro projectile-with-default-dir (dir &rest body)
+ "Invoke in DIR the BODY."
+ (declare (debug t) (indent 1))
+ `(let ((default-directory ,dir))
+ ,@body))
+
+;;;###autoload
+(defun projectile-run-command-in-root ()
+ "Invoke `execute-extended-command' in the project's root."
+ (interactive)
+ (projectile-with-default-dir (projectile-project-root)
+ (call-interactively 'execute-extended-command)))
+
+;;;###autoload
+(defun projectile-run-shell-command-in-root ()
+ "Invoke `shell-command' in the project's root."
+ (interactive)
+ (projectile-with-default-dir (projectile-project-root)
+ (call-interactively 'shell-command)))
+
+;;;###autoload
+(defun projectile-run-async-shell-command-in-root ()
+ "Invoke `async-shell-command' in the project's root."
+ (interactive)
+ (projectile-with-default-dir (projectile-project-root)
+ (call-interactively 'async-shell-command)))
+
+(defun projectile-files-in-project-directory (directory)
+ "Return a list of files in DIRECTORY."
+ (let ((dir (file-relative-name (expand-file-name directory)
+ (projectile-project-root))))
+ (--filter (string-prefix-p dir it)
+ (projectile-current-project-files))))
+
+(defun projectile-unixy-system-p ()
+ "Check to see if unixy text utilities are installed."
+ (--all? (executable-find it) '("grep" "cut" "uniq")))
+
+(defun projectile-files-from-cmd (cmd directory)
+ "Use a grep-like CMD to search for files within DIRECTORY.
+
+CMD should include the necessary search params and should output
+equivalently to grep -HlI (only unique matching filenames).
+Returns a list of expanded filenames."
+ (let ((default-directory directory))
+ (--map (concat directory
+ (if (string-prefix-p "./" it) (substring it 2) it))
+ (-> (shell-command-to-string cmd)
+ projectile-trim-string
+ (split-string "\n+" t)))))
+
+(defun projectile-files-with-string (string directory)
+ "Return a list of all files containing STRING in DIRECTORY.
+
+Tries to use ag, ack, git-grep, and grep in that order. If those
+are impossible (for instance on Windows), returns a list of all
+files in the project."
+ (if (projectile-unixy-system-p)
+ (let* ((search-term (shell-quote-argument string))
+ (cmd (cond ((executable-find "ag")
+ (concat "ag --literal --nocolor --noheading -l -- "
+ search-term))
+ ((executable-find "ack")
+ (concat "ack --noheading --nocolor -l -- "
+ search-term))
+ ((and (executable-find "git")
+ (eq (projectile-project-vcs) 'git))
+ (concat "git grep -HlI "
+ search-term))
+ (t
+ (concat "grep -rHlI "
+ search-term
+ " .")))))
+ (projectile-files-from-cmd cmd directory))
+ ;; we have to reject directories as a workaround to work with git submodules
+ (-reject #'file-directory-p
+ (-map #'projectile-expand-root (projectile-dir-files directory)))))
+
+(defun projectile-replace (&optional arg)
+ "Replace a string in the project using `tags-query-replace'.
+
+With a prefix argument ARG prompts you for a directory on which
+to run the replacement."
+ (interactive "P")
+ (let* ((old-text (read-string
+ (projectile-prepend-project-name "Replace: ")
+ (projectile-symbol-at-point)))
+ (new-text (read-string
+ (projectile-prepend-project-name
+ (format "Replace %s with: " old-text))))
+ (directory (if arg
+ (file-name-as-directory
+ (read-directory-name "Replace in directory: "))
+ (projectile-project-root)))
+ (files (projectile-files-with-string old-text directory)))
+ (tags-query-replace old-text new-text nil (cons 'list files))))
+
+(defun projectile-symbol-at-point ()
+ "Get the symbol at point and strip its properties."
+ (substring-no-properties (or (thing-at-point 'symbol) "")))
+
+;;;###autoload
+(defun projectile-kill-buffers ()
+ "Kill all project buffers."
+ (interactive)
+ (let ((name (projectile-project-name))
+ (buffers (projectile-project-buffers)))
+ (if (yes-or-no-p
+ (format "Are you sure you want to kill %d buffer(s) for '%s'? "
+ (length buffers) name))
+ ;; we take care not to kill indirect buffers directly
+ ;; as we might encounter them after their base buffers are killed
+ (mapc #'kill-buffer (-remove 'buffer-base-buffer buffers)))))
+
+;;;###autoload
+(defun projectile-save-project-buffers ()
+ "Save all project buffers."
+ (interactive)
+ (--each (projectile-project-buffers)
+ (with-current-buffer it
+ (when buffer-file-name
+ (save-buffer)))))
+
+;;;###autoload
+(defun projectile-dired ()
+ "Open `dired' at the root of the project."
+ (interactive)
+ (dired (projectile-project-root)))
+
+;;;###autoload
+(defun projectile-vc (&optional project-root)
+ "Open `vc-dir' at the root of the project.
+
+For git projects `magit-status-internal' is used if available."
+ (interactive)
+ (or project-root (setq project-root (projectile-project-root)))
+ (if (eq (projectile-project-vcs project-root) 'git)
+ (cond ((fboundp 'magit-status-internal)
+ (magit-status-internal project-root))
+ ((fboundp 'magit-status)
+ (with-no-warnings (magit-status project-root)))
+ (t
+ (vc-dir project-root)))
+ (vc-dir project-root)))
+
+;;;###autoload
+(defun projectile-recentf ()
+ "Show a list of recently visited files in a project."
+ (interactive)
+ (if (boundp 'recentf-list)
+ (find-file (projectile-expand-root (projectile-completing-read "Recently visited files: " (projectile-recentf-files))))
+ (message "recentf is not enabled")))
+
+(defun projectile-recentf-files ()
+ "Return a list of recently visited files in a project."
+ (and (boundp 'recentf-list)
+ (let ((project-root (projectile-project-root)))
+ (->> recentf-list
+ (--filter (string-prefix-p project-root it))
+ (--map (file-relative-name it project-root))))))
+
+(defun projectile-serialize-cache ()
+ "Serializes the memory cache to the hard drive."
+ (projectile-serialize projectile-projects-cache projectile-cache-file))
+
+(defvar projectile-rails-compile-cmd "bundle exec rails server")
+(defvar projectile-ruby-compile-cmd "bundle exec rake build")
+(defvar projectile-ruby-test-cmd "bundle exec rake test")
+(defvar projectile-ruby-rspec-cmd "bundle exec rspec")
+(defvar projectile-django-compile-cmd "python manage.py runserver")
+(defvar projectile-django-test-cmd "python manage.py test")
+(defvar projectile-python-compile-cmd "python setup.py build")
+(defvar projectile-python-test-cmd "python -m unittest discover")
+(defvar projectile-python-tox-test-cmd "tox")
+(defvar projectile-symfony-compile-cmd "app/console server:run")
+(defvar projectile-symfony-test-cmd "phpunit -c app ")
+(defvar projectile-scons-compile-cmd "scons")
+(defvar projectile-scons-test-cmd "scons test")
+(defvar projectile-maven-compile-cmd "mvn clean install")
+(defvar projectile-maven-test-cmd "mvn test")
+(defvar projectile-gradle-compile-cmd "gradle build")
+(defvar projectile-gradle-test-cmd "gradle test")
+(defvar projectile-grails-compile-cmd "grails package")
+(defvar projectile-grails-test-cmd "grails test-app")
+(defvar projectile-lein-compile-cmd "lein compile")
+(defvar projectile-lein-test-cmd "lein test")
+(defvar projectile-lein-midje-test-cmd "lein midje")
+(defvar projectile-rebar-compile-cmd "rebar")
+(defvar projectile-rebar-test-cmd "rebar eunit")
+(defvar projectile-sbt-compile-cmd "sbt compile")
+(defvar projectile-sbt-test-cmd "sbt test")
+(defvar projectile-make-compile-cmd "make")
+(defvar projectile-make-test-cmd "make test")
+(defvar projectile-grunt-compile-cmd "grunt")
+(defvar projectile-grunt-test-cmd "grunt test")
+(defvar projectile-gulp-compile-cmd "gulp")
+(defvar projectile-gulp-test-cmd "gulp test")
+(defvar projectile-go-compile-cmd "go build ./...")
+(defvar projectile-go-test-cmd "go test ./...")
+(defvar projectile-haskell-cabal-compile-cmd "cabal build")
+(defvar projectile-haskell-cabal-test-cmd "cabal test")
+(defvar projectile-rust-cargo-compile-cmd "cargo build")
+(defvar projectile-rust-cargo-test-cmd "cargo test")
+(defvar projectile-r-compile-cmd "R CMD INSTALL .")
+(defvar projectile-r-test-cmd (concat "R CMD check -o "
+ temporary-file-directory " ."))
+
+(--each '(projectile-rails-compile-cmd
+ projectile-ruby-compile-cmd
+ projectile-ruby-test-cmd
+ projectile-ruby-rspec-cmd
+ projectile-django-compile-cmd
+ projectile-django-test-cmd
+ projectile-python-compile-cmd
+ projectile-python-test-cmd
+ projectile-python-tox-test-cmd
+ projectile-symfony-compile-cmd
+ projectile-symfony-test-cmd
+ projectile-scons-compile-cmd
+ projectile-scons-test-cmd
+ projectile-maven-compile-cmd
+ projectile-maven-test-cmd
+ projectile-lein-compile-cmd
+ projectile-lein-test-cmd
+ projectile-lein-midje-test-cmd
+ projectile-rebar-compile-cmd
+ projectile-rebar-test-cmd
+ projectile-sbt-compile-cmd
+ projectile-sbt-test-cmd
+ projectile-make-compile-cmd
+ projectile-make-test-cmd
+ projectile-grunt-compile-cmd
+ projectile-grunt-test-cmd
+ projectile-haskell-cabal-compile-cmd
+ projectile-haskell-cabal-test-cmd
+ projectile-rust-cargo-compile-cmd
+ projectile-rust-cargo-test-cmd
+ projectile-r-compile-cmd
+ projectile-r-test-cmd)
+ (put it 'safe-local-variable #'stringp))
+
+
+(defvar projectile-compilation-cmd-map
+ (make-hash-table :test 'equal)
+ "A mapping between projects and the last compilation command used on them.")
+(defvar projectile-test-cmd-map
+ (make-hash-table :test 'equal)
+ "A mapping between projects and the last test command used on them.")
+
+(defun projectile-default-compilation-command (project-type)
+ "Retrieve default compilation command for PROJECT-TYPE."
+ (cond
+ ((member project-type '(rails-rspec rails-test)) projectile-rails-compile-cmd)
+ ((member project-type '(ruby-rspec ruby-test)) projectile-ruby-compile-cmd)
+ ((eq project-type 'django) projectile-django-compile-cmd)
+ ((eq project-type 'python) projectile-python-compile-cmd)
+ ((eq project-type 'symfony) projectile-symfony-compile-cmd)
+ ((member project-type '(lein-test lein-midje)) projectile-lein-compile-cmd)
+ ((eq project-type 'make) projectile-make-compile-cmd)
+ ((eq project-type 'rebar) projectile-rebar-compile-cmd)
+ ((eq project-type 'scons) projectile-scons-compile-cmd)
+ ((eq project-type 'maven) projectile-maven-compile-cmd)
+ ((eq project-type 'gradle) projectile-gradle-compile-cmd)
+ ((eq project-type 'grails) projectile-grails-compile-cmd)
+ ((eq project-type 'sbt) projectile-sbt-compile-cmd)
+ ((eq project-type 'grunt) projectile-grunt-compile-cmd)
+ ((eq project-type 'gulp) projectile-gulp-compile-cmd)
+ ((eq project-type 'go) projectile-go-compile-cmd)
+ ((eq project-type 'haskell-cabal) projectile-haskell-cabal-compile-cmd)
+ ((eq project-type 'rust-cargo) projectile-rust-cargo-compile-cmd)
+ ((eq project-type 'r) projectile-r-compile-cmd)
+ (t projectile-make-compile-cmd)))
+
+(defun projectile-default-test-command (project-type)
+ "Retrieve default test command for PROJECT-TYPE."
+ (cond
+ ((member project-type '(rails-rspec ruby-rspec)) projectile-ruby-rspec-cmd)
+ ((member project-type '(rails-test ruby-test)) projectile-ruby-test-cmd)
+ ((eq project-type 'django) projectile-django-test-cmd)
+ ((eq project-type 'python-tox) projectile-python-tox-test-cmd)
+ ((eq project-type 'python) projectile-python-test-cmd)
+ ((eq project-type 'symfony) projectile-symfony-test-cmd)
+ ((eq project-type 'lein-test) projectile-lein-test-cmd)
+ ((eq project-type 'lein-midje) projectile-lein-midje-test-cmd)
+ ((eq project-type 'make) projectile-make-test-cmd)
+ ((eq project-type 'rebar) projectile-rebar-test-cmd)
+ ((eq project-type 'scons) projectile-scons-test-cmd)
+ ((eq project-type 'maven) projectile-maven-test-cmd)
+ ((eq project-type 'gradle) projectile-gradle-test-cmd)
+ ((eq project-type 'grails) projectile-grails-test-cmd)
+ ((eq project-type 'sbt) projectile-sbt-test-cmd)
+ ((eq project-type 'grunt) projectile-grunt-test-cmd)
+ ((eq project-type 'gulp) projectile-gulp-test-cmd)
+ ((eq project-type 'go) projectile-go-test-cmd)
+ ((eq project-type 'haskell-cabal) projectile-haskell-cabal-test-cmd)
+ ((eq project-type 'rust-cargo) projectile-rust-cargo-test-cmd)
+ ((eq project-type 'r) projectile-r-test-cmd)
+ (t projectile-make-test-cmd)))
+
+(defun projectile-compilation-command (project)
+ "Retrieve the compilation command for PROJECT."
+ (or (gethash project projectile-compilation-cmd-map)
+ (projectile-default-compilation-command (projectile-project-type))))
+
+(defun projectile-test-command (project)
+ "Retrieve the test command for PROJECT."
+ (or (gethash project projectile-test-cmd-map)
+ (projectile-default-test-command (projectile-project-type))))
+
+(defun projectile-compile-project (arg &optional dir)
+ "Run project compilation command.
+
+Normally you'll be prompted for a compilation command, unless
+variable `compilation-read-command'. You can force the prompt
+with a prefix ARG."
+ (interactive "P")
+ (let* ((project-root (if dir
+ dir
+ (projectile-project-root)))
+ (default-directory project-root)
+ (default-cmd (projectile-compilation-command project-root))
+ (compilation-cmd (if (or compilation-read-command arg)
+ (compilation-read-command default-cmd)
+ default-cmd)))
+ (puthash project-root compilation-cmd projectile-compilation-cmd-map)
+ (save-some-buffers (not compilation-ask-about-save)
+ (lambda ()
+ (projectile-project-buffer-p (current-buffer)
+ project-root)))
+ (compilation-start compilation-cmd)))
+
+(defadvice compilation-find-file (around projectile-compilation-find-file)
+ "Try to find a buffer for FILENAME, if we cannot find it,
+fallback to the original function."
+ (let ((filename (ad-get-arg 1)))
+ (setf ad-return-value
+ (or
+ (if (file-exists-p (expand-file-name filename))
+ (find-file-noselect filename))
+ ;; Try to find the filename using projectile
+ (and (projectile-project-p)
+ (let ((root (projectile-project-root))
+ (dirs (cons "" (projectile-current-project-dirs))))
+ (-when-let (full-filename (->> dirs
+ (--map (expand-file-name filename (expand-file-name it root)))
+ (-filter #'file-exists-p)
+ (-first-item)))
+ (find-file-noselect full-filename))))
+ ;; Fall back to the old function `compilation-find-file'
+ ad-do-it))))
+
+;; TODO - factor this duplication out
+(defun projectile-test-project (arg)
+ "Run project test command.
+
+Normally you'll be prompted for a compilation command, unless
+variable `compilation-read-command'. You can force the prompt
+with a prefix ARG."
+ (interactive "P")
+ (let* ((project-root (projectile-project-root))
+ (default-cmd (projectile-test-command project-root))
+ (test-cmd (if (or compilation-read-command arg)
+ (compilation-read-command default-cmd)
+ default-cmd))
+ (default-directory project-root))
+ (puthash project-root test-cmd projectile-test-cmd-map)
+ (compilation-start test-cmd)))
+
+(defun projectile-relevant-known-projects ()
+ "Return a list of known projects except the current one (if present)."
+ (if (projectile-project-p)
+ (-difference projectile-known-projects
+ (list (abbreviate-file-name (projectile-project-root))))
+ projectile-known-projects))
+
+(defun projectile-switch-project (&optional arg)
+ "Switch to a project we have visited before.
+Invokes the command referenced by `projectile-switch-project-action' on switch.
+With a prefix ARG invokes `projectile-commander' instead of
+`projectile-switch-project-action.'"
+ (interactive "P")
+ (-if-let (projects (projectile-relevant-known-projects))
+ (projectile-switch-project-by-name
+ (projectile-completing-read "Switch to project: " projects)
+ arg)
+ (error "There are no known projects")))
+
+(defun projectile-switch-project-by-name (project-to-switch &optional arg)
+ "Switch to project by project name PROJECT-TO-SWITCH.
+Invokes the command referenced by `projectile-switch-project-action' on switch.
+With a prefix ARG invokes `projectile-commander' instead of
+`projectile-switch-project-action.'"
+ (let* ((default-directory project-to-switch)
+ (switch-project-action (if arg
+ 'projectile-commander
+ projectile-switch-project-action)))
+ (funcall switch-project-action)
+ (run-hooks 'projectile-switch-project-hook)))
+
+
+(defun projectile-find-file-in-directory (&optional directory)
+ "Jump to a file in a (maybe regular) DIRECTORY.
+
+This command will first prompt for the directory the file is in."
+ (interactive "DFind file in directory: ")
+ (let ((default-directory directory)
+ (projectile-require-project-root nil))
+ (if (projectile-project-p)
+ ;; target directory is in a project
+ (let ((file (projectile-completing-read "Find file: "
+ (projectile-dir-files directory))))
+ (find-file (expand-file-name file (projectile-project-root)))
+ (run-hooks 'projectile-find-file-hook))
+ ;; target directory is not in a project
+ (projectile-find-file))))
+
+(defun projectile-all-project-files ()
+ "Get a list of all files in all projects."
+ (-mapcat (lambda (project)
+ (when (file-exists-p project)
+ (let ((default-directory project))
+ (-map (lambda (file)
+ (expand-file-name file project))
+ (projectile-current-project-files)))))
+ projectile-known-projects))
+
+;;;###autoload
+(defun projectile-find-file-in-known-projects ()
+ "Jump to a file in any of the known projects."
+ (interactive)
+ (let ((projectile-require-project-root nil))
+ (find-file (projectile-completing-read "Find file in projects: " (projectile-all-project-files)))))
+
+(defcustom projectile-switch-project-hook nil
+ "Hooks run when project is switched."
+ :group 'projectile
+ :type 'hook)
+
+(defun projectile-keep-project-p (project)
+ "Determine wether we should cleanup this project or not.
+
+It handles the case of remote files as well. See `projectile-cleanup-known-projects'."
+ ;; Taken from from `recentf-keep-default-predicate'
+ (cond
+ ((file-remote-p project nil t) (file-readable-p project))
+ ((file-remote-p project))
+ ((file-readable-p project))))
+
+;;;###autoload
+(defun projectile-cleanup-known-projects ()
+ "Remove known projects that don't exist anymore."
+ (interactive)
+ (projectile-merge-known-projects)
+ (let* ((separated-projects
+ (-separate #'projectile-keep-project-p projectile-known-projects))
+ (projects-kept (car separated-projects))
+ (projects-removed (cadr separated-projects)))
+ (setq projectile-known-projects projects-kept)
+ (projectile-merge-known-projects)
+ (if projects-removed
+ (message "Projects removed: %s"
+ (mapconcat #'identity projects-removed ", "))
+ (message "No projects needed to be removed."))))
+
+;;;###autoload
+(defun projectile-clear-known-projects ()
+ "Clear both `projectile-known-projects' and `projectile-known-projects-file'."
+ (interactive)
+ (setq projectile-known-projects nil)
+ (projectile-save-known-projects))
+
+(defun projectile-remove-known-project (&optional project)
+ "Remove PROJECT from the list of known projects."
+ (interactive (list (projectile-completing-read "Remove from known projects: "
+ projectile-known-projects)))
+ (setq projectile-known-projects
+ (--reject (string= project it) projectile-known-projects))
+ (projectile-merge-known-projects)
+ (when projectile-verbose
+ (message "Project %s removed from the list of known projects." project)))
+
+;;;###autoload
+(defun projectile-remove-current-project-from-known-projects ()
+ "Remove the current project from the list of known projects."
+ (interactive)
+ (projectile-remove-known-project (abbreviate-file-name (projectile-project-root))))
+
+(defun projectile-ignored-projects ()
+ "A list of projects that should not be save in `projectile-known-projects'."
+ (-map #'file-truename projectile-ignored-projects))
+
+(defun projectile-ignored-project-p (project-root)
+ "Return t if PROJECT-ROOT should not be added to `projectile-known-projects'."
+ (or (member project-root (projectile-ignored-projects))
+ (and (functionp projectile-ignored-project-function)
+ (funcall projectile-ignored-project-function project-root))))
+
+(defun projectile-add-known-project (project-root)
+ "Add PROJECT-ROOT to the list of known projects."
+ (unless (projectile-ignored-project-p project-root)
+ (setq projectile-known-projects
+ (-distinct
+ (cons (abbreviate-file-name project-root)
+ projectile-known-projects)))))
+
+(defun projectile-load-known-projects ()
+ "Load saved projects from `projectile-known-projects-file'.
+Also set `projectile-known-projects'."
+ (setq projectile-known-projects
+ (projectile-unserialize projectile-known-projects-file))
+ (setq projectile-known-projects-on-file
+ (and (sequencep projectile-known-projects)
+ (copy-sequence projectile-known-projects))))
+
+;; load the known projects
+(projectile-load-known-projects)
+
+(defun projectile-save-known-projects ()
+ "Save PROJECTILE-KNOWN-PROJECTS to PROJECTILE-KNOWN-PROJECTS-FILE."
+ (projectile-serialize projectile-known-projects
+ projectile-known-projects-file)
+ (setq projectile-known-projects-on-file
+ (and (sequencep projectile-known-projects)
+ (copy-sequence projectile-known-projects))))
+
+(defun projectile-merge-known-projects ()
+ "Merge any change from `projectile-known-projects-file' and save to disk.
+
+This enables multiple Emacs processes to make changes without
+overwriting each other's changes."
+ (let* ((known-now projectile-known-projects)
+ (known-on-last-sync projectile-known-projects-on-file)
+ (known-on-file
+ (projectile-unserialize projectile-known-projects-file))
+ (removed-after-sync (-difference known-on-last-sync known-now))
+ (removed-in-other-process
+ (-difference known-on-last-sync known-on-file))
+ (result (-distinct
+ (-difference
+ (-concat known-now known-on-file)
+ (-concat removed-after-sync removed-in-other-process)))))
+ (setq projectile-known-projects result)
+ (projectile-save-known-projects)))
+
+(define-ibuffer-filter projectile-files
+ "Show Ibuffer with all buffers in the current project."
+ (:reader (read-directory-name "Project root: " (ignore-errors (projectile-project-root)))
+ :description nil)
+ (with-current-buffer buf
+ (equal (file-name-as-directory (expand-file-name qualifier))
+ (ignore-errors (projectile-project-root)))))
+
+(defun projectile-ibuffer-by-project (project-root)
+ "Open an IBuffer window showing all buffers in PROJECT-ROOT."
+ (let ((project-name (file-name-nondirectory (directory-file-name project-root))))
+ (ibuffer nil (format "*%s Buffers*" project-name)
+ (list (cons 'projectile-files project-root)))))
+
+(defun projectile-ibuffer (prefix)
+ "Open an IBuffer window showing all buffers in the current project.
+
+Let user choose another project when PREFIX is supplied."
+ (interactive "p")
+ (let ((project-root (if (= prefix 4)
+ (projectile-completing-read
+ "Project name: "
+ (projectile-relevant-known-projects))
+ (projectile-project-root))))
+
+ (projectile-ibuffer-by-project project-root)))
+
+;;;; projectile-commander
+
+(defconst projectile-commander-help-buffer "*Commander Help*")
+
+(defvar projectile-commander-methods nil
+ "List of file-selection methods for the `projectile-commander' command.
+Each element is a list (KEY DESCRIPTION FUNCTION).
+DESCRIPTION is a one-line description of what the key selects.")
+
+;;;###autoload
+(defun projectile-commander ()
+ "Execute a Projectile command with a single letter.
+The user is prompted for a single character indicating the action to invoke.
+The `?' character describes then
+available actions.
+
+See `def-projectile-commander-method' for defining new methods."
+ (interactive)
+ (-let* ((choices (-map #'car projectile-commander-methods))
+ (prompt (concat "Commander [" choices "]: "))
+ (ch (read-char-choice prompt choices))
+ ((_ _ fn) (assq ch projectile-commander-methods)))
+ (funcall fn)))
+
+(defmacro def-projectile-commander-method (key description &rest body)
+ "Define a new `projectile-commander' method.
+
+KEY is the key the user will enter to choose this method.
+
+DESCRIPTION is a one-line sentence describing how the method.
+
+BODY is a series of forms which are evaluated when the find
+is chosen."
+ (let ((method `(lambda ()
+ ,@body)))
+ `(setq projectile-commander-methods
+ (--sort (< (car it) (car other))
+ (cons (list ,key ,description ,method)
+ (assq-delete-all ,key projectile-commander-methods))))))
+
+(def-projectile-commander-method ?? "Commander help buffer."
+ (ignore-errors (kill-buffer projectile-commander-help-buffer))
+ (with-current-buffer (get-buffer-create projectile-commander-help-buffer)
+ (insert "Projectile Commander Methods:\n\n")
+ (--each projectile-commander-methods
+ (-let [(key line _) it]
+ (insert (format "%c:\t%s\n" key line))))
+ (goto-char (point-min))
+ (help-mode)
+ (display-buffer (current-buffer) t))
+ (projectile-commander))
+
+(defun projectile-commander-bindings ()
+ (def-projectile-commander-method ?A
+ "Find ag on project."
+ (call-interactively 'projectile-ag))
+
+ (def-projectile-commander-method ?f
+ "Find file in project."
+ (projectile-find-file))
+
+ (def-projectile-commander-method ?T
+ "Find test file in project."
+ (projectile-find-test-file))
+
+ (def-projectile-commander-method ?b
+ "Switch to project buffer."
+ (projectile-switch-to-buffer))
+
+ (def-projectile-commander-method ?d
+ "Find directory in project."
+ (projectile-find-dir))
+
+ (def-projectile-commander-method ?D
+ "Open project root in dired."
+ (projectile-dired))
+
+ (def-projectile-commander-method ?v
+ "Open project root in vc-dir or magit."
+ (projectile-vc))
+
+ (def-projectile-commander-method ?r
+ "Replace a string in the project."
+ (projectile-replace))
+
+ (def-projectile-commander-method ?R
+ "Regenerate the project's [e|g]tags."
+ (projectile-regenerate-tags))
+
+ (def-projectile-commander-method ?g
+ "Run grep on project."
+ (projectile-grep))
+
+ (def-projectile-commander-method ?s
+ "Switch project."
+ (projectile-switch-project))
+
+ (def-projectile-commander-method ?o
+ "Run multi-occur on project buffers."
+ (projectile-multi-occur))
+
+ (def-projectile-commander-method ?j
+ "Find tag in project."
+ (projectile-find-tag))
+
+ (def-projectile-commander-method ?k
+ "Kill all project buffers."
+ (projectile-kill-buffers))
+
+ (def-projectile-commander-method ?e
+ "Find recently visited file in project."
+ (projectile-recentf)))
+
+(projectile-commander-bindings)
+
+;;; Minor mode
+(defvar projectile-command-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd "4 a") #'projectile-find-other-file-other-window)
+ (define-key map (kbd "4 b") #'projectile-switch-to-buffer-other-window)
+ (define-key map (kbd "4 C-o") #'projectile-display-buffer)
+ (define-key map (kbd "4 d") #'projectile-find-dir-other-window)
+ (define-key map (kbd "4 f") #'projectile-find-file-other-window)
+ (define-key map (kbd "4 g") #'projectile-find-file-dwim-other-window)
+ (define-key map (kbd "4 t") #'projectile-find-implementation-or-test-other-window)
+ (define-key map (kbd "!") #'projectile-run-shell-command-in-root)
+ (define-key map (kbd "&") #'projectile-run-async-shell-command-in-root)
+ (define-key map (kbd "a") #'projectile-find-other-file)
+ (define-key map (kbd "b") #'projectile-switch-to-buffer)
+ (define-key map (kbd "c") #'projectile-compile-project)
+ (define-key map (kbd "d") #'projectile-find-dir)
+ (define-key map (kbd "D") #'projectile-dired)
+ (define-key map (kbd "e") #'projectile-recentf)
+ (define-key map (kbd "f") #'projectile-find-file)
+ (define-key map (kbd "g") #'projectile-find-file-dwim)
+ (define-key map (kbd "F") #'projectile-find-file-in-known-projects)
+ (define-key map (kbd "i") #'projectile-invalidate-cache)
+ (define-key map (kbd "I") #'projectile-ibuffer)
+ (define-key map (kbd "j") #'projectile-find-tag)
+ (define-key map (kbd "k") #'projectile-kill-buffers)
+ (define-key map (kbd "l") #'projectile-find-file-in-directory)
+ (define-key map (kbd "m") #'projectile-commander)
+ (define-key map (kbd "o") #'projectile-multi-occur)
+ (define-key map (kbd "p") #'projectile-switch-project)
+ (define-key map (kbd "P") #'projectile-test-project)
+ (define-key map (kbd "r") #'projectile-replace)
+ (define-key map (kbd "R") #'projectile-regenerate-tags)
+ (define-key map (kbd "s g") #'projectile-grep)
+ (define-key map (kbd "s s") #'projectile-ag)
+ (define-key map (kbd "S") #'projectile-save-project-buffers)
+ (define-key map (kbd "t") #'projectile-toggle-between-implementation-and-test)
+ (define-key map (kbd "T") #'projectile-find-test-file)
+ (define-key map (kbd "v") #'projectile-vc)
+ (define-key map (kbd "z") #'projectile-cache-current-file)
+ (define-key map (kbd "ESC") #'projectile-project-buffers-other-buffer)
+ map)
+ "Keymap for Projectile commands after `projectile-keymap-prefix'.")
+(fset 'projectile-command-map projectile-command-map)
+
+(defvar projectile-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map projectile-keymap-prefix 'projectile-command-map)
+ map)
+ "Keymap for Projectile mode.")
+
+(easy-menu-change
+ '("Tools") "Projectile"
+ '(["Find file" projectile-find-file]
+ ["Find file in known projects" projectile-find-file-in-known-projects]
+ ["Find test file" projectile-find-test-file]
+ ["Find directory" projectile-find-dir]
+ ["Find file in directory" projectile-find-file-in-directory]
+ ["Find other file" projectile-find-other-file]
+ ["Switch to buffer" projectile-switch-to-buffer]
+ ["Jump between implementation file and test file" projectile-toggle-between-implementation-and-test]
+ ["Kill project buffers" projectile-kill-buffers]
+ ["Recent files" projectile-recentf]
+ "--"
+ ["Open project in dired" projectile-dired]
+ ["Switch to project" projectile-switch-project]
+ ["Search in project (grep)" projectile-grep]
+ ["Search in project (ag)" projectile-ag]
+ ["Replace in project" projectile-replace]
+ ["Multi-occur in project" projectile-multi-occur]
+ "--"
+ ["Cache current file" projectile-cache-current-file]
+ ["Invalidate cache" projectile-invalidate-cache]
+ ["Regenerate [e|g]tags" projectile-regenerate-tags]
+ "--"
+ ["Compile project" projectile-compile-project]
+ ["Test project" projectile-test-project]
+ "--"
+ ["Project info" projectile-project-info]
+ ["About" projectile-version])
+ "Search Files (Grep)...")
+
+(easy-menu-change '("Tools") "--" nil "Search Files (Grep)...")
+
+;;;###autoload
+(defcustom projectile-mode-line
+ '(:eval (format " Projectile[%s]" (projectile-project-name)))
+ "Mode line lighter for Projectile.
+
+The value of this variable is a mode line template as in
+`mode-line-format'. See Info Node `(elisp)Mode Line Format' for
+details about mode line templates.
+
+Customize this variable to change how Projectile displays its
+status in the mode line. The default value displays the project
+name. Set this variable to nil to disable the mode line
+entirely."
+ :group 'projectile
+ :type 'sexp
+ :risky t
+ :package-version '(projectile "0.12.0"))
+
+;;;###autoload
+(define-minor-mode projectile-mode
+ "Minor mode to assist project management and navigation.
+
+When called interactively, toggle `projectile-mode'. With prefix
+ARG, enable `projectile-mode' if ARG is positive, otherwise disable
+it.
+
+When called from Lisp, enable `projectile-mode' if ARG is omitted,
+nil or positive. If ARG is `toggle', toggle `projectile-mode'.
+Otherwise behave as if called interactively.
+
+\\{projectile-mode-map}"
+ :lighter projectile-mode-line
+ :keymap projectile-mode-map
+ :group 'projectile
+ :require 'projectile
+ (cond
+ (projectile-mode
+ ;; initialize the projects cache if needed
+ (unless projectile-projects-cache
+ (setq projectile-projects-cache
+ (or (projectile-unserialize projectile-cache-file)
+ (make-hash-table :test 'equal))))
+ (add-hook 'find-file-hook #'projectile-cache-files-find-file-hook t t)
+ (add-hook 'find-file-hook #'projectile-cache-projects-find-file-hook t t)
+ (add-hook 'projectile-find-dir-hook #'projectile-cache-projects-find-file-hook)
+ (add-hook 'find-file-hook #'projectile-visit-project-tags-table t t)
+ (add-hook 'dired-before-readin-hook #'projectile-cache-projects-find-file-hook t t)
+ (ad-activate 'compilation-find-file)
+ (ad-activate 'delete-file))
+ (t
+ (remove-hook 'find-file-hook #'projectile-cache-files-find-file-hook t)
+ (remove-hook 'find-file-hook #'projectile-cache-projects-find-file-hook t)
+ (remove-hook 'find-file-hook #'projectile-visit-project-tags-table t)
+ (remove-hook 'dired-before-readin-hook #'projectile-cache-projects-find-file-hook t)
+ (ad-deactivate 'compilation-find-file)
+ (ad-deactivate 'delete-file))))
+
+;;;###autoload
+(define-globalized-minor-mode projectile-global-mode
+ projectile-mode
+ projectile-mode)
+
+(provide 'projectile)
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
+
+;;; projectile.el ends here
--- /dev/null
+This library provides easy project management and navigation. The
+concept of a project is pretty basic - just a folder containing
+special file. Currently git, mercurial and bazaar repos are
+considered projects by default. If you want to mark a folder
+manually as a project just create an empty .projectile file in
+it. See the README for more details.
+++ /dev/null
-;;; rainbow-delimiters-autoloads.el --- automatically extracted autoloads
-;;
-;;; Code:
-
-\f
-;;;### (autoloads (global-rainbow-delimiters-mode rainbow-delimiters-mode-disable
-;;;;;; rainbow-delimiters-mode-enable rainbow-delimiters-mode) "rainbow-delimiters"
-;;;;;; "rainbow-delimiters.el" (21342 6615))
-;;; Generated autoloads from rainbow-delimiters.el
-
-(autoload 'rainbow-delimiters-mode "rainbow-delimiters" "\
-Highlight nested parentheses, brackets, and braces according to their depth.
-
-\(fn &optional ARG)" t nil)
-
-(autoload 'rainbow-delimiters-mode-enable "rainbow-delimiters" "\
-Not documented
-
-\(fn)" nil nil)
-
-(autoload 'rainbow-delimiters-mode-disable "rainbow-delimiters" "\
-Not documented
-
-\(fn)" nil nil)
-
-(defvar global-rainbow-delimiters-mode nil "\
-Non-nil if Global-Rainbow-Delimiters mode is enabled.
-See the command `global-rainbow-delimiters-mode' for a description of this minor mode.
-Setting this variable directly does not take effect;
-either customize it (see the info node `Easy Customization')
-or call the function `global-rainbow-delimiters-mode'.")
-
-(custom-autoload 'global-rainbow-delimiters-mode "rainbow-delimiters" nil)
-
-(autoload 'global-rainbow-delimiters-mode "rainbow-delimiters" "\
-Toggle Rainbow-Delimiters mode in every possible buffer.
-With prefix ARG, turn Global-Rainbow-Delimiters mode on if and only if ARG is positive.
-Rainbow-Delimiters mode is enabled in all buffers where `rainbow-delimiters-mode-enable' would do it.
-See `rainbow-delimiters-mode' for more information on Rainbow-Delimiters mode.
-
-\(fn &optional ARG)" t nil)
-
-;;;***
-\f
-;;;### (autoloads nil nil ("rainbow-delimiters-pkg.el") (21342 6615
-;;;;;; 468730))
-
-;;;***
-\f
-(provide 'rainbow-delimiters-autoloads)
-;; Local Variables:
-;; version-control: never
-;; no-byte-compile: t
-;; no-update-autoloads: t
-;; coding: utf-8
-;; End:
-;;; rainbow-delimiters-autoloads.el ends here
+++ /dev/null
-(define-package "rainbow-delimiters" "20140329.934" "Highlight nested parens, brackets, braces a different color at each depth." (quote nil))
+++ /dev/null
-;;; rainbow-delimiters.el --- Highlight nested parens, brackets, braces a different color at each depth.
-
-;; Copyright (C) 2010-2013 Jeremy Rayman.
-;; Author: Jeremy Rayman <opensourceNOSPAMjeremyrayman.com>
-;; Maintainer: Jeremy Rayman <opensourceNOSPAMjeremyrayman.com>
-;; Created: 2010-09-02
-;; Version: 20140329.934
-;; X-Original-Version: 1.3.5
-;; Keywords: faces, convenience, lisp, matching, tools, rainbow, rainbow parentheses, rainbow parens
-;; EmacsWiki: http://www.emacswiki.org/emacs/RainbowDelimiters
-;; Github: http://github.com/jlr/rainbow-delimiters
-;; URL: http://github.com/jlr/rainbow-delimiters/raw/master/rainbow-delimiters.el
-
-;; 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 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, see <http://www.gnu.org/licenses/>.
-
-
-;;; Commentary:
-;;
-;; Rainbow-delimiters is a “rainbow parentheses”-like mode which highlights
-;; parentheses, brackets, and braces according to their depth. Each
-;; successive level is highlighted in a different color. This makes it easy
-;; to spot matching delimiters, orient yourself in the code, and tell which
-;; statements are at a given level.
-;;
-;; Great care has been taken to make this mode FAST. You shouldn't see
-;; any discernible change in scrolling or editing speed while using it,
-;; even in delimiter-rich languages like Clojure, Lisp, and Scheme.
-;;
-;; Default colors are subtle, with the philosophy that syntax highlighting
-;; shouldn't be visually intrusive. Color schemes are always a matter of
-;; taste. If you take the time to design a new color scheme, please share
-;; (even a simple list of colors works) on the EmacsWiki page or via github.
-;; EmacsWiki: http://www.emacswiki.org/emacs/RainbowDelimiters
-;; Github: http://github.com/jlr/rainbow-delimiters
-
-
-;;; Installation:
-
-;; 1. Place rainbow-delimiters.el on your emacs load-path.
-;;
-;; 2. Compile the file (necessary for speed):
-;; M-x byte-compile-file <location of rainbow-delimiters.el>
-;;
-;; 3. Add the following to your dot-emacs/init file:
-;; (require 'rainbow-delimiters)
-;;
-;; 4. Activate the mode in your init file.
-;; You can choose to enable it only in certain modes, or Emacs-wide:
-;;
-;; - To enable it only in certain modes, add lines like the following:
-;; (add-hook 'clojure-mode-hook 'rainbow-delimiters-mode)
-;;
-;; - To enable it in all programming-related emacs modes (Emacs 24+):
-;; (add-hook 'prog-mode-hook 'rainbow-delimiters-mode)
-;;
-;; - To activate the mode globally, add to your init file:
-;; (global-rainbow-delimiters-mode)
-;;
-;; - To temporarily activate rainbow-delimiters mode in an open buffer:
-;; M-x rainbow-delimiters-mode
-;;
-;; - To toggle global-rainbow-delimiters-mode:
-;; M-x global-rainbow-delimiters-mode
-
-;;; Customization:
-
-;; To customize various options, including the color scheme:
-;; M-x customize-group rainbow-delimiters
-;;
-;; deftheme / color-theme.el users:
-;; You can specify custom colors by adding the appropriate faces to your theme.
-;; - Faces take the form of:
-;; 'rainbow-delimiters-depth-#-face' with # being the depth.
-;; Depth begins at 1, the outermost color.
-;; Faces exist for depths 1-9.
-;; - The unmatched delimiter face (normally colored red) is:
-;; 'rainbow-delimiters-unmatched-face'
-
-
-;;; Change Log:
-
-;; 1.0 - Initial release.
-;; 1.1 - Stop tracking each delimiter's depth independently.
-;; This had lead to confusing results when viewing clojure
-;; code. Instead, just color based on current nesting inside
-;; all delimiters combined.
-;; - Added 'all-delimiters' faces to apply a color scheme to
-;; all delimiters at once. Other faces inherit from this group.
-;; 1.1.1 - Change color scheme to a lighter, more subtle style.
-;; 1.1.2: (2011-03-25)
-;; - Add an unmatched-delimiter face and correct problem with
-;; coloring of text following unmatched closing delims.
-;; 1.2: (2011-03-28)
-;; - Unify delimiter faces: all delimiter types now use the same depth
-;; faces, of form 'rainbow-delimiters-depth-#-face'.
-;; 1.2.1: (2011-03-29)
-;; - Conform to ELPA conventions.
-;; 1.3: (2011-05-24)
-;; - Add separate color schemes for light and dark background modes.
-;; - Checkboxes to enable/disable highlighting for each delimiter type.
-;; - Improvements to Customize interface.
-;; - Infinite depth support by cycling through defined faces repeatedly.
-;; - Documentation changes.
-;; 1.3.1 (2011-05-25)
-;; - Light color theme appears entirely grey on SRGB monitors. Revert to
-;; old color theme until a nicer light background theme can be added.
-;; - Correct typo in the installation step for users of dark backgrounds.
-;; 1.3.2 (2011-10-14)
-;; - Add 'global-rainbow-delimiters-mode'.
-;; - Respect syntax of current buffer major-mode so delimiters
-;; highlight correctly in non-lisp languages.
-;; 1.3.3 (2011-11-25)
-;; - Backwards compatibility with Emacs versions prior to 23.2.
-;; Defines "with-silent-modifications" if undefined.
-;; 1.3.4 (2012-04-27)
-;; - Further optimize highlighting speed. Benchmarks show 2x improvement.
-;; - Color scheme for light backgrounds.
-;; - Eliminate bottleneck seen in certain large files.
-;; A large file which revealed this bottleneck now highlights ~40x faster.
-;; - Correct broken/incorrect highlighting reported in certain languages.
-
-;;; TODO:
-
-;; - Add support for independent depth tracking of each delimiter type
-;; for users of C-like languages.
-;; - Python style - increase depth with each new indentation.
-;; - Add support for nested tags (XML, HTML)
-;; - Set up proper example defthemes for rainbow-delimiters faces.
-;; - Intelligent support for other languages: Ruby, LaTeX tags, et al.
-
-;;; Issues:
-
-;; - Rainbow-delimiters mode does not appear to change the color of
-;; delimiters when Org-mode is also enabled.
-
-
-;;; Code:
-
-(eval-when-compile (require 'cl))
-
-
-;; Note: some of the functions in this file have been inlined for speed.
-;; Inlining functions can cause problems with debugging. To debug these
-;; functions more easily, change defsubst -> defun.
-;; http://www.gnu.org/s/emacs/manual/html_node/elisp/Compilation-Tips.html
-
-;;; Customize interface:
-
-(defgroup rainbow-delimiters nil
- "Highlight nested parentheses, brackets, and braces according to their depth."
- :prefix "rainbow-delimiters-"
- :link '(url-link :tag "Website for rainbow-delimiters (EmacsWiki)"
- "http://www.emacswiki.org/emacs/RainbowDelimiters")
- :group 'applications)
-
-(defgroup rainbow-delimiters-faces nil
- "Faces for successively nested pairs of delimiters.
-
-When depth exceeds innermost defined face, colors cycle back through."
- :tag "Color Scheme"
- :group 'rainbow-delimiters
- :link '(custom-group-link "rainbow-delimiters")
- :link '(custom-group-link :tag "Toggle Delimiters" "rainbow-delimiters-toggle-delimiter-highlighting")
- :prefix 'rainbow-delimiters-faces-)
-
-;; Choose which delimiters you want to highlight in your preferred language:
-
-(defgroup rainbow-delimiters-toggle-delimiter-highlighting nil
- "Choose which delimiters to highlight."
- :tag "Toggle Delimiters"
- :group 'rainbow-delimiters
- :link '(custom-group-link "rainbow-delimiters")
- :link '(custom-group-link :tag "Color Scheme" "rainbow-delimiters-faces"))
-
-(defcustom rainbow-delimiters-highlight-parens-p t
- "Enable highlighting of nested parentheses -- ().
-
-Non-nil (default) enables highlighting of parentheses.
-Nil disables parentheses highlighting."
- :tag "Highlight Parentheses?"
- :type 'boolean
- :group 'rainbow-delimiters-toggle-delimiter-highlighting)
-
-(defcustom rainbow-delimiters-highlight-brackets-p t
- "Enable highlighting of nested brackets -- [].
-
-Non-nil (default) enables highlighting of brackets.
-Nil disables bracket highlighting."
- :tag "Highlight Brackets?"
- :type 'boolean
- :group 'rainbow-delimiters-toggle-delimiter-highlighting)
-
-(defcustom rainbow-delimiters-highlight-braces-p t
- "Enable highlighting of nested braces -- {}.
-
-Non-nil (default) enables highlighting of braces.
-Nil disables brace highlighting."
- :tag "Highlight Braces?"
- :type 'boolean
- :group 'rainbow-delimiters-toggle-delimiter-highlighting)
-
-
-;;; Faces:
-
-;; Unmatched delimiter face:
-(defface rainbow-delimiters-unmatched-face
- '((((background light)) (:foreground "#88090B"))
- (((background dark)) (:foreground "#88090B")))
- "Face to highlight unmatched closing delimiters in."
- :group 'rainbow-delimiters-faces)
-
-;; Faces for highlighting delimiters by nested level:
-(defface rainbow-delimiters-depth-1-face
- '((((background light)) (:foreground "#707183"))
- (((background dark)) (:foreground "grey55")))
- "Nested delimiters face, depth 1 - outermost set."
- :tag "Rainbow Delimiters Depth 1 Face -- OUTERMOST"
- :group 'rainbow-delimiters-faces)
-
-(defface rainbow-delimiters-depth-2-face
- '((((background light)) (:foreground "#7388d6"))
- (((background dark)) (:foreground "#93a8c6")))
- "Nested delimiters face, depth 2."
- :group 'rainbow-delimiters-faces)
-
-(defface rainbow-delimiters-depth-3-face
- '((((background light)) (:foreground "#909183"))
- (((background dark)) (:foreground "#b0b1a3")))
- "Nested delimiters face, depth 3."
- :group 'rainbow-delimiters-faces)
-
-(defface rainbow-delimiters-depth-4-face
- '((((background light)) (:foreground "#709870"))
- (((background dark)) (:foreground "#97b098")))
- "Nested delimiters face, depth 4."
- :group 'rainbow-delimiters-faces)
-
-(defface rainbow-delimiters-depth-5-face
- '((((background light)) (:foreground "#907373"))
- (((background dark)) (:foreground "#aebed8")))
- "Nested delimiters face, depth 5."
- :group 'rainbow-delimiters-faces)
-
-(defface rainbow-delimiters-depth-6-face
- '((((background light)) (:foreground "#6276ba"))
- (((background dark)) (:foreground "#b0b0b3")))
- "Nested delimiters face, depth 6."
- :group 'rainbow-delimiters-faces)
-
-(defface rainbow-delimiters-depth-7-face
- '((((background light)) (:foreground "#858580"))
- (((background dark)) (:foreground "#90a890")))
- "Nested delimiters face, depth 7."
- :group 'rainbow-delimiters-faces)
-
-(defface rainbow-delimiters-depth-8-face
- '((((background light)) (:foreground "#80a880"))
- (((background dark)) (:foreground "#a2b6da")))
- "Nested delimiters face, depth 8."
- :group 'rainbow-delimiters-faces)
-
-(defface rainbow-delimiters-depth-9-face
- '((((background light)) (:foreground "#887070"))
- (((background dark)) (:foreground "#9cb6ad")))
- "Nested delimiters face, depth 9."
- :group 'rainbow-delimiters-faces)
-
-;;; Faces 10+:
-;; NOTE: Currently unused. Additional faces for depths 9+ can be added on request.
-
-(defconst rainbow-delimiters-max-face-count 9
- "Number of faces defined for highlighting delimiter levels.
-
-Determines depth at which to cycle through faces again.")
-
-(defvar rainbow-delimiters-outermost-only-face-count 0
- "Number of faces to be used only for N outermost delimiter levels.
-
-This should be smaller than `rainbow-delimiters-max-face-count'.")
-
-;;; Face utility functions
-
-(defsubst rainbow-delimiters-depth-face (depth)
- "Return face-name for DEPTH as a string 'rainbow-delimiters-depth-DEPTH-face'.
-
-For example: 'rainbow-delimiters-depth-1-face'."
- (intern-soft
- (concat "rainbow-delimiters-depth-"
- (number-to-string
- (or
- ;; Our nesting depth has a face defined for it.
- (and (<= depth rainbow-delimiters-max-face-count)
- depth)
- ;; Deeper than # of defined faces; cycle back through to
- ;; `rainbow-delimiters-outermost-only-face-count' + 1.
- ;; Return face # that corresponds to current nesting level.
- (+ 1 rainbow-delimiters-outermost-only-face-count
- (mod (- depth rainbow-delimiters-max-face-count 1)
- (- rainbow-delimiters-max-face-count
- rainbow-delimiters-outermost-only-face-count)))))
- "-face")))
-
-;;; Parse partial sexp cache
-
-;; If the block inside the delimiters is too big (where "too big" is
-;; in some way related to `jit-lock-chunk-size'), `syntax-ppss' will
-;; for some reason return wrong depth. Is it because we're misusing
-;; it? Is it because it's buggy? Nobody knows. But users do notice it,
-;; and have reported it as a bug. Hence this workaround: don't use
-;; `syntax-ppss' at all, use the low-level primitive instead. However,
-;; naively replacing `syntax-ppss' with `parse-partial-sexp' slows
-;; down the delimiter highlighting noticeably in big files. Therefore,
-;; we build a simple cache around it. This brings the speed to around
-;; what it used to be, while fixing the bug. See issue #25.
-
-;; TODO: maybe make the cache a little smarter than just caching the
-;; last call?
-
-(defvar rainbow-delimiters-parse-partial-sexp-cache nil
- "Cache of the last `parse-partial-sexp' call.
-
-If it's nil, there's nothing in the cache. Otherwise, it's a cons
-cell, where car is the position for which `parse-partial-sexp' was
-called and cdr is the result of the call.")
-(make-variable-buffer-local 'rainbow-delimiters-parse-partial-sexp-cache)
-
-(defun rainbow-delimiters-syntax-ppss-flush-cache (beg _end)
- "Flush the `parse-partial-sexp' cache starting at position BEG."
- (when (and rainbow-delimiters-parse-partial-sexp-cache
- (<= beg (car rainbow-delimiters-parse-partial-sexp-cache)))
- (setq rainbow-delimiters-parse-partial-sexp-cache nil)))
-
-(defsubst rainbow-delimiters-syntax-ppss (pos)
- "Parse-Partial-Sexp State at POS, defaulting to point.
-
-The returned value is the same as that of `parse-partial-sexp' from
-`point-min' to POS, except that positions 2 and 6 cannot be relied
-upon.
-
-This is essentialy `syntax-ppss', only specific to rainbow-delimiters
-to work around a bug."
- (save-excursion
- (let* ((cache rainbow-delimiters-parse-partial-sexp-cache)
- (ppss (if (and cache (>= pos (car cache)))
- (parse-partial-sexp (car cache) pos nil nil (cdr cache))
- (parse-partial-sexp (point-min) pos))))
- (setq rainbow-delimiters-parse-partial-sexp-cache (cons pos ppss))
- ppss)))
-
-;;; Nesting level
-
-(defvar rainbow-delimiters-syntax-table nil
- "Syntax table (inherited from buffer major-mode) which uses all delimiters.
-
-When rainbow-delimiters-minor-mode is first activated, it sets this variable and
-the other rainbow-delimiters specific syntax tables based on the current
-major-mode. The syntax table is constructed by the function
-'rainbow-delimiters-make-syntax-table'.")
-
-;; syntax-table: used with syntax-ppss for determining current depth.
-(defun rainbow-delimiters-make-syntax-table (syntax-table)
- "Inherit SYNTAX-TABLE and add delimiters intended to be highlighted by mode."
- (let ((table (copy-syntax-table syntax-table)))
- (modify-syntax-entry ?\( "() " table)
- (modify-syntax-entry ?\) ")( " table)
- (modify-syntax-entry ?\[ "(]" table)
- (modify-syntax-entry ?\] ")[" table)
- (modify-syntax-entry ?\{ "(}" table)
- (modify-syntax-entry ?\} "){" table)
- table))
-
-(defsubst rainbow-delimiters-depth (loc)
- "Return # of nested levels of parens, brackets, braces LOC is inside of."
- (let ((depth
- (with-syntax-table rainbow-delimiters-syntax-table
- (car (rainbow-delimiters-syntax-ppss loc)))))
- (if (>= depth 0)
- depth
- 0))) ; ignore negative depths created by unmatched closing parens.
-
-
-;;; Text properties
-
-;; Backwards compatibility: Emacs < v23.2 lack macro 'with-silent-modifications'.
-(eval-and-compile
- (unless (fboundp 'with-silent-modifications)
- (defmacro with-silent-modifications (&rest body)
- "Defined by rainbow-delimiters.el for backwards compatibility with Emacs < 23.2.
- Execute BODY, pretending it does not modify the buffer.
-If BODY performs real modifications to the buffer's text, other
-than cosmetic ones, undo data may become corrupted.
-
-This macro will run BODY normally, but doesn't count its buffer
-modifications as being buffer modifications. This affects things
-like buffer-modified-p, checking whether the file is locked by
-someone else, running buffer modification hooks, and other things
-of that nature.
-
-Typically used around modifications of text-properties which do
-not really affect the buffer's content."
- (declare (debug t) (indent 0))
- (let ((modified (make-symbol "modified")))
- `(let* ((,modified (buffer-modified-p))
- (buffer-undo-list t)
- (inhibit-read-only t)
- (inhibit-modification-hooks t)
- deactivate-mark
- ;; Avoid setting and removing file locks and checking
- ;; buffer's uptodate-ness w.r.t the underlying file.
- buffer-file-name
- buffer-file-truename)
- (unwind-protect
- (progn
- ,@body)
- (unless ,modified
- (restore-buffer-modified-p nil))))))))
-
-(defsubst rainbow-delimiters-propertize-delimiter (loc depth)
- "Highlight a single delimiter at LOC according to DEPTH.
-
-LOC is the location of the character to add text properties to.
-DEPTH is the nested depth at LOC, which determines the face to use.
-
-Sets text properties:
-`font-lock-face' to the appropriate delimiter face.
-`rear-nonsticky' to prevent color from bleeding into subsequent characters typed by the user."
- (with-silent-modifications
- (let ((delim-face (if (<= depth 0)
- 'rainbow-delimiters-unmatched-face
- (rainbow-delimiters-depth-face depth))))
- ;; (when (eq depth -1) (message "Unmatched delimiter at char %s." loc))
- (add-text-properties loc (1+ loc)
- `(font-lock-face ,delim-face
- rear-nonsticky t)))))
-
-
-(defsubst rainbow-delimiters-unpropertize-delimiter (loc)
- "Remove text properties set by rainbow-delimiters mode from char at LOC."
- (with-silent-modifications
- (remove-text-properties loc (1+ loc)
- '(font-lock-face nil
- rear-nonsticky nil))))
-
-(defvar rainbow-delimiters-escaped-char-predicate nil)
-(make-variable-buffer-local 'rainbow-delimiters-escaped-char-predicate)
-
-(defvar rainbow-delimiters-escaped-char-predicate-list
- '((emacs-lisp-mode . rainbow-delimiters-escaped-char-predicate-emacs-lisp)
- (inferior-emacs-lisp-mode . rainbow-delimiters-escaped-char-predicate-emacs-lisp)
- (lisp-mode . rainbow-delimiters-escaped-char-predicate-lisp)
- (scheme-mode . rainbow-delimiters-escaped-char-predicate-lisp)
- (clojure-mode . rainbow-delimiters-escaped-char-predicate-lisp)
- (inferior-scheme-mode . rainbow-delimiters-escaped-char-predicate-lisp)
- ))
-
-(defun rainbow-delimiters-escaped-char-predicate-emacs-lisp (loc)
- (or (and (eq (char-before loc) ?\?) ; e.g. ?) - deprecated, but people use it
- (not (and (eq (char-before (1- loc)) ?\\) ; special case: ignore ?\?
- (eq (char-before (- loc 2)) ?\?))))
- (and (eq (char-before loc) ?\\) ; escaped char, e.g. ?\) - not counted
- (eq (char-before (1- loc)) ?\?))))
-
-(defun rainbow-delimiters-escaped-char-predicate-lisp (loc)
- (eq (char-before loc) ?\\))
-
-(defsubst rainbow-delimiters-char-ineligible-p (loc)
- "Return t if char at LOC should be skipped, e.g. if inside a comment.
-
-Returns t if char at loc meets one of the following conditions:
-- Inside a string.
-- Inside a comment.
-- Is an escaped char, e.g. ?\)"
- (let ((parse-state (rainbow-delimiters-syntax-ppss loc)))
- (or
- (nth 3 parse-state) ; inside string?
- (nth 4 parse-state) ; inside comment?
- (and rainbow-delimiters-escaped-char-predicate
- (funcall rainbow-delimiters-escaped-char-predicate loc)))))
-
-
-(defsubst rainbow-delimiters-apply-color (delim depth loc)
- "Apply color for DEPTH to DELIM at LOC following user settings.
-
-DELIM is a string specifying delimiter type.
-DEPTH is the delimiter depth, or corresponding face # if colors are repeating.
-LOC is location of character (delimiter) to be colorized."
- (and
- ;; Ensure user has enabled highlighting of this delimiter type.
- (symbol-value (intern-soft
- (concat "rainbow-delimiters-highlight-" delim "s-p")))
- (rainbow-delimiters-propertize-delimiter loc
- depth)))
-
-
-;;; JIT-Lock functionality
-
-;; Used to skip delimiter-by-delimiter `rainbow-delimiters-propertize-region'.
-(defconst rainbow-delimiters-delim-regex "\\(\(\\|\)\\|\\[\\|\\]\\|\{\\|\}\\)"
- "Regex matching all opening and closing delimiters the mode highlights.")
-
-;; main function called by jit-lock:
-(defsubst rainbow-delimiters-propertize-region (start end)
- "Highlight delimiters in region between START and END.
-
-Used by jit-lock for dynamic highlighting."
- (setq rainbow-delimiters-escaped-char-predicate
- (cdr (assoc major-mode rainbow-delimiters-escaped-char-predicate-list)))
- (save-excursion
- (goto-char start)
- ;; START can be anywhere in buffer; determine the nesting depth at START loc
- (let ((depth (rainbow-delimiters-depth start)))
- (while (and (< (point) end)
- (re-search-forward rainbow-delimiters-delim-regex end t))
- (backward-char) ; re-search-forward places point after delim; go back.
- (unless (rainbow-delimiters-char-ineligible-p (point))
- (let ((delim (char-after (point))))
- (cond ((eq ?\( delim) ; (
- (setq depth (1+ depth))
- (rainbow-delimiters-apply-color "paren" depth (point)))
- ((eq ?\) delim) ; )
- (rainbow-delimiters-apply-color "paren" depth (point))
- (setq depth (or (and (<= depth 0) 0) ; unmatched paren
- (1- depth))))
- ((eq ?\[ delim) ; [
- (setq depth (1+ depth))
- (rainbow-delimiters-apply-color "bracket" depth (point)))
- ((eq ?\] delim) ; ]
- (rainbow-delimiters-apply-color "bracket" depth (point))
- (setq depth (or (and (<= depth 0) 0) ; unmatched bracket
- (1- depth))))
- ((eq ?\{ delim) ; {
- (setq depth (1+ depth))
- (rainbow-delimiters-apply-color "brace" depth (point)))
- ((eq ?\} delim) ; }
- (rainbow-delimiters-apply-color "brace" depth (point))
- (setq depth (or (and (<= depth 0) 0) ; unmatched brace
- (1- depth)))))))
- ;; move past delimiter so re-search-forward doesn't pick it up again
- (forward-char)))))
-
-(defun rainbow-delimiters-unpropertize-region (start end)
- "Remove highlighting from delimiters between START and END."
- (save-excursion
- (goto-char start)
- (while (and (< (point) end)
- (re-search-forward rainbow-delimiters-delim-regex end t))
- ;; re-search-forward places point 1 further than the delim matched:
- (rainbow-delimiters-unpropertize-delimiter (1- (point))))))
-
-
-;;; Minor mode:
-
-;;;###autoload
-(define-minor-mode rainbow-delimiters-mode
- "Highlight nested parentheses, brackets, and braces according to their depth."
- nil "" nil ; No modeline lighter - it's already obvious when the mode is on.
- (if (not rainbow-delimiters-mode)
- (progn
- (remove-hook 'before-change-functions 'rainbow-delimiters-syntax-ppss-flush-cache t)
- (jit-lock-unregister 'rainbow-delimiters-propertize-region)
- (rainbow-delimiters-unpropertize-region (point-min) (point-max)))
- ;; Flush the ppss cache now in case there's something left in there.
- (setq rainbow-delimiters-parse-partial-sexp-cache nil)
- (add-hook 'before-change-functions 'rainbow-delimiters-syntax-ppss-flush-cache t t)
- (jit-lock-register 'rainbow-delimiters-propertize-region t)
- ;; Create necessary syntax tables inheriting from current major-mode.
- (set (make-local-variable 'rainbow-delimiters-syntax-table)
- (rainbow-delimiters-make-syntax-table (syntax-table)))))
-
-;;;###autoload
-(defun rainbow-delimiters-mode-enable ()
- (rainbow-delimiters-mode 1))
-
-;;;###autoload
-(defun rainbow-delimiters-mode-disable ()
- (rainbow-delimiters-mode 0))
-
-;;;###autoload
-(define-globalized-minor-mode global-rainbow-delimiters-mode
- rainbow-delimiters-mode rainbow-delimiters-mode-enable)
-
-(provide 'rainbow-delimiters)
-
-;;; rainbow-delimiters.el ends here.
--- /dev/null
+;;; rainbow-delimiters-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+(add-to-list 'load-path (or (file-name-directory #$) (car load-path)))
+\f
+;;;### (autoloads nil "rainbow-delimiters" "rainbow-delimiters.el"
+;;;;;; (21908 14195 0 0))
+;;; Generated autoloads from rainbow-delimiters.el
+
+(autoload 'rainbow-delimiters-mode "rainbow-delimiters" "\
+Highlight nested parentheses, brackets, and braces according to their depth.
+
+\(fn &optional ARG)" t nil)
+
+(autoload 'rainbow-delimiters-mode-enable "rainbow-delimiters" "\
+Enable `rainbow-delimiters-mode'.
+
+\(fn)" nil nil)
+
+(autoload 'rainbow-delimiters-mode-disable "rainbow-delimiters" "\
+Disable `rainbow-delimiters-mode'.
+
+\(fn)" nil nil)
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+;;; rainbow-delimiters-autoloads.el ends here
--- /dev/null
+(define-package "rainbow-delimiters" "20150320.17" "Highlight brackets according to their depth" 'nil :url "https://github.com/Fanael/rainbow-delimiters" :keywords '("faces" "convenience" "lisp" "tools"))
--- /dev/null
+;;; rainbow-delimiters.el --- Highlight brackets according to their depth -*- lexical-binding: t -*-
+
+;; Copyright (C)
+;; 2010-2013 Jeremy Rayman
+;; 2013-2015 Fanael Linithien
+;; Author: Jeremy Rayman <opensourceNOSPAMjeremyrayman.com>
+;; Fanael Linithien <fanael4NOSPAMgmail.com>
+;; Maintainer: Fanael Linithien <fanael4NOSPAMgmail.com>
+;; Created: 2010-09-02
+;; Version: 2.1.1
+;; Package-Version: 20150320.17
+;; Keywords: faces, convenience, lisp, tools
+;; Homepage: https://github.com/Fanael/rainbow-delimiters
+
+;; Note: despite `lexical-binding', there's no Emacs 24 dependency.
+;; This is merely an optimization for Emacs 24+, the code is supposed to work
+;; with *both* dynamic and lexical binding.
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Installation:
+
+;; The recommended way is to use MELPA (http://melpa.org/) or MELPA Stable
+;; (http://stable.melpa.org/). If either is in your `package-archives', do
+;; M-x package-install RET rainbow-delimiters RET
+;; Otherwise, open `rainbow-delimiters.el' in Emacs and use
+;; M-x package-install-from-buffer
+;; Any other methods of installation are unsupported.
+
+;;; Commentary:
+;;
+;; Rainbow-delimiters is a "rainbow parentheses"-like mode which highlights
+;; parentheses, brackets, and braces according to their depth. Each
+;; successive level is highlighted in a different color. This makes it easy
+;; to spot matching delimiters, orient yourself in the code, and tell which
+;; statements are at a given level.
+;;
+;; Great care has been taken to make this mode fast. You shouldn't see
+;; any discernible change in scrolling or editing speed while using it,
+;; even in delimiter-rich languages like Clojure, Lisp, and Scheme.
+;;
+;; Usage:
+;;
+;; To toggle the mode in the current buffer:
+;; M-x rainbow-delimiters-mode
+;; To start the mode automatically in `foo-mode', add the following to your init
+;; file:
+;; (add-hook 'foo-mode-hook #'rainbow-delimiters-mode)
+;; To start the mode automatically in most programming modes (Emacs 24 and
+;; above):
+;; (add-hook 'prog-mode-hook #'rainbow-delimiters-mode)
+;;
+;; Customization:
+;;
+;; To customize various options, including the color theme:
+;; M-x customize-group rainbow-delimiters
+;;
+;; You can specify custom colors by customizing following faces:
+;; - Faces take the form `rainbow-delimiters-depth-N-face', with N being the
+;; depth. Depth begins at 1, the outermost color. Faces exist for depths 1-9.
+;; - The unmatched delimiter face: `rainbow-delimiters-unmatched-face'.
+;; - The mismatched delimiter face: `rainbow-delimiters-mismatched-face'.
+
+;;; Code:
+
+(defgroup rainbow-delimiters nil
+ "Highlight nested parentheses, brackets, and braces according to their depth."
+ :prefix "rainbow-delimiters-"
+ :link '(url-link :tag "Website for rainbow-delimiters"
+ "https://github.com/Fanael/rainbow-delimiters")
+ :group 'applications)
+
+(defgroup rainbow-delimiters-faces nil
+ "Faces for successively nested pairs of delimiters.
+
+When depth exceeds innermost defined face, colors cycle back through."
+ :group 'rainbow-delimiters
+ :group 'faces
+ :link '(custom-group-link "rainbow-delimiters")
+ :prefix "rainbow-delimiters-")
+
+(defcustom rainbow-delimiters-pick-face-function
+ #'rainbow-delimiters-default-pick-face
+ "The function used to pick a face used to highlight a delimiter.
+The function should take three arguments (DEPTH MATCH LOC), where:
+ - DEPTH is the delimiter depth; when zero or negative, it's an unmatched
+ delimiter.
+ - MATCH is nil iff the delimiter is a mismatched closing delimiter.
+ - LOC is the location of the delimiter.
+The function should return a value suitable to use as a value of the `face' text
+property, or nil, in which case the delimiter is not highlighted.
+The function should not move the point or mark or change the match data."
+ :tag "Pick face function"
+ :type 'function
+ :group 'rainbow-delimiters)
+
+(defface rainbow-delimiters-unmatched-face
+ '((((background light)) (:foreground "#88090B"))
+ (((background dark)) (:foreground "#88090B")))
+ "Face to highlight unmatched closing delimiters in."
+ :group 'rainbow-delimiters-faces)
+
+(defface rainbow-delimiters-mismatched-face
+ '((t :inherit rainbow-delimiters-unmatched-face))
+ "Face to highlight mismatched closing delimiters in."
+ :group 'rainbow-delimiters-faces)
+
+(eval-when-compile
+ (defmacro rainbow-delimiters--define-depth-faces ()
+ (let ((faces '())
+ (light-colors ["#707183" "#7388d6" "#909183" "#709870" "#907373"
+ "#6276ba" "#858580" "#80a880" "#887070"])
+ (dark-colors ["grey55" "#93a8c6" "#b0b1a3" "#97b098" "#aebed8"
+ "#b0b0b3" "#90a890" "#a2b6da" "#9cb6ad"]))
+ (dotimes (i 9)
+ (push `(defface ,(intern (format "rainbow-delimiters-depth-%d-face" (1+ i)))
+ '((((class color) (background light)) :foreground ,(aref light-colors i))
+ (((class color) (background dark)) :foreground ,(aref dark-colors i)))
+ ,(format "Nested delimiter face, depth %d." (1+ i))
+ :group 'rainbow-delimiters-faces)
+ faces))
+ `(progn ,@faces))))
+(rainbow-delimiters--define-depth-faces)
+
+(defcustom rainbow-delimiters-max-face-count 9
+ "Number of faces defined for highlighting delimiter levels.
+
+Determines depth at which to cycle through faces again.
+
+It's safe to change this variable provided that for all integers from 1 to the
+new value inclusive, a face `rainbow-delimiters-depth-N-face' is defined."
+ :type 'integer
+ :group 'rainbow-delimiters)
+
+(defcustom rainbow-delimiters-outermost-only-face-count 0
+ "Number of faces to be used only for N outermost delimiter levels.
+
+This should be smaller than `rainbow-delimiters-max-face-count'."
+ :type 'integer
+ :group 'rainbow-delimiters)
+
+
+(defun rainbow-delimiters-default-pick-face (depth match _loc)
+ "Return a face name appropriate for nesting depth DEPTH.
+DEPTH and MATCH are as in `rainbow-delimiters-pick-face-function'.
+
+The returned value is either `rainbow-delimiters-unmatched-face',
+`rainbow-delimiters-mismatched-face', or one of the
+`rainbow-delimiters-depth-N-face' faces, obeying
+`rainbow-delimiters-max-face-count' and
+`rainbow-delimiters-outermost-only-face-count'."
+ (cond
+ ((<= depth 0)
+ 'rainbow-delimiters-unmatched-face)
+ ((not match)
+ 'rainbow-delimiters-mismatched-face)
+ (t
+ (intern-soft
+ (concat "rainbow-delimiters-depth-"
+ (number-to-string
+ (if (<= depth rainbow-delimiters-max-face-count)
+ ;; Our nesting depth has a face defined for it.
+ depth
+ ;; Deeper than # of defined faces; cycle back through to
+ ;; `rainbow-delimiters-outermost-only-face-count' + 1.
+ ;; Return face # that corresponds to current nesting level.
+ (+ 1 rainbow-delimiters-outermost-only-face-count
+ (mod (- depth rainbow-delimiters-max-face-count 1)
+ (- rainbow-delimiters-max-face-count
+ rainbow-delimiters-outermost-only-face-count)))))
+ "-face")))))
+
+(defun rainbow-delimiters--apply-color (loc depth match)
+ "Highlight a single delimiter at LOC according to DEPTH.
+
+LOC is the location of the character to add text properties to.
+DEPTH is the nested depth at LOC, which determines the face to use.
+MATCH is nil iff it's a mismatched closing delimiter."
+ (let ((face (funcall rainbow-delimiters-pick-face-function depth match loc)))
+ (when face
+ (font-lock-prepend-text-property loc (1+ loc) 'face face))))
+
+(defun rainbow-delimiters--char-ineligible-p (loc ppss delim-syntax-code)
+ "Return t if char at LOC should not be highlighted.
+PPSS is the `parse-partial-sexp' state at LOC.
+DELIM-SYNTAX-CODE is the `car' of a raw syntax descriptor at LOC.
+
+Returns t if char at loc meets one of the following conditions:
+- Inside a string.
+- Inside a comment.
+- Is an escaped char, e.g. ?\)"
+ (or
+ (nth 3 ppss) ; inside string?
+ (nth 4 ppss) ; inside comment?
+ (nth 5 ppss) ; escaped according to the syntax table?
+ ;; Note: no need to consider single-char openers, they're already handled
+ ;; by looking at ppss.
+ (cond
+ ;; Two character opener, LOC at the first character?
+ ((/= 0 (logand #x10000 delim-syntax-code))
+ (/= 0 (logand #x20000 (or (car (syntax-after (1+ loc))) 0))))
+ ;; Two character opener, LOC at the second character?
+ ((/= 0 (logand #x20000 delim-syntax-code))
+ (/= 0 (logand #x10000 (or (car (syntax-after (1- loc))) 0))))
+ (t
+ nil))))
+
+;; Main function called by font-lock.
+(defun rainbow-delimiters--propertize (end)
+ "Highlight delimiters in region between point and END.
+
+Used by font-lock for dynamic highlighting."
+ (let* ((inhibit-point-motion-hooks t)
+ (last-ppss-pos (point))
+ (ppss (syntax-ppss)))
+ (while (> end (progn (skip-syntax-forward "^()" end)
+ (point)))
+ (let* ((delim-pos (point))
+ (delim-syntax (syntax-after delim-pos)))
+ (setq ppss (parse-partial-sexp last-ppss-pos delim-pos nil nil ppss))
+ (setq last-ppss-pos delim-pos)
+ ;; `skip-syntax-forward' leaves the point at the delimiter, move past
+ ;; it.
+ (forward-char)
+ (let ((delim-syntax-code (car delim-syntax)))
+ (cond
+ ((rainbow-delimiters--char-ineligible-p delim-pos ppss delim-syntax-code)
+ nil)
+ ((= 4 (logand #xFFFF delim-syntax-code))
+ ;; The (1+ ...) is needed because `parse-partial-sexp' returns the
+ ;; depth at the opening delimiter, not in the block being started.
+ (rainbow-delimiters--apply-color delim-pos (1+ (nth 0 ppss)) t))
+ (t
+ ;; Not an opening delimiter, so it's a closing delimiter.
+ (let ((matches-p (eq (cdr delim-syntax) (char-after (nth 1 ppss)))))
+ (rainbow-delimiters--apply-color delim-pos (nth 0 ppss) matches-p))))))))
+ ;; We already fontified the delimiters, tell font-lock there's nothing more
+ ;; to do.
+ nil)
+
+;; NB: no face defined here because we apply the faces ourselves instead of
+;; leaving that to font-lock.
+(defconst rainbow-delimiters--font-lock-keywords
+ '(rainbow-delimiters--propertize))
+
+;;;###autoload
+(define-minor-mode rainbow-delimiters-mode
+ "Highlight nested parentheses, brackets, and braces according to their depth."
+ nil "" nil ; No modeline lighter - it's already obvious when the mode is on.
+ (font-lock-remove-keywords nil rainbow-delimiters--font-lock-keywords)
+ (when rainbow-delimiters-mode
+ (font-lock-add-keywords nil rainbow-delimiters--font-lock-keywords 'append)
+ (set (make-local-variable 'jit-lock-contextually) t)
+ (when (or syntax-begin-function
+ (bound-and-true-p font-lock-beginning-of-syntax-function))
+ ;; We're going to modify `syntax-begin-function', so flush the cache to
+ ;; avoid getting cached values that used the old value.
+ (syntax-ppss-flush-cache 0))
+ ;; `syntax-begin-function' may break the assumption we rely on that
+ ;; `syntax-ppss' is exactly equivalent to `parse-partial-sexp' from
+ ;; `point-min'. Just don't use it, the performance hit should be negligible.
+ (set (make-local-variable 'syntax-begin-function) nil)
+ ;; Obsolete equivalent of `syntax-begin-function'.
+ (when (boundp 'font-lock-beginning-of-syntax-function)
+ (set (make-local-variable 'font-lock-beginning-of-syntax-function) nil)))
+ (when font-lock-mode
+ (if (fboundp 'font-lock-flush)
+ (font-lock-flush)
+ (with-no-warnings (font-lock-fontify-buffer)))))
+
+;;;###autoload
+(defun rainbow-delimiters-mode-enable ()
+ "Enable `rainbow-delimiters-mode'."
+ (rainbow-delimiters-mode 1))
+
+;;;###autoload
+(defun rainbow-delimiters-mode-disable ()
+ "Disable `rainbow-delimiters-mode'."
+ (rainbow-delimiters-mode 0))
+
+(provide 'rainbow-delimiters)
+;;; rainbow-delimiters.el ends here
--- /dev/null
+;;; rainbow-mode-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+
+\f
+;;;### (autoloads (rainbow-mode) "rainbow-mode" "rainbow-mode.el"
+;;;;;; (21850 7749 0 0))
+;;; Generated autoloads from rainbow-mode.el
+
+(autoload 'rainbow-mode "rainbow-mode" "\
+Colorize strings that represent colors.
+This will fontify with colors the string like \"#aabbcc\" or \"blue\".
+
+\(fn &optional ARG)" t nil)
+
+;;;***
+\f
+;;;### (autoloads nil nil ("rainbow-mode-pkg.el") (21850 7749 709135
+;;;;;; 0))
+
+;;;***
+\f
+(provide 'rainbow-mode-autoloads)
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; coding: utf-8
+;; End:
+;;; rainbow-mode-autoloads.el ends here
--- /dev/null
+(define-package "rainbow-mode" "0.11" "Colorize color names in buffers" (quote nil))
--- /dev/null
+;;; rainbow-mode.el --- Colorize color names in buffers
+
+;; Copyright (C) 2010-2015 Free Software Foundation, Inc
+
+;; Author: Julien Danjou <julienNOSPAMdanjou.info>
+;; Keywords: faces
+;; Version: 0.11
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+;; This minor mode sets background color to strings that match color
+;; names, e.g. #0000ff is displayed in white with a blue background.
+;;
+
+;;; Code:
+
+(eval-when-compile
+ (require 'cl))
+
+(require 'regexp-opt)
+(require 'faces)
+(require 'color)
+
+(unless (require 'xterm-color nil t)
+ (require 'ansi-color))
+
+(defgroup rainbow nil
+ "Show color strings with a background color."
+ :tag "Rainbow"
+ :group 'help)
+
+;; Hexadecimal colors
+(defvar rainbow-hexadecimal-colors-font-lock-keywords
+ '(("[^&]\\(#\\(?:[0-9a-fA-F]\\{3\\}\\)+\\{1,4\\}\\)"
+ (1 (rainbow-colorize-itself 1)))
+ ("^\\(#\\(?:[0-9a-fA-F]\\{3\\}\\)+\\{1,4\\}\\)"
+ (0 (rainbow-colorize-itself)))
+ ("[Rr][Gg][Bb]:[0-9a-fA-F]\\{1,4\\}/[0-9a-fA-F]\\{1,4\\}/[0-9a-fA-F]\\{1,4\\}"
+ (0 (rainbow-colorize-itself)))
+ ("[Rr][Gg][Bb][Ii]:[0-9.]+/[0-9.]+/[0-9.]+"
+ (0 (rainbow-colorize-itself)))
+ ("\\(?:[Cc][Ii][Ee]\\(?:[Xx][Yy][Zz]\\|[Uu][Vv][Yy]\\|[Xx][Yy][Yy]\\|[Ll][Aa][Bb]\\|[Ll][Uu][Vv]\\)\\|[Tt][Ee][Kk][Hh][Vv][Cc]\\):[+-]?[0-9.]+\\(?:[Ee][+-]?[0-9]+\\)?/[+-]?[0-9.]+\\(?:[Ee][+-]?[0-9]+\\)?/[+-]?[0-9.]+\\(?:[Ee][+-]?[0-9]+\\)?"
+ (0 (rainbow-colorize-itself))))
+ "Font-lock keywords to add for hexadecimal colors.")
+
+;; rgb() colors
+(defvar rainbow-html-rgb-colors-font-lock-keywords
+ '(("rgb(\s*\\([0-9]\\{1,3\\}\\(?:\.[0-9]\\)?\\(?:\s*%\\)?\\)\s*,\s*\\([0-9]\\{1,3\\}\\(?:\\.[0-9]\\)?\\(?:\s*%\\)?\\)\s*,\s*\\([0-9]\\{1,3\\}\\(?:\\.[0-9]\\)?\\(?:\s*%\\)?\\)\s*)"
+ (0 (rainbow-colorize-rgb)))
+ ("rgba(\s*\\([0-9]\\{1,3\\}\\(?:\\.[0-9]\\)?\\(?:\s*%\\)?\\)\s*,\s*\\([0-9]\\{1,3\\}\\(?:\\.[0-9]\\)?\\(?:\s*%\\)?\\)\s*,\s*\\([0-9]\\{1,3\\}\\(?:\\.[0-9]\\)?\\(?:\s*%\\)?\\)\s*,\s*[0-9]*\.?[0-9]+\s*%?\s*)"
+ (0 (rainbow-colorize-rgb)))
+ ("hsl(\s*\\([0-9]\\{1,3\\}\\)\s*,\s*\\([0-9]\\{1,3\\}\\)\s*%\s*,\s*\\([0-9]\\{1,3\\}\\)\s*%\s*)"
+ (0 (rainbow-colorize-hsl)))
+ ("hsla(\s*\\([0-9]\\{1,3\\}\\)\s*,\s*\\([0-9]\\{1,3\\}\\)\s*%\s*,\s*\\([0-9]\\{1,3\\}\\)\s*%\s*,\s*[0-9]*\.?[0-9]+\s*%?\s*)"
+ (0 (rainbow-colorize-hsl))))
+ "Font-lock keywords to add for RGB colors.")
+
+;; HTML colors name
+(defvar rainbow-html-colors-font-lock-keywords nil
+ "Font-lock keywords to add for HTML colors.")
+(make-variable-buffer-local 'rainbow-html-colors-font-lock-keywords)
+
+(defcustom rainbow-html-colors-alist
+ '(("AliceBlue" . "#F0F8FF")
+ ("AntiqueWhite" . "#FAEBD7")
+ ("Aqua" . "#00FFFF")
+ ("Aquamarine" . "#7FFFD4")
+ ("Azure" . "#F0FFFF")
+ ("Beige" . "#F5F5DC")
+ ("Bisque" . "#FFE4C4")
+ ("Black" . "#000000")
+ ("BlanchedAlmond" . "#FFEBCD")
+ ("Blue" . "#0000FF")
+ ("BlueViolet" . "#8A2BE2")
+ ("Brown" . "#A52A2A")
+ ("BurlyWood" . "#DEB887")
+ ("CadetBlue" . "#5F9EA0")
+ ("Chartreuse" . "#7FFF00")
+ ("Chocolate" . "#D2691E")
+ ("Coral" . "#FF7F50")
+ ("CornflowerBlue" . "#6495ED")
+ ("Cornsilk" . "#FFF8DC")
+ ("Crimson" . "#DC143C")
+ ("Cyan" . "#00FFFF")
+ ("DarkBlue" . "#00008B")
+ ("DarkCyan" . "#008B8B")
+ ("DarkGoldenRod" . "#B8860B")
+ ("DarkGray" . "#A9A9A9")
+ ("DarkGrey" . "#A9A9A9")
+ ("DarkGreen" . "#006400")
+ ("DarkKhaki" . "#BDB76B")
+ ("DarkMagenta" . "#8B008B")
+ ("DarkOliveGreen" . "#556B2F")
+ ("Darkorange" . "#FF8C00")
+ ("DarkOrchid" . "#9932CC")
+ ("DarkRed" . "#8B0000")
+ ("DarkSalmon" . "#E9967A")
+ ("DarkSeaGreen" . "#8FBC8F")
+ ("DarkSlateBlue" . "#483D8B")
+ ("DarkSlateGray" . "#2F4F4F")
+ ("DarkSlateGrey" . "#2F4F4F")
+ ("DarkTurquoise" . "#00CED1")
+ ("DarkViolet" . "#9400D3")
+ ("DeepPink" . "#FF1493")
+ ("DeepSkyBlue" . "#00BFFF")
+ ("DimGray" . "#696969")
+ ("DimGrey" . "#696969")
+ ("DodgerBlue" . "#1E90FF")
+ ("FireBrick" . "#B22222")
+ ("FloralWhite" . "#FFFAF0")
+ ("ForestGreen" . "#228B22")
+ ("Fuchsia" . "#FF00FF")
+ ("Gainsboro" . "#DCDCDC")
+ ("GhostWhite" . "#F8F8FF")
+ ("Gold" . "#FFD700")
+ ("GoldenRod" . "#DAA520")
+ ("Gray" . "#808080")
+ ("Grey" . "#808080")
+ ("Green" . "#008000")
+ ("GreenYellow" . "#ADFF2F")
+ ("HoneyDew" . "#F0FFF0")
+ ("HotPink" . "#FF69B4")
+ ("IndianRed" . "#CD5C5C")
+ ("Indigo" . "#4B0082")
+ ("Ivory" . "#FFFFF0")
+ ("Khaki" . "#F0E68C")
+ ("Lavender" . "#E6E6FA")
+ ("LavenderBlush" . "#FFF0F5")
+ ("LawnGreen" . "#7CFC00")
+ ("LemonChiffon" . "#FFFACD")
+ ("LightBlue" . "#ADD8E6")
+ ("LightCoral" . "#F08080")
+ ("LightCyan" . "#E0FFFF")
+ ("LightGoldenRodYellow" . "#FAFAD2")
+ ("LightGray" . "#D3D3D3")
+ ("LightGrey" . "#D3D3D3")
+ ("LightGreen" . "#90EE90")
+ ("LightPink" . "#FFB6C1")
+ ("LightSalmon" . "#FFA07A")
+ ("LightSeaGreen" . "#20B2AA")
+ ("LightSkyBlue" . "#87CEFA")
+ ("LightSlateGray" . "#778899")
+ ("LightSlateGrey" . "#778899")
+ ("LightSteelBlue" . "#B0C4DE")
+ ("LightYellow" . "#FFFFE0")
+ ("Lime" . "#00FF00")
+ ("LimeGreen" . "#32CD32")
+ ("Linen" . "#FAF0E6")
+ ("Magenta" . "#FF00FF")
+ ("Maroon" . "#800000")
+ ("MediumAquaMarine" . "#66CDAA")
+ ("MediumBlue" . "#0000CD")
+ ("MediumOrchid" . "#BA55D3")
+ ("MediumPurple" . "#9370D8")
+ ("MediumSeaGreen" . "#3CB371")
+ ("MediumSlateBlue" . "#7B68EE")
+ ("MediumSpringGreen" . "#00FA9A")
+ ("MediumTurquoise" . "#48D1CC")
+ ("MediumVioletRed" . "#C71585")
+ ("MidnightBlue" . "#191970")
+ ("MintCream" . "#F5FFFA")
+ ("MistyRose" . "#FFE4E1")
+ ("Moccasin" . "#FFE4B5")
+ ("NavajoWhite" . "#FFDEAD")
+ ("Navy" . "#000080")
+ ("OldLace" . "#FDF5E6")
+ ("Olive" . "#808000")
+ ("OliveDrab" . "#6B8E23")
+ ("Orange" . "#FFA500")
+ ("OrangeRed" . "#FF4500")
+ ("Orchid" . "#DA70D6")
+ ("PaleGoldenRod" . "#EEE8AA")
+ ("PaleGreen" . "#98FB98")
+ ("PaleTurquoise" . "#AFEEEE")
+ ("PaleVioletRed" . "#D87093")
+ ("PapayaWhip" . "#FFEFD5")
+ ("PeachPuff" . "#FFDAB9")
+ ("Peru" . "#CD853F")
+ ("Pink" . "#FFC0CB")
+ ("Plum" . "#DDA0DD")
+ ("PowderBlue" . "#B0E0E6")
+ ("Purple" . "#800080")
+ ("Red" . "#FF0000")
+ ("RosyBrown" . "#BC8F8F")
+ ("RoyalBlue" . "#4169E1")
+ ("SaddleBrown" . "#8B4513")
+ ("Salmon" . "#FA8072")
+ ("SandyBrown" . "#F4A460")
+ ("SeaGreen" . "#2E8B57")
+ ("SeaShell" . "#FFF5EE")
+ ("Sienna" . "#A0522D")
+ ("Silver" . "#C0C0C0")
+ ("SkyBlue" . "#87CEEB")
+ ("SlateBlue" . "#6A5ACD")
+ ("SlateGray" . "#708090")
+ ("SlateGrey" . "#708090")
+ ("Snow" . "#FFFAFA")
+ ("SpringGreen" . "#00FF7F")
+ ("SteelBlue" . "#4682B4")
+ ("Tan" . "#D2B48C")
+ ("Teal" . "#008080")
+ ("Thistle" . "#D8BFD8")
+ ("Tomato" . "#FF6347")
+ ("Turquoise" . "#40E0D0")
+ ("Violet" . "#EE82EE")
+ ("Wheat" . "#F5DEB3")
+ ("White" . "#FFFFFF")
+ ("WhiteSmoke" . "#F5F5F5")
+ ("Yellow" . "#FFFF00")
+ ("YellowGreen" . "#9ACD32"))
+ "Alist of HTML colors.
+Each entry should have the form (COLOR-NAME . HEXADECIMAL-COLOR)."
+ :group 'rainbow)
+
+(defcustom rainbow-html-colors-major-mode-list
+ '(html-mode css-mode php-mode nxml-mode xml-mode)
+ "List of major mode where HTML colors are enabled when
+`rainbow-html-colors' is set to auto."
+ :group 'rainbow)
+
+(defcustom rainbow-html-colors 'auto
+ "When to enable HTML colors.
+If set to t, the HTML colors will be enabled. If set to nil, the
+HTML colors will not be enabled. If set to auto, the HTML colors
+will be enabled if a major mode has been detected from the
+`rainbow-html-colors-major-mode-list'."
+ :group 'rainbow)
+
+;; X colors
+(defvar rainbow-x-colors-font-lock-keywords
+ `((,(regexp-opt (x-defined-colors) 'words)
+ (0 (rainbow-colorize-itself))))
+ "Font-lock keywords to add for X colors.")
+
+(defcustom rainbow-x-colors-major-mode-list
+ '(emacs-lisp-mode lisp-interaction-mode c-mode c++-mode java-mode)
+ "List of major mode where X colors are enabled when
+`rainbow-x-colors' is set to auto."
+ :group 'rainbow)
+
+(defcustom rainbow-x-colors 'auto
+ "When to enable X colors.
+If set to t, the X colors will be enabled. If set to nil, the
+X colors will not be enabled. If set to auto, the X colors
+will be enabled if a major mode has been detected from the
+`rainbow-x-colors-major-mode-list'."
+ :group 'rainbow)
+
+;; LaTeX colors
+(defvar rainbow-latex-rgb-colors-font-lock-keywords
+ '(("{rgb}{\\([0-9.]+\\),\s*\\([0-9.]+\\),\s*\\([0-9.]+\\)}"
+ (0 (rainbow-colorize-rgb-float)))
+ ("{RGB}{\\([0-9]\\{1,3\\}\\),\s*\\([0-9]\\{1,3\\}\\),\s*\\([0-9]\\{1,3\\}\\)}"
+ (0 (rainbow-colorize-rgb)))
+ ("{HTML}{\\([0-9A-Fa-f]\\{6\\}\\)}"
+ (0 (rainbow-colorize-hexadecimal-without-sharp))))
+ "Font-lock keywords to add for LaTeX colors.")
+
+(defcustom rainbow-latex-colors-major-mode-list
+ '(latex-mode)
+ "List of major mode where LaTeX colors are enabled when
+`rainbow-x-colors' is set to auto."
+ :group 'rainbow)
+
+(defcustom rainbow-latex-colors 'auto
+ "When to enable LaTeX colors.
+If set to t, the LaTeX colors will be enabled. If set to nil, the
+LaTeX colors will not be enabled. If set to auto, the LaTeX colors
+will be enabled if a major mode has been detected from the
+`rainbow-latex-colors-major-mode-list'."
+ :group 'rainbow)
+
+;; Shell colors
+(defvar rainbow-ansi-colors-font-lock-keywords
+ '(("\\(\\\\[eE]\\|\\\\033\\|\\\\x1[bB]\\|\033\\)\\[\\([0-9;]*m\\)"
+ (0 (rainbow-colorize-ansi))))
+ "Font-lock keywords to add for ANSI colors.")
+
+(defcustom rainbow-ansi-colors-major-mode-list
+ '(sh-mode c-mode c++-mode)
+ "List of major mode where ANSI colors are enabled when
+`rainbow-ansi-colors' is set to auto."
+ :group 'rainbow)
+
+(defcustom rainbow-ansi-colors 'auto
+ "When to enable ANSI colors.
+If set to t, the ANSI colors will be enabled. If set to nil, the
+ANSI colors will not be enabled. If set to auto, the ANSI colors
+will be enabled if a major mode has been detected from the
+`rainbow-ansi-colors-major-mode-list'."
+ :group 'rainbow)
+
+;; R colors
+
+;; R colors name
+(defvar rainbow-r-colors-font-lock-keywords nil
+ "Font-lock keywords to add for R colors.")
+(make-variable-buffer-local 'rainbow-r-colors-font-lock-keywords)
+
+;; use the following code to generate the list in R
+;; output_colors <- function(colors) {for(color in colors) {col <- col2rgb(color); cat(sprintf("(\"%s\" . \"#%02X%02X%02X\")\n",color,col[1],col[2],col[3]));}}
+;; output_colors(colors())
+(defcustom rainbow-r-colors-alist
+ '(("white" . "#FFFFFF")
+ ("aliceblue" . "#F0F8FF")
+ ("antiquewhite" . "#FAEBD7")
+ ("antiquewhite1" . "#FFEFDB")
+ ("antiquewhite2" . "#EEDFCC")
+ ("antiquewhite3" . "#CDC0B0")
+ ("antiquewhite4" . "#8B8378")
+ ("aquamarine" . "#7FFFD4")
+ ("aquamarine1" . "#7FFFD4")
+ ("aquamarine2" . "#76EEC6")
+ ("aquamarine3" . "#66CDAA")
+ ("aquamarine4" . "#458B74")
+ ("azure" . "#F0FFFF")
+ ("azure1" . "#F0FFFF")
+ ("azure2" . "#E0EEEE")
+ ("azure3" . "#C1CDCD")
+ ("azure4" . "#838B8B")
+ ("beige" . "#F5F5DC")
+ ("bisque" . "#FFE4C4")
+ ("bisque1" . "#FFE4C4")
+ ("bisque2" . "#EED5B7")
+ ("bisque3" . "#CDB79E")
+ ("bisque4" . "#8B7D6B")
+ ("black" . "#000000")
+ ("blanchedalmond" . "#FFEBCD")
+ ("blue" . "#0000FF")
+ ("blue1" . "#0000FF")
+ ("blue2" . "#0000EE")
+ ("blue3" . "#0000CD")
+ ("blue4" . "#00008B")
+ ("blueviolet" . "#8A2BE2")
+ ("brown" . "#A52A2A")
+ ("brown1" . "#FF4040")
+ ("brown2" . "#EE3B3B")
+ ("brown3" . "#CD3333")
+ ("brown4" . "#8B2323")
+ ("burlywood" . "#DEB887")
+ ("burlywood1" . "#FFD39B")
+ ("burlywood2" . "#EEC591")
+ ("burlywood3" . "#CDAA7D")
+ ("burlywood4" . "#8B7355")
+ ("cadetblue" . "#5F9EA0")
+ ("cadetblue1" . "#98F5FF")
+ ("cadetblue2" . "#8EE5EE")
+ ("cadetblue3" . "#7AC5CD")
+ ("cadetblue4" . "#53868B")
+ ("chartreuse" . "#7FFF00")
+ ("chartreuse1" . "#7FFF00")
+ ("chartreuse2" . "#76EE00")
+ ("chartreuse3" . "#66CD00")
+ ("chartreuse4" . "#458B00")
+ ("chocolate" . "#D2691E")
+ ("chocolate1" . "#FF7F24")
+ ("chocolate2" . "#EE7621")
+ ("chocolate3" . "#CD661D")
+ ("chocolate4" . "#8B4513")
+ ("coral" . "#FF7F50")
+ ("coral1" . "#FF7256")
+ ("coral2" . "#EE6A50")
+ ("coral3" . "#CD5B45")
+ ("coral4" . "#8B3E2F")
+ ("cornflowerblue" . "#6495ED")
+ ("cornsilk" . "#FFF8DC")
+ ("cornsilk1" . "#FFF8DC")
+ ("cornsilk2" . "#EEE8CD")
+ ("cornsilk3" . "#CDC8B1")
+ ("cornsilk4" . "#8B8878")
+ ("cyan" . "#00FFFF")
+ ("cyan1" . "#00FFFF")
+ ("cyan2" . "#00EEEE")
+ ("cyan3" . "#00CDCD")
+ ("cyan4" . "#008B8B")
+ ("darkblue" . "#00008B")
+ ("darkcyan" . "#008B8B")
+ ("darkgoldenrod" . "#B8860B")
+ ("darkgoldenrod1" . "#FFB90F")
+ ("darkgoldenrod2" . "#EEAD0E")
+ ("darkgoldenrod3" . "#CD950C")
+ ("darkgoldenrod4" . "#8B6508")
+ ("darkgray" . "#A9A9A9")
+ ("darkgreen" . "#006400")
+ ("darkgrey" . "#A9A9A9")
+ ("darkkhaki" . "#BDB76B")
+ ("darkmagenta" . "#8B008B")
+ ("darkolivegreen" . "#556B2F")
+ ("darkolivegreen1" . "#CAFF70")
+ ("darkolivegreen2" . "#BCEE68")
+ ("darkolivegreen3" . "#A2CD5A")
+ ("darkolivegreen4" . "#6E8B3D")
+ ("darkorange" . "#FF8C00")
+ ("darkorange1" . "#FF7F00")
+ ("darkorange2" . "#EE7600")
+ ("darkorange3" . "#CD6600")
+ ("darkorange4" . "#8B4500")
+ ("darkorchid" . "#9932CC")
+ ("darkorchid1" . "#BF3EFF")
+ ("darkorchid2" . "#B23AEE")
+ ("darkorchid3" . "#9A32CD")
+ ("darkorchid4" . "#68228B")
+ ("darkred" . "#8B0000")
+ ("darksalmon" . "#E9967A")
+ ("darkseagreen" . "#8FBC8F")
+ ("darkseagreen1" . "#C1FFC1")
+ ("darkseagreen2" . "#B4EEB4")
+ ("darkseagreen3" . "#9BCD9B")
+ ("darkseagreen4" . "#698B69")
+ ("darkslateblue" . "#483D8B")
+ ("darkslategray" . "#2F4F4F")
+ ("darkslategray1" . "#97FFFF")
+ ("darkslategray2" . "#8DEEEE")
+ ("darkslategray3" . "#79CDCD")
+ ("darkslategray4" . "#528B8B")
+ ("darkslategrey" . "#2F4F4F")
+ ("darkturquoise" . "#00CED1")
+ ("darkviolet" . "#9400D3")
+ ("deeppink" . "#FF1493")
+ ("deeppink1" . "#FF1493")
+ ("deeppink2" . "#EE1289")
+ ("deeppink3" . "#CD1076")
+ ("deeppink4" . "#8B0A50")
+ ("deepskyblue" . "#00BFFF")
+ ("deepskyblue1" . "#00BFFF")
+ ("deepskyblue2" . "#00B2EE")
+ ("deepskyblue3" . "#009ACD")
+ ("deepskyblue4" . "#00688B")
+ ("dimgray" . "#696969")
+ ("dimgrey" . "#696969")
+ ("dodgerblue" . "#1E90FF")
+ ("dodgerblue1" . "#1E90FF")
+ ("dodgerblue2" . "#1C86EE")
+ ("dodgerblue3" . "#1874CD")
+ ("dodgerblue4" . "#104E8B")
+ ("firebrick" . "#B22222")
+ ("firebrick1" . "#FF3030")
+ ("firebrick2" . "#EE2C2C")
+ ("firebrick3" . "#CD2626")
+ ("firebrick4" . "#8B1A1A")
+ ("floralwhite" . "#FFFAF0")
+ ("forestgreen" . "#228B22")
+ ("gainsboro" . "#DCDCDC")
+ ("ghostwhite" . "#F8F8FF")
+ ("gold" . "#FFD700")
+ ("gold1" . "#FFD700")
+ ("gold2" . "#EEC900")
+ ("gold3" . "#CDAD00")
+ ("gold4" . "#8B7500")
+ ("goldenrod" . "#DAA520")
+ ("goldenrod1" . "#FFC125")
+ ("goldenrod2" . "#EEB422")
+ ("goldenrod3" . "#CD9B1D")
+ ("goldenrod4" . "#8B6914")
+ ("gray" . "#BEBEBE")
+ ("gray0" . "#000000")
+ ("gray1" . "#030303")
+ ("gray2" . "#050505")
+ ("gray3" . "#080808")
+ ("gray4" . "#0A0A0A")
+ ("gray5" . "#0D0D0D")
+ ("gray6" . "#0F0F0F")
+ ("gray7" . "#121212")
+ ("gray8" . "#141414")
+ ("gray9" . "#171717")
+ ("gray10" . "#1A1A1A")
+ ("gray11" . "#1C1C1C")
+ ("gray12" . "#1F1F1F")
+ ("gray13" . "#212121")
+ ("gray14" . "#242424")
+ ("gray15" . "#262626")
+ ("gray16" . "#292929")
+ ("gray17" . "#2B2B2B")
+ ("gray18" . "#2E2E2E")
+ ("gray19" . "#303030")
+ ("gray20" . "#333333")
+ ("gray21" . "#363636")
+ ("gray22" . "#383838")
+ ("gray23" . "#3B3B3B")
+ ("gray24" . "#3D3D3D")
+ ("gray25" . "#404040")
+ ("gray26" . "#424242")
+ ("gray27" . "#454545")
+ ("gray28" . "#474747")
+ ("gray29" . "#4A4A4A")
+ ("gray30" . "#4D4D4D")
+ ("gray31" . "#4F4F4F")
+ ("gray32" . "#525252")
+ ("gray33" . "#545454")
+ ("gray34" . "#575757")
+ ("gray35" . "#595959")
+ ("gray36" . "#5C5C5C")
+ ("gray37" . "#5E5E5E")
+ ("gray38" . "#616161")
+ ("gray39" . "#636363")
+ ("gray40" . "#666666")
+ ("gray41" . "#696969")
+ ("gray42" . "#6B6B6B")
+ ("gray43" . "#6E6E6E")
+ ("gray44" . "#707070")
+ ("gray45" . "#737373")
+ ("gray46" . "#757575")
+ ("gray47" . "#787878")
+ ("gray48" . "#7A7A7A")
+ ("gray49" . "#7D7D7D")
+ ("gray50" . "#7F7F7F")
+ ("gray51" . "#828282")
+ ("gray52" . "#858585")
+ ("gray53" . "#878787")
+ ("gray54" . "#8A8A8A")
+ ("gray55" . "#8C8C8C")
+ ("gray56" . "#8F8F8F")
+ ("gray57" . "#919191")
+ ("gray58" . "#949494")
+ ("gray59" . "#969696")
+ ("gray60" . "#999999")
+ ("gray61" . "#9C9C9C")
+ ("gray62" . "#9E9E9E")
+ ("gray63" . "#A1A1A1")
+ ("gray64" . "#A3A3A3")
+ ("gray65" . "#A6A6A6")
+ ("gray66" . "#A8A8A8")
+ ("gray67" . "#ABABAB")
+ ("gray68" . "#ADADAD")
+ ("gray69" . "#B0B0B0")
+ ("gray70" . "#B3B3B3")
+ ("gray71" . "#B5B5B5")
+ ("gray72" . "#B8B8B8")
+ ("gray73" . "#BABABA")
+ ("gray74" . "#BDBDBD")
+ ("gray75" . "#BFBFBF")
+ ("gray76" . "#C2C2C2")
+ ("gray77" . "#C4C4C4")
+ ("gray78" . "#C7C7C7")
+ ("gray79" . "#C9C9C9")
+ ("gray80" . "#CCCCCC")
+ ("gray81" . "#CFCFCF")
+ ("gray82" . "#D1D1D1")
+ ("gray83" . "#D4D4D4")
+ ("gray84" . "#D6D6D6")
+ ("gray85" . "#D9D9D9")
+ ("gray86" . "#DBDBDB")
+ ("gray87" . "#DEDEDE")
+ ("gray88" . "#E0E0E0")
+ ("gray89" . "#E3E3E3")
+ ("gray90" . "#E5E5E5")
+ ("gray91" . "#E8E8E8")
+ ("gray92" . "#EBEBEB")
+ ("gray93" . "#EDEDED")
+ ("gray94" . "#F0F0F0")
+ ("gray95" . "#F2F2F2")
+ ("gray96" . "#F5F5F5")
+ ("gray97" . "#F7F7F7")
+ ("gray98" . "#FAFAFA")
+ ("gray99" . "#FCFCFC")
+ ("gray100" . "#FFFFFF")
+ ("green" . "#00FF00")
+ ("green1" . "#00FF00")
+ ("green2" . "#00EE00")
+ ("green3" . "#00CD00")
+ ("green4" . "#008B00")
+ ("greenyellow" . "#ADFF2F")
+ ("grey" . "#BEBEBE")
+ ("grey0" . "#000000")
+ ("grey1" . "#030303")
+ ("grey2" . "#050505")
+ ("grey3" . "#080808")
+ ("grey4" . "#0A0A0A")
+ ("grey5" . "#0D0D0D")
+ ("grey6" . "#0F0F0F")
+ ("grey7" . "#121212")
+ ("grey8" . "#141414")
+ ("grey9" . "#171717")
+ ("grey10" . "#1A1A1A")
+ ("grey11" . "#1C1C1C")
+ ("grey12" . "#1F1F1F")
+ ("grey13" . "#212121")
+ ("grey14" . "#242424")
+ ("grey15" . "#262626")
+ ("grey16" . "#292929")
+ ("grey17" . "#2B2B2B")
+ ("grey18" . "#2E2E2E")
+ ("grey19" . "#303030")
+ ("grey20" . "#333333")
+ ("grey21" . "#363636")
+ ("grey22" . "#383838")
+ ("grey23" . "#3B3B3B")
+ ("grey24" . "#3D3D3D")
+ ("grey25" . "#404040")
+ ("grey26" . "#424242")
+ ("grey27" . "#454545")
+ ("grey28" . "#474747")
+ ("grey29" . "#4A4A4A")
+ ("grey30" . "#4D4D4D")
+ ("grey31" . "#4F4F4F")
+ ("grey32" . "#525252")
+ ("grey33" . "#545454")
+ ("grey34" . "#575757")
+ ("grey35" . "#595959")
+ ("grey36" . "#5C5C5C")
+ ("grey37" . "#5E5E5E")
+ ("grey38" . "#616161")
+ ("grey39" . "#636363")
+ ("grey40" . "#666666")
+ ("grey41" . "#696969")
+ ("grey42" . "#6B6B6B")
+ ("grey43" . "#6E6E6E")
+ ("grey44" . "#707070")
+ ("grey45" . "#737373")
+ ("grey46" . "#757575")
+ ("grey47" . "#787878")
+ ("grey48" . "#7A7A7A")
+ ("grey49" . "#7D7D7D")
+ ("grey50" . "#7F7F7F")
+ ("grey51" . "#828282")
+ ("grey52" . "#858585")
+ ("grey53" . "#878787")
+ ("grey54" . "#8A8A8A")
+ ("grey55" . "#8C8C8C")
+ ("grey56" . "#8F8F8F")
+ ("grey57" . "#919191")
+ ("grey58" . "#949494")
+ ("grey59" . "#969696")
+ ("grey60" . "#999999")
+ ("grey61" . "#9C9C9C")
+ ("grey62" . "#9E9E9E")
+ ("grey63" . "#A1A1A1")
+ ("grey64" . "#A3A3A3")
+ ("grey65" . "#A6A6A6")
+ ("grey66" . "#A8A8A8")
+ ("grey67" . "#ABABAB")
+ ("grey68" . "#ADADAD")
+ ("grey69" . "#B0B0B0")
+ ("grey70" . "#B3B3B3")
+ ("grey71" . "#B5B5B5")
+ ("grey72" . "#B8B8B8")
+ ("grey73" . "#BABABA")
+ ("grey74" . "#BDBDBD")
+ ("grey75" . "#BFBFBF")
+ ("grey76" . "#C2C2C2")
+ ("grey77" . "#C4C4C4")
+ ("grey78" . "#C7C7C7")
+ ("grey79" . "#C9C9C9")
+ ("grey80" . "#CCCCCC")
+ ("grey81" . "#CFCFCF")
+ ("grey82" . "#D1D1D1")
+ ("grey83" . "#D4D4D4")
+ ("grey84" . "#D6D6D6")
+ ("grey85" . "#D9D9D9")
+ ("grey86" . "#DBDBDB")
+ ("grey87" . "#DEDEDE")
+ ("grey88" . "#E0E0E0")
+ ("grey89" . "#E3E3E3")
+ ("grey90" . "#E5E5E5")
+ ("grey91" . "#E8E8E8")
+ ("grey92" . "#EBEBEB")
+ ("grey93" . "#EDEDED")
+ ("grey94" . "#F0F0F0")
+ ("grey95" . "#F2F2F2")
+ ("grey96" . "#F5F5F5")
+ ("grey97" . "#F7F7F7")
+ ("grey98" . "#FAFAFA")
+ ("grey99" . "#FCFCFC")
+ ("grey100" . "#FFFFFF")
+ ("honeydew" . "#F0FFF0")
+ ("honeydew1" . "#F0FFF0")
+ ("honeydew2" . "#E0EEE0")
+ ("honeydew3" . "#C1CDC1")
+ ("honeydew4" . "#838B83")
+ ("hotpink" . "#FF69B4")
+ ("hotpink1" . "#FF6EB4")
+ ("hotpink2" . "#EE6AA7")
+ ("hotpink3" . "#CD6090")
+ ("hotpink4" . "#8B3A62")
+ ("indianred" . "#CD5C5C")
+ ("indianred1" . "#FF6A6A")
+ ("indianred2" . "#EE6363")
+ ("indianred3" . "#CD5555")
+ ("indianred4" . "#8B3A3A")
+ ("ivory" . "#FFFFF0")
+ ("ivory1" . "#FFFFF0")
+ ("ivory2" . "#EEEEE0")
+ ("ivory3" . "#CDCDC1")
+ ("ivory4" . "#8B8B83")
+ ("khaki" . "#F0E68C")
+ ("khaki1" . "#FFF68F")
+ ("khaki2" . "#EEE685")
+ ("khaki3" . "#CDC673")
+ ("khaki4" . "#8B864E")
+ ("lavender" . "#E6E6FA")
+ ("lavenderblush" . "#FFF0F5")
+ ("lavenderblush1" . "#FFF0F5")
+ ("lavenderblush2" . "#EEE0E5")
+ ("lavenderblush3" . "#CDC1C5")
+ ("lavenderblush4" . "#8B8386")
+ ("lawngreen" . "#7CFC00")
+ ("lemonchiffon" . "#FFFACD")
+ ("lemonchiffon1" . "#FFFACD")
+ ("lemonchiffon2" . "#EEE9BF")
+ ("lemonchiffon3" . "#CDC9A5")
+ ("lemonchiffon4" . "#8B8970")
+ ("lightblue" . "#ADD8E6")
+ ("lightblue1" . "#BFEFFF")
+ ("lightblue2" . "#B2DFEE")
+ ("lightblue3" . "#9AC0CD")
+ ("lightblue4" . "#68838B")
+ ("lightcoral" . "#F08080")
+ ("lightcyan" . "#E0FFFF")
+ ("lightcyan1" . "#E0FFFF")
+ ("lightcyan2" . "#D1EEEE")
+ ("lightcyan3" . "#B4CDCD")
+ ("lightcyan4" . "#7A8B8B")
+ ("lightgoldenrod" . "#EEDD82")
+ ("lightgoldenrod1" . "#FFEC8B")
+ ("lightgoldenrod2" . "#EEDC82")
+ ("lightgoldenrod3" . "#CDBE70")
+ ("lightgoldenrod4" . "#8B814C")
+ ("lightgoldenrodyellow" . "#FAFAD2")
+ ("lightgray" . "#D3D3D3")
+ ("lightgreen" . "#90EE90")
+ ("lightgrey" . "#D3D3D3")
+ ("lightpink" . "#FFB6C1")
+ ("lightpink1" . "#FFAEB9")
+ ("lightpink2" . "#EEA2AD")
+ ("lightpink3" . "#CD8C95")
+ ("lightpink4" . "#8B5F65")
+ ("lightsalmon" . "#FFA07A")
+ ("lightsalmon1" . "#FFA07A")
+ ("lightsalmon2" . "#EE9572")
+ ("lightsalmon3" . "#CD8162")
+ ("lightsalmon4" . "#8B5742")
+ ("lightseagreen" . "#20B2AA")
+ ("lightskyblue" . "#87CEFA")
+ ("lightskyblue1" . "#B0E2FF")
+ ("lightskyblue2" . "#A4D3EE")
+ ("lightskyblue3" . "#8DB6CD")
+ ("lightskyblue4" . "#607B8B")
+ ("lightslateblue" . "#8470FF")
+ ("lightslategray" . "#778899")
+ ("lightslategrey" . "#778899")
+ ("lightsteelblue" . "#B0C4DE")
+ ("lightsteelblue1" . "#CAE1FF")
+ ("lightsteelblue2" . "#BCD2EE")
+ ("lightsteelblue3" . "#A2B5CD")
+ ("lightsteelblue4" . "#6E7B8B")
+ ("lightyellow" . "#FFFFE0")
+ ("lightyellow1" . "#FFFFE0")
+ ("lightyellow2" . "#EEEED1")
+ ("lightyellow3" . "#CDCDB4")
+ ("lightyellow4" . "#8B8B7A")
+ ("limegreen" . "#32CD32")
+ ("linen" . "#FAF0E6")
+ ("magenta" . "#FF00FF")
+ ("magenta1" . "#FF00FF")
+ ("magenta2" . "#EE00EE")
+ ("magenta3" . "#CD00CD")
+ ("magenta4" . "#8B008B")
+ ("maroon" . "#B03060")
+ ("maroon1" . "#FF34B3")
+ ("maroon2" . "#EE30A7")
+ ("maroon3" . "#CD2990")
+ ("maroon4" . "#8B1C62")
+ ("mediumaquamarine" . "#66CDAA")
+ ("mediumblue" . "#0000CD")
+ ("mediumorchid" . "#BA55D3")
+ ("mediumorchid1" . "#E066FF")
+ ("mediumorchid2" . "#D15FEE")
+ ("mediumorchid3" . "#B452CD")
+ ("mediumorchid4" . "#7A378B")
+ ("mediumpurple" . "#9370DB")
+ ("mediumpurple1" . "#AB82FF")
+ ("mediumpurple2" . "#9F79EE")
+ ("mediumpurple3" . "#8968CD")
+ ("mediumpurple4" . "#5D478B")
+ ("mediumseagreen" . "#3CB371")
+ ("mediumslateblue" . "#7B68EE")
+ ("mediumspringgreen" . "#00FA9A")
+ ("mediumturquoise" . "#48D1CC")
+ ("mediumvioletred" . "#C71585")
+ ("midnightblue" . "#191970")
+ ("mintcream" . "#F5FFFA")
+ ("mistyrose" . "#FFE4E1")
+ ("mistyrose1" . "#FFE4E1")
+ ("mistyrose2" . "#EED5D2")
+ ("mistyrose3" . "#CDB7B5")
+ ("mistyrose4" . "#8B7D7B")
+ ("moccasin" . "#FFE4B5")
+ ("navajowhite" . "#FFDEAD")
+ ("navajowhite1" . "#FFDEAD")
+ ("navajowhite2" . "#EECFA1")
+ ("navajowhite3" . "#CDB38B")
+ ("navajowhite4" . "#8B795E")
+ ("navy" . "#000080")
+ ("navyblue" . "#000080")
+ ("oldlace" . "#FDF5E6")
+ ("olivedrab" . "#6B8E23")
+ ("olivedrab1" . "#C0FF3E")
+ ("olivedrab2" . "#B3EE3A")
+ ("olivedrab3" . "#9ACD32")
+ ("olivedrab4" . "#698B22")
+ ("orange" . "#FFA500")
+ ("orange1" . "#FFA500")
+ ("orange2" . "#EE9A00")
+ ("orange3" . "#CD8500")
+ ("orange4" . "#8B5A00")
+ ("orangered" . "#FF4500")
+ ("orangered1" . "#FF4500")
+ ("orangered2" . "#EE4000")
+ ("orangered3" . "#CD3700")
+ ("orangered4" . "#8B2500")
+ ("orchid" . "#DA70D6")
+ ("orchid1" . "#FF83FA")
+ ("orchid2" . "#EE7AE9")
+ ("orchid3" . "#CD69C9")
+ ("orchid4" . "#8B4789")
+ ("palegoldenrod" . "#EEE8AA")
+ ("palegreen" . "#98FB98")
+ ("palegreen1" . "#9AFF9A")
+ ("palegreen2" . "#90EE90")
+ ("palegreen3" . "#7CCD7C")
+ ("palegreen4" . "#548B54")
+ ("paleturquoise" . "#AFEEEE")
+ ("paleturquoise1" . "#BBFFFF")
+ ("paleturquoise2" . "#AEEEEE")
+ ("paleturquoise3" . "#96CDCD")
+ ("paleturquoise4" . "#668B8B")
+ ("palevioletred" . "#DB7093")
+ ("palevioletred1" . "#FF82AB")
+ ("palevioletred2" . "#EE799F")
+ ("palevioletred3" . "#CD6889")
+ ("palevioletred4" . "#8B475D")
+ ("papayawhip" . "#FFEFD5")
+ ("peachpuff" . "#FFDAB9")
+ ("peachpuff1" . "#FFDAB9")
+ ("peachpuff2" . "#EECBAD")
+ ("peachpuff3" . "#CDAF95")
+ ("peachpuff4" . "#8B7765")
+ ("peru" . "#CD853F")
+ ("pink" . "#FFC0CB")
+ ("pink1" . "#FFB5C5")
+ ("pink2" . "#EEA9B8")
+ ("pink3" . "#CD919E")
+ ("pink4" . "#8B636C")
+ ("plum" . "#DDA0DD")
+ ("plum1" . "#FFBBFF")
+ ("plum2" . "#EEAEEE")
+ ("plum3" . "#CD96CD")
+ ("plum4" . "#8B668B")
+ ("powderblue" . "#B0E0E6")
+ ("purple" . "#A020F0")
+ ("purple1" . "#9B30FF")
+ ("purple2" . "#912CEE")
+ ("purple3" . "#7D26CD")
+ ("purple4" . "#551A8B")
+ ("red" . "#FF0000")
+ ("red1" . "#FF0000")
+ ("red2" . "#EE0000")
+ ("red3" . "#CD0000")
+ ("red4" . "#8B0000")
+ ("rosybrown" . "#BC8F8F")
+ ("rosybrown1" . "#FFC1C1")
+ ("rosybrown2" . "#EEB4B4")
+ ("rosybrown3" . "#CD9B9B")
+ ("rosybrown4" . "#8B6969")
+ ("royalblue" . "#4169E1")
+ ("royalblue1" . "#4876FF")
+ ("royalblue2" . "#436EEE")
+ ("royalblue3" . "#3A5FCD")
+ ("royalblue4" . "#27408B")
+ ("saddlebrown" . "#8B4513")
+ ("salmon" . "#FA8072")
+ ("salmon1" . "#FF8C69")
+ ("salmon2" . "#EE8262")
+ ("salmon3" . "#CD7054")
+ ("salmon4" . "#8B4C39")
+ ("sandybrown" . "#F4A460")
+ ("seagreen" . "#2E8B57")
+ ("seagreen1" . "#54FF9F")
+ ("seagreen2" . "#4EEE94")
+ ("seagreen3" . "#43CD80")
+ ("seagreen4" . "#2E8B57")
+ ("seashell" . "#FFF5EE")
+ ("seashell1" . "#FFF5EE")
+ ("seashell2" . "#EEE5DE")
+ ("seashell3" . "#CDC5BF")
+ ("seashell4" . "#8B8682")
+ ("sienna" . "#A0522D")
+ ("sienna1" . "#FF8247")
+ ("sienna2" . "#EE7942")
+ ("sienna3" . "#CD6839")
+ ("sienna4" . "#8B4726")
+ ("skyblue" . "#87CEEB")
+ ("skyblue1" . "#87CEFF")
+ ("skyblue2" . "#7EC0EE")
+ ("skyblue3" . "#6CA6CD")
+ ("skyblue4" . "#4A708B")
+ ("slateblue" . "#6A5ACD")
+ ("slateblue1" . "#836FFF")
+ ("slateblue2" . "#7A67EE")
+ ("slateblue3" . "#6959CD")
+ ("slateblue4" . "#473C8B")
+ ("slategray" . "#708090")
+ ("slategray1" . "#C6E2FF")
+ ("slategray2" . "#B9D3EE")
+ ("slategray3" . "#9FB6CD")
+ ("slategray4" . "#6C7B8B")
+ ("slategrey" . "#708090")
+ ("snow" . "#FFFAFA")
+ ("snow1" . "#FFFAFA")
+ ("snow2" . "#EEE9E9")
+ ("snow3" . "#CDC9C9")
+ ("snow4" . "#8B8989")
+ ("springgreen" . "#00FF7F")
+ ("springgreen1" . "#00FF7F")
+ ("springgreen2" . "#00EE76")
+ ("springgreen3" . "#00CD66")
+ ("springgreen4" . "#008B45")
+ ("steelblue" . "#4682B4")
+ ("steelblue1" . "#63B8FF")
+ ("steelblue2" . "#5CACEE")
+ ("steelblue3" . "#4F94CD")
+ ("steelblue4" . "#36648B")
+ ("tan" . "#D2B48C")
+ ("tan1" . "#FFA54F")
+ ("tan2" . "#EE9A49")
+ ("tan3" . "#CD853F")
+ ("tan4" . "#8B5A2B")
+ ("thistle" . "#D8BFD8")
+ ("thistle1" . "#FFE1FF")
+ ("thistle2" . "#EED2EE")
+ ("thistle3" . "#CDB5CD")
+ ("thistle4" . "#8B7B8B")
+ ("tomato" . "#FF6347")
+ ("tomato1" . "#FF6347")
+ ("tomato2" . "#EE5C42")
+ ("tomato3" . "#CD4F39")
+ ("tomato4" . "#8B3626")
+ ("turquoise" . "#40E0D0")
+ ("turquoise1" . "#00F5FF")
+ ("turquoise2" . "#00E5EE")
+ ("turquoise3" . "#00C5CD")
+ ("turquoise4" . "#00868B")
+ ("violet" . "#EE82EE")
+ ("violetred" . "#D02090")
+ ("violetred1" . "#FF3E96")
+ ("violetred2" . "#EE3A8C")
+ ("violetred3" . "#CD3278")
+ ("violetred4" . "#8B2252")
+ ("wheat" . "#F5DEB3")
+ ("wheat1" . "#FFE7BA")
+ ("wheat2" . "#EED8AE")
+ ("wheat3" . "#CDBA96")
+ ("wheat4" . "#8B7E66")
+ ("whitesmoke" . "#F5F5F5")
+ ("yellow" . "#FFFF00")
+ ("yellow1" . "#FFFF00")
+ ("yellow2" . "#EEEE00")
+ ("yellow3" . "#CDCD00")
+ ("yellow4" . "#8B8B00")
+ ("yellowgreen" . "#9ACD32"))
+ "Alist of R colors.
+Each entry should have the form (COLOR-NAME . HEXADECIMAL-COLOR)."
+ :group 'rainbow)
+(defcustom rainbow-r-colors-major-mode-list
+ '(ess-mode)
+ "List of major mode where R colors are enabled when
+`rainbow-r-colors' is set to auto."
+ :group 'rainbow)
+
+(defcustom rainbow-r-colors 'auto
+ "When to enable R colors.
+If set to t, the R colors will be enabled. If set to nil, the
+R colors will not be enabled. If set to auto, the R colors
+will be enabled if a major mode has been detected from the
+`rainbow-r-colors-major-mode-list'."
+ :group 'rainbow)
+
+
+;; Functions
+(defun rainbow-colorize-match (color &optional match)
+ "Return a matched string propertized with a face whose
+background is COLOR. The foreground is computed using
+`rainbow-color-luminance', and is either white or black."
+ (let ((match (or match 0)))
+ (put-text-property
+ (match-beginning match) (match-end match)
+ 'face `((:foreground ,(if (> 0.5 (rainbow-x-color-luminance color))
+ "white" "black"))
+ (:background ,color)))))
+
+(defun rainbow-colorize-itself (&optional match)
+ "Colorize a match with itself."
+ (rainbow-colorize-match (match-string-no-properties (or match 0)) match))
+
+(defun rainbow-colorize-hexadecimal-without-sharp ()
+ "Colorize an hexadecimal colors and prepend # to it."
+ (rainbow-colorize-match (concat "#" (match-string-no-properties 1))))
+
+(defun rainbow-colorize-by-assoc (assoc-list)
+ "Colorize a match with its association from ASSOC-LIST."
+ (rainbow-colorize-match (cdr (assoc-string (match-string-no-properties 0)
+ assoc-list t))))
+
+(defun rainbow-rgb-relative-to-absolute (number)
+ "Convert a relative NUMBER to absolute. If NUMBER is absolute, return NUMBER.
+This will convert \"80 %\" to 204, \"100 %\" to 255 but \"123\" to \"123\".
+If the percentage value is above 100, it's converted to 100."
+ (let ((string-length (- (length number) 1)))
+ ;; Is this a number with %?
+ (if (eq (elt number string-length) ?%)
+ (/ (* (min (string-to-number (substring number 0 string-length)) 100) 255) 100)
+ (string-to-number number))))
+
+(defun rainbow-colorize-hsl ()
+ "Colorize a match with itself."
+ (let ((h (/ (string-to-number (match-string-no-properties 1)) 360.0))
+ (s (/ (string-to-number (match-string-no-properties 2)) 100.0))
+ (l (/ (string-to-number (match-string-no-properties 3)) 100.0)))
+ (rainbow-colorize-match
+ (multiple-value-bind (r g b)
+ (color-hsl-to-rgb h s l)
+ (format "#%02X%02X%02X" (* r 255) (* g 255) (* b 255))))))
+
+(defun rainbow-colorize-rgb ()
+ "Colorize a match with itself."
+ (let ((r (rainbow-rgb-relative-to-absolute (match-string-no-properties 1)))
+ (g (rainbow-rgb-relative-to-absolute (match-string-no-properties 2)))
+ (b (rainbow-rgb-relative-to-absolute (match-string-no-properties 3))))
+ (rainbow-colorize-match (format "#%02X%02X%02X" r g b))))
+
+(defun rainbow-colorize-rgb-float ()
+ "Colorize a match with itself, with relative value."
+ (let ((r (* (string-to-number (match-string-no-properties 1)) 255.0))
+ (g (* (string-to-number (match-string-no-properties 2)) 255.0))
+ (b (* (string-to-number (match-string-no-properties 3)) 255.0)))
+ (rainbow-colorize-match (format "#%02X%02X%02X" r g b))))
+
+(defvar ansi-color-context)
+(defvar xterm-color-current)
+
+(defun rainbow-colorize-ansi ()
+ "Return a matched string propertized with ansi color face."
+ (let ((xterm-color? (featurep 'xterm-color))
+ (string (match-string-no-properties 0))
+ color)
+ (save-match-data
+ (let* ((replaced (concat
+ (replace-regexp-in-string
+ "^\\(\\\\[eE]\\|\\\\033\\|\\\\x1[bB]\\)"
+ "\033" string) "x"))
+ xterm-color-current
+ ansi-color-context
+ (applied (funcall (if xterm-color?
+ 'xterm-color-filter
+ 'ansi-color-apply)
+ replaced))
+ (face-property (get-text-property
+ 0
+ (if xterm-color? 'face 'font-lock-face)
+ applied)))
+ (unless (listp (car face-property))
+ (setq face-property (list face-property)))
+ (setq color (funcall (if xterm-color? 'cadr 'cdr)
+ (or (assq (if xterm-color?
+ :foreground
+ 'foreground-color)
+ face-property)
+ (assq (if xterm-color?
+ :background
+ 'background-color)
+ face-property))))))
+ (when color
+ (rainbow-colorize-match color))))
+
+(defun rainbow-color-luminance (red green blue)
+ "Calculate the luminance of color composed of RED, BLUE and GREEN.
+Return a value between 0 and 1."
+ (/ (+ (* .2126 red) (* .7152 green) (* .0722 blue)) 256))
+
+(defun rainbow-x-color-luminance (color)
+ "Calculate the luminance of a color string (e.g. \"#ffaa00\", \"blue\").
+Return a value between 0 and 1."
+ (let* ((values (x-color-values color))
+ (r (/ (car values) 256.0))
+ (g (/ (cadr values) 256.0))
+ (b (/ (caddr values) 256.0)))
+ (rainbow-color-luminance r g b)))
+
+(defun rainbow-turn-on ()
+ "Turn on raibow-mode."
+ (font-lock-add-keywords nil
+ rainbow-hexadecimal-colors-font-lock-keywords)
+ ;; Activate X colors?
+ (when (or (eq rainbow-x-colors t)
+ (and (eq rainbow-x-colors 'auto)
+ (memq major-mode rainbow-x-colors-major-mode-list)))
+ (font-lock-add-keywords nil
+ rainbow-x-colors-font-lock-keywords))
+ ;; Activate LaTeX colors?
+ (when (or (eq rainbow-latex-colors t)
+ (and (eq rainbow-latex-colors 'auto)
+ (memq major-mode rainbow-latex-colors-major-mode-list)))
+ (font-lock-add-keywords nil
+ rainbow-latex-rgb-colors-font-lock-keywords))
+ ;; Activate ANSI colors?
+ (when (or (eq rainbow-ansi-colors t)
+ (and (eq rainbow-ansi-colors 'auto)
+ (memq major-mode rainbow-ansi-colors-major-mode-list)))
+ (font-lock-add-keywords nil
+ rainbow-ansi-colors-font-lock-keywords))
+ ;; Activate HTML colors?
+ (when (or (eq rainbow-html-colors t)
+ (and (eq rainbow-html-colors 'auto)
+ (memq major-mode rainbow-html-colors-major-mode-list)))
+ (setq rainbow-html-colors-font-lock-keywords
+ `((,(regexp-opt (mapcar 'car rainbow-html-colors-alist) 'words)
+ (0 (rainbow-colorize-by-assoc rainbow-html-colors-alist)))))
+ (font-lock-add-keywords nil
+ `(,@rainbow-html-colors-font-lock-keywords
+ ,@rainbow-html-rgb-colors-font-lock-keywords)))
+ ;; Activate R colors?
+ (when (or (eq rainbow-r-colors t)
+ (and (eq rainbow-r-colors 'auto)
+ (memq major-mode rainbow-r-colors-major-mode-list)))
+ (setq rainbow-r-colors-font-lock-keywords
+ `((,(regexp-opt (mapcar 'car rainbow-r-colors-alist) 'words)
+ (0 (rainbow-colorize-by-assoc rainbow-r-colors-alist)))))
+ (font-lock-add-keywords nil
+ rainbow-r-colors-font-lock-keywords
+ )))
+
+(defun rainbow-turn-off ()
+ "Turn off rainbow-mode."
+ (font-lock-remove-keywords
+ nil
+ `(,@rainbow-hexadecimal-colors-font-lock-keywords
+ ,@rainbow-x-colors-font-lock-keywords
+ ,@rainbow-latex-rgb-colors-font-lock-keywords
+ ,@rainbow-r-colors-font-lock-keywords
+ ,@rainbow-html-colors-font-lock-keywords
+ ,@rainbow-html-rgb-colors-font-lock-keywords)))
+
+;;;###autoload
+(define-minor-mode rainbow-mode
+ "Colorize strings that represent colors.
+This will fontify with colors the string like \"#aabbcc\" or \"blue\"."
+ :lighter " Rbow"
+ (progn
+ (if rainbow-mode
+ (rainbow-turn-on)
+ (rainbow-turn-off))
+ ;; Call font-lock-mode to refresh the buffer when used e.g. interactively
+ (font-lock-mode 1)))
+
+;;;; ChangeLog:
+
+;; 2015-03-06 Julien Danjou <julienNOSPAMdanjou.info>
+;;
+;; rainbow: fix font-lock-mode refresh
+;;
+;; 2014-10-15 Stefan Monnier <monnierNOSPAMiro.umontreal.ca>
+;;
+;; * packages/rainbow-mode/rainbow-mode.el (ansi-color-context)
+;; (xterm-color-current): Declare.
+;;
+;; 2014-09-07 Julien Danjou <julienNOSPAMdanjou.info>
+;;
+;; rainbow-mode: support float in CSS and limit to 100%
+;;
+;; 2013-08-05 Julien Danjou <julienNOSPAMdanjou.info>
+;;
+;; rainbow-mode: 0.9, allow spaces in LaTeX colors
+;;
+;; 2013-05-03 Julien Danjou <julienNOSPAMdanjou.info>
+;;
+;; rainbow-mode: add support for R, bump version to 0.8
+;;
+;; Signed-off-by: Julien Danjou <julienNOSPAMdanjou.info>
+;;
+;; 2013-02-26 Julien Danjou <julienNOSPAMdanjou.info>
+;;
+;; rainbow-mode: version 0.7
+;;
+;; * rainbow-mode.el: don't activate font-lock-mode
+;;
+;; 2012-12-11 Julien Danjou <julienNOSPAMdanjou.info>
+;;
+;; * rainbow-mode: update to 0.6, add support for ANSI coloring
+;;
+;; 2012-11-26 Julien Danjou <julienNOSPAMdanjou.info>
+;;
+;; rainbow-mode: fix some LaTex docstrings
+;;
+;; 2012-11-14 Julien Danjou <julienNOSPAMdanjou.info>
+;;
+;; rainbow-mode: version 0.5
+;;
+;; * rainbow-mode.el: fix syntax error on
+;; `rainbow-hexadecimal-colors-font-lock-keywords'.
+;;
+;; 2012-11-09 Julien Danjou <julien@danjou.info>
+;;
+;; rainbow-mode: version 0.4
+;;
+;; * rainbow-mode.el: Use functions from color package to colorize HSL
+;; rather
+;; than our own copy.
+;;
+;; 2012-11-09 Julien Danjou <julienNOSPAMdanjou.info>
+;;
+;; rainbow-mode 0.3
+;;
+;; * rainbow-mode.el: avoid colorizing HTML entities
+;;
+;; 2011-09-23 Julien Danjou <julienNOSPAMdanjou.info>
+;;
+;; Update rainbow-mode to version 0.2
+;;
+;; 2011-07-01 Chong Yidong <cydNOSPAMstupidchicken.com>
+;;
+;; Give every package its own directory in packages/ including single-file
+;; packages.
+;;
+
+
+(provide 'rainbow-mode)
+
+;;; rainbow-mode.el ends here
--- /dev/null
+;;; s-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+(add-to-list 'load-path (or (file-name-directory #$) (car load-path)))
+\f
+;;;### (autoloads nil nil ("s.el") (21908 14193 436630 0))
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+;;; s-autoloads.el ends here
--- /dev/null
+(define-package "s" "20140910.334" "The long lost Emacs string manipulation library." 'nil :keywords '("strings"))
--- /dev/null
+;;; s.el --- The long lost Emacs string manipulation library.
+
+;; Copyright (C) 2012 Magnar Sveen
+
+;; Author: Magnar Sveen <magnarsNOSPAMgmail.com>
+;; Version: 1.9.0
+;; Package-Version: 20140910.334
+;; Keywords: strings
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; The long lost Emacs string manipulation library.
+;;
+;; See documentation on https://github.com/magnars/s.el#functions
+
+;;; Code:
+
+(require 'ucs-normalize)
+
+(defun s-trim-left (s)
+ "Remove whitespace at the beginning of S."
+ (if (string-match "\\`[ \t\n\r]+" s)
+ (replace-match "" t t s)
+ s))
+
+(defun s-trim-right (s)
+ "Remove whitespace at the end of S."
+ (if (string-match "[ \t\n\r]+\\'" s)
+ (replace-match "" t t s)
+ s))
+
+(defun s-trim (s)
+ "Remove whitespace at the beginning and end of S."
+ (s-trim-left (s-trim-right s)))
+
+(defun s-collapse-whitespace (s)
+ "Convert all adjacent whitespace characters to a single space."
+ (replace-regexp-in-string "[ \t\n\r]+" " " s))
+
+(defun s-split (separator s &optional omit-nulls)
+ "Split S into substrings bounded by matches for regexp SEPARATOR.
+If OMIT-NULLS is non-nil, zero-length substrings are omitted.
+
+This is a simple wrapper around the built-in `split-string'."
+ (split-string s separator omit-nulls))
+
+(defun s-split-up-to (separator s n &optional omit-nulls)
+ "Split S up to N times into substrings bounded by matches for regexp SEPARATOR.
+
+If OMIT-NULLS is non-nil, zero-length substrings are omitted.
+
+See also `s-split'."
+ (save-match-data
+ (let ((op 0)
+ (r nil))
+ (with-temp-buffer
+ (insert s)
+ (setq op (goto-char (point-min)))
+ (while (and (re-search-forward separator nil t)
+ (< 0 n))
+ (let ((sub (buffer-substring-no-properties op (match-beginning 0))))
+ (unless (and omit-nulls
+ (equal sub ""))
+ (push sub r)))
+ (setq op (goto-char (match-end 0)))
+ (setq n (1- n)))
+ (if (/= (point) (point-max))
+ (push (buffer-substring-no-properties op (point-max)) r)
+ (unless omit-nulls
+ (push "" r))))
+ (nreverse r))))
+
+(defun s-lines (s)
+ "Splits S into a list of strings on newline characters."
+ (s-split "\\(\r\n\\|[\n\r]\\)" s))
+
+(defun s-join (separator strings)
+ "Join all the strings in STRINGS with SEPARATOR in between."
+ (mapconcat 'identity strings separator))
+
+(defun s-concat (&rest strings)
+ "Join all the string arguments into one string."
+ (apply 'concat strings))
+
+(defun s-prepend (prefix s)
+ "Concatenate PREFIX and S."
+ (concat prefix s))
+
+(defun s-append (suffix s)
+ "Concatenate S and SUFFIX."
+ (concat s suffix))
+
+(defun s-repeat (num s)
+ "Make a string of S repeated NUM times."
+ (let (ss)
+ (while (> num 0)
+ (setq ss (cons s ss))
+ (setq num (1- num)))
+ (apply 'concat ss)))
+
+(defun s-chop-suffix (suffix s)
+ "Remove SUFFIX if it is at end of S."
+ (let ((pos (- (length suffix))))
+ (if (and (>= (length s) (length suffix))
+ (string= suffix (substring s pos)))
+ (substring s 0 pos)
+ s)))
+
+(defun s-chop-suffixes (suffixes s)
+ "Remove SUFFIXES one by one in order, if they are at the end of S."
+ (while suffixes
+ (setq s (s-chop-suffix (car suffixes) s))
+ (setq suffixes (cdr suffixes)))
+ s)
+
+(defun s-chop-prefix (prefix s)
+ "Remove PREFIX if it is at the start of S."
+ (let ((pos (length prefix)))
+ (if (and (>= (length s) (length prefix))
+ (string= prefix (substring s 0 pos)))
+ (substring s pos)
+ s)))
+
+(defun s-chop-prefixes (prefixes s)
+ "Remove PREFIXES one by one in order, if they are at the start of S."
+ (while prefixes
+ (setq s (s-chop-prefix (car prefixes) s))
+ (setq prefixes (cdr prefixes)))
+ s)
+
+(defun s-shared-start (s1 s2)
+ "Returns the longest prefix S1 and S2 have in common."
+ (let ((search-length (min (length s1) (length s2)))
+ (i 0))
+ (while (and (< i search-length)
+ (= (aref s1 i) (aref s2 i)))
+ (setq i (1+ i)))
+ (substring s1 0 i)))
+
+(defun s-shared-end (s1 s2)
+ "Returns the longest suffix S1 and S2 have in common."
+ (let* ((l1 (length s1))
+ (l2 (length s2))
+ (search-length (min l1 l2))
+ (i 0))
+ (while (and (< i search-length)
+ (= (aref s1 (- l1 i 1)) (aref s2 (- l2 i 1))))
+ (setq i (1+ i)))
+ ;; If I is 0, then it means that there's no common suffix between
+ ;; S1 and S2.
+ ;;
+ ;; However, since (substring s (- 0)) will return the whole
+ ;; string, `s-shared-end' should simply return the empty string
+ ;; when I is 0.
+ (if (zerop i)
+ ""
+ (substring s1 (- i)))))
+
+(defun s-chomp (s)
+ "Remove one trailing `\\n`, `\\r` or `\\r\\n` from S."
+ (s-chop-suffixes '("\n" "\r") s))
+
+(defun s-truncate (len s)
+ "If S is longer than LEN, cut it down to LEN - 3 and add ... at the end."
+ (if (> (length s) len)
+ (format "%s..." (substring s 0 (- len 3)))
+ s))
+
+(defun s-word-wrap (len s)
+ "If S is longer than LEN, wrap the words with newlines."
+ (with-temp-buffer
+ (insert s)
+ (let ((fill-column len))
+ (fill-region (point-min) (point-max)))
+ (buffer-substring-no-properties (point-min) (point-max))))
+
+(defun s-center (len s)
+ "If S is shorter than LEN, pad it with spaces so it is centered."
+ (let ((extra (max 0 (- len (length s)))))
+ (concat
+ (make-string (ceiling extra 2) ? )
+ s
+ (make-string (floor extra 2) ? ))))
+
+(defun s-pad-left (len padding s)
+ "If S is shorter than LEN, pad it with PADDING on the left."
+ (let ((extra (max 0 (- len (length s)))))
+ (concat (make-string extra (string-to-char padding))
+ s)))
+
+(defun s-pad-right (len padding s)
+ "If S is shorter than LEN, pad it with PADDING on the right."
+ (let ((extra (max 0 (- len (length s)))))
+ (concat s
+ (make-string extra (string-to-char padding)))))
+
+(defun s-left (len s)
+ "Returns up to the LEN first chars of S."
+ (if (> (length s) len)
+ (substring s 0 len)
+ s))
+
+(defun s-right (len s)
+ "Returns up to the LEN last chars of S."
+ (let ((l (length s)))
+ (if (> l len)
+ (substring s (- l len) l)
+ s)))
+
+(defun s-ends-with? (suffix s &optional ignore-case)
+ "Does S end with SUFFIX?
+
+If IGNORE-CASE is non-nil, the comparison is done without paying
+attention to case differences.
+
+Alias: `s-suffix?'"
+ (let ((start-pos (- (length s) (length suffix))))
+ (and (>= start-pos 0)
+ (eq t (compare-strings suffix nil nil
+ s start-pos nil ignore-case)))))
+
+(defalias 's-ends-with-p 's-ends-with?)
+
+(defun s-starts-with? (prefix s &optional ignore-case)
+ "Does S start with PREFIX?
+
+If IGNORE-CASE is non-nil, the comparison is done without paying
+attention to case differences.
+
+Alias: `s-prefix?'. This is a simple wrapper around the built-in
+`string-prefix-p'."
+ (string-prefix-p prefix s ignore-case))
+
+(defalias 's-starts-with-p 's-starts-with?)
+
+(defalias 's-suffix? 's-ends-with?)
+(defalias 's-prefix? 's-starts-with?)
+(defalias 's-suffix-p 's-ends-with?)
+(defalias 's-prefix-p 's-starts-with?)
+
+(defun s--truthy? (val)
+ (not (null val)))
+
+(defun s-contains? (needle s &optional ignore-case)
+ "Does S contain NEEDLE?
+
+If IGNORE-CASE is non-nil, the comparison is done without paying
+attention to case differences."
+ (let ((case-fold-search ignore-case))
+ (s--truthy? (string-match-p (regexp-quote needle) s))))
+
+(defalias 's-contains-p 's-contains?)
+
+(defun s-equals? (s1 s2)
+ "Is S1 equal to S2?
+
+This is a simple wrapper around the built-in `string-equal'."
+ (string-equal s1 s2))
+
+(defalias 's-equals-p 's-equals?)
+
+(defun s-less? (s1 s2)
+ "Is S1 less than S2?
+
+This is a simple wrapper around the built-in `string-lessp'."
+ (string-lessp s1 s2))
+
+(defalias 's-less-p 's-less?)
+
+(defun s-matches? (regexp s &optional start)
+ "Does REGEXP match S?
+If START is non-nil the search starts at that index.
+
+This is a simple wrapper around the built-in `string-match-p'."
+ (s--truthy? (string-match-p regexp s start)))
+
+(defalias 's-matches-p 's-matches?)
+
+(defun s-blank? (s)
+ "Is S nil or the empty string?"
+ (or (null s) (string= "" s)))
+
+(defun s-present? (s)
+ "Is S anything but nil or the empty string?"
+ (not (s-blank? s)))
+
+(defun s-presence (s)
+ "Return S if it's `s-present?', otherwise return nil."
+ (and (s-present? s) s))
+
+(defun s-lowercase? (s)
+ "Are all the letters in S in lower case?"
+ (let ((case-fold-search nil))
+ (not (string-match-p "[[:upper:]]" s))))
+
+(defun s-uppercase? (s)
+ "Are all the letters in S in upper case?"
+ (let ((case-fold-search nil))
+ (not (string-match-p "[[:lower:]]" s))))
+
+(defun s-mixedcase? (s)
+ "Are there both lower case and upper case letters in S?"
+ (let ((case-fold-search nil))
+ (s--truthy?
+ (and (string-match-p "[[:lower:]]" s)
+ (string-match-p "[[:upper:]]" s)))))
+
+(defun s-capitalized? (s)
+ "In S, is the first letter upper case, and all other letters lower case?"
+ (let ((case-fold-search nil))
+ (s--truthy?
+ (string-match-p "^[[:upper:]][^[:upper:]]*$" s))))
+
+(defun s-numeric? (s)
+ "Is S a number?"
+ (s--truthy?
+ (string-match-p "^[0-9]+$" s)))
+
+(defun s-replace (old new s)
+ "Replaces OLD with NEW in S."
+ (replace-regexp-in-string (regexp-quote old) new s t t))
+
+(defun s--aget (alist key)
+ (cdr (assoc key alist)))
+
+(defun s-replace-all (replacements s)
+ "REPLACEMENTS is a list of cons-cells. Each `car` is replaced with `cdr` in S."
+ (replace-regexp-in-string (regexp-opt (mapcar 'car replacements))
+ (lambda (it) (s--aget replacements it))
+ s))
+
+(defun s-downcase (s)
+ "Convert S to lower case.
+
+This is a simple wrapper around the built-in `downcase'."
+ (downcase s))
+
+(defun s-upcase (s)
+ "Convert S to upper case.
+
+This is a simple wrapper around the built-in `upcase'."
+ (upcase s))
+
+(defun s-capitalize (s)
+ "Convert the first word's first character to upper case and the rest to lower case in S."
+ (concat (upcase (substring s 0 1)) (downcase (substring s 1))))
+
+(defun s-titleize (s)
+ "Convert each word's first character to upper case and the rest to lower case in S.
+
+This is a simple wrapper around the built-in `capitalize'."
+ (capitalize s))
+
+(defmacro s-with (s form &rest more)
+ "Threads S through the forms. Inserts S as the last item
+in the first form, making a list of it if it is not a list
+already. If there are more forms, inserts the first form as the
+last item in second form, etc."
+ (declare (debug (form &rest [&or (function &rest form) fboundp])))
+ (if (null more)
+ (if (listp form)
+ `(,(car form) ,@(cdr form) ,s)
+ (list form s))
+ `(s-with (s-with ,s ,form) ,@more)))
+
+(put 's-with 'lisp-indent-function 1)
+
+(defun s-index-of (needle s &optional ignore-case)
+ "Returns first index of NEEDLE in S, or nil.
+
+If IGNORE-CASE is non-nil, the comparison is done without paying
+attention to case differences."
+ (let ((case-fold-search ignore-case))
+ (string-match-p (regexp-quote needle) s)))
+
+(defun s-reverse (s)
+ "Return the reverse of S."
+ (if (multibyte-string-p s)
+ (let ((input (string-to-list s))
+ (output ()))
+ (while input
+ ;; Handle entire grapheme cluster as a single unit
+ (let ((grapheme (list (pop input))))
+ (while (memql (car input) ucs-normalize-combining-chars)
+ (push (pop input) grapheme))
+ (setq output (nconc (nreverse grapheme) output))))
+ (concat output))
+ (concat (nreverse (string-to-list s)))))
+
+(defun s-match-strings-all (regex string)
+ "Return a list of matches for REGEX in STRING.
+
+Each element itself is a list of matches, as per
+`match-string'. Multiple matches at the same position will be
+ignored after the first."
+ (let ((all-strings ())
+ (i 0))
+ (while (and (< i (length string))
+ (string-match regex string i))
+ (setq i (1+ (match-beginning 0)))
+ (let (strings
+ (num-matches (/ (length (match-data)) 2))
+ (match 0))
+ (while (/= match num-matches)
+ (push (match-string match string) strings)
+ (setq match (1+ match)))
+ (push (nreverse strings) all-strings)))
+ (nreverse all-strings)))
+
+(defun s-match (regexp s &optional start)
+ "When the given expression matches the string, this function returns a list
+of the whole matching string and a string for each matched subexpressions.
+If it did not match the returned value is an empty list (nil).
+
+When START is non-nil the search will start at that index."
+ (save-match-data
+ (if (string-match regexp s start)
+ (let ((match-data-list (match-data))
+ result)
+ (while match-data-list
+ (let* ((beg (car match-data-list))
+ (end (cadr match-data-list))
+ (subs (if (and beg end) (substring s beg end) nil)))
+ (setq result (cons subs result))
+ (setq match-data-list
+ (cddr match-data-list))))
+ (nreverse result)))))
+
+(defun s-slice-at (regexp s)
+ "Slices S up at every index matching REGEXP."
+ (save-match-data
+ (let (i)
+ (setq i (string-match regexp s 1))
+ (if i
+ (cons (substring s 0 i)
+ (s-slice-at regexp (substring s i)))
+ (list s)))))
+
+(defun s-split-words (s)
+ "Split S into list of words."
+ (s-split
+ "[^[:word:]0-9]+"
+ (let ((case-fold-search nil))
+ (replace-regexp-in-string
+ "\\([[:lower:]]\\)\\([[:upper:]]\\)" "\\1 \\2"
+ (replace-regexp-in-string "\\([[:upper:]]\\)\\([[:upper:]][0-9[:lower:]]\\)" "\\1 \\2" s)))
+ t))
+
+(defun s--mapcar-head (fn-head fn-rest list)
+ "Like MAPCAR, but applies a different function to the first element."
+ (if list
+ (cons (funcall fn-head (car list)) (mapcar fn-rest (cdr list)))))
+
+(defun s-lower-camel-case (s)
+ "Convert S to lowerCamelCase."
+ (s-join "" (s--mapcar-head 'downcase 'capitalize (s-split-words s))))
+
+(defun s-upper-camel-case (s)
+ "Convert S to UpperCamelCase."
+ (s-join "" (mapcar 'capitalize (s-split-words s))))
+
+(defun s-snake-case (s)
+ "Convert S to snake_case."
+ (s-join "_" (mapcar 'downcase (s-split-words s))))
+
+(defun s-dashed-words (s)
+ "Convert S to dashed-words."
+ (s-join "-" (mapcar 'downcase (s-split-words s))))
+
+(defun s-capitalized-words (s)
+ "Convert S to Capitalized words."
+ (let ((words (s-split-words s)))
+ (s-join " " (cons (capitalize (car words)) (mapcar 'downcase (cdr words))))))
+
+(defun s-titleized-words (s)
+ "Convert S to Titleized Words."
+ (s-join " " (mapcar 's-titleize (s-split-words s))))
+
+(defun s-word-initials (s)
+ "Convert S to its initials."
+ (s-join "" (mapcar (lambda (ss) (substring ss 0 1))
+ (s-split-words s))))
+
+;; Errors for s-format
+(progn
+ (put 's-format-resolve
+ 'error-conditions
+ '(error s-format s-format-resolve))
+ (put 's-format-resolve
+ 'error-message
+ "Cannot resolve a template to values"))
+
+(defun s-format (template replacer &optional extra)
+ "Format TEMPLATE with the function REPLACER.
+
+REPLACER takes an argument of the format variable and optionally
+an extra argument which is the EXTRA value from the call to
+`s-format'.
+
+Several standard `s-format' helper functions are recognized and
+adapted for this:
+
+ (s-format \"${name}\" 'gethash hash-table)
+ (s-format \"${name}\" 'aget alist)
+ (s-format \"$0\" 'elt sequence)
+
+The REPLACER function may be used to do any other kind of
+transformation."
+ (let ((saved-match-data (match-data)))
+ (unwind-protect
+ (replace-regexp-in-string
+ "\\$\\({\\([^}]+\\)}\\|[0-9]+\\)"
+ (lambda (md)
+ (let ((var
+ (let ((m (match-string 2 md)))
+ (if m m
+ (string-to-number (match-string 1 md)))))
+ (replacer-match-data (match-data)))
+ (unwind-protect
+ (let ((v
+ (cond
+ ((eq replacer 'gethash)
+ (funcall replacer var extra))
+ ((eq replacer 'aget)
+ (funcall 's--aget extra var))
+ ((eq replacer 'elt)
+ (funcall replacer extra var))
+ (t
+ (set-match-data saved-match-data)
+ (if extra
+ (funcall replacer var extra)
+ (funcall replacer var))))))
+ (if v v (signal 's-format-resolve md)))
+ (set-match-data replacer-match-data)))) template
+ ;; Need literal to make sure it works
+ t t)
+ (set-match-data saved-match-data))))
+
+(defvar s-lex-value-as-lisp nil
+ "If `t' interpolate lisp values as lisp.
+
+`s-lex-format' inserts values with (format \"%S\").")
+
+(defun s-lex-fmt|expand (fmt)
+ "Expand FMT into lisp."
+ (list 's-format fmt (quote 'aget)
+ (append '(list)
+ (mapcar
+ (lambda (matches)
+ (list
+ 'cons
+ (cadr matches)
+ `(format
+ (if s-lex-value-as-lisp "%S" "%s")
+ ,(intern (cadr matches)))))
+ (s-match-strings-all "${\\([^}]+\\)}" fmt)))))
+
+(defmacro s-lex-format (format-str)
+ "`s-format` with the current environment.
+
+FORMAT-STR may use the `s-format' variable reference to refer to
+any variable:
+
+ (let ((x 1))
+ (s-lex-format \"x is: ${x}\"))
+
+The values of the variables are interpolated with \"%s\" unless
+the variable `s-lex-value-as-lisp' is `t' and then they are
+interpolated with \"%S\"."
+ (declare (debug (form)))
+ (s-lex-fmt|expand format-str))
+
+(defun s-count-matches (regexp s &optional start end)
+ "Count occurrences of `regexp' in `s'.
+
+`start', inclusive, and `end', exclusive, delimit the part of `s'
+to match. "
+ (with-temp-buffer
+ (insert s)
+ (goto-char (point-min))
+ (count-matches regexp (or start 1) (or end (point-max)))))
+
+(defun s-wrap (s prefix &optional suffix)
+ "Wrap string S with PREFIX and optionally SUFFIX.
+
+Return string S with PREFIX prepended. If SUFFIX is present, it
+is appended, otherwise PREFIX is used as both prefix and
+suffix."
+ (concat prefix s (or suffix prefix)))
+
+(provide 's)
+;;; s.el ends here
+++ /dev/null
-;;; seq-autoloads.el --- automatically extracted autoloads
-;;
-;;; Code:
-
-\f
-;;;### (autoloads nil nil ("seq-pkg.el" "seq.el") (21823 19231 745832
-;;;;;; 0))
-
-;;;***
-\f
-(provide 'seq-autoloads)
-;; Local Variables:
-;; version-control: never
-;; no-byte-compile: t
-;; no-update-autoloads: t
-;; coding: utf-8
-;; End:
-;;; seq-autoloads.el ends here
+++ /dev/null
-(define-package "seq" "1.4" "Sequence manipulation functions" (quote nil))
+++ /dev/null
-;;; seq.el --- Sequence manipulation functions -*- lexical-binding: t -*-
-
-;; Copyright (C) 2014-2015 Free Software Foundation, Inc.
-
-;; Author: Nicolas Petton <nicolasNOSPAMpetton.fr>
-;; Keywords: sequences
-;; Version: 1.4
-;; Package: seq
-
-;; Maintainer: emacs-develNOSPAMgnu.org
-
-;; This file is part of GNU Emacs.
-
-;; GNU Emacs is free software: you can redistribute it and/or modify
-;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>.
-
-;;; Commentary:
-
-;; Sequence-manipulation functions that complement basic functions
-;; provided by subr.el.
-;;
-;; All functions are prefixed with "seq-".
-;;
-;; All provided functions work on lists, strings and vectors.
-;;
-;; Functions taking a predicate or iterating over a sequence using a
-;; function as argument take the function as their first argument and
-;; the sequence as their second argument. All other functions take
-;; the sequence as their first argument.
-;;
-;; All functions are tested in test/automated/seq-tests.el
-
-;;; Code:
-
-(defmacro seq-doseq (spec &rest body)
- "Loop over a sequence.
-Similar to `dolist' but can be applied lists, strings and vectors.
-
-Evaluate BODY with VAR bound to each element of SEQ, in turn.
-Then evaluate RESULT to get return value, default nil.
-
-\(fn (VAR SEQ [RESULT]) BODY...)"
- (declare (indent 1) (debug ((symbolp form &optional form) body)))
- (let ((is-list (make-symbol "is-list"))
- (seq (make-symbol "seq"))
- (index (make-symbol "index")))
- `(let* ((,seq ,(cadr spec))
- (,is-list (listp ,seq))
- (,index (if ,is-list ,seq 0)))
- (while (if ,is-list
- (consp ,index)
- (< ,index (seq-length ,seq)))
- (let ((,(car spec) (if ,is-list
- (car ,index)
- (seq-elt ,seq ,index))))
- ,@body
- (setq ,index (if ,is-list
- (cdr ,index)
- (+ ,index 1)))))
- ,@(if (cddr spec)
- `((setq ,(car spec) nil) ,@(cddr spec))))))
-
-(defun seq-drop (seq n)
- "Return a subsequence of SEQ without its first N elements.
-The result is a sequence of the same type as SEQ.
-
-If N is a negative integer or zero, SEQ is returned."
- (if (<= n 0)
- seq
- (if (listp seq)
- (seq--drop-list seq n)
- (let ((length (seq-length seq)))
- (seq-subseq seq (min n length) length)))))
-
-(defun seq-take (seq n)
- "Return a subsequence of SEQ with its first N elements.
-The result is a sequence of the same type as SEQ.
-
-If N is a negative integer or zero, an empty sequence is
-returned."
- (if (listp seq)
- (seq--take-list seq n)
- (seq-subseq seq 0 (min (max n 0) (seq-length seq)))))
-
-(defun seq-drop-while (pred seq)
- "Return a sequence from the first element for which (PRED element) is nil in SEQ.
-The result is a sequence of the same type as SEQ."
- (if (listp seq)
- (seq--drop-while-list pred seq)
- (seq-drop seq (seq--count-successive pred seq))))
-
-(defun seq-take-while (pred seq)
- "Return the successive elements for which (PRED element) is non-nil in SEQ.
-The result is a sequence of the same type as SEQ."
- (if (listp seq)
- (seq--take-while-list pred seq)
- (seq-take seq (seq--count-successive pred seq))))
-
-(defun seq-filter (pred seq)
- "Return a list of all the elements for which (PRED element) is non-nil in SEQ."
- (let ((exclude (make-symbol "exclude")))
- (delq exclude (seq-map (lambda (elt)
- (if (funcall pred elt)
- elt
- exclude))
- seq))))
-
-(defun seq-remove (pred seq)
- "Return a list of all the elements for which (PRED element) is nil in SEQ."
- (seq-filter (lambda (elt) (not (funcall pred elt)))
- seq))
-
-(defun seq-reduce (function seq initial-value)
- "Reduce the function FUNCTION across SEQ, starting with INITIAL-VALUE.
-
-Return the result of calling FUNCTION with INITIAL-VALUE and the
-first element of SEQ, then calling FUNCTION with that result and
-the second element of SEQ, then with that result and the third
-element of SEQ, etc.
-
-If SEQ is empty, return INITIAL-VALUE and FUNCTION is not called."
- (if (seq-empty-p seq)
- initial-value
- (let ((acc initial-value))
- (seq-doseq (elt seq)
- (setq acc (funcall function acc elt)))
- acc)))
-
-(defun seq-some-p (pred seq)
- "Return any element for which (PRED element) is non-nil in SEQ, nil otherwise."
- (catch 'seq--break
- (seq-doseq (elt seq)
- (when (funcall pred elt)
- (throw 'seq--break elt)))
- nil))
-
-(defun seq-every-p (pred seq)
- "Return non-nil if (PRED element) is non-nil for all elements of the sequence SEQ."
- (catch 'seq--break
- (seq-doseq (elt seq)
- (or (funcall pred elt)
- (throw 'seq--break nil)))
- t))
-
-(defun seq-count (pred seq)
- "Return the number of elements for which (PRED element) is non-nil in SEQ."
- (let ((count 0))
- (seq-doseq (elt seq)
- (when (funcall pred elt)
- (setq count (+ 1 count))))
- count))
-
-(defun seq-empty-p (seq)
- "Return non-nil if the sequence SEQ is empty, nil otherwise."
- (if (listp seq)
- (null seq)
- (= 0 (seq-length seq))))
-
-(defun seq-sort (pred seq)
- "Return a sorted sequence comparing using PRED the elements of SEQ.
-The result is a sequence of the same type as SEQ."
- (if (listp seq)
- (sort (seq-copy seq) pred)
- (let ((result (seq-sort pred (append seq nil))))
- (seq-into result (type-of seq)))))
-
-(defun seq-contains-p (seq elt &optional testfn)
- "Return the first element in SEQ that equals to ELT.
-Equality is defined by TESTFN if non-nil or by `equal' if nil."
- (seq-some-p (lambda (e)
- (funcall (or testfn #'equal) elt e))
- seq))
-
-(defun seq-uniq (seq &optional testfn)
- "Return a list of the elements of SEQ with duplicates removed.
-TESTFN is used to compare elements, or `equal' if TESTFN is nil."
- (let ((result '()))
- (seq-doseq (elt seq)
- (unless (seq-contains-p result elt testfn)
- (setq result (cons elt result))))
- (nreverse result)))
-
-(defun seq-subseq (seq start &optional end)
- "Return the subsequence of SEQ from START to END.
-If END is omitted, it defaults to the length of the sequence.
-If START or END is negative, it counts from the end."
- (cond ((or (stringp seq) (vectorp seq)) (substring seq start end))
- ((listp seq)
- (let (len (errtext (format "Bad bounding indices: %s, %s" start end)))
- (and end (< end 0) (setq end (+ end (setq len (seq-length seq)))))
- (if (< start 0) (setq start (+ start (or len (setq len (seq-length seq))))))
- (when (> start 0)
- (setq seq (nthcdr (1- start) seq))
- (or seq (error "%s" errtext))
- (setq seq (cdr seq)))
- (if end
- (let ((res nil))
- (while (and (>= (setq end (1- end)) start) seq)
- (push (pop seq) res))
- (or (= (1+ end) start) (error "%s" errtext))
- (nreverse res))
- (seq-copy seq))))
- (t (error "Unsupported sequence: %s" seq))))
-
-(defun seq-concatenate (type &rest seqs)
- "Concatenate, into a sequence of type TYPE, the sequences SEQS.
-TYPE must be one of following symbols: vector, string or list.
-
-\n(fn TYPE SEQUENCE...)"
- (pcase type
- (`vector (apply #'vconcat seqs))
- (`string (apply #'concat seqs))
- (`list (apply #'append (append seqs '(nil))))
- (t (error "Not a sequence type name: %s" type))))
-
-(defun seq-mapcat (function seq &optional type)
- "Concatenate the result of applying FUNCTION to each element of SEQ.
-The result is a sequence of type TYPE, or a list if TYPE is nil."
- (apply #'seq-concatenate (or type 'list)
- (seq-map function seq)))
-
-(defun seq-partition (seq n)
- "Return a list of the elements of SEQ grouped into sub-sequences of length N.
-The last sequence may contain less than N elements. If N is a
-negative integer or 0, nil is returned."
- (unless (< n 1)
- (let ((result '()))
- (while (not (seq-empty-p seq))
- (push (seq-take seq n) result)
- (setq seq (seq-drop seq n)))
- (nreverse result))))
-
-(defun seq-intersection (seq1 seq2 &optional testfn)
- "Return a list of the elements that appear in both SEQ1 and SEQ2.
-Equality is defined by TESTFN if non-nil or by `equal' if nil."
- (seq-reduce (lambda (acc elt)
- (if (seq-contains-p seq2 elt testfn)
- (cons elt acc)
- acc))
- (seq-reverse seq1)
- '()))
-
-(defun seq-difference (seq1 seq2 &optional testfn)
- "Return a list of th elements that appear in SEQ1 but not in SEQ2.
-Equality is defined by TESTFN if non-nil or by `equal' if nil."
- (seq-reduce (lambda (acc elt)
- (if (not (seq-contains-p seq2 elt testfn))
- (cons elt acc)
- acc))
- (seq-reverse seq1)
- '()))
-
-(defun seq-group-by (function seq)
- "Apply FUNCTION to each element of SEQ.
-Separate the elements of SEQ into an alist using the results as
-keys. Keys are compared using `equal'."
- (seq-reduce
- (lambda (acc elt)
- (let* ((key (funcall function elt))
- (cell (assoc key acc)))
- (if cell
- (setcdr cell (push elt (cdr cell)))
- (push (list key elt) acc))
- acc))
- (seq-reverse seq)
- nil))
-
-(defalias 'seq-reverse
- (if (ignore-errors (reverse [1 2]))
- #'reverse
- (lambda (seq)
- "Return the reversed copy of list, vector, or string SEQ.
-See also the function `nreverse', which is used more often."
- (let ((result '()))
- (seq-map (lambda (elt) (push elt result))
- seq)
- (if (listp seq)
- result
- (seq-into result (type-of seq)))))))
-
-(defun seq-into (seq type)
- "Convert the sequence SEQ into a sequence of type TYPE.
-TYPE can be one of the following symbols: vector, string or list."
- (pcase type
- (`vector (vconcat seq))
- (`string (concat seq))
- (`list (append seq nil))
- (t (error "Not a sequence type name: %s" type))))
-
-(defun seq--drop-list (list n)
- "Return a list from LIST without its first N elements.
-This is an optimization for lists in `seq-drop'."
- (while (and list (> n 0))
- (setq list (cdr list)
- n (1- n)))
- list)
-
-(defun seq--take-list (list n)
- "Return a list from LIST made of its first N elements.
-This is an optimization for lists in `seq-take'."
- (let ((result '()))
- (while (and list (> n 0))
- (setq n (1- n))
- (push (pop list) result))
- (nreverse result)))
-
-(defun seq--drop-while-list (pred list)
- "Return a list from the first element for which (PRED element) is nil in LIST.
-This is an optimization for lists in `seq-drop-while'."
- (while (and list (funcall pred (car list)))
- (setq list (cdr list)))
- list)
-
-(defun seq--take-while-list (pred list)
- "Return the successive elements for which (PRED element) is non-nil in LIST.
-This is an optimization for lists in `seq-take-while'."
- (let ((result '()))
- (while (and list (funcall pred (car list)))
- (push (pop list) result))
- (nreverse result)))
-
-(defun seq--count-successive (pred seq)
- "Return the number of successive elements for which (PRED element) is non-nil in SEQ."
- (let ((n 0)
- (len (seq-length seq)))
- (while (and (< n len)
- (funcall pred (seq-elt seq n)))
- (setq n (+ 1 n)))
- n))
-
-(defun seq--activate-font-lock-keywords ()
- "Activate font-lock keywords for some symbols defined in seq."
- (font-lock-add-keywords 'emacs-lisp-mode
- '("\\<seq-doseq\\>")))
-
-(defalias 'seq-copy #'copy-sequence)
-(defalias 'seq-elt #'elt)
-(defalias 'seq-length #'length)
-(defalias 'seq-do #'mapc)
-(defalias 'seq-each #'seq-do)
-(defalias 'seq-map #'mapcar)
-
-(add-to-list 'emacs-lisp-mode-hook #'seq--activate-font-lock-keywords)
-
-;;;; ChangeLog:
-
-;; 2015-04-15 Nicolas Petton <nicolasNOSPAMpetton.fr>
-;;
-;; seq.el update
-;;
-;; * packages/seq/seq.el: Update seq.el to version 1.4
-;; * packages/seq/tests/seq-tests.el: Update seq.el to version 1.4
-;;
-;; 2015-03-25 Nicolas Petton <nicolasNOSPAMpetton.fr>
-;;
-;; Rephrases a comment in seq.el about the order of the arguments
-;;
-;; * packages/seq/seq.el: Better comment about the order of the arguments
-;;
-;; 2015-03-09 Nicolas Petton <nicolasNOSPAMpetton.fr>
-;;
-;; Update seq.el to version 1.3
-;;
-;; * packages/seq/seq.el: update to version 1.3
-;; * packages/seq/tests/seq-tests.el: update to version 1.3
-;;
-;; 2015-02-11 Nicolas Petton <nicolasNOSPAMpetton.fr>
-;;
-;; Update seq.el to version 1.2
-;;
-;; * package/seq/seq.el: Update to version 1.2
-;; * packages/seq/tests/seq-tests.el: Update to version 1.2
-;;
-;; 2015-02-09 Nicolas Petton <nicolasNOSPAMpetton.fr>
-;;
-;; Update seq.el to version 1.1.1
-;;
-;; * package/seq/seq.el: Update to version 1.1.1
-;; * packages/seq/tests/seq-tests.el: Update to version 1.1.1
-;;
-;; 2015-02-06 Nicolas Petton <nicolasNOSPAMpetton.fr>
-;;
-;; Update seq.el to version 1.1
-;;
-;; * packages/seq/seq.el: Update to version 1.1
-;; * packages/seq/tests/seq-tests.el: Update to version 1.1
-;;
-;; 2015-01-14 Nicolas Petton <nicolasNOSPAMpetton.fr>
-;;
-;; packages/seq: New package
-;;
-
-
-(provide 'seq)
-;;; seq.el ends here
--- /dev/null
+;;; seq-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+(add-to-list 'load-path (or (file-name-directory #$) (car load-path)))
+\f
+;;;### (autoloads nil nil ("seq.el") (21908 14191 997184 0))
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+;;; seq-autoloads.el ends here
--- /dev/null
+(define-package "seq" "20150515.214" "Sequence manipulation functions" 'nil :keywords '("sequences"))
--- /dev/null
+;;; seq.el --- Sequence manipulation functions -*- lexical-binding: t -*-
+
+;; Copyright (C) 2014-2015 Free Software Foundation, Inc.
+
+;; Author: Nicolas Petton <nicolasNOSPAMpetton.fr>
+;; Keywords: sequences
+;; Package-Version: 20150515.214
+;; Version: 1.7
+;; Package: seq
+
+;; Maintainer: emacs-develNOSPAMgnu.org
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Sequence-manipulation functions that complement basic functions
+;; provided by subr.el.
+;;
+;; All functions are prefixed with "seq-".
+;;
+;; All provided functions work on lists, strings and vectors.
+;;
+;; Functions taking a predicate or iterating over a sequence using a
+;; function as argument take the function as their first argument and
+;; the sequence as their second argument. All other functions take
+;; the sequence as their first argument.
+;;
+;; All functions are tested in test/automated/seq-tests.el
+
+;;; Code:
+
+(defmacro seq-doseq (spec &rest body)
+ "Loop over a sequence.
+Similar to `dolist' but can be applied to lists, strings, and vectors.
+
+Evaluate BODY with VAR bound to each element of SEQ, in turn.
+
+\(fn (VAR SEQ) BODY...)"
+ (declare (indent 1) (debug ((symbolp form &optional form) body)))
+ (let ((length (make-symbol "length"))
+ (seq (make-symbol "seq"))
+ (index (make-symbol "index")))
+ `(let* ((,seq ,(cadr spec))
+ (,length (if (listp ,seq) nil (seq-length ,seq)))
+ (,index (if ,length 0 ,seq)))
+ (while (if ,length
+ (< ,index ,length)
+ (consp ,index))
+ (let ((,(car spec) (if ,length
+ (prog1 (seq-elt ,seq ,index)
+ (setq ,index (+ ,index 1)))
+ (pop ,index))))
+ ,@body)))))
+
+(if (fboundp 'pcase-defmacro)
+ ;; Implementation of `seq-let' based on a `pcase'
+ ;; pattern. Requires Emacs>=25.1.
+ (progn
+ (pcase-defmacro seq (&rest args)
+ "pcase pattern matching sequence elements.
+Matches if the object is a sequence (list, string or vector), and
+binds each element of ARGS to the corresponding element of the
+sequence."
+ `(and (pred seq-p)
+ ,@(seq--make-pcase-bindings args)))
+
+ (defmacro seq-let (args seq &rest body)
+ "Bind the variables in ARGS to the elements of SEQ then evaluate BODY.
+
+ARGS can also include the `&rest' marker followed by a variable
+name to be bound to the rest of SEQ."
+ (declare (indent 2) (debug t))
+ `(pcase-let ((,(seq--make-pcase-patterns args) ,seq))
+ ,@body)))
+
+ ;; Implementation of `seq-let' compatible with Emacs<25.1.
+ (defmacro seq-let (args seq &rest body)
+ "Bind the variables in ARGS to the elements of SEQ then evaluate BODY.
+
+ARGS can also include the `&rest' marker followed by a variable
+name to be bound to the rest of SEQ."
+ (declare (indent 2) (debug t))
+ (let ((seq-var (make-symbol "seq")))
+ `(let* ((,seq-var ,seq)
+ ,@(seq--make-bindings args seq-var))
+ ,@body))))
+
+(defun seq-drop (seq n)
+ "Return a subsequence of SEQ without its first N elements.
+The result is a sequence of the same type as SEQ.
+
+If N is a negative integer or zero, SEQ is returned."
+ (if (<= n 0)
+ seq
+ (if (listp seq)
+ (seq--drop-list seq n)
+ (let ((length (seq-length seq)))
+ (seq-subseq seq (min n length) length)))))
+
+(defun seq-take (seq n)
+ "Return a subsequence of SEQ with its first N elements.
+The result is a sequence of the same type as SEQ.
+
+If N is a negative integer or zero, an empty sequence is
+returned."
+ (if (listp seq)
+ (seq--take-list seq n)
+ (seq-subseq seq 0 (min (max n 0) (seq-length seq)))))
+
+(defun seq-drop-while (pred seq)
+ "Return a sequence from the first element for which (PRED element) is nil in SEQ.
+The result is a sequence of the same type as SEQ."
+ (if (listp seq)
+ (seq--drop-while-list pred seq)
+ (seq-drop seq (seq--count-successive pred seq))))
+
+(defun seq-take-while (pred seq)
+ "Return the successive elements for which (PRED element) is non-nil in SEQ.
+The result is a sequence of the same type as SEQ."
+ (if (listp seq)
+ (seq--take-while-list pred seq)
+ (seq-take seq (seq--count-successive pred seq))))
+
+(defun seq-filter (pred seq)
+ "Return a list of all the elements for which (PRED element) is non-nil in SEQ."
+ (let ((exclude (make-symbol "exclude")))
+ (delq exclude (seq-map (lambda (elt)
+ (if (funcall pred elt)
+ elt
+ exclude))
+ seq))))
+
+(defun seq-remove (pred seq)
+ "Return a list of all the elements for which (PRED element) is nil in SEQ."
+ (seq-filter (lambda (elt) (not (funcall pred elt)))
+ seq))
+
+(defun seq-reduce (function seq initial-value)
+ "Reduce the function FUNCTION across SEQ, starting with INITIAL-VALUE.
+
+Return the result of calling FUNCTION with INITIAL-VALUE and the
+first element of SEQ, then calling FUNCTION with that result and
+the second element of SEQ, then with that result and the third
+element of SEQ, etc.
+
+If SEQ is empty, return INITIAL-VALUE and FUNCTION is not called."
+ (if (seq-empty-p seq)
+ initial-value
+ (let ((acc initial-value))
+ (seq-doseq (elt seq)
+ (setq acc (funcall function acc elt)))
+ acc)))
+
+(defun seq-some-p (pred seq)
+ "Return any element for which (PRED element) is non-nil in SEQ, nil otherwise."
+ (catch 'seq--break
+ (seq-doseq (elt seq)
+ (when (funcall pred elt)
+ (throw 'seq--break elt)))
+ nil))
+
+(defun seq-every-p (pred seq)
+ "Return non-nil if (PRED element) is non-nil for all elements of the sequence SEQ."
+ (catch 'seq--break
+ (seq-doseq (elt seq)
+ (or (funcall pred elt)
+ (throw 'seq--break nil)))
+ t))
+
+(defun seq-count (pred seq)
+ "Return the number of elements for which (PRED element) is non-nil in SEQ."
+ (let ((count 0))
+ (seq-doseq (elt seq)
+ (when (funcall pred elt)
+ (setq count (+ 1 count))))
+ count))
+
+(defun seq-empty-p (seq)
+ "Return non-nil if the sequence SEQ is empty, nil otherwise."
+ (if (listp seq)
+ (null seq)
+ (= 0 (seq-length seq))))
+
+(defun seq-sort (pred seq)
+ "Return a sorted sequence comparing using PRED the elements of SEQ.
+The result is a sequence of the same type as SEQ."
+ (if (listp seq)
+ (sort (seq-copy seq) pred)
+ (let ((result (seq-sort pred (append seq nil))))
+ (seq-into result (type-of seq)))))
+
+(defun seq-contains-p (seq elt &optional testfn)
+ "Return the first element in SEQ that equals to ELT.
+Equality is defined by TESTFN if non-nil or by `equal' if nil."
+ (seq-some-p (lambda (e)
+ (funcall (or testfn #'equal) elt e))
+ seq))
+
+(defun seq-uniq (seq &optional testfn)
+ "Return a list of the elements of SEQ with duplicates removed.
+TESTFN is used to compare elements, or `equal' if TESTFN is nil."
+ (let ((result '()))
+ (seq-doseq (elt seq)
+ (unless (seq-contains-p result elt testfn)
+ (setq result (cons elt result))))
+ (nreverse result)))
+
+(defun seq-subseq (seq start &optional end)
+ "Return the subsequence of SEQ from START to END.
+If END is omitted, it defaults to the length of the sequence.
+If START or END is negative, it counts from the end."
+ (cond ((or (stringp seq) (vectorp seq)) (substring seq start end))
+ ((listp seq)
+ (let (len (errtext (format "Bad bounding indices: %s, %s" start end)))
+ (and end (< end 0) (setq end (+ end (setq len (seq-length seq)))))
+ (if (< start 0) (setq start (+ start (or len (setq len (seq-length seq))))))
+ (when (> start 0)
+ (setq seq (nthcdr (1- start) seq))
+ (or seq (error "%s" errtext))
+ (setq seq (cdr seq)))
+ (if end
+ (let ((res nil))
+ (while (and (>= (setq end (1- end)) start) seq)
+ (push (pop seq) res))
+ (or (= (1+ end) start) (error "%s" errtext))
+ (nreverse res))
+ (seq-copy seq))))
+ (t (error "Unsupported sequence: %s" seq))))
+
+(defun seq-concatenate (type &rest seqs)
+ "Concatenate, into a sequence of type TYPE, the sequences SEQS.
+TYPE must be one of following symbols: vector, string or list.
+
+\n(fn TYPE SEQUENCE...)"
+ (pcase type
+ (`vector (apply #'vconcat seqs))
+ (`string (apply #'concat seqs))
+ (`list (apply #'append (append seqs '(nil))))
+ (t (error "Not a sequence type name: %S" type))))
+
+(defun seq-mapcat (function seq &optional type)
+ "Concatenate the result of applying FUNCTION to each element of SEQ.
+The result is a sequence of type TYPE, or a list if TYPE is nil."
+ (apply #'seq-concatenate (or type 'list)
+ (seq-map function seq)))
+
+(defun seq-partition (seq n)
+ "Return a list of the elements of SEQ grouped into sub-sequences of length N.
+The last sequence may contain less than N elements. If N is a
+negative integer or 0, nil is returned."
+ (unless (< n 1)
+ (let ((result '()))
+ (while (not (seq-empty-p seq))
+ (push (seq-take seq n) result)
+ (setq seq (seq-drop seq n)))
+ (nreverse result))))
+
+(defun seq-intersection (seq1 seq2 &optional testfn)
+ "Return a list of the elements that appear in both SEQ1 and SEQ2.
+Equality is defined by TESTFN if non-nil or by `equal' if nil."
+ (seq-reduce (lambda (acc elt)
+ (if (seq-contains-p seq2 elt testfn)
+ (cons elt acc)
+ acc))
+ (seq-reverse seq1)
+ '()))
+
+(defun seq-difference (seq1 seq2 &optional testfn)
+ "Return a list of th elements that appear in SEQ1 but not in SEQ2.
+Equality is defined by TESTFN if non-nil or by `equal' if nil."
+ (seq-reduce (lambda (acc elt)
+ (if (not (seq-contains-p seq2 elt testfn))
+ (cons elt acc)
+ acc))
+ (seq-reverse seq1)
+ '()))
+
+(defun seq-group-by (function seq)
+ "Apply FUNCTION to each element of SEQ.
+Separate the elements of SEQ into an alist using the results as
+keys. Keys are compared using `equal'."
+ (seq-reduce
+ (lambda (acc elt)
+ (let* ((key (funcall function elt))
+ (cell (assoc key acc)))
+ (if cell
+ (setcdr cell (push elt (cdr cell)))
+ (push (list key elt) acc))
+ acc))
+ (seq-reverse seq)
+ nil))
+
+(defalias 'seq-reverse
+ (if (ignore-errors (reverse [1 2]))
+ #'reverse
+ (lambda (seq)
+ "Return the reversed copy of list, vector, or string SEQ.
+See also the function `nreverse', which is used more often."
+ (let ((result '()))
+ (seq-map (lambda (elt) (push elt result))
+ seq)
+ (if (listp seq)
+ result
+ (seq-into result (type-of seq)))))))
+
+(defun seq-into (seq type)
+ "Convert the sequence SEQ into a sequence of type TYPE.
+TYPE can be one of the following symbols: vector, string or list."
+ (pcase type
+ (`vector (vconcat seq))
+ (`string (concat seq))
+ (`list (append seq nil))
+ (t (error "Not a sequence type name: %S" type))))
+
+(defun seq--drop-list (list n)
+ "Return a list from LIST without its first N elements.
+This is an optimization for lists in `seq-drop'."
+ (while (and list (> n 0))
+ (setq list (cdr list)
+ n (1- n)))
+ list)
+
+(defun seq--take-list (list n)
+ "Return a list from LIST made of its first N elements.
+This is an optimization for lists in `seq-take'."
+ (let ((result '()))
+ (while (and list (> n 0))
+ (setq n (1- n))
+ (push (pop list) result))
+ (nreverse result)))
+
+(defun seq--drop-while-list (pred list)
+ "Return a list from the first element for which (PRED element) is nil in LIST.
+This is an optimization for lists in `seq-drop-while'."
+ (while (and list (funcall pred (car list)))
+ (setq list (cdr list)))
+ list)
+
+(defun seq--take-while-list (pred list)
+ "Return the successive elements for which (PRED element) is non-nil in LIST.
+This is an optimization for lists in `seq-take-while'."
+ (let ((result '()))
+ (while (and list (funcall pred (car list)))
+ (push (pop list) result))
+ (nreverse result)))
+
+(defun seq--count-successive (pred seq)
+ "Return the number of successive elements for which (PRED element) is non-nil in SEQ."
+ (let ((n 0)
+ (len (seq-length seq)))
+ (while (and (< n len)
+ (funcall pred (seq-elt seq n)))
+ (setq n (+ 1 n)))
+ n))
+
+(defun seq--make-pcase-bindings (args)
+ "Return a list of bindings of the variables in ARGS to the elements of a sequence."
+ (let ((bindings '())
+ (index 0)
+ (rest-marker nil))
+ (seq-doseq (name args)
+ (unless rest-marker
+ (pcase name
+ (`&rest
+ (progn (push `(app (pcase--flip seq-drop ,index)
+ ,(seq--elt-safe args (1+ index)))
+ bindings)
+ (setq rest-marker t)))
+ (t
+ (push `(app (pcase--flip seq--elt-safe ,index) ,name) bindings))))
+ (setq index (1+ index)))
+ bindings))
+
+(defun seq--make-pcase-patterns (args)
+ "Return a list of `(seq ...)' pcase patterns from the argument list ARGS."
+ (cons 'seq
+ (seq-map (lambda (elt)
+ (if (seq-p elt)
+ (seq--make-pcase-patterns elt)
+ elt))
+ args)))
+
+;; Helper function for the Backward-compatible version of `seq-let'
+;; for Emacs<25.1.
+(defun seq--make-bindings (args seq &optional bindings)
+ "Return a list of bindings of the variables in ARGS to the elements of a sequence.
+if BINDINGS is non-nil, append new bindings to it, and return
+BINDINGS."
+ (let ((index 0)
+ (rest-marker nil))
+ (seq-doseq (name args)
+ (unless rest-marker
+ (pcase name
+ ((pred seq-p)
+ (setq bindings (seq--make-bindings (seq--elt-safe args index)
+ `(seq--elt-safe ,seq ,index)
+ bindings)))
+ (`&rest
+ (progn (push `(,(seq--elt-safe args (1+ index))
+ (seq-drop ,seq ,index))
+ bindings)
+ (setq rest-marker t)))
+ (t
+ (push `(,name (seq--elt-safe ,seq ,index)) bindings))))
+ (setq index (1+ index)))
+ bindings))
+
+(defun seq--elt-safe (seq n)
+ "Return element of SEQ at the index N.
+If no element is found, return nil."
+ (when (or (listp seq)
+ (and (sequencep seq)
+ (> (seq-length seq) n)))
+ (seq-elt seq n)))
+
+(defun seq--activate-font-lock-keywords ()
+ "Activate font-lock keywords for some symbols defined in seq."
+ (font-lock-add-keywords 'emacs-lisp-mode
+ '("\\<seq-doseq\\>" "\\<seq-let\\>")))
+
+(defalias 'seq-copy #'copy-sequence)
+(defalias 'seq-elt #'elt)
+(defalias 'seq-length #'length)
+(defalias 'seq-do #'mapc)
+(defalias 'seq-each #'seq-do)
+(defalias 'seq-map #'mapcar)
+(defalias 'seq-p #'sequencep)
+
+(unless (fboundp 'elisp--font-lock-flush-elisp-buffers)
+ ;; In Emacs≥25, (via elisp--font-lock-flush-elisp-buffers and a few others)
+ ;; we automatically highlight macros.
+ (add-to-list 'emacs-lisp-mode-hook #'seq--activate-font-lock-keywords))
+
+(provide 'seq)
+;;; seq.el ends here
--- /dev/null
+Installation:
+ Put the smartrep.el to your load-path.
+ And add to .emacs: (require 'smartrep)
--- /dev/null
+Good signature from 474F05837FBDEF9B GNU ELPA Signing Agent <elpasignNOSPAMelpa.gnu.org> (trust undefined) created at 2015-05-03T11:05:02+0200 using DSA
\ No newline at end of file
--- /dev/null
+;;; spinner-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+(add-to-list 'load-path (or (file-name-directory #$) (car load-path)))
+\f
+;;;### (autoloads nil "spinner" "spinner.el" (21908 14206 0 0))
+;;; Generated autoloads from spinner.el
+
+(autoload 'spinner-create "spinner" "\
+Create a spinner of the given TYPE.
+The possible TYPEs are described in `spinner--type-to-frames'.
+
+FPS, if given, is the number of desired frames per second.
+Default is `spinner-frames-per-second'.
+
+If BUFFER-LOCAL is non-nil, the spinner will be automatically
+deactivated if the buffer is killed. If BUFFER-LOCAL is a
+buffer, use that instead of current buffer.
+
+When started, in order to function properly, the spinner runs a
+timer which periodically calls `force-mode-line-update' in the
+curent buffer. If BUFFER-LOCAL was set at creation time, then
+`force-mode-line-update' is called in that buffer instead. When
+the spinner is stopped, the timer is deactivated.
+
+\(fn &optional TYPE BUFFER-LOCAL FPS)" nil nil)
+
+(autoload 'spinner-start "spinner" "\
+Start a mode-line spinner of given TYPE-OR-OBJECT.
+If TYPE-OR-OBJECT is an object created with `make-spinner',
+simply activate it. This method is designed for minor modes, so
+they can use the spinner as part of their lighter by doing:
+ '(:eval (spinner-print THE-SPINNER))
+To stop this spinner, call `spinner-stop' on it.
+
+If TYPE-OR-OBJECT is anything else, a buffer-local spinner is
+created with this type, and it is displayed in the
+`mode-line-process' of the buffer it was created it. Both
+TYPE-OR-OBJECT and FPS are passed to `make-spinner' (which see).
+To stop this spinner, call `spinner-stop' in the same buffer.
+
+Either way, the return value is a function which can be called
+anywhere to stop this spinner. You can also call `spinner-stop'
+in the same buffer where the spinner was created.
+
+FPS, if given, is the number of desired frames per second.
+Default is `spinner-frames-per-second'.
+
+\(fn &optional TYPE-OR-OBJECT FPS)" nil nil)
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+;;; spinner-autoloads.el ends here
--- /dev/null
+(define-package "spinner" "1.3.1" "Add spinners and progress-bars to the mode-line for ongoing operations" 'nil :url "https://github.com/Malabarba/spinner.el" :keywords '("processes" "mode-line"))
--- /dev/null
+;;; spinner.el --- Add spinners and progress-bars to the mode-line for ongoing operations -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2015 Free Software Foundation, Inc.
+
+;; Author: Artur Malabarba <emacsNOSPAMendlessparentheses.com>
+;; Version: 1.3.1
+;; URL: https://github.com/Malabarba/spinner.el
+;; Keywords: processes mode-line
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+;; 1 Usage
+;; ═══════
+;;
+;; First of all, don’t forget to add `(spinner "VERSION")' to your
+;; package’s dependencies.
+;;
+;;
+;; 1.1 Major-modes
+;; ───────────────
+;;
+;; 1. Just call `(spinner-start)' and a spinner will be added to the
+;; mode-line.
+;; 2. Call `(spinner-stop)' on the same buffer when you want to remove
+;; it.
+;;
+;; The default spinner is a line drawing that rotates. You can pass an
+;; argument to `spinner-start' to specify which spinner you want. All
+;; possibilities are listed in the `spinner-types' variable, but here are
+;; a few examples for you to try:
+;;
+;; • `(spinner-start 'vertical-breathing 10)'
+;; • `(spinner-start 'minibox)'
+;; • `(spinner-start 'moon)'
+;; • `(spinner-start 'triangle)'
+;;
+;; You can also define your own as a vector of strings (see the examples
+;; in `spinner-types').
+;;
+;;
+;; 1.2 Minor-modes
+;; ───────────────
+;;
+;; Minor-modes can create a spinner with `spinner-create' and then add it
+;; to their mode-line lighter. They can then start the spinner by setting
+;; a variable and calling `spinner-start-timer'. Finally, they can stop
+;; the spinner (and the timer) by just setting the same variable to nil.
+;;
+;; Here’s an example for a minor-mode named `foo'. Assuming that
+;; `foo--lighter' is used as the mode-line lighter, the following code
+;; will add an *inactive* global spinner to the mode-line.
+;; ┌────
+;; │ (defvar foo--spinner (spinner-create 'rotating-line))
+;; │ (defconst foo--lighter
+;; │ '(" foo" (:eval (spinner-print foo--spinner))))
+;; └────
+;;
+;; 1. To activate the spinner, just call `(spinner-start foo--spinner)'.
+;; It will show up on the mode-line and start animating.
+;; 2. To get rid of it, call `(spinner-stop foo--spinner)'. It will then
+;; disappear again.
+;;
+;; Some minor-modes will need spinners to be buffer-local. To achieve
+;; that, just make the `foo--spinner' variable buffer-local and use the
+;; third argument of the `spinner-create' function. The snippet below is an
+;; example.
+;;
+;; ┌────
+;; │ (defvar-local foo--spinner nil)
+;; │ (defconst foo--lighter
+;; │ '(" foo" (:eval (spinner-print foo--spinner))))
+;; │ (defun foo--start-spinner ()
+;; │ "Create and start a spinner on this buffer."
+;; │ (unless foo--spinner
+;; │ (setq foo--spinner (spinner-create 'moon t)))
+;; │ (spinner-start foo--spinner))
+;; └────
+;;
+;; 1. To activate the spinner, just call `(foo--start-spinner)'.
+;; 2. To get rid of it, call `(spinner-stop foo--spinner)'.
+;;
+;; This will use the `moon' spinner, but you can use any of the names
+;; defined in the `spinner-types' variable or even define your own.
+
+\f
+;;; Code:
+(eval-when-compile
+ (require 'cl))
+
+(defconst spinner-types
+ '((3-line-clock . ["┤" "┘" "┴" "└" "├" "┌" "┬" "┐"])
+ (2-line-clock . ["┘" "└" "┌" "┐"])
+ (flipping-line . ["_" "\\" "|" "/"])
+ (rotating-line . ["-" "\\" "|" "/"])
+ (progress-bar . ["[ ]" "[= ]" "[== ]" "[=== ]" "[====]" "[ ===]" "[ ==]" "[ =]"])
+ (progress-bar-filled . ["| |" "|█ |" "|██ |" "|███ |" "|████|" "| ███|" "| ██|" "| █|"])
+ (vertical-breathing . ["▁" "▂" "▃" "▄" "▅" "▆" "▇" "█" "▇" "▆" "▅" "▄" "▃" "▂" "▁" " "])
+ (vertical-rising . ["▁" "▄" "█" "▀" "▔"])
+ (horizontal-breathing . [" " "▏" "▎" "▍" "▌" "▋" "▊" "▉" "▉" "▊" "▋" "▌" "▍" "▎" "▏"])
+ (horizontal-breathing-long
+ . [" " "▎ " "▌ " "▊ " "█ " "█▎" "█▌" "█▊" "██" "█▊" "█▌" "█▎" "█ " "▊ " "▋ " "▌ " "▍ " "▎ " "▏ "])
+ (horizontal-moving . [" " "▌ " "█ " "▐▌" " █" " ▐"])
+ (minibox . ["▖" "▘" "▝" "▗"])
+ (triangle . ["◢" "◣" "◤" "◥"])
+ (box-in-box . ["◰" "◳" "◲" "◱"])
+ (box-in-circle . ["◴" "◷" "◶" "◵"])
+ (half-circle . ["◐" "◓" "◑" "◒"])
+ (moon . ["🌑" "🌘" "🌖" "🌕" "🌔" "🌒"]))
+ "Predefined alist of spinners.
+Each car is a symbol identifying the spinner, and each cdr is a
+vector, the spinner itself.")
+
+(defvar spinner-current nil
+ "Spinner curently being displayed on the `mode-line-process'.")
+(make-variable-buffer-local 'spinner-current)
+
+(defconst spinner--mode-line-construct
+ '(:eval (spinner-print spinner-current))
+ "Construct used to display a spinner in `mode-line-process'.")
+(put 'spinner--mode-line-construct 'risky-local-variable t)
+
+(defvar spinner-frames-per-second 10
+ "Default speed at which spinners spin, in frames per second.
+Each spinner can override this value.")
+
+\f
+;;; The spinner object.
+(defun spinner--type-to-frames (type)
+ "Return a vector of frames corresponding to TYPE.
+The list of possible built-in spinner types is given by the
+`spinner-types' variable, but you can also use your own (see
+below).
+
+If TYPE is nil, the frames of this spinner are given by the first
+element of `spinner-types'.
+If TYPE is a symbol, it specifies an element of `spinner-types'.
+If TYPE is 'random, use a random element of `spinner-types'.
+If TYPE is a list, it should be a list of symbols, and a random
+one is chosen as the spinner type.
+If TYPE is a vector, it should be a vector of strings and these
+are used as the spinner's frames. This allows you to make your
+own spinner animations."
+ (cond
+ ((vectorp type) type)
+ ((not type) (cdr (car spinner-types)))
+ ((eq type 'random)
+ (cdr (elt spinner-types
+ (random (length spinner-types)))))
+ ((listp type)
+ (cdr (assq (elt type (random (length type)))
+ spinner-types)))
+ ((symbolp type) (cdr (assq type spinner-types)))
+ (t (error "Unknown spinner type: %s" type))))
+
+(defstruct (spinner
+ (:copier nil)
+ (:conc-name spinner--)
+ (:constructor make-spinner (&optional type buffer-local fps)))
+ (frames (spinner--type-to-frames type))
+ (counter 0)
+ (fps spinner-frames-per-second)
+ (timer (timer-create) :read-only)
+ (active-p nil)
+ (buffer (when buffer-local
+ (if (bufferp buffer-local)
+ buffer-local
+ (current-buffer)))))
+
+;;;###autoload
+(defun spinner-create (&optional type buffer-local fps)
+ "Create a spinner of the given TYPE.
+The possible TYPEs are described in `spinner--type-to-frames'.
+
+FPS, if given, is the number of desired frames per second.
+Default is `spinner-frames-per-second'.
+
+If BUFFER-LOCAL is non-nil, the spinner will be automatically
+deactivated if the buffer is killed. If BUFFER-LOCAL is a
+buffer, use that instead of current buffer.
+
+When started, in order to function properly, the spinner runs a
+timer which periodically calls `force-mode-line-update' in the
+curent buffer. If BUFFER-LOCAL was set at creation time, then
+`force-mode-line-update' is called in that buffer instead. When
+the spinner is stopped, the timer is deactivated."
+ (make-spinner type buffer-local fps))
+
+(defun spinner-print (spinner)
+ "Return a string of the current frame of SPINNER.
+If SPINNER is nil, just return nil.
+Designed to be used in the mode-line with:
+ (:eval (spinner-print some-spinner))"
+ (when (and spinner (spinner--active-p spinner))
+ (elt (spinner--frames spinner)
+ (spinner--counter spinner))))
+
+(defun spinner--timer-function (spinner)
+ "Function called to update SPINNER.
+If SPINNER is no longer active, or if its buffer has been killed,
+stop the SPINNER's timer."
+ (let ((buffer (spinner--buffer spinner)))
+ (if (or (not (spinner--active-p spinner))
+ (and buffer (not (buffer-live-p buffer))))
+ (spinner-stop spinner)
+ ;; Increment
+ (callf (lambda (x) (% (1+ x) (length (spinner--frames spinner))))
+ (spinner--counter spinner))
+ ;; Update mode-line.
+ (if (buffer-live-p buffer)
+ (with-current-buffer buffer
+ (force-mode-line-update))
+ (force-mode-line-update)))))
+
+(defun spinner--start-timer (spinner)
+ "Start a SPINNER's timer at FPS frames per second."
+ (let ((old-timer (spinner--timer spinner)))
+ (when (timerp old-timer)
+ (cancel-timer old-timer))
+
+ (setf (spinner--active-p spinner) t)
+ ;; Create timer.
+ (let* ((repeat (/ 1.0 (or (spinner--fps spinner)
+ spinner-frames-per-second)))
+ (time (timer-next-integral-multiple-of-time (current-time) repeat))
+ ;; Create the timer as a lex variable so it can cancel itself.
+ (timer (spinner--timer spinner)))
+ (timer-set-time timer time repeat)
+ (timer-set-function timer #'spinner--timer-function (list spinner))
+ (timer-activate timer)
+ ;; Return a stopping function.
+ (lambda () (spinner-stop spinner)))))
+
+\f
+;;; The main functions
+;;;###autoload
+(defun spinner-start (&optional type-or-object fps)
+ "Start a mode-line spinner of given TYPE-OR-OBJECT.
+If TYPE-OR-OBJECT is an object created with `make-spinner',
+simply activate it. This method is designed for minor modes, so
+they can use the spinner as part of their lighter by doing:
+ '(:eval (spinner-print THE-SPINNER))
+To stop this spinner, call `spinner-stop' on it.
+
+If TYPE-OR-OBJECT is anything else, a buffer-local spinner is
+created with this type, and it is displayed in the
+`mode-line-process' of the buffer it was created it. Both
+TYPE-OR-OBJECT and FPS are passed to `make-spinner' (which see).
+To stop this spinner, call `spinner-stop' in the same buffer.
+
+Either way, the return value is a function which can be called
+anywhere to stop this spinner. You can also call `spinner-stop'
+in the same buffer where the spinner was created.
+
+FPS, if given, is the number of desired frames per second.
+Default is `spinner-frames-per-second'."
+ (unless (spinner-p type-or-object)
+ ;; Choose type.
+ (if (spinner-p spinner-current)
+ (setf (spinner--frames spinner-current)
+ (spinner--type-to-frames type-or-object))
+ (setq spinner-current (make-spinner type-or-object (current-buffer) fps)))
+ (setq type-or-object spinner-current)
+ ;; Maybe add to mode-line.
+ (unless (memq 'spinner--mode-line-construct mode-line-process)
+ (setq mode-line-process
+ (list (or mode-line-process "")
+ 'spinner--mode-line-construct))))
+
+ ;; Create timer.
+ (when fps (setf (spinner--fps type-or-object) fps))
+ (spinner--start-timer type-or-object))
+
+(defun spinner-start-print (spinner)
+ "Like `spinner-print', but also start SPINNER if it's not active."
+ (unless (spinner--active-p spinner)
+ (spinner-start spinner))
+ (spinner-print spinner))
+
+(defun spinner-stop (&optional spinner)
+ "Stop the current buffer's spinner."
+ (let* ((spinner (or spinner spinner-current))
+ (timer (spinner--timer spinner)))
+ (when (timerp timer)
+ (cancel-timer timer))
+ (setf (spinner--active-p spinner) nil)))
+
+;;;; ChangeLog:
+
+;; 2015-05-02 Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+;;
+;; * spinner: Rename constructor.
+;;
+;; 2015-04-30 Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+;;
+;; * spinner/spinner.el: Rewrite spinners as structures
+;;
+;; 2015-04-09 Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+;;
+;; spinner: Fix readme
+;;
+;; 2015-04-09 Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+;;
+;; spinner: Fix leftover mode-line-format code
+;;
+;; 2015-04-09 Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+;;
+;; Merge commit 'c44ef65515f50bd38304a6f50adebc984fb8e431'
+;;
+;; 2015-03-07 Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+;;
+;; Merge commit '7eca7d023c95bc21c7838467b3a58d549afaf68d'
+;;
+;; 2015-03-07 Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+;;
+;; Merge commit 'a7b4e52766977b58c6b9899305e962a2b5235bda'
+;;
+;; 2015-03-07 Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+;;
+;; Add 'packages/spinner/' from commit
+;; '9477ee899d62259d4b946f243cdcdd9cdeb1e910'
+;;
+;; git-subtree-dir: packages/spinner git-subtree-mainline:
+;; 5736e852fd48a0f1ba1c328dd4d03e3fa008a406 git-subtree-split:
+;; 9477ee899d62259d4b946f243cdcdd9cdeb1e910
+;;
+
+
+(provide 'spinner)
+
+;;; spinner.el ends here
--- /dev/null
+;;; sx-auth.el --- user authentication -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Sean Allred
+
+;; Author: Sean Allred <codeNOSPAMseanallred.com>
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file handles logic related to authentication. This includes
+;; determining if a certain filter requires authentication (via the
+;; variable `sx-auth-filter-auth' and function `sx-auth--filter-p'),
+;; determining if a method requires authentication (via the variable
+;; `sx-auth-method-auth' and function `sx-auth--method-p'), and
+;; actually authenticating the user (with `sx-auth-authenticate').
+
+;;; Code:
+
+(require 'sx)
+(require 'sx-request)
+(require 'sx-cache)
+
+(defconst sx-auth-root
+ "https://stackexchange.com/oauth/dialog")
+(defconst sx-auth-redirect-uri
+ "http://vermiculus.github.io/sx.el/auth/auth.htm")
+(defconst sx-auth-client-id
+ "3291")
+(defvar sx-auth-access-token
+ nil
+ "Your access token.
+This is needed to use your account to write questions, make
+comments, and read your inbox. Do not alter this unless you know
+what you are doing!
+
+This variable is set with `sx-auth-authenticate'.")
+
+(defconst sx-auth-method-auth
+ '((me . t)
+ (inbox . t)
+ (notifications . t)
+ (events . t)
+ (posts (comments add))
+ (comments delete
+ edit
+ flags
+ upvote)
+ (answers accept
+ delete
+ downvote
+ edit
+ flags
+ upvote)
+ (questions answers
+ add
+ close
+ delete
+ downvote
+ edit
+ favorite
+ flags
+ render
+ upvote
+ (unanswered my-tags)))
+ "List of methods that require auth.
+Methods are of the form \(METHOD . SUBMETHODS) where SUBMETHODS
+ is \(METHOD METHOD METHOD ...).
+
+If all SUBMETHODS require auth or there are no submethods, form
+will be \(METHOD . t)")
+
+(defconst sx-auth-filter-auth
+ '(question.upvoted
+ question.downvoted
+ answer.upvoted
+ answer.downvoted
+ comment.upvoted)
+ "List of filter types that require auth.
+Keywords are of the form \(OBJECT TYPES) where TYPES is \(FILTER
+FILTER FILTER).")
+
+;;;###autoload
+(defun sx-authenticate ()
+ "Authenticate this application.
+Authentication is required to read your personal data (such as
+notifications) and to write with the API (asking and answering
+questions).
+
+When this function is called, `browse-url' is used to send the
+user to an authorization page managed by StackExchange. The
+following privileges are requested:
+
+* read_inbox
+ use SX to manage and visit items in your inbox
+
+* write_acesss
+ write comments, ask questions, and post answers on your
+ behalf
+
+* no_expiry
+ do not pester you to reauthorize again
+
+After authorization with StackExchange, the user is then
+redirected to a website managed by SX. The access token required
+to use authenticated methods is included in the hash (which is
+parsed and displayed prominently on the page)."
+ (interactive)
+ (setq
+ sx-auth-access-token
+ (let ((url (concat
+ sx-auth-root
+ "?"
+ (sx-request--build-keyword-arguments
+ `((client_id . ,sx-auth-client-id)
+ (scope . (read_inbox
+ no_expiry
+ private_info
+ write_access))
+ (redirect_uri . ,(url-hexify-string
+ sx-auth-redirect-uri)))
+ ","))))
+ (browse-url url)
+ (read-string "Enter the access token displayed on the webpage: ")))
+ (if (string-equal "" sx-auth-access-token)
+ (progn (setq sx-auth-access-token nil)
+ (error "You must enter this code to use this client fully"))
+ (sx-cache-set 'auth `((access_token . ,sx-auth-access-token)))))
+
+(defun sx-auth--method-p (method &optional submethod)
+ "Check if METHOD is one that may require authentication.
+If it has `auth-required' SUBMETHODs, or no submethod, return t."
+ (let ((method-auth (cdr (assoc method sx-auth-method-auth)))
+ ;; If the submethod has additional options, they may all be
+ ;; eligible, in which case we only need to check the `car'.
+ (sub-head (if (listp submethod)
+ (car submethod))))
+ (lwarn " sx-auth method" :debug "Method %s requires auth" method-auth)
+ (and method-auth
+ (or
+ ;; All submethods require auth.
+ (eq t method-auth)
+ ;; All sub-submethods require auth.
+ (member sub-head method-auth)
+ ;; Specific submethod requires auth.
+ (member submethod method-auth)))))
+
+;; Temporary solution. When we switch to pre-defined filters we will
+;; have to change the logic to match against specific filters.
+(defun sx-auth--filter-p (filter)
+ "Check if FILTER contains properties that require authentication.
+If it has `auth-required' properties, return a filter that has
+removed those properties."
+ (let* ((incl-filter (if (listp filter) (car filter)))
+ (rest-filter (if incl-filter (cdr filter)))
+ (auth-filters (remove nil
+ ;; Only retrieve the elements that
+ ;; are issues.
+ (mapcar (lambda (prop)
+ (car
+ (member prop
+ sx-auth-filter-auth)))
+ (or incl-filter filter))))
+ clean-filter out-filter)
+ (lwarn "sx-auth filter" :debug "Filter: %S" filter)
+ ;; Auth-filters is the filters that are issues
+ (when auth-filters
+ (setq clean-filter
+ (cl-remove-if (lambda (prop)
+ (member prop auth-filters))
+ (or incl-filter filter))))
+ (if (and incl-filter clean-filter)
+ (setq out-filter
+ (cons clean-filter rest-filter))
+ (setq out-filter clean-filter))
+ (lwarn "sx-auth filter2" :debug "Filter property %s requires auth. %S"
+ auth-filters out-filter)
+ out-filter))
+
+(provide 'sx-auth)
+;;; sx-auth.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+;;; sx-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+(add-to-list 'load-path (or (file-name-directory #$) (car load-path)))
+\f
+;;;### (autoloads nil "sx" "sx.el" (21910 48876 0 0))
+;;; Generated autoloads from sx.el
+
+(autoload 'sx-bug-report "sx" "\
+File a bug report about the `sx' package.
+
+\(fn)" t nil)
+
+;;;***
+\f
+;;;### (autoloads nil "sx-auth" "sx-auth.el" (21910 48876 0 0))
+;;; Generated autoloads from sx-auth.el
+
+(autoload 'sx-authenticate "sx-auth" "\
+Authenticate this application.
+Authentication is required to read your personal data (such as
+notifications) and to write with the API (asking and answering
+questions).
+
+When this function is called, `browse-url' is used to send the
+user to an authorization page managed by StackExchange. The
+following privileges are requested:
+
+* read_inbox
+ use SX to manage and visit items in your inbox
+
+* write_acesss
+ write comments, ask questions, and post answers on your
+ behalf
+
+* no_expiry
+ do not pester you to reauthorize again
+
+After authorization with StackExchange, the user is then
+redirected to a website managed by SX. The access token required
+to use authenticated methods is included in the hash (which is
+parsed and displayed prominently on the page).
+
+\(fn)" t nil)
+
+;;;***
+\f
+;;;### (autoloads nil "sx-inbox" "sx-inbox.el" (21910 48877 0 0))
+;;; Generated autoloads from sx-inbox.el
+
+(autoload 'sx-inbox "sx-inbox" "\
+Display a buffer listing inbox items.
+With prefix NOTIFICATIONS, list notifications instead of inbox.
+
+\(fn &optional NOTIFICATIONS)" t nil)
+
+(autoload 'sx-inbox-notifications "sx-inbox" "\
+Display a buffer listing notification items.
+
+\(fn)" t nil)
+
+;;;***
+\f
+;;;### (autoloads nil "sx-interaction" "sx-interaction.el" (21910
+;;;;;; 48876 0 0))
+;;; Generated autoloads from sx-interaction.el
+
+(autoload 'sx-ask "sx-interaction" "\
+Start composing a question for SITE.
+SITE is a string, indicating where the question will be posted.
+
+\(fn SITE)" t nil)
+
+;;;***
+\f
+;;;### (autoloads nil "sx-search" "sx-search.el" (21910 48877 0 0))
+;;; Generated autoloads from sx-search.el
+
+(autoload 'sx-search "sx-search" "\
+Display search on SITE for question titles containing QUERY.
+When TAGS is given, it is a lists of tags, one of which must
+match. When EXCLUDED-TAGS is given, it is a list of tags, none
+of which is allowed to match.
+
+Interactively, the user is asked for SITE and QUERY. With a
+prefix argument, the user is asked for everything.
+
+\(fn SITE QUERY &optional TAGS EXCLUDED-TAGS)" t nil)
+
+(autoload 'sx-search-tag-at-point "sx-search" "\
+Follow tag under position POS or point.
+
+\(fn &optional POS)" t nil)
+
+;;;***
+\f
+;;;### (autoloads nil "sx-switchto" "sx-switchto.el" (21910 48876
+;;;;;; 0 0))
+;;; Generated autoloads from sx-switchto.el
+
+(define-prefix-command 'sx-switchto-map)
+
+;;;***
+\f
+;;;### (autoloads nil "sx-tab" "sx-tab.el" (21910 48876 0 0))
+;;; Generated autoloads from sx-tab.el
+
+(autoload 'sx-tab-all-questions (expand-file-name "sx-tab" (when load-file-name (file-name-directory load-file-name))) nil t)
+
+(autoload 'sx-tab-unanswered (expand-file-name "sx-tab" (when load-file-name (file-name-directory load-file-name))) nil t)
+
+(autoload 'sx-tab-unanswered-my-tags (expand-file-name "sx-tab" (when load-file-name (file-name-directory load-file-name))) nil t)
+
+(autoload 'sx-tab-featured (expand-file-name "sx-tab" (when load-file-name (file-name-directory load-file-name))) nil t)
+
+(autoload 'sx-tab-starred (expand-file-name "sx-tab" (when load-file-name (file-name-directory load-file-name))) nil t)
+
+(autoload 'sx-tab-frontpage (expand-file-name "sx-tab" (when load-file-name (file-name-directory load-file-name))) nil t)
+
+(autoload 'sx-tab-newest (expand-file-name "sx-tab" (when load-file-name (file-name-directory load-file-name))) nil t)
+
+(autoload 'sx-tab-topvoted (expand-file-name "sx-tab" (when load-file-name (file-name-directory load-file-name))) nil t)
+
+(autoload 'sx-tab-hot (expand-file-name "sx-tab" (when load-file-name (file-name-directory load-file-name))) nil t)
+
+(autoload 'sx-tab-week (expand-file-name "sx-tab" (when load-file-name (file-name-directory load-file-name))) nil t)
+
+(autoload 'sx-tab-month (expand-file-name "sx-tab" (when load-file-name (file-name-directory load-file-name))) nil t)
+
+;;;***
+\f
+;;;### (autoloads nil nil ("sx-babel.el" "sx-button.el" "sx-cache.el"
+;;;;;; "sx-compose.el" "sx-encoding.el" "sx-favorites.el" "sx-filter.el"
+;;;;;; "sx-load.el" "sx-method.el" "sx-networks.el" "sx-notify.el"
+;;;;;; "sx-pkg.el" "sx-question-list.el" "sx-question-mode.el" "sx-question-print.el"
+;;;;;; "sx-question.el" "sx-request.el" "sx-site.el" "sx-tag.el"
+;;;;;; "sx-time.el" "sx-user.el") (21910 48877 289626 0))
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+;;; sx-autoloads.el ends here
--- /dev/null
+;;; sx-babel.el --- font-locking pre blocks according to language -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Artur Malabarba
+
+;; Author: Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file contains functions and a variable for font-locking the
+;; content of markdown pre blocks according to their language. The
+;; main configuration point, for both the user and the developer is
+;; the variable `sx-babel-major-mode-alist', which see.
+
+\f
+;;; Code:
+(require 'sx-button)
+
+(defvar sx-babel-major-mode-alist
+ `((,(rx (or "*" "#+")) org-mode)
+ (,(rx (or "[" "(" ";" "#(")) emacs-lisp-mode)
+ ;; @TODO: Make shell-mode work here. Currently errors because it
+ ;; needs a process. `sh-mode' isn't as nice.
+ (,(rx (or "$ " "# ")) sh-mode)
+ ;; Not sure if leaving out "[{" might lead to false positives.
+ (,(rx "\\" (+ alnum) (any "[{")) latex-mode)
+ ;; Right now, this will match a lot of stuff. Once we are capable
+ ;; of determining major-mode from tags, site, and comments, this
+ ;; will work as a last case fallback.
+ (,(rx (or (and "int" (+ space) "main" (* space) "("))) c-mode)
+ )
+ "List of cons cells determining which major-mode to use when.
+Each car is a rule and each cdr is a major-mode. The first rule
+which is satisfied activates the major-mode.
+
+Point is moved to the first non-blank character before testing
+the rule, which can either be a string or a function. If it is a
+string, is tested as a regexp starting from point. If it is a
+function, is called with no arguments and should return non-nil
+on a match.")
+(put 'sx-babel-major-mode-alist 'risky-local-variable-p t)
+
+\f
+;;; Font-locking the text
+(defun sx-babel--make-pre-button (beg end)
+ "Turn the region between BEG and END into a button."
+ (let ((text (buffer-substring-no-properties beg end))
+ indent mode copy)
+ (with-temp-buffer
+ (insert text)
+ (setq indent (sx-babel--unindent-buffer))
+ (goto-char (point-min))
+ (setq mode (sx-babel--determine-major-mode))
+ (setq copy (replace-regexp-in-string "[[:space:]]+\\'" "" (buffer-string)))
+ (when mode
+ (delay-mode-hooks (funcall mode)))
+ (font-lock-fontify-region (point-min) (point-max))
+ (goto-char (point-min))
+ (let ((space (make-string indent ?\s)))
+ (while (not (eobp))
+ (insert-and-inherit space)
+ (forward-line 1)))
+ (setq text (buffer-string)))
+ (goto-char beg)
+ (delete-region beg end)
+ (insert-text-button
+ text
+ 'sx-button-copy copy
+ ;; We store the mode here so it can be used if the user wants
+ ;; to edit the code block.
+ 'sx-mode mode
+ :type 'sx-question-mode-code-block)))
+
+(defun sx-babel--determine-major-mode ()
+ "Return the major-mode most suitable for the current buffer."
+ (let ((alist sx-babel-major-mode-alist)
+ cell out)
+ (while (setq cell (pop alist))
+ (goto-char (point-min))
+ (skip-chars-forward "\r\n[:blank:]")
+ (let ((kar (car cell)))
+ (when (if (stringp kar) (looking-at kar) (funcall kar))
+ (setq alist nil)
+ (setq out (cadr cell)))))
+ out))
+
+(defun sx-babel--unindent-buffer ()
+ "Remove absolute indentation in current buffer.
+Finds the least indented line, and removes that amount of
+indentation from all lines. Primarily designed to extract the
+content of markdown code blocks.
+
+Returns the amount of indentation removed."
+ (save-excursion
+ (goto-char (point-min))
+ (let (result)
+ ;; Get indentation of each non-blank line
+ (while (null (eobp))
+ (skip-chars-forward "[:blank:]")
+ (unless (looking-at "$")
+ (push (current-column) result))
+ (forward-line 1))
+ (when result
+ (setq result (apply #'min result))
+ ;; Build a regexp with the smallest indentation
+ (let ((rx (format "^ \\{0,%s\\}" result)))
+ (goto-char (point-min))
+ ;; Use this regexp to remove that much indentation
+ ;; throughout the buffer.
+ (while (and (null (eobp))
+ (search-forward-regexp rx nil 'noerror))
+ (replace-match "")
+ (forward-line 1))))
+ (or result 0))))
+
+(provide 'sx-babel)
+;;; sx-babel.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+;;; sx-button.el --- defining buttons -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Artur Malabarba
+
+;; Author: Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+;; This file defines all buttons used by SX. For information on
+;; buttons, see:
+;; http://www.gnu.org/software/emacs/manual/html_node/elisp/Buttons.html
+;;
+;; Most interactive parts of the SX buffers are buttons. Wherever you
+;; are, you can always cycle through all buttons by hitting `TAB',
+;; that should help identify what's a button in each buffer.
+;;
+;; To define a new type of button follow the examples below using
+;; `define-button-type' with :supertype `sx-button'. Required
+;; properties are `action' and `help-echo'. You'll probably want to
+;; give it a `face' as well, unless you want it to look like a link.
+;;
+;; Buttons can then be inserted in their respective files using
+;; `insert-text-button'. Give it the string, the `:type' you defined,
+;; and any additional properties that can only be determined at
+;; creation. Existing text can be transformed into a button with
+;; `make-text-button' instead.
+
+\f
+;;; Code:
+(require 'button)
+
+(require 'sx)
+(require 'sx-question)
+
+\f
+;;; Face
+(defface sx-custom-button
+ '((((type x w32 ns) (class color)) ; Like default mode line
+ :box (:line-width 3 :style released-button)
+ :height 0.9
+ :background "lightgrey" :foreground "black"))
+ "Face used on buttons such as \"Write an Answer\"."
+ :group 'sx)
+
+\f
+;;; Command definitions
+;; This extends `button-map', which already defines RET and mouse-1.
+(defvar sx-button-map
+ (let ((map (copy-keymap button-map)))
+ (define-key map "w" #'sx-button-copy)
+ map)
+ "Keymap used on buttons.")
+
+(defun sx-button-copy ()
+ "Copy the content of thing at point.
+This is usually a link's URL, or the content of a code block."
+ (interactive)
+ (let ((content
+ (get-text-property (point) 'sx-button-copy)))
+ (if (null content)
+ (sx-message "Nothing to copy here.")
+ (kill-new content)
+ (sx-message "Copied %s to kill ring."
+ (or (get-text-property
+ (point) 'sx-button-copy-type)
+ content)))))
+
+(defun sx-button-edit-this (text-or-marker &optional majormode)
+ "Open a temp buffer populated with the string TEXT-OR-MARKER using MAJORMODE.
+When given a marker (or interactively), use the 'sx-button-copy
+and the 'sx-mode text-properties under the marker. These are
+usually part of a code-block."
+ (interactive (list (point-marker)))
+ ;; Buttons receive markers.
+ (when (markerp text-or-marker)
+ (setq majormode (get-text-property text-or-marker 'sx-mode))
+ (unless (setq text-or-marker
+ (get-text-property text-or-marker 'sx-button-copy))
+ (sx-message "Nothing of interest here.")))
+ (with-current-buffer (pop-to-buffer (generate-new-buffer
+ "*sx temp buffer*"))
+ (insert text-or-marker)
+ (when majormode
+ (funcall majormode))))
+
+(defun sx-button-follow-link (&optional pos)
+ "Follow link at POS. If POS is nil, use `point'."
+ (interactive)
+ (let ((url (or (get-text-property (or pos (point)) 'sx-button-url)
+ (sx-user-error "No url under point: %s" (or pos (point))))))
+ ;; If we didn't recognize the link, this errors immediately. If
+ ;; we mistakenly recognize it, it will error when we try to fetch
+ ;; whatever we thought it was.
+ (condition-case nil (sx-open-link url)
+ ;; When it errors, don't blame the user, just visit externally.
+ (error (browse-url url)))))
+
+\f
+;;; Help-echo definitions
+(defconst sx-button--help-echo
+ (concat "mouse-1, RET"
+ (propertize ": %s -- " 'face 'minibuffer-prompt)
+ "w"
+ (propertize ": copy %s" 'face 'minibuffer-prompt))
+ "Base help-echo on which others can be written.")
+
+(defconst sx-button--user-help-echo
+ (format sx-button--help-echo
+ "visit user page"
+ "link")
+ "Help echoed in the minibuffer when point is on a user.")
+
+(defconst sx-button--tag-help-echo
+ (format sx-button--help-echo
+ "Tag search"
+ "tag")
+ "Help echoed in the minibuffer when point is on a tag.")
+
+(defconst sx-button--question-title-help-echo
+ (format sx-button--help-echo
+ "hide content"
+ "link")
+ "Help echoed in the minibuffer when point is on a section.")
+
+(defconst sx-button--link-help-echo
+ (format sx-button--help-echo
+ "visit %s"
+ "URL")
+ "Help echoed in the minibuffer when point is on a section.")
+
+\f
+;;; Type definitions
+(define-button-type 'sx-button
+ 'follow-link t
+ 'keymap sx-button-map)
+
+(define-button-type 'sx-question-mode-title
+ 'face 'sx-question-mode-title
+ 'action #'sx-question-mode-hide-show-section
+ 'help-echo sx-button--question-title-help-echo
+ 'sx-button-copy-type "Share Link"
+ :supertype 'sx-button)
+
+(define-button-type 'sx-question-mode-code-block
+ 'action #'sx-button-edit-this
+ 'face nil
+ :supertype 'sx-button)
+
+(define-button-type 'sx-button-link
+ 'action #'sx-button-follow-link
+ :supertype 'sx-button)
+
+(define-button-type 'sx-button-user
+ 'action #'sx-button-follow-link
+ 'help-echo sx-button--user-help-echo
+ ;; We use different faces on different parts of the user button.
+ 'face 'sx-user-name
+ :supertype 'sx-button)
+
+(declare-function sx-search-tag-at-point "sx-search")
+(define-button-type 'sx-button-tag
+ 'action #'sx-search-tag-at-point
+ 'help-echo sx-button--tag-help-echo
+ 'face 'sx-tag
+ :supertype 'sx-button)
+
+(define-button-type 'sx-button-comment
+ 'help-echo (concat "mouse-1, RET"
+ (propertize ": write a comment"
+ 'face 'minibuffer-prompt))
+ 'face 'sx-custom-button
+ 'action #'sx-comment
+ :supertype 'sx-button)
+
+(define-button-type 'sx-button-accept
+ 'help-echo (concat "mouse-1, RET"
+ (propertize ": accept answer"
+ 'face 'minibuffer-prompt))
+ 'face 'sx-custom-button
+ 'action #'sx-accept
+ :supertype 'sx-button)
+
+(define-button-type 'sx-button-answer
+ 'help-echo (concat "mouse-1, RET"
+ (propertize ": write an answer"
+ 'face 'minibuffer-prompt))
+ 'face 'sx-custom-button
+ 'action #'sx-answer
+ :supertype 'sx-button)
+
+(provide 'sx-button)
+;;; sx-button.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+;;; sx-cache.el --- caching -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Sean Allred
+
+;; Author: Sean Allred <codeNOSPAMseanallred.com>
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file handles the cache system. All caches are retrieved and
+;; set using symbols. The symbol should be the sub-package that is
+;; using the cache. For example, `sx-pkg' would use
+;;
+;; `(sx-cache-get 'pkg)'
+;;
+;; This symbol is then converted into a filename within
+;; `sx-cache-directory' using `sx-cache-get-file-name'.
+;;
+;; Currently, the cache is written at every `sx-cache-set', but this
+;; write will eventually be done by some write-all function which will
+;; be set on an idle timer.
+
+;;; Code:
+
+(defcustom sx-cache-directory (locate-user-emacs-file ".sx")
+ "Directory containing cached data."
+ :type 'directory
+ :group 'sx)
+
+(defun sx-cache--ensure-sx-cache-directory-exists ()
+ "Ensure `sx-cache-directory' exists."
+ (unless (file-exists-p sx-cache-directory)
+ (mkdir sx-cache-directory)))
+
+(defun sx-cache-get-file-name (filename)
+ "Expand FILENAME in the context of `sx-cache-directory'."
+ (expand-file-name
+ (concat (symbol-name filename) ".el")
+ sx-cache-directory))
+
+(defun sx-cache-get (cache &optional form)
+ "Return the data within CACHE.
+If CACHE does not exist, use `sx-cache-set' to set CACHE to the
+result of evaluating FORM.
+
+CACHE is resolved to a file name by `sx-cache-get-file-name'."
+ (sx-cache--ensure-sx-cache-directory-exists)
+ (let ((file (sx-cache-get-file-name cache)))
+ ;; If the file exists, return the data it contains
+ (if (file-exists-p file)
+ (with-temp-buffer
+ (insert-file-contents (sx-cache-get-file-name cache))
+ (read (buffer-string)))
+ ;; Otherwise, set CACHE to the evaluation of FORM.
+ ;; `sx-cache-set' returns the data that CACHE was set to.
+ (sx-cache-set cache (eval form)))))
+
+(defun sx-cache-set (cache data)
+ "Set the content of CACHE to DATA and save.
+DATA will be written as returned by `prin1'.
+
+CACHE is resolved to a file name by `sx-cache-get-file-name'."
+ (sx-cache--ensure-sx-cache-directory-exists)
+ (let (print-length print-level)
+ (write-region (prin1-to-string data) nil
+ (sx-cache-get-file-name cache)))
+ data)
+
+(defun sx-cache--invalidate (cache &optional vars init-method)
+ "Set cache CACHE to nil.
+
+VARS is a list of variables to unbind to ensure cache is cleared.
+If INIT-METHOD is defined, call it after all invalidation to
+re-initialize the cache."
+ (let ((file (sx-cache-get-file-name cache)))
+ (delete-file file))
+ (mapc #'makunbound vars)
+ (when init-method
+ (funcall init-method)))
+
+(defun sx-cache-invalidate-all (&optional save-auth)
+ "Invalidate all caches using `sx-cache--invalidate'.
+Afterwards reinitialize caches using `sx-initialize'. If
+SAVE-AUTH is non-nil, do not clear AUTH cache.
+
+Interactively, SAVE-AUTH is the negation of the prefix argument.
+That is, by default the auth cache is PRESERVED interactively.
+If you provide a prefix argument, the auth cache is INVALIDATED.
+
+Note: This will also remove read/unread status of questions as well
+as delete the list of hidden questions."
+ (interactive (list (not current-prefix-arg)))
+ (let* ((default-directory sx-cache-directory)
+ (caches (file-expand-wildcards "*.el")))
+ (when save-auth
+ (setq caches (cl-remove-if (lambda (x)
+ (string= x "auth.el")) caches)))
+ (lwarn 'sx :debug "Invalidating: %S" caches)
+ (mapc #'delete-file caches)
+ (sx-initialize 'force)))
+
+(provide 'sx-cache)
+;;; sx-cache.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+;;; sx-compose.el --- major-mode for composing questions and answers -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Artur Malabarba
+
+;; Author: Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file defines `sx-compose-mode' and its auxiliary functions and
+;; variables. In order to use `sx-compose-mode', it is vital that the
+;; variable `sx-compose--send-function' be set. Otherwise it's just a
+;; regular markdown buffer.
+;;
+;; In order to help avoid mistakes, there is the function
+;; `sx-compose-create'. This is the preferred way of activating the
+;; mode. It creates a buffer, activates the major mode, and sets the
+;; `send-function' variable according to the arguments it is given.
+
+\f
+;;; Code:
+(require 'markdown-mode)
+
+(require 'sx)
+(require 'sx-tag)
+
+(defgroup sx-compose-mode nil
+ "Customization group for sx-compose-mode."
+ :prefix "sx-compose-mode-"
+ :tag "SX compose Mode"
+ :group 'sx)
+
+\f
+;;; Faces and Variables
+(defvar sx-compose-before-send-hook nil
+ "Hook run before POSTing to the API.
+Functions are called without arguments and should return non-nil.
+
+Returning nil indicates something went wrong and the sending will
+be aborted. In this case, the function is responsible for
+notifying the user.
+
+Current buffer is the compose-mode buffer whose content is about
+to be POSTed.")
+
+(defvar sx-compose-after-send-functions nil
+ "Hook run after POSTing to the API.
+Functions on this hook should take two arguments, the
+`sx-compose-mode' buffer (which not be live) and the data
+returned by `sx-compose--send-function' (usually the object
+created by the API). They are only called if the transaction
+succeeds.")
+
+(defvar sx-compose--send-function nil
+ "Function used by `sx-compose-send' to send the data.
+Is invoked between `sx-compose-before-send-hook' and
+`sx-compose-after-send-functions'.")
+
+(defconst sx-compose--question-headers
+ (concat
+ #("Title: " 0 7 (intangible t read-only t rear-nonsticky t))
+ "%s"
+ #("\n" 0 1 (read-only t))
+ #("Tags : " 0 7 (read-only t intangible t rear-nonsticky t))
+ "%s"
+ #("\n" 0 1 (read-only t rear-nonsticky t))
+ #("________________________________________\n"
+ 0 41 (read-only t rear-nonsticky t intangible t
+ sx-compose-separator t))
+ "\n")
+ "Headers inserted when composing a new question.
+Used by `sx-compose-create'.")
+
+(defconst sx-compose--header-line
+ '(" "
+ (:propertize "C-c C-c" face mode-line-buffer-id)
+ ": Finish and Send"
+ (sx-compose--is-question-p
+ (" "
+ (:propertize "C-c C-q" face mode-line-buffer-id)
+ ": Insert tags"))
+ " "
+ (:propertize "C-c C-k" face mode-line-buffer-id)
+ ": Discard Draft")
+ "Header-line used on `sx-compose-mode' drafts.")
+
+(defvar sx-compose--is-question-p nil
+ "Non-nil if this `sx-compose-mode' buffer is a question.")
+(make-variable-buffer-local 'sx-compose--is-question-p)
+
+(defvar sx-compose--site nil
+ "Site which the curent compose buffer belongs to.")
+(make-variable-buffer-local 'sx-compose--site)
+
+\f
+;;; Major-mode
+(define-derived-mode sx-compose-mode markdown-mode "Compose"
+ "Major mode for coposing questions and answers.
+Most of the functionality comes from `markdown-mode'. This mode
+just implements some extra features related to posting to the
+API.
+
+This mode won't function if `sx-compose--send-function' isn't
+set. To make sure you set it correctly, you can create the
+buffer with the `sx-compose-create' function.
+
+If creating a question draft, the `sx-compose--is-question-p'
+variable should also be set to enable more functionality.
+
+\\<sx-compose-mode>
+\\{sx-compose-mode}"
+ (setq header-line-format sx-compose--header-line)
+ (add-hook 'sx-compose-after-send-functions
+ #'sx-compose-quit nil t)
+ (add-hook 'sx-compose-after-send-functions
+ #'sx-compose--copy-as-kill nil t))
+
+(define-key sx-compose-mode-map "\C-c\C-c" #'sx-compose-send)
+(define-key sx-compose-mode-map "\C-c\C-k" #'sx-compose-quit)
+(sx--define-conditional-key
+ sx-compose-mode-map "\C-c\C-q" #'sx-compose-insert-tags
+ sx-compose--is-question-p)
+
+(defun sx-compose-send ()
+ "Finish composing current buffer and send it.
+Calls `sx-compose-before-send-hook', POSTs the the current buffer
+contents to the API, then calls `sx-compose-after-send-functions'."
+ (interactive)
+ (when (run-hook-with-args-until-failure
+ 'sx-compose-before-send-hook)
+ (let ((result (funcall sx-compose--send-function))
+ (buf (current-buffer)))
+ (run-hook-wrapped
+ 'sx-compose-after-send-functions
+ (lambda (func)
+ (with-demoted-errors
+ "[sx] Error encountered AFTER sending post, but the post was sent successfully: %s"
+ (funcall func buf result))
+ nil)))))
+
+(defun sx-compose-insert-tags ()
+ "Prompt for a tag list for this draft and insert them."
+ (interactive)
+ (save-excursion
+ (let* ((old (sx-compose--goto-tag-header))
+ (new
+ (save-match-data
+ (mapconcat
+ #'identity
+ (sx-tag-multiple-read sx-compose--site "Tags" old)
+ " "))))
+ (if (match-string 1)
+ (replace-match new :fixedcase nil nil 1)
+ (insert new)))))
+
+\f
+;;; Functions for use in hooks
+(defun sx-compose-quit (buffer _)
+ "Close BUFFER's window and kill it."
+ (interactive (list (current-buffer) nil))
+ (when (buffer-live-p buffer)
+ (let ((w (get-buffer-window buffer)))
+ (when (window-live-p w)
+ (ignore-errors (delete-window w))))
+ (kill-buffer buffer)))
+
+(defun sx-compose--copy-as-kill (buffer _)
+ "Copy BUFFER contents to the kill-ring."
+ (when (buffer-live-p buffer)
+ (with-current-buffer buffer
+ (kill-new (buffer-string)))))
+
+(defun sx-compose--goto-tag-header ()
+ "Move to the \"Tags:\" header.
+Match data is set so group 1 encompasses any already inserted
+tags. Return a list of already inserted tags."
+ (goto-char (point-min))
+ (unless (search-forward-regexp
+ (rx bol "Tags : " (group-n 1 (* not-newline)) eol)
+ (next-single-property-change (point-min) 'sx-compose-separator)
+ 'noerror)
+ (error "No Tags header found"))
+ (save-match-data
+ (sx--split-string (match-string 1) (rx (any space ",;")))))
+
+(defun sx-compose--check-tags ()
+ "Check if tags in current compose buffer are valid."
+ (save-excursion
+ (let ((invalid-tags
+ (sx-tag--invalid-name-p
+ sx-compose--site (sx-compose--goto-tag-header))))
+ (if invalid-tags
+ ;; If the user doesn't want to create the tags, we return
+ ;; nil and sending is aborted.
+ (y-or-n-p (format "Following tags don't exist. Create them? %s " invalid-tags))
+ t))))
+
+\f
+;;; Functions to help preparing buffers
+(defun sx-compose-create (site parent &optional before-functions after-functions)
+ "Create an `sx-compose-mode' buffer.
+SITE is the site where it will be posted.
+
+If composing questions, PARENT is nil.
+If composing answers, it is the `question_id'.
+If editing answers or questions, it should be the alist data
+related to that object.
+
+Each element of BEFORE-FUNCTIONS and AFTER-FUNCTIONS are
+respectively added locally to `sx-compose-before-send-hook' and
+`sx-compose-after-send-functions'."
+ (or (integerp parent) (listp parent)
+ (error "Invalid PARENT"))
+ (let ((is-question
+ (and (listp parent)
+ (or (null parent)
+ (cdr (assoc 'title parent))))))
+ (with-current-buffer (sx-compose--get-buffer-create site parent)
+ (sx-compose-mode)
+ (setq sx-compose--site site)
+ (setq sx-compose--is-question-p is-question)
+ (setq sx-compose--send-function
+ (if (consp parent)
+ (sx-assoc-let parent
+ (lambda () (sx-method-call (cond
+ (.title 'questions)
+ (.comment_id 'comments)
+ (t 'answers))
+ :auth 'warn
+ :url-method 'post
+ :filter sx-browse-filter
+ :site site
+ :keywords (sx-compose--generate-keywords is-question)
+ :id (or .comment_id .answer_id .question_id)
+ :submethod 'edit)))
+ (lambda () (sx-method-call 'questions
+ :auth 'warn
+ :url-method 'post
+ :filter sx-browse-filter
+ :site site
+ :keywords (sx-compose--generate-keywords is-question)
+ :id parent
+ :submethod (if parent 'answers/add 'add)))))
+ ;; Reverse so they're left in the same order.
+ (dolist (it (reverse before-functions))
+ (add-hook 'sx-compose-before-send-hook it nil t))
+ (dolist (it (reverse after-functions))
+ (add-hook 'sx-compose-after-send-functions it nil t))
+ (when is-question
+ (add-hook 'sx-compose-before-send-hook #'sx-compose--check-tags nil t))
+ ;; If the buffer is empty, the draft didn't exist. So prepare the
+ ;; question.
+ (when (or (string= (buffer-string) "")
+ (y-or-n-p "Draft buffer exists. Reset it? "))
+ (let ((inhibit-point-motion-hooks t)
+ (inhibit-read-only t))
+ (erase-buffer)
+ (when (consp parent)
+ (insert (cdr (assoc 'body_markdown parent))))
+ (when is-question
+ (sx-compose--print-question-headers
+ (when (consp parent) parent))
+ (unless (consp parent)
+ (goto-char (point-min))
+ (goto-char (line-end-position))))))
+ ;; Return the buffer
+ (current-buffer))))
+
+(defun sx-compose--print-question-headers (question)
+ "Print question headers for the compose buffer.
+If QUESTION is non-nil, fill the headers with the data from
+QUESTION."
+ (sx-assoc-let question
+ (goto-char (point-min))
+ (insert
+ (format sx-compose--question-headers
+ (or .title "") (mapconcat #'identity .tags " ")))))
+
+(defun sx-compose--generate-keywords (is-question)
+ "Reading current buffer, generate a keywords alist.
+Keywords meant to be used in `sx-method-call'.
+
+`body' is read as the `buffer-string'. If IS-QUESTION is non-nil,
+other keywords are read from the header "
+ (goto-char (point-min))
+ `(,@(when is-question
+ (let ((inhibit-point-motion-hooks t)
+ (header-end
+ (next-single-property-change
+ (point-min) 'sx-compose-separator))
+ keywords)
+ ;; Read the Title.
+ (unless (search-forward-regexp
+ "^Title: *\\(.*\\) *$" header-end 'noerror)
+ (error "No Title header found"))
+ (push (cons 'title (match-string 1)) keywords)
+ ;; And the tags
+ (goto-char (point-min))
+ (unless (search-forward-regexp "^Tags : *\\([^[:space:]].*\\) *$"
+ header-end 'noerror)
+ (error "No Tags header found"))
+ (push (cons 'tags (sx--split-string (match-string 1) "[[:space:],;]"))
+ keywords)
+ ;; And move past the header so it doesn't get sent.
+ (goto-char (next-single-property-change
+ header-end 'sx-compose-separator))
+ keywords))
+ (body . ,(buffer-substring-no-properties (point) (point-max)))))
+
+(defun sx-compose--get-buffer-create (site data)
+ "Get or create a buffer for use with `sx-compose-mode'.
+SITE is the site for which composing is aimed (just used to
+uniquely identify the buffers).
+
+If DATA is nil, get a fresh compose buffer.
+If DATA is an integer, try to find an existing buffer
+corresponding to that integer, otherwise create one.
+If DATA is an alist (question or answer data), like above but use
+the id property."
+ (cond
+ ((null data)
+ (generate-new-buffer
+ (format "*sx draft question %s*" site)))
+ ((integerp data)
+ (get-buffer-create
+ (format "*sx draft answer %s %s*"
+ site data)))
+ (t
+ (get-buffer-create
+ (sx-assoc-let data
+ (format "*sx draft edit %s %s %s*"
+ site
+ (cond (.title "question")
+ (.comment_id "comment")
+ (t "answer"))
+ (or .comment_id .answer_id .question_id)))))))
+
+(provide 'sx-compose)
+;;; sx-compose.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+;;; sx-encoding.el --- encoding -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Sean Allred
+
+;; Author: Sean Allred <codeNOSPAMseanallred.com>
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file handles decoding the responses we get from the API. They
+;; are received either as plain-text or as a `gzip' compressed archive.
+;; For this, `sx-encoding-gzipped-p' is used to determine if content
+;; has been compressed under `gzip'.
+
+;;; Code:
+
+(require 'cl-lib)
+
+\f
+;;;; HTML Encoding
+
+(defcustom sx-encoding-html-entities-plist
+ '(Aacute "Á" aacute "á" Acirc "Â" acirc "â" acute "´" AElig "Æ" aelig "æ"
+ Agrave "À" agrave "à" alefsym "ℵ" Alpha "Α" alpha "α" amp "&" and "∧"
+ ang "∠" apos "'" aring "å" Aring "Å" asymp "≈" atilde "ã" Atilde "Ã"
+ auml "ä" Auml "Ä" bdquo "„" Beta "Β" beta "β" brvbar "¦" bull "•"
+ cap "∩" ccedil "ç" Ccedil "Ç" cedil "¸" cent "¢" Chi "Χ" chi "χ"
+ circ "ˆ" clubs "♣" cong "≅" copy "©" crarr "↵" cup "∪" curren "¤"
+ Dagger "‡" dagger "†" darr "↓" dArr "⇓" deg "°" Delta "Δ" delta "δ"
+ diams "♦" divide "÷" eacute "é" Eacute "É" ecirc "ê" Ecirc "Ê" egrave "è"
+ Egrave "È" empty "∅" emsp " " ensp " " Epsilon "Ε" epsilon "ε" equiv "≡"
+ Eta "Η" eta "η" eth "ð" ETH "Ð" euml "ë" Euml "Ë" euro "€"
+ exist "∃" fnof "ƒ" forall "∀" frac12 "½" frac14 "¼" frac34 "¾" frasl "⁄"
+ Gamma "Γ" gamma "γ" ge "≥" gt ">" harr "↔" hArr "⇔" hearts "♥"
+ hellip "…" iacute "í" Iacute "Í" icirc "î" Icirc "Î" iexcl "¡" igrave "ì"
+ Igrave "Ì" image "ℑ" infin "∞" int "∫" Iota "Ι" iota "ι" iquest "¿"
+ isin "∈" iuml "ï" Iuml "Ï" Kappa "Κ" kappa "κ" Lambda "Λ" lambda "λ"
+ lang "〈" laquo "«" larr "←" lArr "⇐" lceil "⌈" ldquo "“" le "≤"
+ lfloor "⌊" lowast "∗" loz "◊" lrm "" lsaquo "‹" lsquo "‘" lt "<"
+ macr "¯" mdash "—" micro "µ" middot "·" minus "−" Mu "Μ" mu "μ"
+ nabla "∇" nbsp " " ndash "–" ne "≠" ni "∋" not "¬" notin "∉"
+ nsub "⊄" ntilde "ñ" Ntilde "Ñ" Nu "Ν" nu "ν" oacute "ó" Oacute "Ó"
+ ocirc "ô" Ocirc "Ô" OElig "Œ" oelig "œ" ograve "ò" Ograve "Ò" oline "‾"
+ omega "ω" Omega "Ω" Omicron "Ο" omicron "ο" oplus "⊕" or "∨" ordf "ª"
+ ordm "º" oslash "ø" Oslash "Ø" otilde "õ" Otilde "Õ" otimes "⊗" ouml "ö"
+ Ouml "Ö" para "¶" part "∂" permil "‰" perp "⊥" Phi "Φ" phi "φ"
+ Pi "Π" pi "π" piv "ϖ" plusmn "±" pound "£" Prime "″" prime "′"
+ prod "∏" prop "∝" Psi "Ψ" psi "ψ" quot "\"" radic "√" rang "〉"
+ raquo "»" rarr "→" rArr "⇒" rceil "⌉" rdquo "”" real "ℜ" reg "®"
+ rfloor "⌋" Rho "Ρ" rho "ρ" rlm "" rsaquo "›" rsquo "’" sbquo "‚"
+ scaron "š" Scaron "Š" sdot "⋅" sect "§" shy "" Sigma "Σ" sigma "σ"
+ sigmaf "ς" sim "∼" spades "♠" sub "⊂" sube "⊆" sum "∑" sup "⊃"
+ sup1 "¹" sup2 "²" sup3 "³" supe "⊇" szlig "ß" Tau "Τ" tau "τ"
+ there4 "∴" Theta "Θ" theta "θ" thetasym "ϑ" thinsp " " thorn "þ" THORN "Þ"
+ tilde "˜" times "×" trade "™" uacute "ú" Uacute "Ú" uarr "↑" uArr "⇑"
+ ucirc "û" Ucirc "Û" ugrave "ù" Ugrave "Ù" uml "¨" upsih "ϒ" Upsilon "Υ"
+ upsilon "υ" uuml "ü" Uuml "Ü" weierp "℘" Xi "Ξ" xi "ξ" yacute "ý"
+ Yacute "Ý" yen "¥" yuml "ÿ" Yuml "Ÿ" Zeta "Ζ" zeta "ζ" zwj "" zwnj "")
+ "Plist of HTML entities and their respective glyphs.
+See `sx-encoding-decode-entities'."
+ :type '(repeat (choice symbol string))
+ :group 'sx)
+
+(defun sx-encoding-decode-entities (string)
+ "Decode HTML entities (e.g. \""\") in STRING.
+
+Done according to `sx-encoding-html-entities-plist'. If this
+list does not contain the entity, it is assumed to be a number
+and converted to a string (with `char-to-string').
+
+Return the decoded string."
+ (let* ((plist sx-encoding-html-entities-plist)
+ (get-function
+ (lambda (s)
+ (let ((ss (substring s 1 -1)))
+ ;; Handle things like "
+ (or (plist-get plist (intern ss))
+ ;; Handle things like '
+ (char-to-string
+ (string-to-number
+ ;; Skip the `#'
+ (substring ss 1))))))))
+ (replace-regexp-in-string "&[^; ]*;" get-function string)))
+
+\f
+;;;; Convenience Functions
+
+(defun sx-encoding-normalize-line-endings (string)
+ "Normalize the line endings for STRING.
+The API returns strings that use Windows-style line endings.
+These are largely useless in an Emacs environment. Windows uses
+\"\\r\\n\", Unix uses just \"\\n\". Deleting \"\\r\" is sufficient for
+conversion."
+ (delete ?\r string))
+
+(defun sx-encoding-clean-content (string)
+ "Clean STRING for display.
+Applies `sx-encoding-normalize-line-endings' and
+`sx-encoding-decode-entities' (in that order) to prepare STRING
+for sane display."
+ (sx-encoding-decode-entities
+ (sx-encoding-normalize-line-endings
+ string)))
+
+(defun sx-encoding-clean-content-deep (data)
+ "Clean DATA recursively where necessary.
+
+If DATA is a list or a vector, map this function over DATA and
+return as the the same type of structure.
+
+If DATA is a cons cell (but not a list), use
+`sx-encoding-clean-content-deep' on the `cdr' of DATA.
+
+If DATA is a string, return DATA after applying
+`sx-encoding-clean-content'.
+
+Otherwise, return DATA.
+
+This function is highly specialized for the data structures
+returned by `json-read' via `sx-request-make'. It may fail in
+some cases."
+ (if (consp data)
+ (if (listp (cdr data))
+ (cl-map #'list #'sx-encoding-clean-content-deep data)
+ (cons (car data) (sx-encoding-clean-content-deep (cdr data))))
+ (cond
+ ((stringp data)
+ (sx-encoding-clean-content data))
+ ((vectorp data)
+ (cl-map #'vector #'sx-encoding-clean-content-deep data))
+ (t data))))
+
+\f
+;;;; GZIP
+
+(defun sx-encoding-gzipped-p (data)
+ "Check for magic bytes in DATA.
+Check if the first two bytes of a string in DATA match the magic
+numbers identifying the gzip file format.
+
+See URL `http://www.gzip.org/zlib/rfc-gzip.html'."
+ ;; Credit: http://emacs.stackexchange.com/a/2978
+ (equal (substring (string-as-unibyte data) 0 2)
+ (unibyte-string 31 139)))
+
+(defun sx-encoding-gzipped-buffer-p (buffer)
+ "Check if BUFFER is gzip-compressed.
+See `sx-encoding-gzipped-p'."
+ (with-current-buffer buffer
+ (sx-encoding-gzipped-p
+ (buffer-string))))
+
+(defun sx-encoding-gzipped-file-p (file)
+ "Check if the FILE is gzip-compressed.
+See `sx-encoding-gzipped-p'."
+ (let ((first-two-bytes (with-temp-buffer
+ (set-buffer-multibyte nil)
+ (insert-file-contents-literally file nil 0 2)
+ (buffer-string))))
+ (sx-encoding-gzipped-p first-two-bytes)))
+
+(provide 'sx-encoding)
+;;; sx-encoding.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+;;; sx-favorites.el --- starred questions -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Sean Allred
+
+;; Author: Sean Allred <codeNOSPAMseanallred.com>
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file provides logic for retrieving and managing a user's
+;; starred questions.
+
+;;; Code:
+
+(require 'sx-method)
+(require 'sx-cache)
+(require 'sx-site)
+(require 'sx-networks)
+(require 'sx-filter)
+
+(defconst sx-favorite-list-filter
+ (sx-filter-from-nil
+ (question.question_id)))
+
+(defvar sx-favorites--user-favorite-list nil
+ "Alist of questions favorited by the user.
+Each element has the form (SITE FAVORITE-LIST). And each element
+in FAVORITE-LIST is the numerical QUESTION_ID.")
+
+(defun sx-favorites--initialize ()
+ "Ensure question-favorites cache is available.
+Added as hook to initialization."
+ (or (setq sx-favorites--user-favorite-list
+ (sx-cache-get 'question-favorites))
+ (sx-favorites-update)))
+;; ;; Append to ensure `sx-network--initialize' is run before it.
+;; This is removed for now because it performs a lot of API calls and
+;; was never used.
+;; (add-hook 'sx-init--internal-hook #'sx-favorites--initialize 'append)
+
+(defun sx-favorites--retrieve-favorites (site)
+ "Obtain list of starred QUESTION_IDs for SITE."
+ (sx-method-call 'me
+ :submethod 'favorites
+ :site site
+ :filter sx-favorite-list-filter
+ :auth t))
+
+(defun sx-favorites--update-site-favorites (site)
+ "Update list of starred QUESTION_IDs for SITE.
+Writes list to cache QUESTION-FAVORITES."
+ (let* ((favs (sx-favorites--retrieve-favorites site))
+ (site-cell (assoc site
+ sx-favorites--user-favorite-list))
+ (fav-cell (mapcar #'cdar favs)))
+ (if site-cell
+ (setcdr site-cell fav-cell)
+ (push (cons site fav-cell) sx-favorites--user-favorite-list))
+ (sx-cache-set 'question-favorites sx-favorites--user-favorite-list)))
+
+(defun sx-favorites-update ()
+ "Update all sites retrieved from `sx-network--user-sites'."
+ (mapc #'sx-favorites--update-site-favorites
+ sx-network--user-sites))
+
+(provide 'sx-favorites)
+;;; sx-favorites.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+;;; sx-filter.el --- handles retrieval of filters -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Sean Allred
+
+;; Author: Sean Allred <codeNOSPAMseanallred.com>
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file manages filters and provides an API to compile filters
+;; and retrieve them from the cache. See `sx-filter-compile' and
+;; `sx-filter-get-var', respectively.
+
+;;; Code:
+
+\f
+;;; Dependencies
+
+(require 'sx)
+(require 'sx-cache)
+(require 'sx-request)
+
+\f
+;;; Customizations
+
+(defvar sx--filter-alist
+ (sx-cache-get 'filter)
+ "An alist of known filters. See `sx-filter-compile'.
+Structure:
+
+ (((INCLUDE EXCLUDE BASE ) . \"compiled filter \")
+ ((INCLUDE2 EXCLUDE2 BASE2) . \"compiled filter2\")
+ ...)")
+
+\f
+;;; Creation
+(defmacro sx-filter-from-nil (included)
+ "Create a filter data structure with INCLUDED fields.
+All wrapper fields are included by default."
+ `(quote
+ ((,@(sx--tree-expand
+ (lambda (path)
+ (intern (mapconcat #'symbol-name path ".")))
+ included)
+ .backoff
+ .error_id
+ .error_message
+ .error_name
+ .has_more
+ .items
+ .page
+ .page_size
+ .quota_max
+ .quota_remaining
+ )
+ nil nil)))
+
+;;; @TODO allow BASE to be a precompiled filter name
+(defun sx-filter-compile (&optional include exclude base)
+ "Compile INCLUDE and EXCLUDE into a filter derived from BASE.
+INCLUDE and EXCLUDE must both be lists; BASE should be a symbol.
+
+Returns the compiled filter as a string."
+ (let ((keyword-arguments
+ `((include . ,(if include (sx--thing-as-string include)))
+ (exclude . ,(if exclude (sx--thing-as-string exclude)))
+ (base . ,(if base base)))))
+ (let ((result (elt (sx-request-make "filter/create" keyword-arguments) 0)))
+ (sx-assoc-let result
+ .filter))))
+
+\f
+;;; Storage and Retrieval
+
+(defun sx-filter-get-var (filter-variable)
+ "Return the string representation of FILTER-VARIABLE."
+ (apply #'sx-filter-get filter-variable))
+
+(defun sx-filter-get (&optional include exclude base)
+ "Return the string representation of the given filter.
+
+If the filter data exists in `sx--filter-alist', that value will
+be returned. Otherwise, compile INCLUDE, EXCLUDE, and BASE into
+a filter with `sx-filter-compile' and push the association onto
+`sx--filter-alist'. Re-cache the alist with `sx-cache-set' and
+return the compiled filter."
+ (or (cdr (assoc (list include exclude base) sx--filter-alist))
+ (let ((filter (sx-filter-compile include exclude base)))
+ (when filter
+ (push (cons (list include exclude base) filter) sx--filter-alist)
+ (sx-cache-set 'filter sx--filter-alist)
+ filter))))
+
+\f
+;;; Browsing filter
+(defconst sx-browse-filter
+ (sx-filter-from-nil
+ ((question body_markdown
+ bounty_amount
+ comments
+ creation_date
+ closed_reason
+ closed_date
+ closed_details
+ answers
+ answer_count
+ score
+ title
+ owner
+ tags
+ last_editor
+ last_activity_date
+ accepted_answer_id
+ link
+ upvoted
+ downvoted
+ question_id
+ share_link)
+ (user display_name
+ link
+ accept_rate
+ reputation)
+ (shallow_user display_name
+ link
+ accept_rate
+ reputation)
+ (comment owner
+ body_markdown
+ body
+ link
+ edited
+ creation_date
+ upvoted
+ score
+ post_type
+ post_id
+ comment_id)
+ (answer answer_id
+ creation_date
+ last_editor
+ last_activity_date
+ link
+ share_link
+ score
+ owner
+ body_markdown
+ upvoted
+ downvoted
+ comments)))
+ "The filter applied when retrieving question data.
+See `sx-question-get-questions' and `sx-question-get-question'.")
+
+(provide 'sx-filter)
+;;; sx-filter.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+;;; sx-inbox.el --- base inbox logic -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Artur Malabarba
+
+;; Author: Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'sx)
+(require 'sx-filter)
+(require 'sx-method)
+(require 'sx-question-list)
+(require 'sx-interaction)
+
+\f
+;;; API
+(defconst sx-inbox-filter
+ '((inbox_item.answer_id
+ inbox_item.body
+ inbox_item.comment_id
+ inbox_item.creation_date
+ inbox_item.is_unread
+ inbox_item.item_type
+ inbox_item.link
+ inbox_item.question_id
+ inbox_item.site
+ inbox_item.title)
+ (site.logo_url
+ site.audience
+ site.icon_url
+ site.high_resolution_icon_url
+ site.site_state
+ site.launch_date
+ site.markdown_extensions
+ site.related_sites
+ site.styling))
+ "Filter used when retrieving inbox items.")
+
+(defcustom sx-inbox-fill-column 40
+ "`fill-column' used in `sx-inbox-mode'."
+ :type 'integer
+ :group 'sx)
+
+(defun sx-inbox-get (&optional notifications page keywords)
+ "Get an array of inbox items for the current user.
+If NOTIFICATIONS is non-nil, query from `notifications' method,
+otherwise use `inbox' method.
+
+Return an array of items. Each item is an alist of properties
+returned by the API.
+See https://api.stackexchange.com/docs/types/inbox-item
+
+KEYWORDS are added to the method call along with PAGE.
+
+`sx-method-call' is used with `sx-inbox-filter'."
+ (sx-method-call (if notifications 'notifications 'inbox)
+ :keywords keywords
+ :page page
+ :filter sx-inbox-filter))
+
+\f
+;;; Major-mode
+(defvar sx-inbox--notification-p nil
+ "If non-nil, current buffer lists notifications, not inbox.")
+(make-variable-buffer-local 'sx-inbox--notification-p)
+
+(defvar sx-inbox--unread-inbox nil
+ "List of inbox items still unread.")
+
+(defvar sx-inbox--unread-notifications nil
+ "List of notifications items still unread.")
+
+(defvar sx-inbox--read-inbox nil
+ "List of inbox items which are read.
+These are identified by their links.")
+
+(defvar sx-inbox--read-notifications nil
+ "List of notification items which are read.
+These are identified by their links.")
+
+(defconst sx-inbox--header-line
+ '(" "
+ (:propertize "n p j k" face mode-line-buffer-id)
+ ": Navigate"
+ " "
+ (:propertize "RET" face mode-line-buffer-id)
+ ": View"
+ " "
+ (:propertize "v" face mode-line-buffer-id)
+ ": Visit externally"
+ " "
+ (:propertize "q" face mode-line-buffer-id)
+ ": Quit")
+ "Header-line used on the inbox list.")
+
+(defconst sx-inbox--mode-line
+ '(" "
+ (:propertize
+ (sx-inbox--notification-p
+ "Notifications"
+ "Inbox")
+ face mode-line-buffer-id))
+ "Mode-line used on the inbox list.")
+
+(define-derived-mode sx-inbox-mode
+ sx-question-list-mode "Question List"
+ "Mode used to list inbox and notification items."
+ (toggle-truncate-lines 1)
+ (setq fill-column sx-inbox-fill-column)
+ (setq sx-question-list--print-function #'sx-inbox--print-info)
+ (setq sx-question-list--next-page-function
+ (lambda (page) (sx-inbox-get sx-inbox--notification-p page)))
+ (setq tabulated-list-format
+ [("Type" 30 t nil t) ("Date" 10 t :right-align t) ("Title" 0)])
+ (setq mode-line-format sx-inbox--mode-line)
+ (setq header-line-format sx-inbox--header-line))
+
+\f
+;;; Keybinds
+(mapc (lambda (x) (define-key sx-inbox-mode-map (car x) (cadr x)))
+ '(
+ ("t" nil)
+ ("a" nil)
+ ("h" nil)
+ ("m" sx-inbox-mark-read)
+ ([?\r] sx-display)
+ ))
+
+\f
+;;; print-info
+(defun sx-inbox--print-info (data)
+ "Convert `json-read' DATA into tabulated-list format.
+
+This is the default printer used by `sx-inbox'. It assumes DATA
+is an alist containing the elements:
+ `answer_id', `body', `comment_id', `creation_date', `is_unread',
+ `item_type', `link', `question_id', `site', `title'."
+ (list
+ data
+ (sx-assoc-let data
+ (vector
+ (list
+ (concat (capitalize
+ (replace-regexp-in-string
+ "_" " " (or .item_type .notification_type)))
+ (cond (.answer_id " on Answer at:")
+ (.question_id " on:")))
+ 'face 'font-lock-keyword-face)
+ (list
+ (concat (sx-time-since .creation_date)
+ sx-question-list-ago-string)
+ 'face 'sx-question-list-date)
+ (list
+ (propertize
+ " " 'display
+ (concat "\n " (propertize .title 'face 'sx-question-list-date) "\n"
+ (let ((col fill-column))
+ (with-temp-buffer
+ (setq fill-column col)
+ (insert " " .body)
+ (fill-region (point-min) (point-max))
+ (buffer-string))))
+ 'face 'default))))))
+
+\f
+;;; Entry commands
+(defvar sx-inbox--buffer nil
+ "Buffer being used to display inbox.")
+
+;;;###autoload
+(defun sx-inbox (&optional notifications)
+ "Display a buffer listing inbox items.
+With prefix NOTIFICATIONS, list notifications instead of inbox."
+ (interactive "P")
+ (setq sx-inbox--buffer (get-buffer-create "*sx-inbox*"))
+ (let ((inhibit-read-only t))
+ (with-current-buffer sx-inbox--buffer
+ (erase-buffer)
+ (sx-inbox-mode)
+ (setq sx-inbox--notification-p notifications)
+ (tabulated-list-revert)))
+ (let ((w (get-buffer-window sx-inbox--buffer)))
+ (if (window-live-p w)
+ (select-window w)
+ (pop-to-buffer sx-inbox--buffer)
+ (enlarge-window
+ (- (+ fill-column 4) (window-width))
+ 'horizontal))))
+
+;;;###autoload
+(defun sx-inbox-notifications ()
+ "Display a buffer listing notification items."
+ (interactive)
+ (sx-inbox t))
+
+(provide 'sx-inbox)
+;;; sx-inbox.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+;;; sx-interaction.el --- voting, commenting, and other interaction -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Artur Malabarba
+
+;; Author: Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file holds a series of functions for performing arbitrary
+;; interactions with arbitrary objects (objects here always mean the
+;; alist of a question, answer, or comment). All commands take at
+;; least a DATA argument corresponding to the object which, when
+;; called interactively, is always derived from the context at point
+;; (usually using the `sx--data-here' function).
+;;
+;; Interactions represented here involve voting, commenting, asking,
+;; answering, editing.
+;;
+;; These are commands are meant to be available throughout the
+;; interface. So it didn't make sense to put them in a specific
+;; module. They also rely on a lot of dependencies, so they couldn't
+;; be put in sx.el.
+
+\f
+;;; Code:
+(eval-when-compile
+ '(require 'cl-lib))
+
+(require 'sx)
+(require 'sx-question)
+(require 'sx-question-mode)
+(require 'sx-question-list)
+(require 'sx-compose)
+(require 'sx-cache)
+
+\f
+;;; Using data in buffer
+(defun sx--data-here (&optional type noerror)
+ "Get the alist regarding object under point of type TYPE.
+Looks at the text property `sx--data-here'. If it's not set, it
+looks at a few other reasonable variables. If those fail too, it
+throws an error.
+
+TYPE is a symbol restricting the type of object desired. Possible
+values are 'question, 'answer, 'comment, or nil (for any type).
+
+If no object of the requested type could be returned, an error is
+thrown unless NOERROR is non-nil."
+ (or (let ((data (get-char-property (point) 'sx--data-here)))
+ (if (null type) data
+ (sx-assoc-let data
+ ;; Is data of the right type?
+ (cl-case type
+ (question (when .title data))
+ (answer (when .answer_id data))
+ (comment (when .comment_id data))))))
+ ;; The following two only ever return questions.
+ (when (or (null type) (eq type 'question))
+ ;; @TODO: `sx-question-list-mode' may one day display answers.
+ ;; Ideally, it would use the `sx--data-here' (so no special
+ ;; handling would be necessary.
+ (or (and (derived-mode-p 'sx-question-list-mode)
+ (tabulated-list-get-id))
+ (and (derived-mode-p 'sx-question-mode)
+ sx-question-mode--data)))
+ ;; Nothing was found
+ (and (null noerror)
+ (error "No %s found here" (or type "data")))))
+
+(defun sx--marker-to-data (marker &rest rest)
+ "Get the data at MARKER.
+REST is passed to `sx--data-here'."
+ (save-excursion
+ (goto-char marker)
+ (apply #'sx--data-here rest)))
+
+(defun sx--error-if-unread (data)
+ "Throw a user-error if DATA is an unread question.
+If it's not a question, or if it is read, return DATA."
+ ;; If we found a question, we may need to check if it's read.
+ (if (and (assoc 'title data)
+ (null (sx-question--read-p data)))
+ (sx-user-error "Question not yet read. View it before acting on it")
+ data))
+
+(defun sx--maybe-update-display (&optional buffer site id)
+ "Refresh whatever is displayed in BUFFER or the current buffer.
+If BUFFER is not live, nothing is done.
+
+If SITE is given but ID isn't, only update if BUFFER appears to
+be a question-list displaying SITE.
+If both SITE and ID are given, only update if BUFFER appears to
+be a question matching SITE and ID."
+ (setq buffer (or buffer (current-buffer)))
+ (when (buffer-live-p buffer)
+ (with-current-buffer buffer
+ (cond ((derived-mode-p 'sx-question-list-mode)
+ (when (or (not site)
+ (and (not id)
+ (string= site sx-question-list--site)))
+ (sx-question-list-refresh 'redisplay 'no-update)))
+ ((derived-mode-p 'sx-question-mode)
+ (when (or (not site)
+ (and id
+ (equal
+ (let-alist (sx--data-here 'question)
+ (cons .site_par .question_id))
+ (cons site id))))
+ (sx-question-mode-refresh 'no-update)))))))
+
+(defun sx--copy-data (from to)
+ "Copy all fields of alist FORM onto TO.
+Only fields contained in TO are copied."
+ (setcar to (car from))
+ (setcdr to (cdr from)))
+
+(defun sx-ensure-authentication ()
+ "Signal user-error if the user refuses to authenticate.
+Note that `sx-method-call' already does authentication checking.
+This function is meant to be used by commands that don't
+immediately perform method calls, such as `sx-ask'. This way,
+the unauthenticated user will be prompted before going through
+the trouble of composing an entire question."
+ (unless (sx-cache-get 'auth)
+ (if (y-or-n-p "This command requires authentication, would you like to authenticate? ")
+ (sx-authenticate)
+ (sx-user-error "This command requires authentication, please run `M-x sx-authenticate' and try again."))))
+
+(defmacro sx--make-update-callback (&rest body)
+ "Return a function that runs BODY and updates display.
+`sx--maybe-update-display' is only called if the buffer where the
+function was created still exists. In that case, BODY is also
+run in this buffer."
+ (declare (debug t))
+ `(let ((buffer (current-buffer)))
+ (lambda (result)
+ ;; See http://emacs.stackexchange.com/a/10725/50
+ (ignore result)
+ (if (buffer-live-p buffer)
+ (with-current-buffer buffer
+ ,@body
+ (sx--maybe-update-display))
+ ,@body))))
+
+(defun sx--copy-update-callback (data)
+ "Return a function that overwrites DATA and updates display.
+First, DATA is destructively overwritten with the car of the
+argument passed to the function. Then,
+`sx--maybe-update-display' is called in the original buffer."
+ (sx--make-update-callback
+ ;; The api returns the new DATA.
+ (when result
+ (sx--copy-data (elt result 0) data))))
+
+\f
+;;; Visiting
+(defun sx-visit-externally (data &optional copy-as-kill)
+ "Visit DATA in a web browser.
+DATA can be a question, answer, or comment. Interactively, it is
+derived from point position.
+
+If copy-as-kill is non-nil, do not call `browse-url'.
+Instead, copy the link as a new kill with `kill-new'.
+Interactively, this is specified with a prefix argument.
+
+If DATA is a question, also mark it as read."
+ (interactive (list (sx--data-here) current-prefix-arg))
+ (sx-assoc-let data
+ (if (not (stringp .link))
+ (sx-message "Nothing to visit here.")
+ (funcall (if copy-as-kill #'kill-new #'browse-url) .link)
+ (when (and (called-interactively-p 'any) copy-as-kill)
+ (message "Copied: %S" .link))
+ (when (and .title (not copy-as-kill))
+ (sx-question--mark-read data)
+ (sx--maybe-update-display)))))
+
+(defun sx-open-link (link)
+ "Visit element given by LINK inside Emacs.
+Element can be a question, answer, or comment."
+ (interactive
+ (let ((def (with-temp-buffer
+ (save-excursion (yank))
+ (thing-at-point 'url))))
+ (list (read-string (concat "Link (" def "): ") nil nil def))))
+ ;; For now, we have no chance of handling chat links, let's just
+ ;; send them to the browser.
+ (if (string-match (rx string-start "http" (opt "s") "://chat.") link)
+ (sx-visit-externally link)
+ (let ((data (sx--link-to-data link)))
+ (sx-assoc-let data
+ (cl-case .type
+ (comment
+ (sx-display-question
+ (sx-question-get-from-comment .site_par .id) 'focus)
+ (sx--find-in-buffer 'comment .id))
+ (answer
+ (sx-display-question
+ (sx-question-get-from-answer .site_par .id) 'focus)
+ (sx--find-in-buffer 'answer .id))
+ (question
+ (sx-display-question
+ (sx-question-get-question .site_par .id) 'focus))
+ (t (error "Don't know how to open this link, please file a bug report: %s"
+ link)
+ nil))))))
+
+\f
+;;; Displaying
+(defun sx-display (&optional data)
+ "Display object given by DATA.
+Interactively, display object under point. Object can be a
+question, an answer, or an inbox_item.
+
+This is meant for interactive use. In lisp code, use
+object-specific functions such as `sx-display-question' and the
+likes."
+ (interactive (list (sx--data-here)))
+ (sx-assoc-let data
+ (cond
+ ;; This is an attempt to identify when we have the question
+ ;; object itself, so there's no need to fetch anything. This
+ ;; happens inside the question-list, but it can be easily
+ ;; confused with the inbox (whose items have a title, a body, and
+ ;; a question_id).
+ ((and .title .question_id .score
+ (not .item_type) (not .notification_type))
+ (sx-display-question data 'focus))
+ (.answer_id
+ (sx-display-question
+ (sx-question-get-from-answer .site_par .answer_id)
+ 'focus)
+ (if .comment_id
+ (sx--find-in-buffer 'comment .comment_id)
+ (sx--find-in-buffer 'answer .answer_id)))
+ (.question_id
+ (sx-display-question
+ (sx-question-get-question .site_par .question_id) 'focus)
+ (when .comment_id
+ (sx--find-in-buffer 'comment .comment_id)))
+ ;; `sx-question-get-from-comment' takes 2 api requests, so we
+ ;; test it last.
+ (.comment_id
+ (sx-display-question
+ (sx-question-get-from-comment .site_par .comment_id) 'focus)
+ (sx--find-in-buffer 'comment .comment_id))
+ (.notification_type
+ (sx-message "Viewing notifications is not yet implemented"))
+ (.item_type (sx-open-link .link)))))
+
+(defun sx-display-question (&optional data focus window)
+ "Display question given by DATA, on WINDOW.
+Interactively, display question under point. When FOCUS is
+non-nil (the default when called interactively), also focus the
+relevant window.
+
+If WINDOW nil, the window is decided by
+`sx-question-mode-display-buffer-function'."
+ (interactive (list (sx--data-here 'question) t))
+ (when (sx-question--mark-read data)
+ (sx--maybe-update-display))
+ ;; Display the question.
+ (setq window
+ (get-buffer-window
+ (sx-question-mode--display data window)))
+ (when focus
+ (if (window-live-p window)
+ (select-window window)
+ (switch-to-buffer sx-question-mode--buffer))))
+
+\f
+;;; Simple interactions
+(defun sx-favorite (data &optional undo)
+ "Favorite question given by DATA.
+Interactively, it is guessed from context at point.
+With the UNDO prefix argument, unfavorite the question instead."
+ (interactive (list (sx--error-if-unread (sx--data-here 'question))
+ current-prefix-arg))
+ (sx-method-post-from-data data
+ (if undo 'favorite/undo 'favorite)
+ :callback (sx--copy-update-callback data)))
+(defalias 'sx-star #'sx-favorite)
+
+(defun sx-accept (data &optional undo)
+ "Accept answer given by DATA.
+Interactively, it is guessed from context at point.
+With the UNDO prefix argument, unaccept the question instead."
+ (interactive (list (sx--data-here 'answer)
+ current-prefix-arg))
+ (sx-ensure-authentication)
+ ;; When clicking the "Accept" button, first arg is a marker.
+ (when (markerp data)
+ (setq data (sx--marker-to-data data 'answer)))
+ (sx-method-post-from-data data
+ (if undo 'accept/undo 'accept)
+ :callback (sx--copy-update-callback data)))
+
+\f
+;;; Voting
+(defun sx-upvote (data &optional undo)
+ "Upvote an object given by DATA.
+DATA can be a question, answer, or comment. Interactively, it is
+guessed from context at point.
+With UNDO prefix argument, remove upvote instead of applying it."
+ (interactive (list (sx--error-if-unread (sx--data-here))
+ current-prefix-arg))
+ (sx-set-vote data "upvote" (not undo)))
+
+(defun sx-downvote (data &optional undo)
+ "Downvote an object given by DATA.
+DATA can be a question or an answer. Interactively, it is guessed
+from context at point.
+With UNDO prefix argument, remove downvote instead of applying it."
+ (interactive (list (sx--error-if-unread (sx--data-here))
+ current-prefix-arg))
+ (sx-set-vote data "downvote" (not undo)))
+
+(defun sx-set-vote (data type status)
+ "Set the DATA's vote TYPE to STATUS.
+DATA can be a question, answer, or comment. TYPE can be
+\"upvote\" or \"downvote\". STATUS is a boolean.
+
+Besides posting to the api, DATA is also altered to reflect the
+changes."
+ (sx-ensure-authentication)
+ (sx-method-post-from-data data
+ (concat type (unless status "/undo"))
+ :callback (sx--copy-update-callback data)))
+
+\f
+;;; Delete
+(defun sx-delete (data &optional undo)
+ "Delete an object given by DATA.
+DATA can be a question, answer, or comment. Interactively, it is
+guessed from context at point.
+With UNDO prefix argument, undelete instead."
+ (interactive (list (sx--error-if-unread (sx--data-here))
+ current-prefix-arg))
+ (sx-ensure-authentication)
+ (when (y-or-n-p (format "DELETE this %s? "
+ (let-alist data
+ (cond (.comment_id "comment")
+ (.answer_id "answer")
+ (.question_id "question")))))
+ (sx-method-post-from-data data
+ (if undo 'delete/undo 'delete)
+ :callback (sx--make-update-callback
+ ;; Indicate to ourselves this has been deleted.
+ (setcdr data (cons (car data) (cdr data)))
+ (setcar data 'deleted)))))
+
+\f
+;;; Commenting
+(defun sx-comment (data &optional text)
+ "Post a comment on DATA given by TEXT.
+DATA can be a question, an answer, or a comment. Interactively,
+it is guessed from context at point.
+If DATA is a comment, the comment is posted as a reply to it.
+
+TEXT is a string. Interactively, it is read from the minibufer."
+ (interactive (list (sx--error-if-unread (sx--data-here)) 'query))
+ (sx-ensure-authentication)
+ ;; When clicking the "Add a Comment" button, first arg is a marker.
+ (when (markerp data)
+ (setq data (sx--marker-to-data data))
+ (setq text 'query))
+ (sx-assoc-let data
+ ;; Get the comment text
+ (when (eq text 'query)
+ (setq text (read-string
+ "Comment text: "
+ (when .comment_id
+ (substring-no-properties (sx-user--format "%@ " .owner)))))
+ (while (not (sx--comment-valid-p text 'silent))
+ (setq text (read-string "Comment text (between 16 and 600 characters): " text))))
+ ;; If non-interactive, `text' could be anything.
+ (unless (stringp text)
+ (error "Comment body must be a string"))
+ ;; And post
+ (let ((result
+ (sx-method-call 'posts
+ :id (or .post_id .answer_id .question_id)
+ :submethod "comments/add"
+ :auth 'warn
+ :url-method 'post
+ :filter sx-browse-filter
+ :site .site_par
+ :keywords `((body . ,text)))))
+ ;; The api returns the new DATA.
+ (when result
+ (sx--add-comment-to-object
+ (sx--ensure-owner-in-object (list (cons 'display_name "(You)")) (elt result 0))
+ (if .post_id (sx--get-post .post_type .site_par .post_id) data))
+ ;; Display the changes in `data'.
+ (sx--maybe-update-display)))))
+
+(defun sx--comment-valid-p (&optional text silent)
+ "Non-nil if TEXT fits stack exchange comment length limits.
+If TEXT is nil, use `buffer-string'. Must have more than 15 and
+less than 601 characters.
+If SILENT is nil, message the user about this limit."
+ (let ((w (string-width (or text (buffer-string)))))
+ (if (and (< 15 w) (< w 601))
+ t
+ (unless silent
+ (message "Comments must be within 16 and 600 characters."))
+ nil)))
+
+(defun sx--get-post (type site id)
+ "Find in the database a post identified by TYPE, SITE and ID.
+TYPE is `question' or `answer'.
+SITE is a string.
+ID is an integer."
+ (let ((db (cons sx-question-mode--data
+ sx-question-list--dataset)))
+ (setq db
+ (cond
+ ((string= type "question") db)
+ ((string= type "answer")
+ (apply #'cl-map 'list #'identity
+ (mapcar (lambda (x) (cdr (assoc 'answers x))) db)))))
+ (car (cl-member-if
+ (lambda (x) (sx-assoc-let x
+ (and (equal (or .answer_id .question_id) id)
+ (equal .site_par site))))
+ db))))
+
+(defun sx--add-comment-to-object (comment object)
+ "Add COMMENT to OBJECT's `comments' property.
+OBJECT can be a question or an answer."
+ (let ((com-cell (assoc 'comments object)))
+ (if com-cell
+ (progn
+ (setcdr
+ com-cell
+ (append (cdr com-cell)
+ (list comment))))
+ ;; No previous comments, add it manually.
+ (setcdr object (cons (car object) (cdr object)))
+ (setcar object `(comments . (,comment)))))
+ object)
+
+(defun sx--ensure-owner-in-object (owner object)
+ "Add `owner' property with value OWNER to OBJECT."
+ (unless (cdr-safe (assq 'owner object))
+ (setcdr object (cons (car object) (cdr object)))
+ (setcar object `(owner . ,owner)))
+ object)
+
+\f
+;;; Editing
+(defun sx-edit (data)
+ "Start editing an answer or question given by DATA.
+DATA is an answer or question alist. Interactively, it is guessed
+from context at point."
+ (interactive (list (sx--data-here)))
+ (sx-ensure-authentication)
+ ;; If we ever make an "Edit" button, first arg is a marker.
+ (when (markerp data) (setq data (sx--data-here)))
+ (sx-assoc-let data
+ (let ((buffer (current-buffer)))
+ (pop-to-buffer
+ (sx-compose-create
+ .site_par data
+ ;; Before send hook
+ (when .comment_id (list #'sx--comment-valid-p))
+ ;; After send functions
+ (list (lambda (_ res)
+ (sx--copy-data (elt res 0) data)
+ (sx--maybe-update-display buffer))))))))
+
+\f
+;;; Asking
+(defcustom sx-default-site "emacs"
+ "Name of the site to use by default when listing questions."
+ :type 'string
+ :group 'sx)
+
+(defun sx--interactive-site-prompt ()
+ "Query the user for a site."
+ (let ((default (or sx-question-list--site
+ (sx-assoc-let sx-question-mode--data .site_par)
+ sx-default-site)))
+ (sx-completing-read
+ (format "Site (%s): " default)
+ (sx-site-get-api-tokens) nil t nil nil
+ default)))
+
+(defun sx--maybe-site-prompt (arg)
+ "Get a site token conditionally in an interactive context.
+If ARG is non-nil, use `sx--interactive-site-prompt'.
+Otherwise, use `sx-question-list--site' if non-nil.
+If nil, use `sx--interactive-site-prompt' anyway."
+ ;; This could eventually be generalized into (sx--maybe-prompt
+ ;; prefix-arg value-if-non-nil #'prompt-function).
+ (if arg
+ (sx--interactive-site-prompt)
+ (or sx-question-list--site
+ (sx--interactive-site-prompt))))
+
+;;;###autoload
+(defun sx-ask (site)
+ "Start composing a question for SITE.
+SITE is a string, indicating where the question will be posted."
+ (interactive (list (sx--interactive-site-prompt)))
+ (sx-ensure-authentication)
+ (let ((buffer (current-buffer)))
+ (pop-to-buffer
+ (sx-compose-create
+ site nil nil
+ ;; After send functions
+ (list (lambda (_b _res) (sx--maybe-update-display buffer)))))))
+
+\f
+;;; Answering
+(defun sx-answer (data)
+ "Start composing an answer for question given by DATA.
+DATA is a question alist. Interactively, it is guessed from
+context at point. "
+ ;; If the user tries to answer a question that's not viewed, he
+ ;; probaby hit the button by accident.
+ (interactive
+ (list (sx--error-if-unread (sx--data-here 'question))))
+ (sx-ensure-authentication)
+ ;; When clicking the "Write an Answer" button, first arg is a marker.
+ (when (markerp data) (setq data (sx--data-here)))
+ (let ((buffer (current-buffer)))
+ (sx-assoc-let data
+ (pop-to-buffer
+ (sx-compose-create
+ .site_par .question_id nil
+ ;; After send functions
+ (list (lambda (_ res)
+ (sx--add-answer-to-question-object (elt res 0) data)
+ (sx--maybe-update-display buffer .site_par .question_id))))))))
+
+(defun sx--add-answer-to-question-object (answer question)
+ "Add alist ANSWER to alist QUESTION in the right place."
+ (let ((cell (assoc 'answers question)))
+ (if cell
+ (setcdr cell (append (cdr cell) (list answer)))
+ ;; No previous comments, add it manually.
+ (setcdr question (cons (car question) (cdr question)))
+ (setcar question `(answers . (,answer))))
+ question))
+
+(provide 'sx-interaction)
+;;; sx-interaction.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+;;; sx-load.el --- load all files of the SX package -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Artur Malabarba
+
+;; Author: Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; Code:
+(mapc #'require
+ '(sx
+ sx-time
+ sx-auth
+ sx-button
+ sx-babel
+ sx-cache
+ sx-compose
+ sx-encoding
+ sx-favorites
+ sx-filter
+ sx-inbox
+ sx-interaction
+ sx-method
+ sx-networks
+ sx-notify
+ sx-question
+ sx-question-list
+ sx-question-mode
+ sx-question-print
+ sx-request
+ sx-search
+ sx-site
+ sx-switchto
+ sx-tab
+ sx-tag
+ ))
+
+(provide 'sx-load)
+;;; sx-load.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+;;; sx-method.el --- method calls -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Sean Allred
+
+;; Author: Sean Allred <codeNOSPAMseanallred.com>
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; This file is effectively a common-use wrapper for
+;;; `sx-request-make'. It provides higher-level handling such as
+;;; (authentication, filters, ...) that `sx-request-make' doesn't need
+;;; to handle.
+
+;;; Code:
+(require 'json)
+(require 'url)
+(require 'sx)
+(require 'sx-auth)
+(require 'sx-request)
+(require 'sx-filter)
+
+(cl-defun sx-method-call (method &key id
+ submethod
+ keywords
+ page
+ (pagesize 100)
+ (filter '(()))
+ auth
+ (url-method 'get)
+ get-all
+ (process-function
+ #'sx-request-response-get-items)
+ callback
+ site)
+ "Call METHOD with additional keys.
+
+ID is the id associated with a question, answer, comment, post or
+user.
+SUBMETHOD is the additional segments of the method.
+KEYWORDS are the api parameters. Some parameters have their own
+keywords too, for convenience. The KEYWORDS argument overrides
+parameter specific keywords.
+FILTER is the set of filters to control the returned information
+AUTH defines how to act if the method or filters require
+authentication.
+URL-METHOD is either `post' or `get'
+SITE is the api parameter specifying the site.
+GET-ALL is nil or non-nil
+PROCESS-FUNCTION is a response-processing function
+PAGE is the page number which will be requested
+PAGESIZE is the number of items to retrieve per request, default
+100
+CALLBACK is a function to be called if the request succeeds. It
+is given the returned result as an argument.
+
+When AUTH is nil, it is assumed that no auth-requiring filters or
+methods will be used. If they are an error will be signaled. This is
+to ensure awareness of where auth is needed.
+
+When AUTH Is t, filters will automatically use a non-auth subset if
+no `access_token' is available. Methods requiring auth will instead
+use `sx-request-fallback' rather than have a failed API response.
+This is meant to allow for UI pages where portions may require AUTH
+but could still be used without authenticating (i.e a launch/home page).
+
+When AUTH is 'warn, methods will signal a `user-error'. This is meant
+for interactive commands that absolutely require authentication
+\(submitting questions/answers, reading inbox, etc). Filters will
+treat 'warn as equivalent to t.
+
+If GET-ALL is nil, this method will only return the first (or
+specified) page available from this method call. If t, all pages
+will be retrieved (`sx-request-all-stop-when-no-more') .
+Otherwise, it is a function STOP-WHEN for `sx-request-all-items'.
+
+If PROCESS-FUNCTION is nil, only the items of the response will
+be returned (`sx-request-response-get-items'). Otherwise, it is
+a function that processes the entire response (as returned by
+`json-read').
+
+See `sx-request-make' and `sx-request-all-items'.
+
+Return the entire response as a complex alist."
+ (declare (indent 1))
+ (let ((access-token (sx-cache-get 'auth))
+ (method-auth (sx-auth--method-p method submethod))
+ (filter-auth (sx-auth--filter-p filter))
+ (full-method (concat (format "%s" method)
+ (when id
+ (format "/%s" id))
+ (when submethod
+ (format "/%s" submethod))
+ ;; On GET methods site is buggy, so we
+ ;; need to provide it as a url argument.
+ (when (and site (eq url-method 'get))
+ (prog1
+ (format "?site=%s" site)
+ (setq site nil)))))
+ (call (if get-all #'sx-request-all-items #'sx-request-make))
+ (get-all
+ (cond
+ ((eq get-all t) #'sx-request-all-stop-when-no-more)
+ (t get-all))))
+ (lwarn "sx-call-method" :debug "A: %S T: %S. M: %S,%s. F: %S" (equal 'warn auth)
+ access-token method-auth full-method filter-auth)
+ (unless access-token
+ (cond
+ ;; 1. Need auth and warn user (interactive use)
+ ((and method-auth (equal 'warn auth))
+ (sx-user-error
+ "This request requires authentication. Please run `M-x sx-authenticate' and try again."))
+ ;; 2. Need auth to populate UI, cannot provide subset
+ ((and method-auth auth)
+ (setq call 'sx-request-fallback))
+ ;; 3. Need auth for type. Use auth-less filter.
+ ((and filter-auth auth)
+ (setq filter filter-auth))
+ ;; 4. Requires auth but no value set for auth
+ ((and (or filter-auth method-auth) (not auth))
+ (error "This request requires authentication."))))
+ ;; Concatenate all parameters now that filter is ensured.
+ (push `(filter . ,(sx-filter-get-var filter)) keywords)
+ (unless (assq 'page keywords)
+ (push `(page . ,page) keywords))
+ (unless (assq 'pagesize keywords)
+ (push `(pagesize . ,pagesize) keywords))
+ (when site
+ (push `(site . ,site) keywords))
+ (let ((result (funcall call
+ full-method
+ keywords
+ url-method
+ (or get-all process-function))))
+ (when callback
+ (funcall callback result))
+ result)))
+
+(defun sx-method-post-from-data (data &rest keys)
+ "Make a POST `sx-method-call', deriving parameters from DATA.
+KEYS are [KEYWORD VALUE] pairs passed to `sx-method-call', except
+the following which are decided by this function:
+
+ METHOD :site and :id are derived from DATA, where METHOD is
+ either \"answers\", \"comments\", or \"questions\".
+ :url-method is post.
+ :filter is `sx-browse-filter'.
+ :auth is warn.
+
+As a special exception, if the car of KEYS is not a keyword, it
+is assumed to be the :submethod argument."
+ (declare (indent 1))
+ (sx-assoc-let data
+ (apply #'sx-method-call
+ (cond (.comment_id "comments")
+ (.answer_id "answers")
+ (.question_id "questions"))
+ :id (or .comment_id .answer_id .question_id)
+ :auth 'warn
+ :url-method 'post
+ :filter sx-browse-filter
+ :site .site_par
+ (if (keywordp (car keys))
+ keys
+ (cons :submethod keys)))))
+
+(provide 'sx-method)
+;;; sx-method.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+;;; sx-networks.el --- user network information -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Sean Allred
+
+;; Author: Sean Allred <codeNOSPAMseanallred.com>
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file provides logic for retrieving information about the user
+;; across the entire network, e.g. their registered sites.
+
+;;; Code:
+
+(require 'sx-method)
+(require 'sx-cache)
+(require 'sx-site)
+(require 'sx-filter)
+
+(defvar sx-network--user-information nil
+ "User information for the various sites.")
+
+(defvar sx-network--user-information-alist nil
+ "User information for the various site parameters.")
+
+(defvar sx-network--user-sites nil
+ "List of sites where user already has an account.")
+
+(defconst sx-network--user-filter
+ (sx-filter-from-nil
+ ((badge_count bronze
+ silver
+ gold)
+ (network_user account_id
+ answer_count
+ badge_counts
+ creation_date
+ last_access_date
+ reputation
+ site_name
+ site_url
+ user_id
+ user_type))))
+
+(defun sx-network--get-associated ()
+ "Retrieve cached information for network user.
+If cache is not available, retrieve current data."
+ (unless (setq sx-network--user-information (sx-cache-get 'network-user))
+ (sx-network--update))
+ (let ((url-par-alist (mapcar (lambda (x)
+ (cons (cdr (assoc 'site_url x))
+ (cdr (assoc 'api_site_parameter
+ x))))
+ (sx-site--get-site-list))))
+ (setq sx-network--user-sites nil)
+ (setq sx-network--user-information-alist nil)
+ (mapc (lambda (nu)
+ (let ((parameter (cdr (assoc (cdr (assq 'site_url nu))
+ url-par-alist))))
+ (push parameter sx-network--user-sites)
+ (push (cons parameter nu)
+ sx-network--user-information-alist)))
+ sx-network--user-information)))
+
+(defun sx-network--update ()
+ "Update user information.
+Sets cache and then uses `sx-network--get-associated' to update
+the variables."
+ (setq sx-network--user-information
+ (sx-method-call 'me
+ :submethod 'associated
+ :keywords '((types . (main_site meta_site)))
+ :filter sx-network--user-filter
+ :auth t))
+ (sx-cache-set 'network-user sx-network--user-information))
+
+(defun sx-network--initialize ()
+ "Ensure network-user cache is available.
+Added as hook to initialization."
+ ;; Cache was not retrieved, retrieve it.
+ (sx-network--get-associated))
+(add-hook 'sx-init--internal-hook #'sx-network--initialize)
+
+(defun sx-network-user (site)
+ "Return an alist containing user information for SITE."
+ (cdr (assoc site sx-network--user-information-alist)))
+
+(provide 'sx-networks)
+;;; sx-networks.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+;;; sx-notify.el --- mode-line notifications -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Artur Malabarba
+
+;; Author: Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+\f
+;;; Code:
+
+(require 'sx)
+(require 'sx-inbox)
+
+\f
+;;; mode-line notification
+(defconst sx-notify--mode-line
+ '((sx-inbox--unread-inbox (sx-inbox--unread-notifications " ["))
+ (sx-inbox--unread-inbox
+ (:propertize
+ (:eval (format "i:%s" (length sx-inbox--unread-inbox)))
+ face mode-line-buffer-id
+ mouse-face mode-line-highlight))
+ (sx-inbox--unread-inbox (sx-inbox--unread-notifications " "))
+ (sx-inbox--unread-notifications
+ (:propertize
+ (:eval (format "n:%s" (length sx-inbox--unread-notifications)))
+ mouse-face mode-line-highlight))
+ (sx-inbox--unread-inbox (sx-notify--unread-notifications "]")))
+ "")
+(put 'sx-notify--mode-line 'risky-local-variable t)
+
+\f
+;;; minor-mode definition
+(defcustom sx-notify-timer-delay (* 60 5)
+ "Idle time, in seconds, before querying for inbox items."
+ :type 'integer
+ :group 'sx-notify)
+
+(defvar sx-notify--timer nil
+ "Timer used for fetching notifications.")
+
+(define-minor-mode sx-notify-mode nil nil nil nil
+ :global t
+ (if sx-notify-mode
+ (progn
+ (add-to-list 'global-mode-string '(t sx-notify--mode-line) 'append)
+ (setq sx-notify--timer
+ (run-with-idle-timer sx-notify-timer-delay 'repeat
+ #'sx-notify--update-unread)))
+ (when (timerp sx-notify--timer)
+ (cancel-timer sx-notify--timer)
+ (setq sx-notify--timer nil))
+ (setq global-mode-string
+ (delete '(t sx-notify--mode-line) global-mode-string))))
+
+(defun sx-notify--update-unread ()
+ "Update the lists of unread notifications."
+ (setq sx-inbox--unread-inbox
+ (cl-remove-if
+ (lambda (x) (member (cdr (assq 'link x)) sx-inbox--read-inbox))
+ (sx-inbox-get)))
+ (setq sx-inbox--unread-notifications
+ (cl-remove-if
+ (lambda (x) (member (cdr (assq 'link x)) sx-inbox--read-notifications))
+ (sx-inbox-get t))))
+
+(provide 'sx-notify)
+;;; sx-notify.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+(define-package "sx" "20150702.818" "StackExchange client. Ask and answer questions on Stack Overflow, Super User, and the likes"
+ '((emacs "24.1")
+ (cl-lib "0.5")
+ (json "1.3")
+ (markdown-mode "2.0")
+ (let-alist "1.0.3"))
+ :url "https://github.com/vermiculus/sx.el/" :keywords
+ '("help" "hypermedia" "tools"))
+;; Local Variables:
+;; no-byte-compile: t
+;; End:
--- /dev/null
+;;; sx-question-list.el --- major-mode for navigating questions list -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Artur Malabarba
+
+;; Author: Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Provides question list logic (as used in e.g. `sx-tab-frontpage').
+
+;;; Code:
+(require 'tabulated-list)
+(require 'cl-lib)
+
+(require 'sx)
+(require 'sx-switchto)
+(require 'sx-time)
+(require 'sx-tag)
+(require 'sx-site)
+(require 'sx-question)
+(require 'sx-question-mode)
+(require 'sx-favorites)
+
+(defgroup sx-question-list nil
+ "Customization group for sx-question-list."
+ :prefix "sx-question-list-"
+ :tag "SX Question List"
+ :group 'sx)
+
+(defgroup sx-question-list-faces nil
+ "Customization group for the faces of `sx-question-list'."
+ :prefix "sx-question-list-"
+ :tag "SX Question List Faces"
+ :group 'sx-question-list)
+
+\f
+;;; Customization
+(defcustom sx-question-list-height 12
+ "Height, in lines, of SX's *question-list* buffer."
+ :type 'integer
+ :group 'sx-question-list)
+
+(defcustom sx-question-list-excluded-tags nil
+ "List of tags (strings) to be excluded from the question list."
+ :type '(repeat string)
+ :group 'sx-question-list)
+
+(defface sx-question-list-parent
+ '((t :inherit default))
+ ""
+ :group 'sx-question-list-faces)
+
+(defface sx-question-list-answers
+ '((((background light)) :foreground "SeaGreen4"
+ :height 1.0 :inherit sx-question-list-parent)
+ (((background dark)) :foreground "#D1FA71"
+ :height 1.0 :inherit sx-question-list-parent)
+ (t :inherit sx-question-list-parent))
+ ""
+ :group 'sx-question-list-faces)
+
+(defface sx-question-list-answers-accepted
+ '((t :box 1 :inherit sx-question-list-answers))
+ ""
+ :group 'sx-question-list-faces)
+
+(defface sx-question-list-score
+ '((t :height 1.0 :inherit sx-question-list-parent))
+ ""
+ :group 'sx-question-list-faces)
+
+(defface sx-question-list-score-upvoted
+ '((t :weight bold
+ :inherit sx-question-list-score))
+ ""
+ :group 'sx-question-list-faces)
+
+(defface sx-question-list-date
+ '((t :inherit font-lock-comment-face))
+ ""
+ :group 'sx-question-list-faces)
+
+(defface sx-question-list-read-question
+ '((t :height 1.0 :inherit sx-question-list-parent))
+ ""
+ :group 'sx-question-list-faces)
+
+(defface sx-question-list-unread-question
+ '((t :weight bold :inherit sx-question-list-read-question))
+ ""
+ :group 'sx-question-list-faces)
+
+(defface sx-question-list-favorite
+ '((t :inherit sx-question-list-score-upvoted))
+ ""
+ :group 'sx-question-list-faces)
+
+(defface sx-question-list-bounty
+ '((t :inherit font-lock-warning-face))
+ ""
+ :group 'sx-question-list-faces)
+
+\f
+;;; Backend variables
+
+(defvar sx-question-list--site nil
+ "Site being displayed in the *question-list* buffer.")
+(make-variable-buffer-local 'sx-question-list--site)
+
+(defvar sx-question-list--order nil
+ "Order being displayed in the *question-list* buffer.
+This is also affected by `sx-question-list--descending'.")
+(make-variable-buffer-local 'sx-question-list--order)
+
+(defvar sx-question-list--descending t
+ "In which direction should `sx-question-list--order' be sorted.
+If non-nil (default), descending.
+If nil, ascending.")
+(make-variable-buffer-local 'sx-question-list--order)
+
+(defvar sx-question-list--print-function #'sx-question-list--print-info
+ "Function to convert a question alist into a tabulated-list entry.
+Used by `sx-question-list-mode', the default value is
+`sx-question-list--print-info'.
+
+If this is set to a different value, it may be necessary to
+change `tabulated-list-format' accordingly.")
+(make-variable-buffer-local 'sx-question-list--print-function)
+
+(defcustom sx-question-list-ago-string " ago"
+ "String appended to descriptions of the time since something happened.
+Used in the questions list to indicate a question was updated
+\"4d ago\"."
+ :type 'string
+ :group 'sx-question-list)
+
+(defun sx-question-list--print-info (question-data)
+ "Convert `json-read' QUESTION-DATA into tabulated-list format.
+
+This is the default printer used by `sx-question-list'. It
+assumes QUESTION-DATA is an alist containing (at least) the
+elements:
+ `question_id', `site_par', `score', `upvoted', `answer_count',
+ `title', `bounty_amount', `bounty_amount', `bounty_amount',
+ `last_activity_date', `tags', `owner'.
+
+Also see `sx-question-list-refresh'."
+ (sx-assoc-let question-data
+ (let ((favorite (if (member .question_id
+ (assoc .site_par
+ sx-favorites--user-favorite-list))
+ (if (char-displayable-p ?\x2b26) "\x2b26" "*") " ")))
+ (list
+ question-data
+ (vector
+ (list (int-to-string .score)
+ 'face (if .upvoted 'sx-question-list-score-upvoted
+ 'sx-question-list-score))
+ (list (int-to-string .answer_count)
+ 'face (if (sx-question--accepted-answer-id question-data)
+ 'sx-question-list-answers-accepted
+ 'sx-question-list-answers))
+ (concat
+ ;; First line
+ (propertize
+ .title
+ 'face (if (sx-question--read-p question-data)
+ 'sx-question-list-read-question
+ 'sx-question-list-unread-question))
+ (propertize " " 'display "\n ")
+ ;; Second line
+ (propertize favorite 'face 'sx-question-list-favorite)
+ (if (and (numberp .bounty_amount) (> .bounty_amount 0))
+ (propertize (format "%4d" .bounty_amount)
+ 'face 'sx-question-list-bounty)
+ " ")
+ " "
+ (propertize (format "%3s%s"
+ (sx-time-since .last_activity_date)
+ sx-question-list-ago-string)
+ 'face 'sx-question-list-date)
+ " "
+ ;; @TODO: Make this width customizable. (Or maybe just make
+ ;; the whole thing customizable)
+ (format "%-40s" (sx-tag--format-tags .tags sx-question-list--site))
+ " "
+ (sx-user--format "%15d %4r" .owner)
+ (propertize " " 'display "\n")))))))
+
+(defvar sx-question-list--pages-so-far 0
+ "Number of pages currently being displayed.
+This variable gets reset to 0 before every refresh.
+It should be used by `sx-question-list--next-page-function'.")
+(make-variable-buffer-local 'sx-question-list--pages-so-far)
+
+(defvar sx-question-list--refresh-function nil
+ "Function used to refresh the list of questions to be displayed.
+Used by `sx-question-list-mode', this is a function, called with
+no arguments, which returns a list questions to be displayed,
+like the one returned by `sx-question-get-questions'.
+
+If this is not set, the value of `sx-question-list--dataset' is
+used, and the list is simply redisplayed.")
+(make-variable-buffer-local 'sx-question-list--refresh-function)
+
+(defvar sx-question-list--next-page-function nil
+ "Function used to fetch the next page of questions to be displayed.
+Used by `sx-question-list-mode'. This is a function, called with
+no arguments, which returns a list questions to be displayed,
+like the one returned by `sx-question-get-questions'.
+
+This function will be called when the user presses \\<sx-question-list-mode-map>\\[sx-question-list-next] at the end
+of the question list. It should either return nil (indicating
+\"no more questions\") or return a list of questions which will
+appended to the currently displayed list.
+
+If this is not set, it's the same as a function which always
+returns nil.")
+(make-variable-buffer-local 'sx-question-list--next-page-function)
+
+(defvar sx-question-list--dataset nil
+ "The logical data behind the displayed list of questions.
+This dataset contains even questions that are hidden by the user,
+and thus not displayed in the list of questions.
+
+This is ignored if `sx-question-list--refresh-function' is set.")
+(make-variable-buffer-local 'sx-question-list--dataset)
+
+(defconst sx-question-list--key-definitions
+ '(
+ ;; S-down and S-up would collide with `windmove'.
+ ("<down>" sx-question-list-next)
+ ("<up>" sx-question-list-previous)
+ ("RET" sx-display "Display")
+ ("n" sx-question-list-next "Navigate")
+ ("p" sx-question-list-previous "Navigate")
+ ("j" sx-question-list-view-next "Navigate")
+ ("k" sx-question-list-view-previous "Navigate")
+ ("N" sx-question-list-next-far)
+ ("P" sx-question-list-previous-far)
+ ("J" sx-question-list-next-far)
+ ("K" sx-question-list-previous-far)
+ ("g" sx-question-list-refresh)
+ ("t" sx-tab-switch "tab")
+ ("a" sx-ask "ask")
+ ("S" sx-search "Search")
+ ("s" sx-switchto-map "switch-to")
+ ("v" sx-visit-externally "visit")
+ ("u" sx-upvote)
+ ("d" sx-downvote)
+ ("h" sx-question-list-hide "hide")
+ ("m" sx-question-list-mark-read "mark-read")
+ ("*" sx-favorite)
+ )
+ "List of key definitions for `sx-question-list-mode'.
+This list must follow the form described in
+`sx--key-definitions-to-header-line'.")
+
+(defconst sx-question-list--header-line
+ (sx--key-definitions-to-header-line
+ sx-question-list--key-definitions)
+ "Header-line used on the question list.")
+
+(defvar sx-question-list--order-methods
+ '(("Recent Activity" . activity)
+ ("Creation Date" . creation)
+ ("Most Voted" . votes)
+ ("Score" . votes))
+ "Alist of possible values to be passed to the `sort' keyword.")
+(make-variable-buffer-local 'sx-question-list--order-methods)
+
+(defun sx-question-list--interactive-order-prompt (&optional prompt)
+ "Interactively prompt for a sorting order.
+PROMPT is displayed to the user. If it is omitted, a default one
+is used."
+ (let ((order (sx-completing-read
+ (or prompt "Order questions by: ")
+ (mapcar #'car sx-question-list--order-methods))))
+ (cdr-safe (assoc-string order sx-question-list--order-methods))))
+
+(defun sx-question-list--make-pager (method &optional submethod)
+ "Return a function suitable for use as a question list pager.
+Meant to be used as `sx-question-list--next-page-function'."
+ (lambda (page)
+ (sx-method-call method
+ :keywords `((page . ,page)
+ ,@(when sx-question-list--order
+ `((order . ,(if sx-question-list--descending 'desc 'asc))
+ (sort . ,sx-question-list--order))))
+ :site sx-question-list--site
+ :auth t
+ :submethod submethod
+ :filter sx-browse-filter)))
+
+\f
+;;; Mode Definition
+
+(defvar sx-question-list--current-tab "Latest"
+ ;; @TODO Other values (once we implement them) are "Top Voted",
+ ;; "Unanswered", etc.
+ "Variable describing current tab being viewed.")
+
+(defconst sx-question-list--mode-line-format
+ '(" "
+ (:propertize
+ (:eval (sx--pretty-site-parameter sx-question-list--site))
+ face mode-line-buffer-id)
+ " " mode-name ": "
+ (:propertize sx-question-list--current-tab
+ face mode-line-buffer-id)
+ " ["
+ "Unread: "
+ (:propertize
+ (:eval (sx-question-list--unread-count))
+ face mode-line-buffer-id)
+ ", "
+ "Total: "
+ (:propertize
+ (:eval (int-to-string (length tabulated-list-entries)))
+ face mode-line-buffer-id)
+ "] ")
+ "Mode-line construct to use in question-list buffers.")
+
+(define-derived-mode sx-question-list-mode
+ tabulated-list-mode "Question List"
+ "Major mode for browsing a list of questions from StackExchange.
+Letters do not insert themselves; instead, they are commands.
+
+The recommended way of using this mode is to activate it and then
+set `sx-question-list--next-page-function'. The return value of
+this function is mapped with `sx-question-list--print-function',
+so you may need to customize the latter if the former does not
+return a list of questions.
+
+The full list of variables which can be set is:
+ 1. `sx-question-list--site'
+ Set this to the name of the site if that makes sense. If it
+ doesn't leave it as nil.
+ 2. `sx-question-list--print-function'
+ Change this if the data you're dealing with is not strictly a
+ list of questions (see the doc for details).
+ 3. `sx-question-list--refresh-function'
+ This is used to populate the initial list. It is only necessary
+ if item 4 does not fit your needs.
+ 4. `sx-question-list--next-page-function'
+ This is used to fetch further questions. If item 3 is nil, it is
+ also used to populate the initial list.
+ 5. `sx-question-list--dataset'
+ This is only used if both 3 and 4 are nil. It can be used to
+ display a static list.
+ 6. `sx-question-list--order'
+ Set this to the `sort' method that should be used when
+ requesting the list, if that makes sense. If it doesn't
+ leave it as nil.
+\\<sx-question-list-mode-map>
+If none of these is configured, the behaviour is that of a
+\"Frontpage\", for the site given by
+`sx-question-list--site'.
+
+Item 2 is mandatory, but it also has a sane default which is
+usually enough.
+
+As long as one of 3, 4, or 5 is provided, the other two are
+entirely optional. Populating or refreshing the list of questions
+is done in the following way:
+ - Set `sx-question-list--pages-so-far' to 1.
+ - Call function 2.
+ - If function 2 is not given, call function 3 with argument 1.
+ - If 3 is not given use the value of 4.
+
+Adding further questions to the bottom of the list is done by:
+ - Increment `sx-question-list--pages-so-far'.
+ - Call function 3 with argument `sx-question-list--pages-so-far'.
+ - If it returns anything, append to the dataset and refresh the
+ display; otherwise, decrement `sx-question-list--pages-so-far'.
+
+If `sx-question-list--site' is given, items 3 and 4 should take it
+into consideration. The same holds for `sx-question-list--order'.
+
+\\{sx-question-list-mode-map}"
+ (hl-line-mode 1)
+ (setq mode-line-format
+ sx-question-list--mode-line-format)
+ (setq sx-question-list--pages-so-far 0)
+ (setq tabulated-list-format
+ [(" V" 3 t :right-align t)
+ (" A" 3 t :right-align t)
+ ("Title" 0 sx-question-list--date-more-recent-p)])
+ (setq tabulated-list-padding 1)
+ ;; Sorting by title actually sorts by date. It's what we want, but
+ ;; it's not terribly intuitive.
+ (setq tabulated-list-sort-key nil)
+ (add-hook 'tabulated-list-revert-hook
+ #'sx-question-list-refresh nil t)
+ ;; This is the default value, but we'll error out if the user has
+ ;; set it to nil.
+ (setq tabulated-list-use-header-line t)
+ (tabulated-list-init-header)
+ (setq header-line-format sx-question-list--header-line))
+
+(defcustom sx-question-list-date-sort-method 'last_activity_date
+ "Parameter which controls date sorting."
+ ;; This should be made into a (choice ...) of constants.
+ :type 'symbol
+ ;; Add a setter to protect the value.
+ :group 'sx-question-list)
+
+(sx--create-comparator sx-question-list--date-more-recent-p
+ "Non-nil if tabulated-entry A is newer than B."
+ #'> (lambda (x)
+ (cdr (assq sx-question-list-date-sort-method (car x)))))
+
+\f
+;;; Keybinds
+;; We need this quote+eval combo because `kbd' was a macro in 24.2.
+(mapc (lambda (x) (eval `(define-key sx-question-list-mode-map
+ (kbd ,(car x)) #',(cadr x))))
+ sx-question-list--key-definitions)
+
+(sx--define-conditional-key sx-question-list-mode-map "O" #'sx-question-list-order-by
+ (and (boundp 'sx-question-list--order) sx-question-list--order))
+
+(defun sx-question-list-hide (data)
+ "Hide question under point.
+Non-interactively, DATA is a question alist."
+ (interactive
+ (list (if (derived-mode-p 'sx-question-list-mode)
+ (tabulated-list-get-id)
+ (sx-user-error "Not in `sx-question-list-mode'"))))
+ (sx-question--mark-hidden data)
+ ;; The current entry will not be present after the list is
+ ;; redisplayed. To avoid `tabulated-list-mode' getting lost (and
+ ;; sending us to the top) we move to the next entry before
+ ;; redisplaying.
+ (forward-line 1)
+ (when (called-interactively-p 'any)
+ (sx-question-list-refresh 'redisplay 'noupdate)))
+
+(defun sx-question-list-mark-read (data)
+ "Mark as read question under point.
+Non-interactively, DATA is a question alist."
+ (interactive
+ (list (if (derived-mode-p 'sx-question-list-mode)
+ (tabulated-list-get-id)
+ (sx-user-error "Not in `sx-question-list-mode'"))))
+ (sx-question--mark-read data)
+ (sx-question-list-next 1)
+ (when (called-interactively-p 'any)
+ (sx-question-list-refresh 'redisplay 'noupdate)))
+
+(defun sx-question-list--unread-count ()
+ "Number of unread questions in current dataset, as a string."
+ (int-to-string
+ (cl-count-if-not
+ #'sx-question--read-p sx-question-list--dataset)))
+
+(defun sx-question-list--remove-excluded-tags (question-list)
+ "Return QUESTION-LIST, with some questions removed.
+Removes all questions hidden by the user, as well as those
+containing a tag in `sx-question-list-excluded-tags'."
+ (cl-remove-if (lambda (q)
+ (or (sx-question--hidden-p q)
+ (cl-intersection (let-alist q .tags)
+ sx-question-list-excluded-tags
+ :test #'string=)))
+ question-list))
+
+(defun sx-question-list-refresh (&optional redisplay no-update)
+ "Update the list of questions.
+If REDISPLAY is non-nil (or if interactive), also call `tabulated-list-print'.
+If the prefix argument NO-UPDATE is nil, query StackExchange for
+a new list before redisplaying."
+ (interactive "p\nP")
+ ;; Reset the mode-line unread count (we rebuild it here).
+ (unless no-update
+ (setq sx-question-list--pages-so-far 1))
+ (let* ((question-list
+ (or (and no-update sx-question-list--dataset)
+ (and (functionp sx-question-list--refresh-function)
+ (funcall sx-question-list--refresh-function))
+ (and (functionp sx-question-list--next-page-function)
+ (funcall sx-question-list--next-page-function 1))
+ sx-question-list--dataset))
+ ;; Preserve window positioning.
+ (window (get-buffer-window (current-buffer)))
+ (old-start (when window (window-start window))))
+ ;; The dataset contains everything. Hiding and filtering is done
+ ;; on the `tabulated-list-entries' below.
+ (setq sx-question-list--dataset question-list)
+ ;; Print the result.
+ (setq tabulated-list-entries
+ (mapcar sx-question-list--print-function
+ (sx-question-list--remove-excluded-tags
+ sx-question-list--dataset)))
+ (when redisplay
+ (tabulated-list-print 'remember))
+ (when window
+ (set-window-start window old-start)))
+ (sx-message "Done."))
+
+(defun sx-question-list-view-previous (n)
+ "Move cursor up N questions up and display this question.
+Displayed in `sx-question-mode--window', replacing any question
+that may currently be there."
+ (interactive "p")
+ (sx-question-list-view-next (- n)))
+
+(defun sx-question-list-view-next (n)
+ "Move cursor down N questions and display this question.
+Displayed in `sx-question-mode--window', replacing any question
+that may currently be there."
+ (interactive "p")
+ (sx-question-list-next n)
+ (sx-question-mode--display
+ (tabulated-list-get-id)
+ (sx-question-list--create-question-window)))
+
+(defun sx-question-list--create-question-window ()
+ "Create or find a window where a question can be displayed.
+
+If any current window displays a question, that window is
+returned. If none do, a new one is created such that the
+question-list window remains `sx-question-list-height' lines
+high (if possible)."
+ (or (sx-question-mode--get-window)
+ ;; Create a proper window.
+ (let ((window
+ (condition-case er
+ (split-window (selected-window) sx-question-list-height 'below)
+ (error
+ ;; If the window is too small to split, use any one.
+ (if (string-match
+ "Window #<window .*> too small for splitting"
+ (car (cdr-safe er)))
+ (next-window)
+ (error (cdr er)))))))
+ ;; Configure the window to be closed on `q'.
+ (set-window-prev-buffers window nil)
+ (set-window-parameter
+ window 'quit-restore
+ ;; See (info "(elisp) Window Parameters")
+ `(window window ,(selected-window) ,sx-question-mode--buffer))
+ window)))
+
+(defvar sx-question-list--last-refresh (current-time)
+ "Time of the latest refresh.")
+
+(defun sx-question-list-next (n)
+ "Move cursor down N questions.
+This does not update `sx-question-mode--window'."
+ (interactive "p")
+ (if (and (< n 0) (bobp))
+ (when (> (time-to-seconds
+ (time-subtract (current-time) sx-question-list--last-refresh))
+ 1)
+ (sx-question-list-refresh 'redisplay)
+ (setq sx-question-list--last-refresh (current-time)))
+ (forward-line n)
+ ;; If we were trying to move forward, but we hit the end.
+ (when (eobp)
+ ;; Try to get more questions.
+ (sx-question-list-next-page))
+ (sx-question-list--ensure-line-good-line-position)))
+
+(defun sx-question-list--ensure-line-good-line-position ()
+ "Scroll window such that current line is a good place.
+Check if we're at least 6 lines from the bottom. Scroll up if
+we're not. Do the same for 3 lines from the top."
+ ;; At least one entry below us.
+ (let ((lines-to-bottom (count-screen-lines (point) (window-end))))
+ (unless (>= lines-to-bottom 6)
+ (recenter (- 6))))
+ ;; At least one entry above us.
+ (let ((lines-to-top (count-screen-lines (point) (window-start))))
+ (unless (>= lines-to-top 3)
+ (recenter 3))))
+
+(defun sx-question-list-next-page ()
+ "Fetch and display the next page of questions."
+ (interactive)
+ ;; Stay at the last line.
+ (goto-char (point-max))
+ (forward-line -1)
+ (when (functionp sx-question-list--next-page-function)
+ ;; Try to get more questions
+ (let ((list
+ (funcall sx-question-list--next-page-function
+ (1+ sx-question-list--pages-so-far))))
+ (if (null list)
+ (message "No further questions.")
+ ;; If it worked, increment the variable.
+ (cl-incf sx-question-list--pages-so-far)
+ ;; And update the dataset.
+ ;; @TODO: Check for duplicates.
+ (setq sx-question-list--dataset
+ (append sx-question-list--dataset list))
+ (sx-question-list-refresh 'redisplay 'no-update)
+ (forward-line 1)))))
+
+(defun sx-question-list-previous (n)
+ "Move cursor up N questions.
+This does not update `sx-question-mode--window'."
+ (interactive "p")
+ (sx-question-list-next (- n)))
+
+(defcustom sx-question-list-far-step-size 5
+ "How many questions `sx-question-list-next-far' skips."
+ :type 'integer
+ :group 'sx-question-list
+ :package-version '(sx-question-list . ""))
+
+(defun sx-question-list-next-far (n)
+ "Move cursor up N*`sx-question-list-far-step-size' questions.
+This does not update `sx-question-mode--window'."
+ (interactive "p")
+ (sx-question-list-next (* n sx-question-list-far-step-size)))
+
+(defun sx-question-list-previous-far (n)
+ "Move cursor up N questions.
+This does not update `sx-question-mode--window'."
+ (interactive "p")
+ (sx-question-list-next-far (- n)))
+
+(defun sx-question-list-switch-site (site)
+ "Switch the current site to SITE and display its questions.
+Retrieve completions from `sx-site-get-api-tokens'.
+Sets `sx-question-list--site' and then call
+`sx-question-list-refresh' with `redisplay'."
+ (interactive
+ (list (sx-completing-read
+ "Switch to site: " (sx-site-get-api-tokens)
+ (lambda (site) (not (equal site sx-question-list--site)))
+ t)))
+ (when (and (stringp site) (> (length site) 0))
+ (setq sx-question-list--site site)
+ (sx-question-list-refresh 'redisplay)))
+
+(defun sx-question-list-order-by (sort &optional ascend)
+ "Order questions in the current list by the method SORT.
+Sets `sx-question-list--order' and then calls
+`sx-question-list-refresh' with `redisplay'.
+
+With a prefix argument or a non-nil ASCEND, invert the sorting
+order."
+ (interactive
+ (list (when sx-question-list--order
+ (sx-question-list--interactive-order-prompt))
+ current-prefix-arg))
+ (unless sx-question-list--order
+ (sx-user-error "This list can't be reordered"))
+ (when (and sort (symbolp sort))
+ (setq sx-question-list--order sort)
+ (setq sx-question-list--descending (not ascend))
+ (sx-question-list-refresh 'redisplay)))
+
+(provide 'sx-question-list)
+;;; sx-question-list.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+;;; sx-question-mode.el --- major-mode for displaying questions -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Artur Malabarba
+
+;; Author: Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file provides a means to print questions with their answers
+;; and all comments. See the customizable group `sx-question-mode'.
+
+\f
+;;; Code:
+(eval-when-compile
+ (require 'rx))
+
+(require 'sx)
+(require 'sx-switchto)
+(require 'sx-question)
+(require 'sx-question-print)
+
+\f
+;;; Displaying a question
+(defcustom sx-question-mode-display-buffer-function #'pop-to-buffer
+ "Function used to display the question buffer.
+Called, for instance, when hitting \\<sx-question-list-mode-map>`\\[sx-question-list-display-question]' on an entry in the
+question list.
+This is not used when navigating the question list with `\\[sx-question-list-view-next].
+
+Common values for this variable are `pop-to-buffer' and `switch-to-buffer'."
+ :type 'function
+ :group 'sx-question-mode)
+
+(defvar sx-question-mode--buffer nil
+ "Buffer being used to display questions.")
+
+(defvar sx-question-mode--data nil
+ "The data of the question being displayed.")
+(make-variable-buffer-local 'sx-question-mode--data)
+
+(defun sx-question-mode--get-window ()
+ "Return a window displaying a question, or nil."
+ (car-safe
+ (cl-member-if
+ (lambda (x) (with-selected-window x
+ (derived-mode-p 'sx-question-mode)))
+ (window-list nil 'never nil))))
+
+(defun sx-question-mode--display (data &optional window)
+ "Display question given by DATA on WINDOW.
+If WINDOW is nil, use selected one.
+
+Returns the question buffer."
+ (with-current-buffer
+ (sx-question-mode--display-buffer window)
+ (sx-question-mode--erase-and-print-question data)))
+
+(defun sx-question-mode--erase-and-print-question (data)
+ "Erase contents of buffer and print question given by DATA.
+Also marks the question as read with `sx-question--mark-read'."
+ (sx--ensure-site data)
+ (sx-question--mark-read data)
+ (let ((inhibit-read-only t))
+ (erase-buffer)
+ (sx-question-mode)
+ (sx-question-mode--print-question data)
+ (current-buffer)))
+
+(defun sx-question-mode--display-buffer (window)
+ "Display and return the buffer used for displaying a question.
+Create `sx-question-mode--buffer' if necessary.
+If WINDOW is given, use that to display the buffer."
+ ;; Create the buffer if necessary.
+ (unless (buffer-live-p sx-question-mode--buffer)
+ (setq sx-question-mode--buffer
+ (generate-new-buffer "*sx-question*")))
+ (cond
+ ;; Window was given, use it.
+ ((window-live-p window)
+ (set-window-buffer window sx-question-mode--buffer))
+ ;; No window, but the buffer is already being displayed somewhere.
+ ((get-buffer-window sx-question-mode--buffer 'visible))
+ ;; Neither, so we create the window.
+ (t (funcall sx-question-mode-display-buffer-function
+ sx-question-mode--buffer)))
+ sx-question-mode--buffer)
+
+\f
+;;; Movement commands
+;; Sections are headers placed above a question's content or an
+;; answer's content, or above the list of comments. They are
+;; identified with the `sx-question-mode--section' text property.
+;; To move between sections, just search for the property. The value
+;; of the text-property is the depth of the section (1 for contents, 2
+;; for comments).
+(defcustom sx-question-mode-recenter-line 0
+ "Screen line to which we recenter after moving between sections.
+This is used as an argument to `recenter', only used if the end
+of section is outside the window.
+
+If nil, no recentering is performed."
+ :type '(choice (const :tag "Don't recenter" nil)
+ integer)
+ :group 'sx-question-mode)
+
+(defun sx-question-mode-next-section (&optional n)
+ "Move down to next section (question or answer) of this buffer.
+Prefix argument N moves N sections down or up."
+ (interactive "p")
+ (let ((count (if n (abs n) 1)))
+ (while (> count 0)
+ ;; This will either move us to the next section, or move out of
+ ;; the current one.
+ (unless (sx--goto-property-change 'sx-question-mode--section n)
+ ;; If all we did was move out the current one, then move again
+ ;; and we're guaranteed to reach the next section.
+ (sx--goto-property-change 'sx-question-mode--section n))
+ (unless (get-char-property (point) 'invisible)
+ (cl-decf count))))
+ (when (equal (selected-window) (get-buffer-window))
+ (when sx-question-mode-recenter-line
+ (let ((ov (sx-question-mode--section-overlays-at (line-end-position))))
+ (when (and (overlayp ov) (> (overlay-end ov) (window-end)))
+ (recenter sx-question-mode-recenter-line))))
+ (sx-message-help-echo)))
+
+(defun sx-question-mode-previous-section (&optional n)
+ "Move down to previous section (question or answer) of this buffer.
+Prefix argument moves N sections up or down."
+ (interactive "p")
+ (sx-question-mode-next-section (- (or n 1))))
+
+(defun sx-question-mode-hide-show-section (&optional _)
+ "Hide or show section under point.
+Optional argument _ is for `push-button'."
+ (interactive)
+ (let ((ov (or (sx-question-mode--section-overlays-at
+ (line-end-position))
+ (sx-question-mode--section-overlays-at (point)))))
+ (unless (overlayp ov)
+ (sx-user-error "Not inside a question or answer"))
+ (goto-char (overlay-start ov))
+ (forward-line 0)
+ (overlay-put
+ ov 'invisible
+ (null (overlay-get ov 'invisible)))))
+
+(defun sx-question-mode--section-overlays-at (pos)
+ "Return the highest priority section overlay at POS.
+A section overlay has a `sx-question-mode--section-content'
+property."
+ (cdr-safe (get-char-property-and-overlay
+ pos 'sx-question-mode--section-content nil)))
+
+\f
+;;; Major-mode constants
+(defconst sx-question-mode--key-definitions
+ '(
+ ("<down>" sx-question-mode-next-section)
+ ("<up>" sx-question-mode-previous-section)
+ ("n" sx-question-mode-next-section "Navigate")
+ ("p" sx-question-mode-previous-section "Navigate")
+ ("g" sx-question-mode-refresh)
+ ("v" sx-visit-externally)
+ ("u" sx-upvote "upvote")
+ ("d" sx-downvote "downvote")
+ ("q" quit-window)
+ ("SPC" scroll-up-command)
+ ("e" sx-edit "edit")
+ ("S" sx-search)
+ ("*" sx-favorite "star")
+ ("K" sx-delete "Delete")
+ ("s" sx-switchto-map "switch-to")
+ ("O" sx-question-mode-order-by "Order")
+ ("c" sx-comment "comment")
+ ("a" sx-answer "answer")
+ ("TAB" forward-button "Navigate")
+ ("<S-iso-lefttab>" backward-button)
+ ("<S-tab>" backward-button)
+ ("<backtab>" backward-button))
+ "List of key definitions for `sx-question-mode'.
+This list must follow the form described in
+`sx--key-definitions-to-header-line'.")
+
+(defconst sx-question-mode--header-line
+ (sx--key-definitions-to-header-line
+ sx-question-mode--key-definitions)
+ "Header-line used on the question list.")
+
+\f
+;;; Major-mode definition
+(defconst sx-question-mode--mode-line
+ '(" "
+ ;; `sx-question-mode--data' is guaranteed to have through
+ ;; `sx--ensure-site' already, so we use `let-alist' instead of
+ ;; `sx-assoc-let' to improve performance (since the mode-line is
+ ;; updated a lot).
+ (:propertize
+ (:eval (sx--pretty-site-parameter
+ (let-alist sx-question-mode--data .site_par)))
+ face mode-line-buffer-id)
+ " " mode-name
+ " ["
+ "Answers: "
+ (:propertize
+ (:eval (number-to-string (let-alist sx-question-mode--data .answer_count)))
+ face mode-line-buffer-id)
+ ", "
+ "Stars: "
+ (:propertize
+ (:eval (number-to-string (or (let-alist sx-question-mode--data .favorite_count) 0)))
+ face mode-line-buffer-id)
+ ", "
+ "Views: "
+ (:propertize
+ (:eval (number-to-string (let-alist sx-question-mode--data .view_count)))
+ face mode-line-buffer-id)
+ "] ")
+ "Mode-line construct to use in `sx-question-mode' buffers.")
+
+(define-derived-mode sx-question-mode special-mode "Question"
+ "Major mode to display and navigate a question and its answers.
+Letters do not insert themselves; instead, they are commands.
+
+Don't activate this mode directly. Instead, to print a question
+on the current buffer use
+`sx-question-mode--erase-and-print-question'.
+
+\\<sx-question-mode>
+\\{sx-question-mode}"
+ (setq header-line-format sx-question-mode--header-line)
+ (setq mode-line-format sx-question-mode--mode-line)
+ (set (make-local-variable 'nobreak-char-display) nil)
+ ;; Determine how to close this window.
+ (unless (window-parameter nil 'quit-restore)
+ (set-window-parameter
+ nil 'quit-restore
+ `(other window nil ,(current-buffer))))
+ ;; We call font-lock-region manually. See `sx-question-mode--insert-markdown'.
+ (font-lock-mode -1)
+ (remove-hook 'after-change-functions 'markdown-check-change-for-wiki-link t)
+ (remove-hook 'window-configuration-change-hook
+ 'markdown-fontify-buffer-wiki-links t))
+
+;; We need this quote+eval combo because `kbd' was a macro in 24.2.
+(mapc (lambda (x) (eval `(define-key sx-question-mode-map
+ (kbd ,(car x)) #',(cadr x))))
+ sx-question-mode--key-definitions)
+
+(defun sx-question-mode-refresh (&optional no-update)
+ "Refresh currently displayed question.
+Queries the API for any changes to the question or its answers or
+comments, and redisplays it.
+
+With non-nil prefix argument NO-UPDATE, just redisplay, don't
+query the api."
+ (interactive "P")
+ (sx-question-mode--ensure-mode)
+ (let ((point (point))
+ (line (count-screen-lines
+ (window-start) (point))))
+ (sx-question-mode--erase-and-print-question
+ (if no-update
+ sx-question-mode--data
+ (sx-assoc-let sx-question-mode--data
+ (sx-question-get-question .site_par .question_id))))
+ (goto-char point)
+ (when (equal (selected-window)
+ (get-buffer-window (current-buffer)))
+ (recenter line)))
+ (sx-message "Done."))
+
+(defun sx-question-mode--ensure-mode ()
+ "Ensures we are in question mode, erroring otherwise."
+ (unless (derived-mode-p 'sx-question-mode)
+ (error "Not in `sx-question-mode'")))
+
+(defun sx-question-mode-order-by (sort)
+ "Order answers in the current buffer by the method SORT.
+Sets `sx-question-list--order' and then calls
+`sx-question-list-refresh' with `redisplay'."
+ (interactive
+ (list (let ((order (sx-completing-read "Order answers by: "
+ (mapcar #'car sx-question-mode--sort-methods))))
+ (cdr-safe (assoc-string order sx-question-mode--sort-methods)))))
+ (when (and sort (functionp sort))
+ (setq sx-question-mode-answer-sort-function sort)
+ (sx-question-mode-refresh 'no-update)))
+
+(provide 'sx-question-mode)
+;;; sx-question-mode.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+;;; sx-question-print.el --- populating the question-mode buffer with content -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Artur Malabarba
+
+;; Author: Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+\f
+;;; Code:
+(require 'markdown-mode)
+(require 'sx-button)
+(require 'sx)
+(require 'sx-question)
+(require 'sx-babel)
+(require 'sx-user)
+
+(defvar sx-question-mode--data)
+
+(defgroup sx-question-mode nil
+ "Customization group for sx-question-mode."
+ :prefix "sx-question-mode-"
+ :tag "SX Question Mode"
+ :group 'sx)
+
+(defgroup sx-question-mode-faces '((sx-user custom-group))
+ "Customization group for the faces of `sx-question-mode'.
+Some faces of this mode might be defined in the `sx-user' group."
+ :prefix "sx-question-mode-"
+ :tag "SX Question Mode Faces"
+ :group 'sx-question-mode)
+
+\f
+;;; Faces and Variables
+(defface sx-question-mode-header
+ '((t :inherit font-lock-variable-name-face))
+ "Face used on the question headers in the question buffer."
+ :group 'sx-question-mode-faces)
+
+(defface sx-question-mode-title
+ '((t :weight bold :inherit default))
+ "Face used on the question title in the question buffer."
+ :group 'sx-question-mode-faces)
+
+(defface sx-question-mode-title-comments
+ '((t :inherit sx-question-mode-title))
+ "Face used on the question title in the question buffer."
+ :group 'sx-question-mode-faces)
+
+(defcustom sx-question-mode-header-title "\n"
+ "String used before the question title at the header."
+ :type 'string
+ :group 'sx-question-mode)
+
+(defcustom sx-question-mode-header-author-format "\nAuthor: %d %r"
+ "String used to display the question author at the header.
+% constructs have special meaning here. See `sx-user--format'."
+ :type 'string
+ :group 'sx-question-mode)
+
+(defface sx-question-mode-date
+ '((t :inherit font-lock-string-face))
+ "Face used on the question date in the question buffer."
+ :group 'sx-question-mode-faces)
+
+(defcustom sx-question-mode-header-date "\nPosted on: "
+ "String used before the question date at the header."
+ :type 'string
+ :group 'sx-question-mode)
+
+(defface sx-question-mode-score
+ '((t))
+ "Face used for the score in the question buffer."
+ :group 'sx-question-mode-faces)
+
+(defface sx-question-mode-score-downvoted
+ '((t :inherit (font-lock-warning-face sx-question-mode-score)))
+ "Face used for downvoted score in the question buffer."
+ :group 'sx-question-mode-faces)
+
+(defface sx-question-mode-score-upvoted
+ '((t :weight bold
+ :inherit (font-lock-function-name-face sx-question-mode-score)))
+ "Face used for downvoted score in the question buffer."
+ :group 'sx-question-mode-faces)
+
+(defcustom sx-question-mode-header-tags "\nTags: "
+ "String used before the question tags at the header."
+ :type 'string
+ :group 'sx-question-mode)
+
+(defcustom sx-question-mode-header-score "\nScore: "
+ "String used before the question score at the header."
+ :type 'string
+ :group 'sx-question-mode)
+
+(defface sx-question-mode-content-face
+ '((((background dark)) :background "#090909")
+ (((background light)) :background "#f4f4f4"))
+ "Face used on the question body in the question buffer.
+This shouldn't have a foreground, or this will interfere with
+font-locking."
+ :group 'sx-question-mode-faces)
+
+(defcustom sx-question-mode-last-edit-format " (edited %s ago by %s)"
+ "Format used to describe last edit date in the header.
+First \"%s\" is replaced with the date and the second \"%s\" with
+the editor's name."
+ :type 'string
+ :group 'sx-question-mode)
+
+(defcustom sx-question-mode-separator
+ (concat (propertize (make-string 72 ?\s)
+ 'face '(underline sx-question-mode-header))
+ "\n")
+ "Separator used between header and body."
+ :type 'string
+ :group 'sx-question-mode)
+
+(defcustom sx-question-mode-answer-title "Answer"
+ "Title used at the start of \"Answer\" sections."
+ :type 'string
+ :group 'sx-question-mode)
+
+(defface sx-question-mode-accepted
+ '((((background dark)) :foreground "LimeGreen"
+ :height 1.3 :inherit sx-question-mode-title)
+ (((background light)) :foreground "ForestGreen"
+ :height 1.3 :inherit sx-question-mode-title))
+ "Face used for accepted answers in the question buffer."
+ :group 'sx-question-mode-faces)
+
+(defface sx-question-mode-closed
+ '((t :box 2 :inherit font-lock-warning-face))
+ "Face used for closed question header in the question buffer."
+ :group 'sx-question-mode-faces)
+
+(defface sx-question-mode-closed-reason
+ `((t :box (:line-width 2 :color ,(face-attribute 'sx-question-mode-closed
+ :foreground nil t))
+ :inherit sx-question-mode-title))
+ "Face used for closed question header in the question buffer.
+Aesthetically, it's important that the color of this face's :box
+attribute match the color of the face `sx-question-mode-closed'."
+ :group 'sx-question-mode-faces)
+
+(defcustom sx-question-mode-answer-accepted-title "Accepted Answer"
+ "Title used at the start of accepted \"Answer\" section."
+ :type 'string
+ :group 'sx-question-mode)
+
+(defcustom sx-question-mode-comments-title " Comments"
+ "Title used at the start of \"Comments\" sections."
+ :type 'string
+ :group 'sx-question-mode)
+
+(defcustom sx-question-mode-comments-format "%s: %s\n"
+ "Format used to display comments.
+First \"%s\" is replaced with user name. Second \"%s\" is
+replaced with the comment."
+ :type 'string
+ :group 'sx-question-mode)
+
+(defcustom sx-question-mode-pretty-links t
+ "If non-nil, markdown links are displayed in a compact form."
+ :type 'boolean
+ :group 'sx-question-mode)
+
+(defconst sx-question-mode--sort-methods
+ (let ((methods
+ '(("Higher-scoring" . sx-answer-higher-score-p)
+ ("Newer" . sx-answer-newer-p)
+ ("More active" . sx-answer-more-active-p))))
+ (append (mapcar (lambda (x) (cons (concat (car x) " first") (cdr x)))
+ methods)
+ (mapcar (lambda (x) (cons (concat (car x) " last")
+ (sx--invert-predicate (cdr x))))
+ methods))))
+
+(defcustom sx-question-mode-answer-sort-function
+ #'sx-answer-higher-score-p
+ "Function used to sort answers in the question buffer."
+ :type
+ (cons 'choice
+ (mapcar (lambda (x) `(const :tag ,(car x) ,(cdr x)))
+ sx-question-mode--sort-methods))
+ :group 'sx-question-mode)
+
+(defcustom sx-question-mode-use-images
+ (eval-when-compile
+ (image-type-available-p 'imagemagick))
+ "Non-nil if SX should download and display images.
+By default, this is `t' if the `imagemagick' image type is
+available (checked with `image-type-available-p'). If this image
+type is not available, images won't work."
+ :type 'boolean
+ :group 'sx-question-mode)
+
+(defcustom sx-question-mode-image-max-width 550
+ "Maximum width, in pixels, of images in the question buffer."
+ :type 'integer
+ :group 'sx-question-mode)
+
+\f
+;;; Functions
+;;;; Printing the general structure
+(defconst sx-question-mode--closed-mode-line-string
+ '(:propertize " [CLOSED] " face font-lock-warning-face)
+ "String indicating closed questions in the mode-line.")
+
+(defun sx-question-mode--print-question (question)
+ "Print a buffer describing QUESTION.
+QUESTION must be a data structure returned by `json-read'."
+ (when (sx--deleted-p question)
+ (sx-user-error "This is a deleted question"))
+ (setq sx-question-mode--data question)
+ ;; Clear the overlays
+ (mapc #'delete-overlay sx--overlays)
+ (setq sx--overlays nil)
+ ;; Print everything
+ (sx-assoc-let question
+ (when .closed_reason
+ (add-to-list 'mode-line-format sx-question-mode--closed-mode-line-string)
+ (sx-question-mode--print-close-reason .closed_reason .closed_date .closed_details))
+ (sx-question-mode--print-section question)
+ (mapc #'sx-question-mode--print-section
+ (cl-remove-if
+ #'sx--deleted-p
+ (cl-sort .answers sx-question-mode-answer-sort-function))))
+ (insert "\n\n ")
+ (insert-text-button "Write an Answer" :type 'sx-button-answer)
+ ;; Go up
+ (goto-char (point-min))
+ (sx-question-mode-next-section))
+
+(defun sx-question-mode--print-close-reason (reason date &optional details)
+ "Print a header explaining REASON and DATE.
+DATE is an integer.
+
+DETAILS, when given is an alist further describing the close."
+ (let ((l (point)))
+ (let-alist details
+ (insert "\n "
+ (propertize (format " %s as %s, %s ago. "
+ (if .on_hold "Put on hold" "Closed")
+ reason
+ (sx-time-since date))
+ 'face 'sx-question-mode-closed)
+ "\n")
+ (when .description
+ (insert (replace-regexp-in-string "<[^>]+>" "" .description)
+ "\n")))
+ (save-excursion
+ (goto-char l)
+ (search-forward " as " nil 'noerror)
+ (setq l (point))
+ (skip-chars-forward "^,")
+ (let ((ov (make-overlay l (point))))
+ (overlay-put ov 'face 'sx-question-mode-closed-reason)
+ (push ov sx--overlays)))))
+
+(defun sx-question-mode--maybe-print-accept-button ()
+ "Print accept button if you own this question."
+ (when (sx-assoc-let sx-question-mode--data
+ (ignore-errors
+ (= .owner.user_id
+ (cdr (assq 'user_id (sx-network-user .site_par))))))
+ (insert " ")
+ (insert-text-button "Accept" :type 'sx-button-accept)))
+
+(defun sx-question-mode--print-section (data)
+ "Print a section corresponding to DATA.
+DATA can represent a question or an answer."
+ ;; This makes `data' accessible through `sx--data-here'.
+ (sx--wrap-in-overlay
+ (list 'sx--data-here data)
+ (sx-assoc-let data
+ (insert sx-question-mode-header-title)
+ (insert-text-button
+ ;; Questions have title, Answers don't
+ (cond (.title)
+ (.is_accepted sx-question-mode-answer-accepted-title)
+ (t sx-question-mode-answer-title))
+ ;; Section level
+ 'sx-question-mode--section (if .title 1 2)
+ 'sx-button-copy .share_link
+ 'face (if .is_accepted 'sx-question-mode-accepted
+ 'sx-question-mode-title)
+ :type 'sx-question-mode-title)
+ (when (not (or .title .is_accepted))
+ (sx-question-mode--maybe-print-accept-button))
+
+ ;; Sections can be hidden with overlays
+ (sx--wrap-in-overlay
+ '(sx-question-mode--section-content t)
+
+ ;; Author
+ (insert
+ (sx-user--format
+ (propertize sx-question-mode-header-author-format
+ 'face 'sx-question-mode-header)
+ .owner))
+
+ ;; Date
+ (sx-question-mode--insert-header
+ sx-question-mode-header-date
+ (concat
+ (sx-time-seconds-to-date .creation_date)
+ (when .last_edit_date
+ (format sx-question-mode-last-edit-format
+ (sx-time-since .last_edit_date)
+ (sx-user--format "%d" .last_editor))))
+ 'sx-question-mode-date)
+
+ ;; Score and upvoted/downvoted status.
+ (sx-question-mode--insert-header
+ sx-question-mode-header-score
+ (format "%s%s" .score
+ (cond (.upvoted "↑") (.downvoted "↓") (t "")))
+ (cond (.upvoted 'sx-question-mode-score-upvoted)
+ (.downvoted 'sx-question-mode-score-downvoted)
+ (t 'sx-question-mode-score)))
+
+ ;; Tags
+ (when .title
+ ;; Tags
+ (sx-question-mode--insert-header
+ sx-question-mode-header-tags
+ (sx-tag--format-tags .tags .site_par)
+ nil))
+ ;; Body
+ (insert "\n" sx-question-mode-separator)
+ (sx--wrap-in-overlay
+ '(face sx-question-mode-content-face)
+ (insert "\n")
+ (sx-question-mode--insert-markdown .body_markdown)
+ (insert "\n" sx-question-mode-separator))
+ ;; Clean up commments manually deleted. The `append' call is
+ ;; to ensure `comments' is a list and not a vector.
+ (let ((comments (cl-remove-if #'sx--deleted-p .comments)))
+ (when comments
+ (insert "\n")
+ (insert-text-button
+ sx-question-mode-comments-title
+ 'face 'sx-question-mode-title-comments
+ 'sx-question-mode--section 3
+ 'sx-button-copy .share_link
+ :type 'sx-question-mode-title)
+ (sx--wrap-in-overlay
+ '(sx-question-mode--section-content t)
+ (insert "\n")
+ (sx--wrap-in-overlay
+ '(face sx-question-mode-content-face)
+ ;; Comments have their own `sx--data-here' property (so they can
+ ;; be upvoted too).
+ (mapc #'sx-question-mode--print-comment comments))
+ ;; If there are comments, we want part of this margin to go
+ ;; inside them, so the button get's placed beside the
+ ;; "Comments" header when you hide them.
+ (insert " ")))
+ ;; If there are no comments, we have to add this margin here.
+ (unless comments
+ (insert " ")))
+ (insert " ")
+ ;; This is where the "add a comment" button is printed.
+ (insert-text-button "Add a Comment"
+ :type 'sx-button-comment)
+ (insert "\n")))))
+
+(defun sx-question-mode--print-comment (comment-data)
+ "Print the comment described by alist COMMENT-DATA.
+The comment is indented, filled, and then printed according to
+`sx-question-mode-comments-format'."
+ (sx--wrap-in-overlay
+ (list 'sx--data-here comment-data)
+ (sx-assoc-let comment-data
+ (when (and (numberp .score) (> .score 0))
+ (insert (number-to-string .score)
+ (if .upvoted "^" "")
+ " "))
+ (insert
+ (format sx-question-mode-comments-format
+ (sx-user--format "%d" .owner)
+ (substring
+ ;; We use temp buffer, so that image overlays don't get
+ ;; inserted with the comment.
+ (with-temp-buffer
+ ;; We fill with three spaces at the start, so the comment is
+ ;; slightly indented.
+ (sx-question-mode--insert-markdown (concat " " (sx--squash-whitespace .body_markdown)))
+ (buffer-string))
+ ;; Then we remove the spaces from the first line, since we'll
+ ;; add the username there anyway.
+ 3))))))
+
+(defun sx-question-mode--insert-header (&rest args)
+ "Insert propertized ARGS.
+ARGS is a list of repeating values -- `header', `value', and
+`face'. `header' is given `sx-question-mode-header' as a face,
+where `value' is given `face' as its face.
+
+\(fn HEADER VALUE FACE [HEADER VALUE FACE] [HEADER VALUE FACE] ...)"
+ (while args
+ (insert
+ (propertize (pop args) 'face 'sx-question-mode-header)
+ (let ((header (pop args))
+ (face (pop args)))
+ (if face (propertize header 'face face)
+ header)))))
+
+\f
+;;;; Printing and Font-locking the content (body)
+(defvar sx-question-mode-bullet-appearance
+ (propertize (if (char-displayable-p ?•) "•" "*")
+ 'face 'markdown-list-face)
+ "String to be displayed as the bullet of markdown list items.")
+
+(defconst sx-question-mode--reference-regexp
+ (rx line-start (0+ blank) "[%s]:" (0+ blank)
+ (group-n 1 (1+ (not (any blank "\n\r")))))
+ "Regexp used to find the url of labeled links.
+E.g.:
+ [1]: https://...")
+
+(defconst sx-question-mode--link-regexp
+ ;; Done at compile time.
+ (rx (or (and "[" (optional (group-n 6 "meta-")) "tag:"
+ (group-n 5 (+ (not (any " ]")))) "]")
+ (and (opt "!") "[" (group-n 1 (1+ (not (any "[]")))) "]"
+ (or (and "(" (group-n 2 (1+ (not (any ")")))) ")")
+ (and "[" (group-n 3 (1+ (not (any "]")))) "]")))
+ (group-n 4 (and "http" (opt "s") "://"
+ (>= 2 (any lower numeric "_%"))
+ "."
+ (>= 2 (any lower numeric "_%"))
+ (* (any lower numeric "-/._%&#?=;"))))))
+ "Regexp matching markdown links.")
+
+(defun sx-question-mode--process-line-breaks (beg end-marker)
+ "Process Markdown line breaks between BEG and END-MARKER.
+Double space at the end of a line becomes an invisible \"\\n\".
+Consecutive blank lines beyond the first are consensed.
+Assumes `marker-insertion-type' of END-MARKER is t."
+ (goto-char beg)
+ (while (search-forward-regexp
+ (rx line-start (* blank) "\n"
+ (group-n 1 (+ (any blank "\n"))))
+ end-marker 'noerror)
+ ;; An invisible newline ensures the previous text
+ ;; will get filled as a separate paragraph.
+ (replace-match "" nil nil nil 1))
+ (goto-char beg)
+ (while (search-forward-regexp " $" end-marker 'noerror)
+ ;; An invisible newline ensures the previous text
+ ;; will get filled as a separate paragraph.
+ (replace-match (propertize "\n" 'invisible t))))
+
+(defun sx-question-mode--process-markdown-in-region (beg end)
+ "Process Markdown text between BEG and END.
+This does not do Markdown font-locking. Instead, it fills text,
+propertizes links, inserts images, cleans up html comments, and
+font-locks code-blocks according to mode."
+ ;; Paragraph filling
+ (let ((paragraph-start
+ "\f\\|[ \t]*$\\|[ \t]*[*+-] \\|[ \t]*[0-9]+\\.[ \t]\\|[ \t]*: ")
+ (paragraph-separate "\\(?:[ \t\f]*\\|.* \\)$")
+ (adaptive-fill-first-line-regexp "\\`[ \t]*>[ \t]*?\\'")
+ (adaptive-fill-function #'markdown-adaptive-fill-function))
+ (save-restriction
+ (narrow-to-region beg end)
+ ;; html tags can span many paragraphs, so we handle them
+ ;; globally first.
+ (sx-question-mode--process-html-tags (point-min) (copy-marker (point-max)))
+ ;; And now the filling and other handlings.
+ (goto-char (point-min))
+ (while (null (eobp))
+ ;; Don't fill pre blocks.
+ (unless (sx-question-mode--dont-fill-here)
+ (let ((beg (point)))
+ (skip-chars-forward "\r\n[:blank:]")
+ (forward-paragraph)
+ (let ((end (point-marker)))
+ (set-marker-insertion-type end t)
+ ;; Turn markdown linebreaks into their final form
+ (sx-question-mode--process-line-breaks beg end)
+ ;; Compactify links by paragraph, so we don't linkify
+ ;; inside code-blocks. This will still linkify inside
+ ;; code tags, unfortunately.
+ (sx-question-mode--process-links beg end)
+ ;; Filling is done after all of the above, since those
+ ;; steps change the length of text.
+ (fill-region beg end)
+ (goto-char end)))))
+ (goto-char (point-max)))))
+
+(defun sx-question-mode--insert-markdown (text)
+ "Return TEXT fontified according to `markdown-mode'."
+ (let ((beg (point)))
+ (insert
+ ;; Font-locking needs to be done in a temp buffer, because it
+ ;; affects the entire buffer even if we narrow.
+ (with-temp-buffer
+ (insert text)
+ ;; Trim whitespace
+ (goto-char (point-max))
+ (skip-chars-backward "\r\n[:blank:]")
+ (delete-region (point) (point-max))
+ (goto-char (point-min))
+ (skip-chars-forward "\r\n[:blank:]")
+ (forward-line 0)
+ (delete-region (point-min) (point))
+ ;; Font lock
+ (delay-mode-hooks (markdown-mode))
+ (font-lock-mode -1)
+ (when sx-question-mode-bullet-appearance
+ (font-lock-add-keywords ;; Bullet items.
+ nil
+ `((,(rx line-start (0+ blank) (group-n 1 (any "*+-")) blank)
+ 1 '(face nil display ,sx-question-mode-bullet-appearance) prepend))))
+ (font-lock-add-keywords ;; Highlight usernames.
+ nil
+ `((,(rx (or blank line-start)
+ (group-n 1 (and "@" (1+ (not space))))
+ symbol-end)
+ 1 font-lock-builtin-face)))
+ ;; Everything.
+ (font-lock-fontify-region (point-min) (point-max))
+ (replace-regexp-in-string "[[:blank:]]+\\'" "" (buffer-string))))
+ ;; This part can and should be done in place, this way it can
+ ;; create overlays.
+ (sx-question-mode--process-markdown-in-region beg (point))))
+
+\f
+;;; HTML tags
+(defconst sx-question-mode--html-tag-regexp
+ (rx "<" (group-n 1 "%s") (* (not (any ">"))) ">"))
+
+(defface sx-question-mode-sub-sup-tag
+ '((t :height 0.7))
+ "Face used on <sub> and <sup> tags."
+ :group 'sx-question-mode-faces)
+
+(defface sx-question-mode-kbd-tag
+ '((((background dark))
+ :height 0.9
+ :weight semi-bold
+ :box (:line-width 3 :style released-button :color "gray30"))
+ (((background light))
+ :height 0.9
+ :weight semi-bold
+ :box (:line-width 3 :style released-button :color "gray70")))
+ "Face used on <kbd> tags."
+ :group 'sx-question-mode-faces)
+
+(defun sx-question-mode--inside-code-p ()
+ "Return non-nil if point is inside code.
+This can be inline Markdown code or a Markdown code-block."
+ (save-match-data
+ (or (markdown-code-at-point-p)
+ (save-excursion
+ (sx-question-mode--skip-and-fontify-pre 'dont-fontify)))))
+
+(defun sx-question-mode--process-html-tags (beg end-marker)
+ "Hide all html tags between BEG and END and possibly interpret them.
+END-MARKER should be a marker."
+ ;; This code understands nested html, but not if the same tag is
+ ;; nested in itself (e.g., <kbd><kbd></kbd></kbd>).
+ (set-marker-insertion-type end-marker t)
+ (goto-char beg)
+ (while (search-forward-regexp
+ (format sx-question-mode--html-tag-regexp "[[:alpha:]]+")
+ end-marker 'noerror)
+ (unless (sx-question-mode--inside-code-p)
+ (let ((tag (match-string 1))
+ (l (match-beginning 0)))
+ (replace-match "")
+ (when (search-forward-regexp
+ (format sx-question-mode--html-tag-regexp (concat "/" tag))
+ ;; Searching for a match has no bounds.
+ end-marker 'noerror)
+ (let ((r (copy-marker (match-beginning 0))))
+ ;; The code tag is special, because it quotes everything inside.
+ (if (string= tag "code")
+ (progn (replace-match "`")
+ (save-excursion (goto-char l) (insert "`")))
+ (replace-match "")
+ ;; Handle stuff between the two tags.
+ (save-match-data (sx-question-mode--process-html-tags l r))
+ (cond
+ ((string= tag "kbd")
+ (add-text-properties l r '(face sx-question-mode-kbd-tag))
+ (when (looking-at-p
+ (format sx-question-mode--html-tag-regexp "kbd"))
+ (insert " ")))
+ ((string= tag "sub")
+ (add-text-properties
+ l r '(face sx-question-mode-sub-sup-tag display (raise -0.3))))
+ ((string= tag "sup")
+ (add-text-properties
+ l r '(face sx-question-mode-sub-sup-tag display (raise +0.3))))))))))))
+
+\f
+;;; Handling links
+(defun sx-question-mode--process-links (beg end-marker)
+ "Turn all markdown links between BEG and ENG into compact format.
+Image links are downloaded and displayed, if
+`sx-question-mode-use-images' is non-nil.
+Assumes `marker-insertion-type' of END-MARKER is t."
+ (goto-char beg)
+ (while (search-forward-regexp sx-question-mode--link-regexp end-marker t)
+ ;; Tags are tag-buttons.
+ (let ((tag (match-string-no-properties 5)))
+ (if (and tag (> (length tag) 0))
+ (progn (replace-match "")
+ (sx-tag--insert tag))
+ ;; Other links are link-buttons.
+ (let* ((text (match-string-no-properties 1))
+ (url (or (match-string-no-properties 2)
+ (match-string-no-properties 4)
+ (sx-question-mode-find-reference
+ (match-string-no-properties 3)
+ text)))
+ (full-text (match-string-no-properties 0))
+ (image-p (and sx-question-mode-use-images
+ (eq ?! (elt full-text 0)))))
+ (when (stringp url)
+ (replace-match "")
+ (sx-question-mode--insert-link
+ (cond (image-p (sx-question-mode--create-image url))
+ ((and sx-question-mode-pretty-links text))
+ ((not text) (sx--shorten-url url))
+ (t full-text))
+ url)))))))
+
+(defun sx-question-mode--create-image (url)
+ "Get and create an image from URL and insert it at POINT.
+The image will take the place of the character at POINT.
+Its size is bound by `sx-question-mode-image-max-width' and
+`window-body-width'."
+ (let* ((ov (make-overlay (point) (point) (current-buffer) t nil))
+ (callback
+ (lambda (data)
+ (let* ((image (create-image data 'imagemagick t))
+ (image-width (car (image-size image 'pixels))))
+ (overlay-put
+ ov 'display
+ (append image
+ (list :width (min sx-question-mode-image-max-width
+ (window-body-width nil 'pixel)
+ image-width))))))))
+ (sx-request-get-url url callback)
+ (overlay-put ov 'face 'default)
+ ov))
+
+(defun sx-question-mode--insert-link (text url)
+ "Return a link propertized version of TEXT-OR-IMAGE.
+URL is used as 'help-echo and 'url properties."
+ ;; Try to handle an image/link inside another link.
+ (when (eq (char-before) ?\[)
+ (insert "a")
+ (forward-char -2)
+ (if (looking-at sx-question-mode--link-regexp)
+ (progn (setq url (or (match-string-no-properties 2)
+ (match-string-no-properties 4)
+ (sx-question-mode-find-reference
+ (match-string-no-properties 3)
+ (if (stringp text) text "¶"))
+ url))
+ (replace-match ""))
+ (forward-char 1)
+ (delete-char 1)))
+ (unless (stringp text)
+ ;; Images need to be at the start of a line.
+ (unless (looking-at-p "^") (insert "\n"))
+ ;; And need an empty line above so they don't get wrapped into
+ ;; text when we do filling.
+ (insert (propertize "\n" 'display "")))
+ ;; Insert the link button.
+ (insert-text-button (if (stringp text) text "¶")
+ ;; Mouse-over
+ 'help-echo
+ (format sx-button--link-help-echo
+ ;; If TEXT is a shortened url, we don't shorten URL.
+ (propertize (if (and (stringp text)
+ (string-match "^https?:" text))
+ url (sx--shorten-url url))
+ 'face 'font-lock-function-name-face))
+ ;; For visiting and stuff.
+ 'sx-button-url url
+ 'sx-button-copy url
+ :type 'sx-button-link)
+ ;; Images need to be at the end of a line too.
+ (unless (stringp text)
+ (move-overlay text (1- (point)) (point) (current-buffer))
+ (insert (propertize "\n\n" 'display "\n"))))
+
+(defun sx-question-mode-find-reference (id &optional fallback-id)
+ "Find url identified by reference ID in current buffer.
+If ID is nil, use FALLBACK-ID instead."
+ (save-excursion
+ (save-match-data
+ (goto-char (point-min))
+ (when (search-forward-regexp
+ (format sx-question-mode--reference-regexp
+ (or id fallback-id))
+ nil t)
+ (match-string-no-properties 1)))))
+
+\f
+;;; Things we don't fill
+(defun sx-question-mode--dont-fill-here ()
+ "If text shouldn't be filled here, return t and skip over it."
+ (catch 'sx-question-mode-done
+ (let ((before (point)))
+ (skip-chars-forward "\r\n[:blank:]")
+ (let ((first-non-blank (point)))
+ (dolist (it '(sx-question-mode--skip-and-fontify-pre
+ sx-question-mode--skip-headline
+ sx-question-mode--skip-references
+ sx-question-mode--skip-comments))
+ ;; If something worked, keep point where it is and return t.
+ (if (funcall it) (throw 'sx-question-mode-done t)
+ ;; Before calling each new function. Go back to the first
+ ;; non-blank char.
+ (goto-char first-non-blank)))
+ ;; If nothing matched, go back to the very beginning.
+ (goto-char before)
+ ;; And return nil
+ nil))))
+
+(defun sx-question-mode--skip-and-fontify-pre (&optional dont-fontify)
+ "If there's a pre block ahead, handle it, skip it and return t.
+Handling means to turn it into a button and remove erroneous
+font-locking.
+
+If DONT-FONTIFY is non-nil, just return the result and possibly
+move point, don't create the code-block button."
+ (let ((beg (line-beginning-position)))
+ ;; To identify code-blocks we need to be at start of line.
+ (goto-char beg)
+ (when (markdown-match-pre-blocks (line-end-position))
+ (unless dont-fontify
+ (sx-babel--make-pre-button beg (point)))
+ t)))
+
+(defun sx-question-mode--skip-comments ()
+ "If there's an html comment ahead, skip it and return t."
+ ;; @TODO: Handle the comment.
+ ;; "Handling means to store any relevant metadata it might be holding."
+ (let ((end (save-excursion
+ (when (markdown-match-comments (line-end-position))
+ (point)))))
+ (when end
+ (delete-region (point) end)
+ (skip-chars-backward "[:blank:]")
+ (when (looking-at "^[:blank:]*\n")
+ (replace-match ""))
+ t)))
+
+(defun sx-question-mode--skip-headline ()
+ "If there's a headline ahead, skip it and return non-nil."
+ (when (or (looking-at-p "^#+ ")
+ (progn (forward-line 1) (looking-at-p "===\\|---")))
+ ;; Returns non-nil.
+ (forward-line 1)))
+
+(defun sx-question-mode--skip-references ()
+ "If there's a reference ahead, skip it and return non-nil."
+ (forward-line 0)
+ (when (looking-at-p (format sx-question-mode--reference-regexp ".+"))
+ ;; Returns non-nil
+ (forward-paragraph 1)
+ t))
+
+(provide 'sx-question-print)
+;;; sx-question-print.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+;;; sx-question.el --- question logic -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Sean Allred
+
+;; Author: Sean Allred <codeNOSPAMseanallred.com>
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file provides an API for retrieving questions and defines
+;; additional logic for marking questions as read or hidden.
+
+\f
+;;; Code:
+
+(require 'sx)
+(require 'sx-filter)
+(require 'sx-method)
+
+(defun sx-question-get-questions (site &optional page keywords submethod)
+ "Get SITE questions. Return page PAGE (the first if nil).
+Return a list of question. Each question is an alist of
+properties returned by the API with an added (site SITE)
+property.
+
+KEYWORDS are added to the method call along with PAGE.
+
+`sx-method-call' is used with `sx-browse-filter'."
+ (sx-method-call 'questions
+ :keywords `((page . ,page) ,@keywords)
+ :site site
+ :auth t
+ :submethod submethod
+ :filter sx-browse-filter))
+
+(defun sx-question-get-question (site question-id)
+ "Query SITE for a QUESTION-ID and return it.
+If QUESTION-ID doesn't exist on SITE, raise an error."
+ (let ((res (sx-method-call 'questions
+ :id question-id
+ :site site
+ :auth t
+ :filter sx-browse-filter)))
+ (if res (elt res 0)
+ (error "Couldn't find question %S in %S"
+ question-id site))))
+
+(defun sx-question-get-from-answer (site answer-id)
+ "Get question from SITE to which ANSWER-ID belongs.
+If ANSWER-ID doesn't exist on SITE, raise an error."
+ (let ((res (sx-method-call 'answers
+ :id answer-id
+ :site site
+ :submethod 'questions
+ :auth t
+ :filter sx-browse-filter)))
+ (if res (elt res 0)
+ (error "Couldn't find answer %S in %S"
+ answer-id site))))
+
+(defun sx-question-get-from-comment (site comment-id)
+ "Get question from SITE to which COMMENT-ID belongs.
+If COMMENT-ID doesn't exist on SITE, raise an error.
+
+Note this requires two API requests. One for the comment and one
+for the post."
+ (let ((res (sx-method-call 'comments
+ :id comment-id
+ :site site
+ :auth t
+ :filter sx-browse-filter)))
+ (unless res
+ (error "Couldn't find comment %S in %S" comment-id site))
+ (sx-assoc-let (elt res 0)
+ (funcall (if (string= .post_type "answer")
+ #'sx-question-get-from-answer
+ #'sx-question-get-question)
+ .site_par
+ .post_id))))
+
+\f
+;;; Question Properties
+
+;;;; Read/unread
+(defvar sx-question--user-read-list nil
+ "Alist of questions read by the user.
+
+Each element has the form
+
+ (SITE . QUESTION-LIST)
+
+where each element in QUESTION-LIST has the form
+
+ (QUESTION_ID . LAST-VIEWED-DATE).")
+
+(defun sx-question--ensure-read-list (site)
+ "Ensure `sx-question--user-read-list' has been read from cache.
+If no cache exists for it, initialize one with SITE."
+ (unless sx-question--user-read-list
+ (setq sx-question--user-read-list
+ (sx-cache-get 'read-questions `'((,site))))))
+
+(defun sx-question--read-p (question)
+ "Non-nil if QUESTION has been read since last updated.
+See `sx-question--user-read-list'."
+ (sx-assoc-let question
+ (sx-question--ensure-read-list .site_par)
+ (let ((ql (cdr (assoc .site_par sx-question--user-read-list))))
+ (and ql
+ (>= (or (cdr (assoc .question_id ql)) 0)
+ .last_activity_date)))))
+
+(defmacro sx-sorted-insert-skip-first (newelt list &optional predicate)
+ "Inserted NEWELT into LIST sorted by PREDICATE.
+This is designed for the (site id id ...) lists. So the first car
+is intentionally skipped."
+ `(let ((tail ,list)
+ (x ,newelt))
+ (while (and ;; We're not at the end.
+ (cdr-safe tail)
+ ;; We're not at the right place.
+ (funcall (or #',predicate #'<) x (cadr tail)))
+ (setq tail (cdr tail)))
+ (setcdr tail (cons x (cdr tail)))))
+
+(defun sx-question--mark-read (question)
+ "Mark QUESTION as being read until it is updated again.
+Returns nil if question (in its current state) was already marked
+read, i.e., if it was `sx-question--read-p'.
+See `sx-question--user-read-list'."
+ (prog1
+ (sx-assoc-let question
+ (sx-question--ensure-read-list .site_par)
+ (let ((site-cell (assoc .site_par sx-question--user-read-list))
+ (q-cell (cons .question_id .last_activity_date))
+ cell)
+ (cond
+ ;; First question from this site.
+ ((null site-cell)
+ (push (list .site_par q-cell) sx-question--user-read-list))
+ ;; Question already present.
+ ((setq cell (assoc .question_id site-cell))
+ ;; Current version is newer than cached version.
+ (when (or (not (numberp (cdr cell)))
+ (> .last_activity_date (cdr cell)))
+ (setcdr cell .last_activity_date)))
+ ;; Question wasn't present.
+ (t
+ (sx-sorted-insert-skip-first
+ q-cell site-cell
+ (lambda (x y) (> (or (car x) -1) (or (car y) -1))))))))
+ ;; Save the results.
+ ;; @TODO This causes a small lag on `j' and `k' as the list gets
+ ;; large. Should we do this on a timer?
+ (sx-cache-set 'read-questions sx-question--user-read-list)))
+
+\f
+;;;; Hidden
+(defvar sx-question--user-hidden-list nil
+ "Alist of questions hidden by the user.
+
+Each element has the form
+
+ (SITE QUESTION_ID QUESTION_ID ...)")
+
+(defun sx-question--ensure-hidden-list (site)
+ "Ensure the `sx-question--user-hidden-list' has been read from cache.
+
+If no cache exists for it, initialize one with SITE."
+ (unless sx-question--user-hidden-list
+ (setq sx-question--user-hidden-list
+ (sx-cache-get 'hidden-questions `'((,site))))))
+
+(defun sx-question--hidden-p (question)
+ "Non-nil if QUESTION has been hidden."
+ (sx-assoc-let question
+ (sx-question--ensure-hidden-list .site_par)
+ (let ((ql (cdr (assoc .site_par sx-question--user-hidden-list))))
+ (and ql (memq .question_id ql)))))
+
+(defun sx-question--mark-hidden (question)
+ "Mark QUESTION as being hidden."
+ (sx-assoc-let question
+ (let ((site-cell (assoc .site_par sx-question--user-hidden-list)))
+ ;; If question already hidden, do nothing.
+ (unless (memq .question_id site-cell)
+ (if (null site-cell)
+ ;; First question from this site.
+ (push (list .site_par .question_id) sx-question--user-hidden-list)
+ ;; Not first question and question wasn't present.
+ ;; Add it in, but make sure it's sorted (just in case we
+ ;; decide to rely on it later).
+ (sx-sorted-insert-skip-first .question_id site-cell >))
+ ;; Save the results.
+ (sx-cache-set 'hidden-questions sx-question--user-hidden-list)))))
+
+\f
+;;;; Other data
+(defun sx-question--accepted-answer-id (question)
+ "Return accepted answer in QUESTION or nil if none exists."
+ (sx-assoc-let question
+ (and (integerp .accepted_answer_id)
+ .accepted_answer_id)))
+
+\f
+;;; Question Mode Answer-Sorting Functions
+(sx--create-comparator sx-answer-higher-score-p
+ "Return t if answer A has a higher score than answer B."
+ #'> (lambda (x) (cdr (assq 'score x))))
+
+(sx--create-comparator sx-answer-newer-p
+ "Return t if answer A was posted later than answer B."
+ #'> (lambda (x) (cdr (assq 'creation_date x))))
+
+(sx--create-comparator sx-answer-more-active-p
+ "Return t if answer A was updated after answer B."
+ #'> (lambda (x) (cdr (assq 'last_activity_date x))))
+
+(provide 'sx-question)
+;;; sx-question.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+;;; sx-request.el --- requests and url manipulation -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Sean Allred
+
+;; Author: Sean Allred <codeNOSPAMseanallred.com>
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; API requests are handled on three separate tiers:
+;;
+;; `sx-method-call':
+;;
+;; This is the function that should be used most often, since it
+;; runs necessary checks (authentication) and provides basic
+;; processing of the result for consistency.
+;;
+;; `sx-request-make':
+;;
+;; This is the fundamental function for interacting with the API.
+;; It makes no provisions for 'common' usage, but it does ensure
+;; data is retrieved successfully or an appropriate signal is
+;; thrown.
+;;
+;; `url.el' and `json.el':
+;;
+;; The whole solution is built upon `url-retrieve-synchronously'
+;; for making the request and `json-read-from-string' for parsing
+;; it into a properly symbolic data structure.
+;;
+;; When at all possible, use `sx-method-call'. There are specialized
+;; cases for the use of `sx-request-make' outside of sx-method.el, but
+;; these must be well-documented inline with the code.
+
+;;; Code:
+
+(require 'url)
+(require 'json)
+
+(require 'sx)
+(require 'sx-encoding)
+
+\f
+;;; Variables
+
+(defconst sx-request-api-key
+ "0TE6s1tveCpP9K5r5JNDNQ(("
+ "When passed, this key provides a higher request quota.")
+
+(defconst sx-request-api-version
+ "2.2"
+ "The current version of the API.")
+
+(defconst sx-request-api-root
+ (format "https://api.stackexchange.com/%s/" sx-request-api-version)
+ "The base URL to make requests from.")
+
+(defcustom sx-request-unzip-program
+ "gunzip"
+ "Program used to unzip the response if it is compressed.
+This program must accept compressed data on standard input.
+
+This is only used (and necessary) if the function
+`zlib-decompress-region' is not defined, which is the case for
+Emacs versions < 24.4."
+ :group 'sx
+ :type 'string)
+
+(defvar sx-request-remaining-api-requests
+ nil
+ "The number of API requests remaining.
+Set by `sx-request-make'.")
+
+(defcustom sx-request-remaining-api-requests-message-threshold
+ 50
+ "Lower bound for printed warnings of API usage limits.
+After `sx-request-remaining-api-requests' drops below this
+number, `sx-request-make' will begin printing out the
+number of requests left every time it finishes a call."
+ :group 'sx
+ :type 'integer)
+
+(defvar sx-request-all-items-delay 0
+ "Delay in seconds with each `sx-request-all-items' iteration.
+It is good to use a reasonable delay to avoid rate-limiting.")
+
+\f
+;;; Making Requests
+(defvar sx--backoff-time nil)
+
+(defun sx-request--wait-while-backoff ()
+ (when sx--backoff-time
+ (message "Waiting for backoff time: %s" sx--backoff-time)
+ (let ((time (cadr (current-time))))
+ (if (> (- sx--backoff-time time) 1000)
+ ;; If backoff-time is more than 1000 seconds in the future,
+ ;; we've likely just looped around the "least significant"
+ ;; bits of `current-time'.
+ (setq sx--backoff-time time)
+ (when (< time sx--backoff-time)
+ (message "Backoff detected, waiting %s seconds" (- sx--backoff-time time))
+ (sleep-for (+ 0.3 (- sx--backoff-time time))))))))
+
+(defun sx-request-all-items (method &optional args request-method
+ stop-when)
+ "Call METHOD with ARGS until there are no more items.
+STOP-WHEN is a function that takes the entire response and
+returns non-nil if the process should stop.
+
+All other arguments are identical to `sx-request-make', but
+PROCESS-FUNCTION is given the default value of `identity' (rather
+than `sx-request-response-get-items') to allow STOP-WHEN to
+access the response wrapper."
+ ;; @TODO: Refactor. This is the product of a late-night jam
+ ;; session... it is not intended to be model code.
+ (declare (indent 1))
+ (let* ((return-value nil)
+ (current-page 1)
+ (stop-when (or stop-when #'sx-request-all-stop-when-no-more))
+ (process-function #'identity)
+ (response
+ (sx-request-make method `((page . ,current-page) ,@args)
+ request-method process-function)))
+ (while (not (funcall stop-when response))
+ (let-alist response
+ (setq current-page (1+ current-page)
+ return-value
+ (nconc return-value .items)))
+ (sleep-for sx-request-all-items-delay)
+ (setq response
+ (sx-request-make method `((page . ,current-page) ,@args)
+ request-method process-function)))
+ (nconc return-value
+ (cdr (assoc 'items response)))))
+
+;;; NOTE: Whenever this is arglist changes, `sx-request-fallback' must
+;;; also change.
+(defun sx-request-make (method &optional args request-method process-function)
+ "Make a request to the API, executing METHOD with ARGS.
+You should almost certainly be using `sx-method-call' instead of
+this function. REQUEST-METHOD is one of `get' (default) or `post'.
+
+Returns the entire response as processed by PROCESS-FUNCTION.
+This defaults to `sx-request-response-get-items'.
+
+The full set of arguments is built with
+`sx-request--build-keyword-arguments', prepending
+`sx-request-api-key' to receive a higher quota. It will also
+include user's `access_token` if it is avaialble. This call is
+then resolved with `url-retrieve-synchronously' to a temporary
+buffer that it returns. The headers are then stripped using a
+search a blank line (\"\\n\\n\"). The main body of the response
+is then tested with `sx-encoding-gzipped-buffer-p' for
+compression. If it is compressed, `sx-request-unzip-program' is
+called to uncompress the response. The uncompressed respons is
+then read with `json-read-from-string'.
+
+`sx-request-remaining-api-requests' is updated appropriately and
+the main content of the response is returned."
+ (declare (indent 1))
+ (sx-request--wait-while-backoff)
+ (let* ((url-automatic-caching t)
+ (url-inhibit-uncompression t)
+ (url-request-data (sx-request--build-keyword-arguments args nil))
+ (request-url (concat sx-request-api-root method))
+ (url-request-method (and request-method (upcase (symbol-name request-method))))
+ (url-request-extra-headers
+ '(("Content-Type" . "application/x-www-form-urlencoded")))
+ (response-buffer (url-retrieve-synchronously request-url)))
+ (if (not response-buffer)
+ (error "Something went wrong in `url-retrieve-synchronously'")
+ (with-current-buffer response-buffer
+ (let* ((data (progn
+ ;; @TODO use url-http-end-of-headers
+ (goto-char (point-min))
+ (if (not (search-forward "\n\n" nil t))
+ (error "Headers missing; response corrupt")
+ (delete-region (point-min) (point))
+ (buffer-string))))
+ (response-zipped-p (sx-encoding-gzipped-p data))
+ (data
+ ;; Turn string of bytes into string of characters. See
+ ;; http://emacs.stackexchange.com/q/4100/50
+ (decode-coding-string
+ (if (not response-zipped-p) data
+ (if (fboundp 'zlib-decompress-region)
+ (zlib-decompress-region (point-min) (point-max))
+ (shell-command-on-region
+ (point-min) (point-max)
+ sx-request-unzip-program nil t))
+ (buffer-string))
+ 'utf-8 'nocopy))
+ ;; @TODO should use `condition-case' here -- set
+ ;; RESPONSE to 'corrupt or something
+ (response (with-demoted-errors "`json' error: %S"
+ (let ((json-false nil)
+ (json-array-type 'list)
+ (json-null :null))
+ (json-read-from-string data)))))
+ (kill-buffer response-buffer)
+ (when (not response)
+ (error "Invalid response to the url request: %s" data))
+ ;; If we get here, the response is a valid data structure
+ (sx-assoc-let response
+ (when .error_id
+ (error "Request failed: (%s) [%i %s] %S"
+ .method .error_id .error_name .error_message))
+ (when .backoff
+ (message "Backoff received %s" .backoff)
+ (setq sx--backoff-time (+ (cadr (current-time)) .backoff)))
+ (when (< (setq sx-request-remaining-api-requests .quota_remaining)
+ sx-request-remaining-api-requests-message-threshold)
+ (sx-message "%d API requests remaining"
+ sx-request-remaining-api-requests))
+ (funcall (or process-function #'sx-request-response-get-items)
+ response)))))))
+
+(defun sx-request-fallback (_method &optional _args _request-method _process-function)
+ "Fallback method when authentication is not available.
+This is for UI generation when the associated API call would
+require authentication.
+
+Currently returns nil."
+ '(()))
+
+\f
+;;; Our own generated data
+(defconst sx-request--data-url-format
+ "https://raw.githubusercontent.com/vermiculus/sx.el/data/data/%s.el"
+ "Url of the \"data\" directory inside the SX `data' branch.")
+
+(defun sx-request--read-buffer-data ()
+ "Return the buffer contents after any url headers.
+Error if url headers are absent or if they indicate something
+went wrong."
+ (goto-char (point-min))
+ (unless (string-match "200" (thing-at-point 'line))
+ (error "Page not found."))
+ (if (not (search-forward "\n\n" nil t))
+ (error "Headers missing; response corrupt")
+ (prog1 (buffer-substring (point) (point-max))
+ (kill-buffer (current-buffer)))))
+
+(defun sx-request-get-url (url &optional callback)
+ "Fetch and return data stored online at URL.
+If CALLBACK is nil, fetching is done synchronously and the
+data (buffer contents sans headers) is returned as a string.
+
+Otherwise CALLBACK must be a function of a single argument. Then
+`url-retrieve' is called asynchronously and CALLBACK is passed
+the retrieved data."
+ (let* ((url-automatic-caching t)
+ (url-inhibit-uncompression t)
+ (url-request-method "GET")
+ (url-request-extra-headers
+ '(("Content-Type" . "application/x-www-form-urlencoded")))
+ (callback-internal
+ (when callback
+ ;; @TODO: Error check in STATUS.
+ (lambda (_status)
+ (funcall callback (sx-request--read-buffer-data)))))
+ (response-buffer
+ (if callback (url-retrieve url callback-internal nil 'silent)
+ (url-retrieve-synchronously url))))
+ (unless callback
+ (if (not response-buffer)
+ (error "Something went wrong in `url-retrieve-synchronously'")
+ (with-current-buffer response-buffer
+ (sx-request--read-buffer-data))))))
+
+(defun sx-request-get-data (file)
+ "Fetch and return data stored online by SX.
+FILE is a string or symbol, the name of the file which holds the
+desired data, relative to `sx-request--data-url-format'. For
+instance, `tags/emacs' returns the list of tags on Emacs.SE."
+ (read (sx-request-get-url
+ (format sx-request--data-url-format file))))
+
+\f
+;;; Support Functions
+(defun sx-request--build-keyword-arguments (alist &optional kv-sep)
+ "Format ALIST as a key-value list joined with KV-SEP.
+If authentication is needed, include it also or error if it is
+not available.
+
+Build a \"key=value&key=value&...\"-style string with the elements
+of ALIST. If any value in the alist is nil, that pair will not
+be included in the return. If you wish to pass a notion of
+false, use the symbol `false'. Each element is processed with
+`sx--thing-as-string'."
+ ;; Add API key to list of arguments, this allows for increased quota
+ ;; automatically.
+ (let ((api-key (cons "key" sx-request-api-key))
+ (auth (car (sx-cache-get 'auth))))
+ (push api-key alist)
+ (when auth
+ (push auth alist))
+ (mapconcat
+ (lambda (pair)
+ (concat
+ (sx--thing-as-string (car pair))
+ "="
+ (sx--thing-as-string (cdr pair) kv-sep t)))
+ (delq nil (mapcar
+ (lambda (pair)
+ (when (cdr pair) pair))
+ alist))
+ "&")))
+
+\f
+;;; Response Processors
+(defun sx-request-response-get-items (response)
+ "Returns the items from RESPONSE."
+ (sx-assoc-let response
+ (sx-encoding-clean-content-deep .items)))
+
+(defun sx-request-all-stop-when-no-more (response)
+ (or (not response)
+ (not (cdr (assoc 'has_more response)))))
+
+(provide 'sx-request)
+;;; sx-request.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+;;; sx-search.el --- searching for questions -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Artur Malabarba
+
+;; Author: Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Implements search functionality. The basic function is
+;; `sx-search-get-questions', which returns an array of questions
+;; according to a search term.
+;;
+;; This also defines a user-level command, `sx-search', which is an
+;; interactive wrapper around `sx-search-get-questions' and
+;; `sx-question-list-mode'.
+
+\f
+;;; Code:
+
+(require 'sx)
+(require 'sx-question-list)
+(require 'sx-question-mode)
+(require 'sx-tag)
+(require 'sx-interaction)
+
+(defvar sx-search--query-history nil
+ "Query history for interactive prompts.")
+
+\f
+;;; Basic function
+(defun sx-search-get-questions (site page query
+ &optional tags excluded-tags
+ &rest keywords)
+ "Like `sx-question-get-questions', but restrict results by a search.
+
+Perform search on SITE. PAGE is an integer indicating which page
+of results to return. QUERY, TAGS, and EXCLUDED-TAGS restrict the
+possible returned questions as per `sx-search'.
+
+Either QUERY or TAGS must be non-nil, or the search will
+fail. EXCLUDED-TAGS is only is used if TAGS is also provided.
+
+KEYWORDS is passed to `sx-method-call'."
+ (sx-method-call 'search/advanced
+ :keywords `((page . ,page)
+ (q . ,query)
+ (tagged . ,tags)
+ (nottagged . ,excluded-tags)
+ ,@keywords)
+ :site site
+ :auth t
+ :filter sx-browse-filter))
+
+(defconst sx-search--order-methods
+ (cons '("Relevance" . relevance)
+ (default-value 'sx-question-list--order-methods))
+ "Alist of possible values to be passed to the `sort' keyword.")
+
+(defcustom sx-search-default-order 'activity
+ "Default ordering method used on new searches.
+Possible values are the cdrs of `sx-search--order-methods'."
+ :type (cons 'choice
+ (mapcar (lambda (c) `(const :tag ,(car c) ,(cdr c)))
+ (cl-remove-duplicates
+ sx-search--order-methods
+ :key #'cdr)))
+ :group 'sx-question-list)
+
+\f
+;;;###autoload
+(defun sx-search (site query &optional tags excluded-tags)
+ "Display search on SITE for question titles containing QUERY.
+When TAGS is given, it is a lists of tags, one of which must
+match. When EXCLUDED-TAGS is given, it is a list of tags, none
+of which is allowed to match.
+
+Interactively, the user is asked for SITE and QUERY. With a
+prefix argument, the user is asked for everything."
+ (interactive
+ (let ((site (sx--maybe-site-prompt current-prefix-arg))
+ (query (read-string
+ (format "Query (%s): "
+ (if current-prefix-arg "optional" "mandatory"))
+ ""
+ 'sx-search--query-history))
+ tags excluded-tags)
+ (when (string= query "")
+ (setq query nil))
+ (when current-prefix-arg
+ (setq tags (sx-tag-multiple-read
+ site (concat "Tags" (when query " (optional)"))))
+ (unless (or query tags)
+ (sx-user-error "Must supply either QUERY or TAGS"))
+ (setq excluded-tags
+ (sx-tag-multiple-read site "Excluded tags (optional)")))
+ (list site query tags excluded-tags)))
+
+ ;; Here starts the actual function
+ (sx-initialize)
+ (with-current-buffer (get-buffer-create "*sx-search-result*")
+ (sx-question-list-mode)
+ (setq sx-question-list--next-page-function
+ (lambda (page)
+ (sx-search-get-questions
+ sx-question-list--site page
+ query tags excluded-tags
+ (cons 'order (if sx-question-list--descending 'desc 'asc))
+ (cons 'sort sx-question-list--order))))
+ (setq sx-question-list--site site)
+ (setq sx-question-list--order sx-search-default-order)
+ (setq sx-question-list--order-methods sx-search--order-methods)
+ (sx-question-list-refresh 'redisplay)
+ (switch-to-buffer (current-buffer))))
+
+\f
+;;; Tag
+;;;###autoload
+(defun sx-search-tag-at-point (&optional pos)
+ "Follow tag under position POS or point."
+ (interactive)
+ (let ((tag (save-excursion
+ (when pos (goto-char pos))
+ (or (get-text-property (point) 'sx-tag)
+ (thing-at-point 'symbol))))
+ (meta (save-excursion
+ (when pos (goto-char pos))
+ (get-text-property (point) 'sx-tag-meta)))
+ (site (replace-regexp-in-string
+ (rx string-start "meta.") ""
+ (or sx-question-list--site
+ (sx-assoc-let sx-question-mode--data .site_par)))))
+ (sx-search (concat (when meta "meta.") site)
+ nil tag)))
+
+(provide 'sx-search)
+;;; sx-search.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+;;; sx-site.el --- browsing sites -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Sean Allred
+
+;; Author: Sean Allred <codeNOSPAMseanallred.com>
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file provides various pieces of site logic, such as retrieving
+;; the list of sites and the list of a user's favorited questions.
+
+;;; Code:
+
+(require 'sx-method)
+(require 'sx-cache)
+(require 'sx-filter)
+
+(defconst sx-site-browse-filter
+ (sx-filter-from-nil
+ ((site site_type
+ name
+ api_site_parameter
+ site_url
+ related_sites)
+ (related_site api_site_parameter
+ relation)))
+ "Filter for browsing sites.")
+
+(defun sx-site--get-site-list ()
+ "Return all sites with `sx-site-browse-filter'."
+ (sx-cache-get
+ 'site-list
+ '(sx-method-call 'sites
+ :pagesize 999
+ :filter sx-site-browse-filter)))
+
+(defcustom sx-site-favorites
+ nil
+ "List of favorite sites.
+Each entry is a string corresponding to a single site's
+api_site_parameter."
+ :group 'sx)
+
+(defun sx-site-get-api-tokens ()
+ "Return a list of all known site tokens."
+ (mapcar
+ (lambda (site) (cdr (assoc 'api_site_parameter site)))
+ (sx-site--get-site-list)))
+
+(provide 'sx-site)
+;;; sx-site.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+;;; sx-switchto.el --- keymap for navigating between pages -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Artur Malabarba
+
+;; Author: Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'sx)
+
+\f
+;;; Keybinds
+;;;###autoload
+(define-prefix-command 'sx-switchto-map)
+
+(mapc (lambda (x) (define-key sx-switchto-map (car x) (cadr x)))
+ '(
+ ;; These imitate the site's G hotkey.
+ ("a" sx-ask)
+ ("h" sx-tab-frontpage)
+ ("m" sx-tab-meta-or-main)
+ ;; This is `n' on the site.
+ ("u" sx-tab-unanswered)
+ ;; These are extra things we can do, because we're awesome.
+ ("f" sx-tab-featured)
+ ("i" sx-inbox)
+ ("n" sx-tab-newest)
+ ("t" sx-tab-switch)
+ ("U" sx-tab-unanswered-my-tags)
+ ("v" sx-tab-topvoted)
+ ("w" sx-tab-week)
+ ("*" sx-tab-starred)
+ ))
+
+\f
+;;; These are keys which depend on context.
+;;;; For instance, it makes no sense to have `switch-site' bound to a
+;;;; key on a buffer with no `sx-question-list--site' variable.
+(defvar sx-question-list--site)
+(sx--define-conditional-key sx-switchto-map "s" #'sx-question-list-switch-site
+ (and (boundp 'sx-question-list--site) sx-question-list--site))
+
+(provide 'sx-switchto)
+;;; sx-switchto.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+;;; sx-tab.el --- functions for viewing different tabs -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Artur Malabarba
+
+;; Author: Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file provides a single macro to define 'tabs' to view lists of
+;; questions.
+
+;;; Tabs:
+
+;; - `sx-tab-all-questions' :: All questions.
+;; - `sx-tab-unanswered' :: Unanswered questions.
+;; - `sx-tab-unanswered-my-tags' :: Unanswered questions in your followed tags.
+;; - `sx-tab-featured' :: Featured questions.
+;; - `sx-tab-starred' :: Starred questions.
+
+;;; Code:
+
+(require 'sx)
+(require 'sx-question-list)
+(require 'sx-interaction)
+
+(defvar sx-tab--list nil
+ "List of the names of all defined tabs.")
+
+(defun sx-tab-switch (tab)
+ "Switch to another question-list TAB."
+ (interactive
+ (list (sx-completing-read
+ "Switch to tab: " sx-tab--list
+ (lambda (tab) (not (equal tab sx-question-list--current-tab)))
+ t)))
+ (funcall (intern (format "sx-tab-%s" (downcase tab)))))
+
+(defconst sx-tab--order-methods
+ `(,@(default-value 'sx-question-list--order-methods)
+ ("Hottest Now" . hot)
+ ("Weekly Hottest" . week)
+ ("Monthly Hottest" . month))
+ "Alist of possible values to be passed to the `sort' keyword.")
+
+(defcustom sx-tab-default-order 'activity
+ "Default ordering method used on `sx-tab-questions' and the likes.
+Possible values are the cdrs of `sx-tab--order-methods'."
+ :type (cons 'choice
+ (mapcar (lambda (c) `(const :tag ,(car c) ,(cdr c)))
+ (cl-remove-duplicates
+ sx-tab--order-methods
+ :key #'cdr)))
+ :group 'sx-question-list)
+
+(eval-and-compile
+ (defconst sx-tab--docstring-format
+ "Display a list of %s questions for SITE.
+The variable `sx-tab-default-order' can be used to customize the
+sorting of the resulting list.
+
+NO-UPDATE (the prefix arg) is passed to `sx-question-list-refresh'.
+If SITE is nil, use `sx-default-site'."
+ "Format used on the docstring of `sx-tab-*' commands."))
+
+\f
+;;; The main macro
+(defmacro sx-tab--define (tab pager &optional printer refresher obsolete
+ &rest body)
+ "Define a StackExchange tab called TAB.
+TAB is a capitalized string.
+
+This defines a command `sx-tab-TAB' for displaying the tab,
+and a variable `sx-tab--TAB-buffer' for holding the bufer.
+
+The arguments PAGER, PRINTER, and REFRESHER, if non-nil, are
+respectively used to set the value of the variables
+`sx-question-list--print-function',
+`sx-question-list--refresh-function', and
+`sx-question-list--next-page-function'.
+
+If OBSOLETE is non-nil, it should be a string indicating the tab
+to use instead of this one.
+
+BODY is evaluated after activating the mode and setting these
+variables, but before refreshing the display."
+ (declare (indent 1) (debug t))
+ (let* ((name (downcase tab))
+ (buffer-variable
+ (intern (format "sx-tab--%s-buffer"
+ (if obsolete (downcase obsolete)
+ name))))
+ (function-name
+ (intern (concat "sx-tab-" name)))
+ (use-instead
+ (when obsolete (intern (concat "sx-tab-" (downcase obsolete))))))
+ `(progn
+ ,(unless obsolete
+ `(defvar ,buffer-variable nil
+ ,(format "Buffer where the %s questions are displayed." tab)))
+ (defun ,function-name (&optional no-update site)
+ ,(format sx-tab--docstring-format tab)
+ (interactive
+ (list current-prefix-arg
+ (sx--interactive-site-prompt)))
+ (sx-initialize)
+ (unless site (setq site sx-default-site))
+ ;; Create the buffer
+ (unless (buffer-live-p ,buffer-variable)
+ (setq ,buffer-variable
+ (generate-new-buffer
+ ,(format "*question-list: %s *" (or obsolete tab)))))
+ ;; Fill the buffer with content.
+ (with-current-buffer ,buffer-variable
+ (sx-question-list-mode)
+ (when ,printer (setq sx-question-list--print-function ,printer))
+ (when ,refresher (setq sx-question-list--refresh-function ,refresher))
+ (setq sx-question-list--next-page-function ,pager)
+ (setq sx-question-list--site site)
+ (setq sx-question-list--order 'activity)
+ (setq sx-question-list--current-tab ,(or obsolete tab))
+ ,@body
+ (sx-question-list-refresh 'redisplay no-update))
+ (switch-to-buffer ,buffer-variable))
+ ,(when obsolete
+ `(make-obsolete ',function-name ',use-instead nil))
+ ;; Add this tab to the list of existing tabs. So we can prompt
+ ;; the user with completion and stuff.
+ (unless ,obsolete
+ (add-to-list 'sx-tab--list ,tab)))))
+
+\f
+;;; Entry commands
+(sx-tab--define "All-Questions"
+ (sx-question-list--make-pager 'questions)
+ nil nil nil
+ (setq sx-question-list--order-methods
+ sx-tab--order-methods))
+;;;###autoload
+(autoload 'sx-tab-all-questions
+ (expand-file-name
+ "sx-tab" (when load-file-name (file-name-directory load-file-name)))
+ nil t)
+
+(sx-tab--define "Unanswered"
+ (sx-question-list--make-pager 'questions 'unanswered))
+;;;###autoload
+(autoload 'sx-tab-unanswered
+ (expand-file-name
+ "sx-tab" (when load-file-name (file-name-directory load-file-name)))
+ nil t)
+
+(sx-tab--define "Unanswered-My-Tags"
+ (sx-question-list--make-pager 'questions 'unanswered/my-tags))
+;;;###autoload
+(autoload 'sx-tab-unanswered-my-tags
+ (expand-file-name
+ "sx-tab" (when load-file-name (file-name-directory load-file-name)))
+ nil t)
+
+(sx-tab--define "Featured"
+ (sx-question-list--make-pager 'questions 'featured))
+;;;###autoload
+(autoload 'sx-tab-featured
+ (expand-file-name
+ "sx-tab" (when load-file-name (file-name-directory load-file-name)))
+ nil t)
+
+(sx-tab--define "Starred"
+ (sx-question-list--make-pager 'me 'favorites))
+;;;###autoload
+(autoload 'sx-tab-starred
+ (expand-file-name
+ "sx-tab" (when load-file-name (file-name-directory load-file-name)))
+ nil t)
+
+\f
+;;; Inter-modes navigation
+(defun sx-tab-meta-or-main ()
+ "Switch to the meta version of a main site, or vice-versa.
+Inside a question, go to the frontpage of the site this question
+belongs to."
+ (interactive)
+ (if (and (derived-mode-p 'sx-question-list-mode)
+ sx-question-list--site)
+ (sx-question-list-switch-site
+ (if (string-match "\\`meta\\." sx-question-list--site)
+ (replace-match "" :fixedcase nil sx-question-list--site)
+ (concat "meta." sx-question-list--site)))
+ (sx-tab-all-questions nil (sx--site (sx--data-here 'question)))))
+
+\f
+;;; Obsolete tabs
+(defconst sx-tab--basic-question-pager
+ (sx-question-list--make-pager 'questions))
+
+(sx-tab--define "FrontPage"
+ sx-tab--basic-question-pager
+ nil nil "All-Questions"
+ (setq sx-question-list--order 'activity)
+ (setq sx-question-list--order-methods
+ sx-tab--order-methods))
+;;;###autoload
+(autoload 'sx-tab-frontpage
+ (expand-file-name
+ "sx-tab" (when load-file-name (file-name-directory load-file-name)))
+ nil t)
+
+(sx-tab--define "Newest"
+ sx-tab--basic-question-pager
+ nil nil "All-Questions"
+ (setq sx-question-list--order 'creation)
+ (setq sx-question-list--order-methods
+ sx-tab--order-methods))
+;;;###autoload
+(autoload 'sx-tab-newest
+ (expand-file-name
+ "sx-tab" (when load-file-name (file-name-directory load-file-name)))
+ nil t)
+
+(sx-tab--define "TopVoted"
+ sx-tab--basic-question-pager
+ nil nil "All-Questions"
+ (setq sx-question-list--order 'votes)
+ (setq sx-question-list--order-methods
+ sx-tab--order-methods))
+;;;###autoload
+(autoload 'sx-tab-topvoted
+ (expand-file-name
+ "sx-tab" (when load-file-name (file-name-directory load-file-name)))
+ nil t)
+
+(sx-tab--define "Hot"
+ sx-tab--basic-question-pager
+ nil nil "All-Questions"
+ (setq sx-question-list--order 'hot)
+ (setq sx-question-list--order-methods
+ sx-tab--order-methods))
+;;;###autoload
+(autoload 'sx-tab-hot
+ (expand-file-name
+ "sx-tab" (when load-file-name (file-name-directory load-file-name)))
+ nil t)
+
+(sx-tab--define "Week"
+ sx-tab--basic-question-pager
+ nil nil "All-Questions"
+ (setq sx-question-list--order 'week)
+ (setq sx-question-list--order-methods
+ sx-tab--order-methods))
+;;;###autoload
+(autoload 'sx-tab-week
+ (expand-file-name
+ "sx-tab" (when load-file-name (file-name-directory load-file-name)))
+ nil t)
+
+(sx-tab--define "Month"
+ sx-tab--basic-question-pager
+ nil nil "All-Questions"
+ (setq sx-question-list--order 'month)
+ (setq sx-question-list--order-methods
+ sx-tab--order-methods))
+;;;###autoload
+(autoload 'sx-tab-month
+ (expand-file-name
+ "sx-tab" (when load-file-name (file-name-directory load-file-name)))
+ nil t)
+
+(provide 'sx-tab)
+;;; sx-tab.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+;;; sx-tag.el --- retrieving list of tags and handling tags -*- lexical-binding: t; -*-
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+
+\f
+;;; Code:
+(eval-when-compile
+ '(require 'cl-lib))
+
+(require 'sx)
+(require 'sx-method)
+(require 'sx-button)
+
+(defface sx-tag
+ '((t :underline nil :inherit font-lock-function-name-face))
+ "Face used on the question tags in the question buffer."
+ :group 'sx-question-mode-faces
+ :group 'sx-question-list-faces)
+
+\f
+;;; Getting the list from a site
+(defconst sx-tag-filter
+ (sx-filter-from-nil
+ (tag.name
+ tag.synonyms))
+ "Filter used when querying tags.")
+
+(defun sx-tag--get-all (site &optional no-synonyms)
+ "Retrieve all tags for SITE.
+If NO-SYNONYMS is non-nil, don't return synonyms."
+ (cl-reduce
+ (lambda (so-far tag)
+ (let-alist tag
+ (cons .name
+ (if no-synonyms so-far
+ (append .synonyms so-far)))))
+ (sx-method-call 'tags
+ :get-all t
+ :filter sx-tag-filter
+ :site site)
+ :initial-value nil))
+
+(defun sx-tag--get-some-tags-containing (site string)
+ "Return at most 100 tags for SITE containing STRING.
+Returns an array."
+ (sx-method-call 'tags
+ :auth nil
+ :filter sx-tag-filter
+ :site site
+ :keywords `((inname . ,string))))
+
+(defun sx-tag--get-some-tag-names-containing (site string)
+ "Return at most 100 tag names for SITE containing STRING.
+Returns a list."
+ (mapcar (lambda (x) (cdr (assoc 'name x)))
+ (sx-tag--get-some-tags-containing site string)))
+
+\f
+;;; Getting tags from our data branch. Without the API.
+;;;; @TODO: Once the cache is finished, this can probably be made into
+;;;; a cache variasble with 1 day expiration time.
+(defvar sx-tag-list-alist nil
+ "Alist where the tag list for each site is stored.
+Elements are of the type (SITE . TAG-LIST).")
+
+(defun sx-tag-list--get (site)
+ "Retrieve all tags from SITE in a single request.
+This does not access the API. Instead, it uses
+`sx-request-get-data', which accesses SX's tag cache."
+ (or (cdr (assoc site sx-tag-list-alist))
+ (let ((list (sx-request-get-data (concat "tags/" site))))
+ (push (cons site list) sx-tag-list-alist)
+ list)))
+
+\f
+;;; Check tag validity
+(defun sx-tag--invalid-name-p (site tags)
+ "Nil if TAGS exist in SITE.
+TAGS can be a string (the tag name) or a list of strings.
+Fails if TAGS is a list with more than 100 items.
+Return the list of invalid tags in TAGS."
+ (and (listp tags) (> (length tags) 100)
+ (error "Invalid argument. TAG has more than 100 items"))
+ (let ((result
+ (mapcar
+ (lambda (x) (cdr (assoc 'name x)))
+ (sx-method-call 'tags
+ :id (sx--thing-as-string tags)
+ :submethod 'info
+ :auth nil
+ :filter sx-tag-filter
+ :site site))))
+ (cl-remove-if (lambda (x) (member x result)) tags)))
+
+\f
+;;; Prompt the user for tags.
+(defvar sx-tag-history nil
+ "Tags history for interactive prompts.")
+
+;;; @TODO: Make it so that hitting BACKSPACE with an empty input
+;;; deletes a previously submitted tag.
+(defun sx-tag-multiple-read (site prompt &optional initial-value)
+ "Interactively read a list of tags for SITE.
+Call `sx-completing-read' multiple times, until input is empty,
+with completion options given by the tag list of SITE.
+Return a list of tags given by the user.
+
+PROMPT is a string displayed to the user and should not end with
+a space nor a colon. INITIAL-VALUE is a list of already-selected
+tags."
+ (let ((completion-list (sx-tag-list--get site))
+ (list (reverse initial-value))
+ (empty-string
+ (propertize "--\x000-some-string-representing-empty-\x000--"
+ 'display "DONE"))
+ input)
+ (while (not (string=
+ empty-string
+ (setq input (sx-completing-read
+ (concat prompt " ["
+ (mapconcat #'identity (reverse list) ",")
+ "]: ")
+ completion-list
+ nil 'require-match nil 'sx-tag-history
+ empty-string))))
+ (push input list))
+ (reverse list)))
+
+\f
+;;; Printing
+(defun sx-tag--format (tag &optional meta)
+ "Format and return TAG for display.
+If META is non-nil, the tag is for the meta site."
+ (with-temp-buffer
+ (sx-tag--insert tag meta)
+ (buffer-string)))
+
+(defun sx-tag--insert (tag &optional meta)
+ "Insert TAG button.
+If META is non-nil, the tag is for the meta site."
+ (insert-text-button (concat "[" tag "]")
+ 'sx-button-copy tag
+ 'sx-tag tag
+ 'sx-tag-meta meta
+ :type 'sx-button-tag))
+
+(defun sx-tag--format-tags (tags &optional site)
+ "Format and concatenate a sequence of TAGS.
+Returns a string of all tags in TAGS, separated by a space.
+
+SITE is the site to which the tags refer, it is only used to
+decide whether they are main or meta tags. SITE can also be t or
+nil, which respectively indicate meta and main."
+ (let ((is-meta
+ (if (stringp site) (string-match (rx string-start "meta.") site)
+ site)))
+ (mapconcat (lambda (tag) (sx-tag--format tag is-meta))
+ tags " ")))
+
+(provide 'sx-tag)
+;;; sx-tag.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+;;; sx-time.el --- time -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Sean Allred
+
+;; Author: Sean Allred <codeNOSPAMseanallred.com>
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file provides functions for manipulating and displaying
+;; timestamps.
+
+;;; Code:
+
+(require 'time-date)
+
+(defconst sx-time-seconds-to-string
+ ;; (LIMIT NAME VALUE)
+ ;; We use an entry if the number of seconds in question is less than
+ ;; LIMIT, but more than the previous entry's LIMIT.
+ ;; For instance, if time is less than 100 sec, we write it in seconds;
+ ;; if it is between 100 and 6000 sec, we use minutes.
+ ;; VALUE is the actual number of seconds which NAME represents.
+ '((100 "s" 1)
+ (6000 "m" 60.0)
+ (108000 "h" 3600.0)
+ (3456000 "d" 86400.0)
+ (31622400 "mo" 2628000.0)
+ (nil "y" 31557600.0))
+ "Auxiliary variable used by `sx-time-since'.")
+
+(defun sx-time-since (time)
+ "Convert the time interval since TIME (in seconds) to a short string."
+ (let ((delay (- (float-time) time)))
+ (concat
+ (if (> 0 delay) "-" "")
+ (if (= 0 delay) "0s"
+ (setq delay (abs delay))
+ (let ((sts sx-time-seconds-to-string) here)
+ (while (and (car (setq here (pop sts)))
+ (<= (car here) delay)))
+ (concat (format "%.0f" (/ delay (car (cddr here))))
+ (cadr here)))))))
+
+(defcustom sx-time-date-format-year "%H:%M %e %b %Y"
+ "Format used for dates on a past year.
+See also `sx-time-date-format'."
+ :type 'string
+ :group 'sx)
+
+(defcustom sx-time-date-format "%H:%M - %d %b"
+ "Format used for dates on this year.
+See also `sx-time-date-format-year'."
+ :type 'string
+ :group 'sx)
+
+(defun sx-time-seconds-to-date (seconds)
+ "Return the integer SECONDS as a date string."
+ (let ((time (seconds-to-time seconds)))
+ (format-time-string
+ (if (string= (format-time-string "%Y")
+ (format-time-string "%Y" time))
+ sx-time-date-format
+ sx-time-date-format-year)
+ time)))
+
+(provide 'sx-time)
+;;; sx-time.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+;;; sx-user.el --- handling and printing user information -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Artur Malabarba
+
+;; Author: Artur Malabarba <bruce.connor.amNOSPAMgmail.com>
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+\f
+;;; Code:
+(require 'sx)
+(require 'sx-button)
+
+(defgroup sx-user nil
+ "How users are displayed by SX."
+ :prefix "sx-user-"
+ :tag "SX User"
+ :group 'sx)
+
+(defcustom sx-question-mode-fallback-user
+ '(
+ (about_me . "")
+ (accept_rate . -1)
+ (account_id . -1)
+ (age . -1)
+ (answer_count . -1)
+ (badge_counts . ((bronze . -1) (silver . -1) (gold . -1)))
+ (creation_date . -1)
+ (display_name . "(unknown user)")
+ (down_vote_count . -1)
+ (is_employee . nil)
+ (last_access_date . -1)
+ (last_modified_date . -1)
+ (link . "")
+ (location . "")
+ (profile_image . ":(")
+ (question_count . -1)
+ (reputation . -1)
+ (reputation_change_day . -1)
+ (reputation_change_month . -1)
+ (reputation_change_quarter . -1)
+ (reputation_change_week . -1)
+ (reputation_change_year . -1)
+ (timed_penalty_date . -1)
+ (up_vote_count . -1)
+ (user_id . -1)
+ (user_type . does_not_exist)
+ (view_count . -1)
+ (website_url . "")
+ )
+ "The structure used to represent missing user information.
+NOOTE: SX relies on this variable containing all necessary user
+information. You may edit any of its fields, but you'll run into
+errors if you remove them."
+ :type '(alist :options ((about_me string)
+ (accept_rate integer)
+ (account_id integer)
+ (age integer)
+ (answer_count integer)
+ (badge_counts alist)
+ (creation_date integer)
+ (display_name string)
+ (down_vote_count integer)
+ (is_employee boolean)
+ (last_access_date integer)
+ (last_modified_date integer)
+ (link string)
+ (location string)
+ (profile_image string)
+ (question_count integer)
+ (reputation integer)
+ (reputation_change_day integer)
+ (reputation_change_month integer)
+ (reputation_change_quarter integer)
+ (reputation_change_week integer)
+ (reputation_change_year integer)
+ (timed_penalty_date integer)
+ (up_vote_count integer)
+ (user_id integer)
+ (user_type symbol)
+ (view_count integer)
+ (website_url string)))
+ :group 'sx-user)
+
+\f
+;;; Text properties
+(defface sx-user-name
+ '((t :inherit font-lock-builtin-face))
+ "Face used for user names."
+ :group 'sx-user)
+
+(defface sx-user-reputation
+ '((t :inherit font-lock-comment-face))
+ "Face used for user reputations."
+ :group 'sx-user)
+
+(defface sx-user-accept-rate
+ '((t))
+ "Face used for user accept-rates."
+ :group 'sx-user)
+
+(defvar sx-user--format-property-alist
+ `((?d button ,(list t) category ,(button-category-symbol 'sx-button-user) face sx-user-name)
+ (?n button ,(list t) category ,(button-category-symbol 'sx-button-user) face sx-user-name)
+ (?@ button ,(list t) category ,(button-category-symbol 'sx-button-user) face sx-user-name)
+ (?r face sx-user-reputation)
+ (?a face sx-user-accept-rate))
+ "Alist relating % constructs with text properties.
+See `sx-user--format'.")
+
+\f
+;;; Formatting function
+(defun sx-user--format (format-string user)
+ "Use FORMAT-STRING to format the user object USER.
+The value is a copy of FORMAT-STRING, but with certain constructs
+replaced by text that describes the specified USER:
+
+%d is the display name.
+%@ is the display name in a format suitable for @mentions.
+%l is the link to the profile.
+%r is the reputation.
+%a is the accept rate.
+
+The string replaced in each of these construct is also given the
+text-properties specified in `sx-user--format-property-alist'.
+Specially, %d and %@ are turned into buttons with the
+`sx-button-user' category."
+ (sx-assoc-let (append user sx-question-mode-fallback-user)
+ (let* ((text (sx-format-replacements
+ format-string
+ `((?d . ,\.display_name)
+ (?n . ,\.display_name)
+ (?l . ,\.link)
+ (?r . ,\.reputation)
+ (?a . ,\.accept_rate)
+ (?@ . ,(when (string-match "%@" format-string)
+ (sx-user--@name .display_name)))
+ )
+ sx-user--format-property-alist)))
+ (if (< 0 (string-width .link))
+ (propertize text
+ ;; For visiting and stuff.
+ 'sx-button-url .link
+ 'sx-button-copy .link)
+ text))))
+
+\f
+;;; @name conversion
+(defconst sx-user--ascii-replacement-list
+ '(("[:space:]" . "")
+ ("àåáâäãåą" . "a")
+ ("èéêëę" . "e")
+ ("ìíîïı" . "i")
+ ("òóôõöøőð" . "o")
+ ("ùúûüŭů" . "u")
+ ("çćčĉ" . "c")
+ ("żźž" . "z")
+ ("śşšŝ" . "s")
+ ("ñń" . "n")
+ ("ýÿ" . "y")
+ ("ğĝ" . "g")
+ ("ř" . "r")
+ ("ł" . "l")
+ ("đ" . "d")
+ ("ß" . "ss")
+ ("Þ" . "th")
+ ("ĥ" . "h")
+ ("ĵ" . "j")
+ ("^[:ascii:]" . ""))
+ "List of replacements to use for non-ascii characters.
+Used to convert user names into @mentions.")
+
+(defun sx-user--@name (display-name)
+ "Convert DISPLAY-NAME into an @mention.
+In order to correctly @mention the user, all whitespace is
+removed from DISPLAY-NAME and a series of unicode conversions are
+performed before it is returned.
+See `sx-user--ascii-replacement-list'.
+
+If all you need is the @name, this is very slightly faster than
+using `sx-user--format', but it doesn't do any sanity checking."
+ (concat "@" (sx--recursive-replace
+ sx-user--ascii-replacement-list display-name)))
+
+(provide 'sx-user)
+;;; sx-user.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+;;; sx.el --- StackExchange client. Ask and answer questions on Stack Overflow, Super User, and the likes -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2014 Sean Allred
+
+;; Author: Sean Allred <codeNOSPAMseanallred.com>
+;; URL: https://github.com/vermiculus/sx.el/
+;; Version: 0.3
+;; Keywords: help, hypermedia, tools
+;; Package-Requires: ((emacs "24.1") (cl-lib "0.5") (json "1.3") (markdown-mode "2.0") (let-alist "1.0.3"))
+
+;; 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 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file defines basic commands used by all other parts of SX.
+
+;;; Code:
+(require 'tabulated-list)
+
+(defconst sx-version "0.3" "Version of the `sx' package.")
+
+(defgroup sx nil
+ "Customization group for the `sx' package."
+ :prefix "sx-"
+ :tag "SX"
+ :group 'applications)
+
+\f
+;;; User commands
+(defun sx-version ()
+ "Print and return the version of the `sx' package."
+ (interactive)
+ (message "%s: %s" 'sx-version sx-version)
+ sx-version)
+
+;;;###autoload
+(defun sx-bug-report ()
+ "File a bug report about the `sx' package."
+ (interactive)
+ (browse-url "https://github.com/vermiculus/sx.el/issues/new"))
+
+\f
+;;; Site
+(defun sx--site (data)
+ "Get the site in which DATA belongs.
+DATA can be a question, answer, comment, or user (or any object
+with a `link' property).
+DATA can also be the link itself."
+ (let ((link (if (stringp data) data
+ (cdr (assoc 'link data)))))
+ (when (stringp link)
+ (replace-regexp-in-string
+ (rx string-start
+ "http" (optional "s") "://"
+ (or
+ (sequence
+ (group-n 1 (+ (not (any "/"))))
+ ".stackexchange")
+ (group-n 2 (+ (not (any "/")))))
+ "." (+ (not (any ".")))
+ "/" (* any)
+ string-end)
+ "\\1\\2" link))))
+
+(defun sx--ensure-site (data)
+ "Add a `site' property to DATA if it doesn't have one. Return DATA.
+DATA can be a question, answer, comment, or user (or any object
+with a `link' property)."
+ (when data
+ (let-alist data
+ (unless .site_par
+ ;; @TODO: Change this to .site.api_site_parameter sometime
+ ;; after February.
+ (setcdr data (cons (cons 'site_par
+ (or (cdr (assq 'api_site_parameter .site))
+ (sx--site data)))
+ (cdr data)))))
+ data))
+
+(defun sx--link-to-data (link)
+ "Convert string LINK into data that can be displayed."
+ (let ((result (list (cons 'site_par (sx--site link)))))
+ ;; Try to strip a question or answer ID
+ (when (cond ;; Comment
+ ((or ;; If there's a #commentNUMBER_NUMBER at the end, we
+ ;; know it's a comment with that ID.
+ (string-match (rx "#comment" (group-n 1 (+ digit))
+ "_" (+ digit) string-end)
+ link)
+ ;; From inbox items
+ (string-match (rx "/posts/comments/"
+ ;; Comment ID
+ (group-n 1 (+ digit))
+ ;; Optional stuff at the end
+ (or (and (any "?#") (* any)) "")
+ string-end)
+ link))
+ (push '(type . comment) result))
+ ;; Answer
+ ((or ;; If there's a #NUMBER at the end, we know it's an
+ ;; answer with that ID.
+ (string-match (rx "#" (group-n 1 (+ digit)) string-end) link)
+ ;; From 'Share' button
+ (string-match (rx "/a/"
+ ;; Answer ID
+ (group-n 1 (+ digit)) "/"
+ ;; User ID
+ (+ digit)
+ ;; Garbage at the end
+ (optional (and (any "?#") (* any)))
+ string-end)
+ link)
+ ;; From URL
+ (string-match (rx "/questions/" (+ digit) "/"
+ ;; Question title
+ (+ (not (any "/"))) "/"
+ ;; Answer ID. If this is absent, we match on
+ ;; Question clause below.
+ (group-n 1 (+ digit))
+ (opt "/")
+ ;; Garbage at the end
+ (optional (and (any "?#") (* any)))
+ string-end)
+ link))
+ (push '(type . answer) result))
+ ;; Question
+ ((or ;; From 'Share' button
+ (string-match (rx "/q/"
+ ;; Question ID
+ (group-n 1 (+ digit))
+ ;; User ID
+ (optional "/" (+ digit))
+ ;; Garbage at the end
+ (optional (and (any "?#") (* any)))
+ string-end)
+ link)
+ ;; From URL
+ (string-match (rx "/questions/"
+ ;; Question ID
+ (group-n 1 (+ digit)) "/")
+ link))
+ (push '(type . question) result)))
+ (push (cons 'id (string-to-number (match-string-no-properties 1 link)))
+ result))
+ result))
+
+(defun sx--tree-paths (tree)
+ "Return a list of all paths in TREE.
+Adapted from http://stackoverflow.com/q/3019250."
+ (if (atom tree)
+ (list (list tree))
+ (apply #'append
+ (mapcar (lambda (node)
+ (mapcar (lambda (path)
+ (cons (car tree) path))
+ (sx--tree-paths node)))
+ (cdr tree)))))
+
+(defun sx--tree-expand (path-func tree)
+ "Apply PATH-FUNC to every path in TREE.
+Return the result. See `sx--tree-paths'."
+ (mapcar path-func
+ (apply #'append
+ (mapcar #'sx--tree-paths
+ tree))))
+
+(defmacro sx-assoc-let (alist &rest body)
+ "Use ALIST with `let-alist' to execute BODY.
+`.site_par' has a special meaning, thanks to `sx--ensure-site'.
+If ALIST doesn't have a `site' property, one is created using the
+`link' property."
+ (declare (indent 1) (debug t))
+ (require 'let-alist)
+ `(progn
+ (sx--ensure-site ,alist)
+ ,(macroexpand
+ `(let-alist ,alist ,@body))))
+
+(defun sx--pretty-site-parameter (site)
+ "Returned a pretty and capitalized version of string SITE."
+ (mapconcat #'capitalize
+ (split-string site "\\.")
+ " "))
+
+\f
+;;; Utility Functions
+(defun sx--split-string (string &optional separators)
+ "Split STRING into substrings bounded by matches for SEPARATORS."
+ (mapcar (lambda (s) (replace-regexp-in-string "\\` +\\| +\\'" "" s))
+ (split-string string separators 'omit-nulls)))
+
+(defun sx-completing-read (&rest args)
+ "Like `completing-read', but possibly use ido.
+All ARGS are passed to `completing-read' or `ido-completing-read'."
+ (apply (if ido-mode #'ido-completing-read #'completing-read)
+ args))
+
+(defun sx-user-error (format-string &rest args)
+ "Like `user-error', but prepend FORMAT-STRING with \"[sx]\".
+See `format'."
+ (signal 'user-error
+ (list (apply #'format (concat "[sx] " format-string) args))))
+
+(defun sx-message (format-string &rest args)
+ "Display FORMAT-STRING as a message with ARGS.
+See `format'."
+ (message "[sx] %s" (apply #'format format-string args)))
+
+(defun sx-message-help-echo ()
+ "If there's a 'help-echo property under point, message it."
+ (let ((echo (get-text-property (point) 'help-echo)))
+ (when echo (message "%s" echo))))
+
+(defun sx--thing-as-string (thing &optional sequence-sep url-hexify)
+ "Return a string representation of THING.
+If THING is already a string, just return it.
+
+Optional argument SEQUENCE-SEP is the separator applied between
+elements of a sequence. If SEQUENCE-SEP is a list, use the first
+element for the top level joining, the second for the next level,
+etc. \";\" is used as a default.
+
+If optional argument URL-HEXIFY is non-nil, this function behaves
+as `url-hexify-string'; this option is only effective on strings
+and sequences of strings."
+ (let ((process (if url-hexify #'url-hexify-string #'identity))
+ (first-f (if (listp sequence-sep) #'car #'identity))
+ (rest-f (if (listp sequence-sep) #'cdr #'identity)))
+ (cond
+ ((stringp thing) (funcall process thing))
+ ((symbolp thing) (funcall process (symbol-name thing)))
+ ((numberp thing) (number-to-string thing))
+ ((sequencep thing)
+ (mapconcat (lambda (thing)
+ (sx--thing-as-string
+ thing (funcall rest-f sequence-sep) url-hexify))
+ thing (if sequence-sep
+ (funcall first-f sequence-sep)
+ ";"))))))
+
+(defun sx--shorten-url (url)
+ "Shorten URL hiding anything other than the domain.
+Paths after the domain are replaced with \"...\".
+Anything before the (sub)domain is removed."
+ (replace-regexp-in-string
+ ;; Remove anything after domain.
+ (rx (group-n 1 (and (1+ (any word ".")) "/"))
+ (1+ anything) string-end)
+ (eval-when-compile
+ (concat "\\1" (if (char-displayable-p ?…) "…" "...")))
+ ;; Remove anything before subdomain.
+ (replace-regexp-in-string
+ (rx string-start (or (and (0+ word) (optional ":") "//")))
+ "" url)))
+
+(defmacro sx--define-conditional-key (keymap key def &rest body)
+ "In KEYMAP, define key sequence KEY as DEF conditionally.
+This is like `define-key', except the definition \"disappears\"
+whenever BODY evaluates to nil."
+ (declare (indent 3)
+ (debug (form form form &rest sexp)))
+ `(define-key ,keymap ,key
+ '(menu-item
+ ,(format "maybe-%s" (or (car (cdr-safe def)) def)) ignore
+ :filter (lambda (&optional _)
+ (when (progn ,@body) ,def)))))
+
+(defun sx--goto-property-change (prop &optional direction)
+ "Move forward to the next change of text-property PROP.
+Return the new value of PROP at point.
+
+If DIRECTION is negative, move backwards instead."
+ (let ((func (if (and (numberp direction)
+ (< direction 0))
+ #'previous-single-property-change
+ #'next-single-property-change))
+ (limit (if (and (numberp direction)
+ (< direction 0))
+ (point-min) (point-max))))
+ (goto-char (funcall func (point) prop nil limit))
+ (get-text-property (point) prop)))
+
+(defun sx--find-in-buffer (type id)
+ "Move point to an object of TYPE and ID.
+That is, move forward from beginning of buffer until
+`sx--data-here' is an object of type TYPE with the respective id
+ID. If point is left at the of a line, move over the line break.
+
+TYPE is either question, answer, or comment.
+ID is an integer."
+ (let* ((id-symbol (cl-case type
+ (answer 'answer_id)
+ (comment 'comment_id)
+ (question 'question_id)))
+ (pos
+ (save-excursion
+ (goto-char (point-min))
+ (while (not (or (eobp)
+ (let ((data (sx--data-here type t)))
+ (and data
+ (= id (or (cdr (assq id-symbol data))))))))
+ (forward-char 1))
+ (point))))
+ (if (equal pos (point-max))
+ (sx-message "Can't find the specified %s" type)
+ (goto-char pos)
+ (when (looking-at-p "$")
+ (forward-char 1)))))
+
+(defmacro sx--create-comparator (name doc compare-func get-func)
+ "Define a new comparator called NAME with documentation DOC.
+COMPARE-FUNC is a function that takes the return value of
+GET-FUNC and performs the actual comparison."
+ (declare (indent 1) (doc-string 2))
+ `(defun ,name (a b)
+ ,doc
+ (funcall ,compare-func
+ (funcall ,get-func a)
+ (funcall ,get-func b))))
+
+(defun sx--squash-whitespace (string)
+ "Return STRING with consecutive whitespace squashed together."
+ (replace-regexp-in-string "[ \r\n]+" " " string))
+
+(defun sx--deleted-p (data)
+ "Return non-nil if DATA represents a deleted object."
+ (eq (car data) 'deleted))
+
+(defun sx--invert-predicate (predicate)
+ "Return PREDICATE function with arguments inverted.
+For instance (sx--invert-predicate #'<) is the same as #'>.
+Note this is not the same as negating PREDICATE."
+ (lambda (&rest args) (apply predicate (reverse args))))
+
+\f
+;;; Printing request data
+(defvar sx--overlays nil
+ "Overlays created by sx on this buffer.")
+(make-variable-buffer-local 'sx--overlays)
+
+(defvar sx--overlay-printing-depth 0
+ "Track how many overlays we're printing on top of each other.
+Used for assigning higher priority to inner overlays.")
+(make-variable-buffer-local 'sx--overlay-printing-depth)
+
+(defmacro sx--wrap-in-overlay (properties &rest body)
+ "Start a scope with overlay PROPERTIES and execute BODY.
+Overlay is pushed on the buffer-local variable `sx--overlays' and
+given PROPERTIES.
+
+Return the result of BODY."
+ (declare (indent 1)
+ (debug t))
+ `(let ((p (point-marker))
+ (result (progn ,@body))
+ ;; The first overlay is the shallowest. Any overlays created
+ ;; while the first one is still being created go deeper and
+ ;; deeper.
+ (sx--overlay-printing-depth (1+ sx--overlay-printing-depth)))
+ (let ((ov (make-overlay p (point)))
+ (props ,properties))
+ (while props
+ (overlay-put ov (pop props) (pop props)))
+ ;; Let's multiply by 10 just in case we ever want to put
+ ;; something in the middle.
+ (overlay-put ov 'priority (* 10 sx--overlay-printing-depth))
+ (push ov sx--overlays))
+ result))
+
+(defun sx--recursive-replace (alist string)
+ "Replace each car of ALIST with its cdr in STRING."
+ (if alist
+ (sx--recursive-replace
+ (cdr alist)
+ (let ((kar (car alist)))
+ (replace-regexp-in-string
+ (format "[%s]" (car kar)) (cdr kar) string)))
+ string))
+
+(defun sx-format-replacements (format alist &optional property-alist)
+ "Use FORMAT-STRING to format the values in ALIST.
+ALIST is a list with elements of the form (CHAR . STRING).
+The value is a copy of FORMAT-STRING, but with certain constructs
+replaced by text as given by ALIST.
+
+The construct is a `%' character followed by any other character.
+The replacement is the STRING corresponding to CHAR in ALIST. In
+addition, if CHAR is also the car of an element in
+PROPERTY-ALIST, the cdr of that element should be a list of text
+properties which will be applied on the replacement.
+
+The %% construct is special, it is replaced with a single %, even
+if ALIST contains a different string at the ?% entry."
+ (let ((alist (cons '(?% . "%") alist)))
+ (with-temp-buffer
+ (insert format)
+ (goto-char (point-min))
+ (while (search-forward-regexp
+ (rx "%" (group-n 1 (* (any "-+ #0-9.")))) nil 'noerror)
+ (let* ((char (char-after))
+ ;; Understand flags
+ (flag (match-string 1))
+ (val (cdr-safe (assq char alist))))
+ (unless val
+ (error "Invalid format character: `%%%c'" char))
+ ;; Insert first, to preserve text properties.
+ (insert-and-inherit (format (concat "%" flag "s") val))
+ (when property-alist
+ (add-text-properties (match-end 0) (point)
+ (cdr-safe (assq char property-alist))))
+ ;; Delete the specifier body.
+ (delete-region (match-beginning 0)
+ (match-end 0))
+ ;; Delete `char-after'.
+ (delete-char 1)))
+ (buffer-string))))
+
+\f
+;;; Key definitions
+(defun sx--key-definitions-to-header-line (definitions)
+ "Return a `header-line-format' from DEFINITIONS.
+DEFINITIONS is a list where each element has one of the following two forms
+ (KEY COMMAND)
+ (KEY COMMAND DESCRIPTION)
+
+The latter are used to build the return value, the former are
+ignored."
+ (let ((ptize (lambda (x) `(:propertize ,x face mode-line-buffer-id)))
+ alist out)
+ (dolist (it definitions)
+ (when (> (length it) 2)
+ (let* ((key (car it))
+ (desc (elt it 2))
+ (cell (assoc desc alist)))
+ (if cell (push key (cdr cell))
+ (push (cons desc (list key)) alist)))))
+ (dolist (it alist out)
+ (let ((desc (car it))
+ (keys (cdr it)))
+ (push (list " "
+ (cons (funcall ptize (car keys))
+ (mapcar (lambda (k) `("," ,(funcall ptize k))) (cdr keys)))
+ (let ((match
+ (and (= 1 (length keys))
+ (string-match (regexp-quote (car keys)) desc))))
+ (if (and (numberp match) (= 0 match))
+ (substring desc (length (car keys)))
+ (concat ":" desc))))
+ out)))))
+
+\f
+(defcustom sx-init-hook nil
+ "Hook run when SX initializes.
+Run after `sx-init--internal-hook'."
+ :group 'sx
+ :type 'hook)
+
+(defvar sx-init--internal-hook nil
+ "Hook run when SX initializes.
+This is used internally to set initial values for variables such
+as filters.")
+
+(defmacro sx-init-variable (variable value &optional setter)
+ "Set VARIABLE to VALUE using SETTER.
+SETTER should be a function of two arguments. If SETTER is nil,
+`set' is used."
+ (eval
+ `(add-hook
+ 'sx-init--internal-hook
+ (lambda ()
+ (,(or setter #'setq) ,variable ,value))))
+ nil)
+
+(defvar sx-initialized nil
+ "Nil if sx hasn't been initialized yet.
+If it has, holds the time at which initialization happened.")
+
+(defun sx-initialize (&optional force)
+ "Run initialization hooks if they haven't been run yet.
+These are `sx-init--internal-hook' and `sx-init-hook'.
+
+If FORCE is non-nil, run them even if they've already been run."
+ (when (or force (not sx-initialized))
+ (prog1
+ (run-hooks 'sx-init--internal-hook
+ 'sx-init-hook)
+ (setq sx-initialized (current-time)))))
+
+(provide 'sx)
+;;; sx.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
--- /dev/null
+This file defines basic commands used by all other parts of SX.
--- /dev/null
+This is the file .../info/dir, which contains the
+topmost node of the Info hierarchy, called (dir)Top.
+The first time you invoke Info you start off looking at this node.
+\1f
+File: dir, Node: Top This is the top of the INFO tree
+
+ This (the Directory node) gives a menu of major topics.
+ Typing "q" exits, "?" lists all Info commands, "d" returns here,
+ "h" gives a primer for first-timers,
+ "mEmacs<Return>" visits the Emacs manual, etc.
+
+ In Emacs, you can click mouse button 2 on a menu item or cross reference
+ to select it.
+
+* Menu:
+
+Emacs
+* With-Editor: (with-editor). Using the Emacsclient as $EDITOR.
--- /dev/null
+;;; with-editor-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+(add-to-list 'load-path (or (file-name-directory #$) (car load-path)))
+\f
+;;;### (autoloads nil nil ("with-editor-pkg.el" "with-editor.el")
+;;;;;; (21910 48887 384174 0))
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+;;; with-editor-autoloads.el ends here
--- /dev/null
+(define-package "with-editor" "20150701.1246" "Use the Emacsclient as $EDITOR"
+ '((emacs "24.4")
+ (dash "2.10.0"))
+ :url "https://github.com/magit/magit" :keywords
+ '("tools"))
+;; Local Variables:
+;; no-byte-compile: t
+;; End:
--- /dev/null
+;;; with-editor.el --- Use the Emacsclient as $EDITOR -*- lexical-binding: t -*-
+
+;; Copyright (C) 2014-2015 The Magit Project Contributors
+;;
+;; You should have received a copy of the AUTHORS.md file which
+;; lists all contributors. If not, see http://magit.vc/authors.
+
+;; Author: Jonas Bernoulli <jonasNOSPAMbernoul.li>
+;; Maintainer: Jonas Bernoulli <jonasNOSPAMbernoul.li>
+
+;; Package-Requires: ((emacs "24.4") (dash "2.10.0"))
+;; Keywords: tools
+;; Homepage: https://github.com/magit/magit
+
+;; This file is not part of GNU Emacs.
+
+;; This file 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 file 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 file. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Use the Emacsclient as `$EDITOR' of child processes, making sure
+;; they know how to call home. For remote processes a substitute is
+;; provided, which communicates with Emacs on stdout instead of using
+;; a socket as the Emacsclient does.
+
+;; The commands `with-editor-async-shell-command' and
+;; `with-editor-shell-command' are intended as drop in replacements
+;; for `async-shell-command' and `shell-command'. They automatically
+;; export `$EDITOR' making sure the executed command uses the current
+;; Emacs instance as "the editor". With a prefix argument these
+;; commands prompt for an alternative environment variable such as
+;; `$GIT_EDITOR'. To always use these variants add this to you init
+;; file:
+;;
+;; (define-key (current-global-map)
+;; [remap async-shell-command] 'with-editor-async-shell-command)
+;; (define-key (current-global-map)
+;; [remap shell-command] 'with-editor-shell-command)
+
+;; Alternatively use the global `shell-command-with-editor-mode',
+;; which always sets `$EDITOR' for all Emacs commands which ultimately
+;; use `shell-command' to asynchronously run some shell command.
+
+;; The command `with-editor-export-editor' exports `$EDITOR' or
+;; another such environment variable in `shell-mode', `term-mode' and
+;; `eshell-mode' buffers. Use this Emacs command before executing a
+;; shell command which needs the editor set, or always arrange for the
+;; current Emacs instance to be used as editor by adding it to the
+;; appropriate mode hooks:
+;;
+;; (add-hook 'shell-mode-hook 'with-editor-export-editor)
+;; (add-hook 'term-mode-hook 'with-editor-export-editor)
+;; (add-hook 'eshell-mode-hook 'with-editor-export-editor)
+
+;; Some variants of this function exist, these two forms are
+;; equivalent:
+;;
+;; (add-hook 'shell-mode-hook
+;; (apply-partially 'with-editor-export-editor "GIT_EDITOR"))
+;; (add-hook 'shell-mode-hook 'with-editor-export-git-editor)
+
+;; This library can also be used by other packages which need to use
+;; the current Emacs instance as editor. In fact this library was
+;; written for Magit and its `git-commit-mode' and `git-rebase-mode'.
+;; Consult `git-rebase.el' and the related code in `magit-sequence.el'
+;; for a simple example.
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'dash)
+(require 'server)
+(require 'tramp)
+(require 'tramp-sh nil t)
+
+(eval-when-compile
+ (progn (require 'dired nil t)
+ (require 'eshell nil t)
+ (require 'term nil t)
+ (require 'warnings nil t)))
+(declare-function dired-get-filename 'dired)
+(declare-function term-emulate-terminal 'term)
+(defvar eshell-preoutput-filter-functions)
+
+;;; Options
+
+(defgroup with-editor nil
+ "Use the Emacsclient as $EDITOR."
+ :group 'external
+ :group 'server)
+
+(defun with-editor-locate-emacsclient ()
+ "Search for a suitable Emacsclient executable."
+ (--if-let (with-editor-locate-emacsclient-1 (with-editor-emacsclient-path) 3)
+ (shell-quote-argument it)
+ (display-warning 'with-editor (format "\
+Cannot determine a suitable Emacsclient
+
+Determining an Emacsclient executable suitable for the
+current Emacs instance failed. For more information
+please see https://github.com/magit/magit/wiki/Emacsclient."))
+ nil))
+
+(defun with-editor-locate-emacsclient-1 (path depth)
+ (let* ((version-lst (-take depth (split-string emacs-version "\\.")))
+ (version-reg (concat "^" (mapconcat #'identity version-lst "\\."))))
+ (or (locate-file-internal
+ "emacsclient" path
+ (cl-mapcan
+ (lambda (v) (cl-mapcar (lambda (e) (concat v e)) exec-suffixes))
+ (nconc (cl-mapcon (lambda (v)
+ (setq v (mapconcat #'identity (reverse v) "."))
+ (list v (concat "-" v)))
+ (reverse version-lst))
+ (list "")))
+ (lambda (exec)
+ (ignore-errors
+ (string-match-p version-reg
+ (with-editor-emacsclient-version exec)))))
+ (and (> depth 1)
+ (with-editor-locate-emacsclient-1 path (1- depth))))))
+
+(defun with-editor-emacsclient-version (exec)
+ (cadr (split-string (car (process-lines exec "--version")))))
+
+(defun with-editor-emacsclient-path ()
+ (let ((path exec-path))
+ (when invocation-directory
+ (push (directory-file-name invocation-directory) path)
+ (let* ((linkname (expand-file-name invocation-name invocation-directory))
+ (truename (file-chase-links linkname)))
+ (unless (equal truename linkname)
+ (push (directory-file-name (file-name-directory truename)) path)))
+ (when (eq system-type 'darwin)
+ (let ((dir (expand-file-name "bin" invocation-directory)))
+ (when (file-directory-p dir)
+ (push dir path)))
+ (when (string-match-p "Cellar" invocation-directory)
+ (let ((dir (expand-file-name "../../../bin" invocation-directory)))
+ (when (file-directory-p dir)
+ (push dir path))))))
+ (cl-remove-duplicates path :test 'equal)))
+
+(defcustom with-editor-emacsclient-executable (with-editor-locate-emacsclient)
+ "The Emacsclient executable used by the `with-editor' macro."
+ :group 'with-editor
+ :type '(choice (string :tag "Executable")
+ (const :tag "Don't use Emacsclient" nil)))
+
+(defcustom with-editor-sleeping-editor "\
+sh -c '\
+echo \"WITH-EDITOR: $$ OPEN $0\"; \
+sleep 604800 & sleep=$!; \
+trap \"kill $sleep; exit 0\" USR1; \
+trap \"kill $sleep; exit 1\" USR2; \
+wait $sleep'"
+ "The looping editor, used when the Emacsclient cannot be used.
+
+This fallback is used for asynchronous process started inside the
+macro `with-editor', when the process runs on a remote machine or
+for local processes when `with-editor-emacsclient-executable' is
+nil (i.e. when no suitable Emacsclient was found, or the user
+decided not to use it).
+
+Where the latter uses a socket to communicate with Emacs' server,
+this substitute prints edit requests to its standard output on
+which a process filter listens for such requests. As such it is
+not a complete substitute for a proper Emacsclient, it can only
+be used as $EDITOR of child process of the current Emacs instance."
+ :group 'with-editor
+ :type 'string)
+
+(defcustom with-editor-finish-query-functions nil
+ "List of functions called to query before finishing session.
+
+The buffer in question is current while the functions are called.
+If any of them returns nil, then the session is not finished and
+the buffer is not killed. The user should then fix the issue and
+try again. The functions are called with one argument. If it is
+non-nil then that indicates that the user used a prefix argument
+to force finishing the session despite issues. Functions should
+usually honor that and return non-nil."
+ :group 'with-editor
+ :type 'hook)
+(put 'with-editor-finish-query-functions 'permanent-local t)
+
+(defcustom with-editor-cancel-query-functions nil
+ "List of functions called to query before canceling session.
+
+The buffer in question is current while the functions are called.
+If any of them returns nil, then the session is not canceled and
+the buffer is not killed. The user should then fix the issue and
+try again. The functions are called with one argument. If it is
+non-nil then that indicates that the user used a prefix argument
+to force canceling the session despite issues. Functions should
+usually honor that and return non-nil."
+ :group 'with-editor
+ :type 'hook)
+(put 'with-editor-cancel-query-functions 'permanent-local t)
+
+(defcustom with-editor-mode-lighter " WE"
+ "The mode-line lighter of the With-Editor mode."
+ :group 'with-editor
+ :type '(choice (const :tag "No lighter" "") string))
+
+(defvar with-editor-server-window-alist nil
+ "Alist of filename patterns vs corresponding `server-window'.
+
+Each element looks like (REGEXP . FUNCTION). Files matching
+REGEXP are selected using FUNCTION instead of the default in
+`server-window'.
+
+Note that when a package adds an entry here then it probably
+has a reason to disrespect `server-window' and it likely is
+not a good idea to change such entries.")
+
+;;; Mode Commands
+
+(defvar with-editor-pre-finish-hook nil)
+(defvar with-editor-pre-cancel-hook nil)
+(defvar with-editor-post-cancel-hook nil)
+(defvar with-editor-post-cancel-hook-1 nil)
+(defvar with-editor-cancel-alist nil)
+(put 'with-editor-pre-finish-hook 'permanent-local t)
+(put 'with-editor-pre-cancel-hook 'permanent-local t)
+(put 'with-editor-post-cancel-hook 'permanent-local t)
+
+(defvar with-editor-show-usage t)
+(defvar with-editor-cancel-message nil)
+(defvar with-editor-previous-winconf nil)
+(make-variable-buffer-local 'with-editor-show-usage)
+(make-variable-buffer-local 'with-editor-cancel-message)
+(make-variable-buffer-local 'with-editor-previous-winconf)
+(put 'with-editor-cancel-message 'permanent-local t)
+(put 'with-editor-previous-winconf 'permanent-local t)
+
+(defvar-local with-editor--pid nil "For internal use.")
+(put 'with-editor--pid 'permanent-local t)
+
+(defun with-editor-finish (force)
+ "Finish the current edit session."
+ (interactive "P")
+ (when (run-hook-with-args-until-failure
+ 'with-editor-finish-query-functions force)
+ (run-hooks 'with-editor-pre-finish-hook)
+ (with-editor-return nil)))
+
+(defun with-editor-cancel (force)
+ "Cancel the current edit session."
+ (interactive "P")
+ (when (run-hook-with-args-until-failure
+ 'with-editor-cancel-query-functions force)
+ (let ((message with-editor-cancel-message))
+ (when (functionp message)
+ (setq message (funcall message)))
+ (let ((with-editor-post-cancel-hook-1
+ (ignore-errors (delq t with-editor-post-cancel-hook)))
+ (with-editor-cancel-alist nil))
+ (run-hooks 'with-editor-pre-cancel-hook)
+ (with-editor-return t)
+ (accept-process-output nil 0.1)
+ (run-hooks 'with-editor-post-cancel-hook-1))
+ (message (or message "Canceled by user")))))
+
+(defun with-editor-return (cancel)
+ (let ((winconf with-editor-previous-winconf)
+ (clients server-buffer-clients)
+ (dir default-directory)
+ (pid with-editor--pid))
+ (remove-hook 'kill-buffer-query-functions
+ 'with-editor-kill-buffer-noop t)
+ (cond (cancel
+ (save-buffer)
+ (if clients
+ (dolist (client clients)
+ (ignore-errors
+ (server-send-string client "-error Canceled by user"))
+ (delete-process client))
+ (kill-buffer)))
+ (t
+ (save-buffer)
+ (if clients (server-edit) (kill-buffer))))
+ (when pid
+ (let ((default-directory dir))
+ (process-file "kill" nil nil nil
+ "-s" (if cancel "USR2" "USR1") pid)))
+ (when (and winconf (eq (window-configuration-frame winconf)
+ (selected-frame)))
+ (set-window-configuration winconf))))
+
+;;; Mode
+
+(defvar with-editor-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map "\C-c\C-c" 'with-editor-finish)
+ (define-key map [remap server-edit] 'with-editor-finish)
+ (define-key map "\C-c\C-k" 'with-editor-cancel)
+ (define-key map [remap kill-buffer] 'with-editor-cancel)
+ (define-key map [remap ido-kill-buffer] 'with-editor-cancel)
+ (define-key map [remap iswitchb-kill-buffer] 'with-editor-cancel)
+ map))
+
+(define-minor-mode with-editor-mode
+ "Edit a file as the $EDITOR of an external process."
+ :lighter with-editor-mode-lighter
+ ;; Protect the user from killing the buffer without using
+ ;; either `with-editor-finish' or `with-editor-cancel',
+ ;; and from removing the key bindings for these commands.
+ (unless with-editor-mode
+ (error "With-Editor mode cannot be turned off"))
+ (add-hook 'kill-buffer-query-functions
+ 'with-editor-kill-buffer-noop nil t)
+ ;; `server-excecute' displays a message which is not
+ ;; correct when using this mode.
+ (when with-editor-show-usage
+ (with-editor-usage-message)))
+
+(put 'with-editor-mode 'permanent-local t)
+
+(defun with-editor-kill-buffer-noop ()
+ (message (substitute-command-keys "\
+Don't kill this buffer. Instead cancel using \\[with-editor-cancel]")))
+
+(defun with-editor-usage-message ()
+ ;; Run after `server-execute', which is run using
+ ;; a timer which starts immediately.
+ (run-with-timer
+ 0.01 nil `(lambda ()
+ (with-current-buffer ,(current-buffer)
+ (message (substitute-command-keys "\
+Type \\[with-editor-finish] to finish, \
+or \\[with-editor-cancel] to cancel"))))))
+
+;;; Wrappers
+
+(defvar with-editor--envvar nil "For internal use.")
+
+(defmacro with-editor (&rest body)
+ "Use the Emacsclient as $EDITOR while evaluating BODY.
+Modify the `process-environment' for processes started in BODY,
+instructing them to use the Emacsclient as $EDITOR. If optional
+ENVVAR is provided then bind that environment variable instead.
+\n(fn [ENVVAR] BODY...)"
+ (declare (indent defun))
+ `(let ((with-editor--envvar ,(if (stringp (car body))
+ (pop body)
+ '(or with-editor--envvar "EDITOR")))
+ (process-environment process-environment))
+ (if (or (not with-editor-emacsclient-executable)
+ (file-remote-p default-directory))
+ (setenv with-editor--envvar with-editor-sleeping-editor)
+ ;; Make sure server-use-tcp's value is valid.
+ (unless (featurep 'make-network-process '(:family local))
+ (setq server-use-tcp t))
+ ;; Make sure the server is running.
+ (unless server-process
+ (when (server-running-p server-name)
+ (setq server-name (format "server%s" (emacs-pid)))
+ (when (server-running-p server-name)
+ (server-force-delete server-name)))
+ (server-start))
+ ;; Tell $EDITOR to use the Emacsclient.
+ (setenv with-editor--envvar
+ (concat with-editor-emacsclient-executable
+ ;; Tell the process where the server file is.
+ (and (not server-use-tcp)
+ (concat " --socket-name="
+ (expand-file-name server-name
+ server-socket-dir)))))
+ (when server-use-tcp
+ (setenv "EMACS_SERVER_FILE"
+ (expand-file-name server-name server-auth-dir)))
+ ;; As last resort fallback to the looping editor.
+ (setenv "ALTERNATE_EDITOR" with-editor-sleeping-editor))
+ ,@body))
+
+(defun with-editor-server-window ()
+ (or (and buffer-file-name
+ (cdr (--first (string-match-p (car it) buffer-file-name)
+ with-editor-server-window-alist)))
+ server-window))
+
+(defadvice server-switch-buffer (around with-editor activate)
+ "Honor `with-editor-server-window-alist' (which see)."
+ (let ((server-window (with-current-buffer
+ (or next-buffer (current-buffer))
+ (with-editor-server-window))))
+ ad-do-it))
+
+(defadvice start-file-process (around with-editor activate)
+ "When called inside a `with-editor' form and the Emacsclient
+cannot be used, then give the process the filter function
+`with-editor-process-filter'. To avoid overriding the filter
+being added here you should use `with-editor-set-process-filter'
+instead of `set-process-filter' inside `with-editor' forms.
+
+When the `default-directory' is located on a remote machine,
+then also manipulate PROGRAM and PROGRAM-ARGS in order to set
+the appropriate editor environment variable."
+ ;; (fn NAME BUFFER PROGRAM &rest PROGRAM-ARGS)
+ (if (not with-editor--envvar)
+ ad-do-it
+ (when (file-remote-p default-directory)
+ (let ((prog (ad-get-arg 2))
+ (args (ad-get-args 3)))
+ (unless (equal program "env")
+ (push prog args)
+ (setq prog "env"))
+ (push (concat with-editor--envvar "=" with-editor-sleeping-editor) args)
+ (ad-set-arg 2 prog)
+ (ad-set-args 3 args)))
+ (let ((process ad-do-it))
+ (set-process-filter process 'with-editor-process-filter)
+ (process-put process 'default-dir default-directory)
+ process)))
+
+(defun with-editor-set-process-filter (process filter)
+ "Like `set-process-filter' but keep `with-editor-process-filter'.
+Give PROCESS the new FILTER but keep `with-editor-process-filter'
+if that was added earlier by the adviced `start-file-process'.
+
+Do so by wrapping the two filter functions using a lambda, which
+becomes the actual filter. It calls `with-editor-process-filter'
+first, passing t as NO-STANDARD-FILTER. Then it calls FILTER,
+which may or may not insert the text into the PROCESS' buffer."
+ (set-process-filter
+ process
+ (if (eq (process-filter process) 'with-editor-process-filter)
+ `(lambda (proc str)
+ (,filter proc str)
+ (with-editor-process-filter proc str t))
+ filter)))
+
+(defun with-editor-output-filter (string)
+ (save-match-data
+ (if (string-match "^WITH-EDITOR: \\([0-9]+\\) OPEN \\(.+?\\)\r?$" string)
+ (let ((pid (match-string 1 string))
+ (file (match-string 2 string)))
+ (with-current-buffer
+ (find-file-noselect
+ (if (file-name-absolute-p file)
+ (if (tramp-tramp-file-p default-directory)
+ (with-parsed-tramp-file-name default-directory nil
+ (tramp-make-tramp-file-name method user host file hop))
+ file)
+ (expand-file-name file)))
+ (with-editor-mode 1)
+ (setq with-editor--pid pid)
+ (run-hooks 'with-editor-filter-visit-hook)
+ (funcall (or (with-editor-server-window) 'switch-to-buffer)
+ (current-buffer))
+ (kill-local-variable 'server-window))
+ nil)
+ string)))
+
+(defun with-editor-process-filter
+ (process string &optional no-default-filter)
+ "Listen for edit requests by child processes."
+ (let ((default-directory (process-get process 'default-dir)))
+ (with-editor-output-filter string))
+ (unless no-default-filter
+ (internal-default-process-filter process string)))
+
+;;; Augmentations
+
+(cl-defun with-editor-export-editor (&optional (envvar "EDITOR"))
+ "Teach subsequent commands to use current Emacs instance as editor.
+
+Set and export the environment variable ENVVAR, by default
+\"EDITOR\". The value is automatically generated to teach
+commands use the current Emacs instance as \"the editor\".
+
+This works in `shell-mode', `term-mode' and `eshell-mode'."
+ (interactive (list (with-editor-read-envvar)))
+ (cond
+ ((derived-mode-p 'comint-mode 'term-mode)
+ (let* ((process (get-buffer-process (current-buffer)))
+ (filter (process-filter process)))
+ (set-process-filter process 'ignore)
+ (goto-char (process-mark process))
+ (process-send-string
+ process (format "export %s=%s\n" envvar
+ (shell-quote-argument with-editor-sleeping-editor)))
+ (while (accept-process-output process 0.1))
+ (set-process-filter process filter)
+ (if (derived-mode-p 'term-mode)
+ (with-editor-set-process-filter process 'with-editor-emulate-terminal)
+ (add-hook 'comint-output-filter-functions 'with-editor-output-filter
+ nil t))))
+ ((derived-mode-p 'eshell-mode)
+ (add-to-list 'eshell-preoutput-filter-functions
+ 'with-editor-output-filter)
+ (setenv envvar with-editor-sleeping-editor))
+ (t
+ (error "Cannot export environment variables in this buffer")))
+ (message "Successfully exported %s" envvar))
+
+(defun with-editor-export-git-editor ()
+ "Like `with-editor-export-editor' but always set `$GIT_EDITOR'."
+ (interactive)
+ (with-editor-export-editor "GIT_EDITOR"))
+
+(defun with-editor-export-hg-editor ()
+ "Like `with-editor-export-editor' but always set `$HG_EDITOR'."
+ (interactive)
+ (with-editor-export-editor "HG_EDITOR"))
+
+(defun with-editor-emulate-terminal (process string)
+ "Like `term-emulate-terminal' but also handle edit requests."
+ (when (with-editor-output-filter string)
+ (term-emulate-terminal process string)))
+
+(defvar with-editor-envvars '("EDITOR" "GIT_EDITOR" "HG_EDITOR"))
+
+(cl-defun with-editor-read-envvar
+ (&optional (prompt "Set environment variable")
+ (default "EDITOR"))
+ (let ((reply (completing-read (if default
+ (format "%s (%s): " prompt default)
+ (concat prompt ": "))
+ with-editor-envvars nil nil nil nil default)))
+ (if (string= reply "") (user-error "Nothing selected") reply)))
+
+(define-minor-mode shell-command-with-editor-mode
+ "Teach `shell-command' to use current Emacs instance as editor.
+
+Teach `shell-command', and all commands that ultimately call that
+command, to use the current Emacs instance as editor by executing
+\"EDITOR=CLIENT COMMAND&\" instead of just \"COMMAND&\".
+
+CLIENT is automatically generated; EDITOR=CLIENT instructs
+COMMAND to use to the current Emacs instance as \"the editor\",
+assuming no other variable overrides the effect of \"$EDITOR\".
+CLIENT may be the path to an appropriate emacsclient executable
+with arguments, or a script which also works over Tramp.
+
+Alternatively you can use the `with-editor-async-shell-command',
+which also allows the use of another variable instead of
+\"EDITOR\"."
+ :global t)
+
+(defun with-editor-async-shell-command
+ (command &optional output-buffer error-buffer envvar)
+ "Like `async-shell-command' but with `$EDITOR' set.
+
+Execute string \"ENVVAR=CLIENT COMMAND\" in an inferior shell;
+display output, if any. With a prefix argument prompt for an
+environment variable, otherwise the default \"EDITOR\" variable
+is used. With a negative prefix argument additionally insert
+the COMMAND's output at point.
+
+CLIENT is automatically generated; ENVVAR=CLIENT instructs
+COMMAND to use to the current Emacs instance as \"the editor\",
+assuming it respects ENVVAR as an \"EDITOR\"-like variable.
+CLIENT maybe the path to an appropriate emacsclient executable
+with arguments, or a script which also works over Tramp.
+
+Also see `async-shell-command' and `shell-command'."
+ (interactive (with-editor-shell-command-read-args "Async shell command: " t))
+ (let ((with-editor--envvar envvar))
+ (with-editor
+ (async-shell-command command output-buffer error-buffer))))
+
+(defun with-editor-shell-command
+ (command &optional output-buffer error-buffer envvar)
+ "Like `shell-command' or `with-editor-async-shell-command'.
+If COMMAND ends with \"&\" behave like the latter,
+else like the former."
+ (interactive (with-editor-shell-command-read-args "Shell command: "))
+ (if (string-match "&[ \t]*\\'" command)
+ (with-editor-async-shell-command
+ command output-buffer error-buffer envvar)
+ (shell-command command output-buffer error-buffer)))
+
+(defun with-editor-shell-command-read-args (prompt &optional async)
+ (let ((command (read-shell-command
+ prompt nil nil
+ (--when-let (or (buffer-file-name)
+ (and (eq major-mode 'dired-mode)
+ (dired-get-filename nil t)))
+ (file-relative-name it)))))
+ (list command
+ (if (or async (setq async (string-match-p "&[ \t]*\\'" command)))
+ (< (prefix-numeric-value current-prefix-arg) 0)
+ current-prefix-arg)
+ shell-command-default-error-buffer
+ (and async current-prefix-arg (with-editor-read-envvar)))))
+
+(defadvice shell-command (around with-editor activate)
+ (cond ((or (not (or with-editor--envvar shell-command-with-editor-mode))
+ (not (string-match-p "&$" (ad-get-arg 0))))
+ ad-do-it)
+ ((and with-editor-emacsclient-executable
+ (not (file-remote-p default-directory)))
+ (with-editor ad-do-it))
+ (t
+ (ad-set-arg
+ 0 (format "%s=%s %s"
+ (or with-editor--envvar "EDITOR")
+ (shell-quote-argument with-editor-sleeping-editor)
+ (ad-get-arg 0)))
+ (let ((process ad-do-it))
+ (set-process-filter
+ process (lambda (proc str)
+ (comint-output-filter proc str)
+ (with-editor-process-filter proc str t)))
+ process))))
+
+;;; with-editor.el ends soon
+
+(defun with-editor-debug ()
+ "Debug configuration issues.
+See `with-editor.info' for instructions."
+ (interactive)
+ (with-current-buffer (get-buffer-create "*with-editor-debug*")
+ (pop-to-buffer (current-buffer))
+ (erase-buffer)
+ (ignore-errors (with-editor))
+ (insert
+ (format "with-editor: %s\n" (locate-library "with-editor.el"))
+ (format "emacs: %s (%s)\n"
+ (expand-file-name invocation-name invocation-directory)
+ emacs-version)
+ "system:\n"
+ (format " system-type: %s\n" system-type)
+ (format " system-configuration: %s\n" system-configuration)
+ (format " system-configuration-options: %s\n" system-configuration-options)
+ "server:\n"
+ (format " server-running-p: %s\n" (server-running-p))
+ (format " server-process: %S\n" server-process)
+ (format " server-use-tcp: %s\n" server-use-tcp)
+ (format " server-name: %s\n" server-name)
+ (format " server-socket-dir: %s\n" server-socket-dir))
+ (if (file-accessible-directory-p server-socket-dir)
+ (--each (directory-files server-socket-dir nil "^[^.]")
+ (insert (format " %s\n" it)))
+ (insert (format " %s: not an accessible directory\n"
+ (if server-use-tcp "WARNING" "ERROR"))))
+ (insert (format " server-auth-dir: %s\n" server-auth-dir))
+ (if (file-accessible-directory-p server-auth-dir)
+ (--each (directory-files server-auth-dir nil "^[^.]")
+ (insert (format " %s\n" it)))
+ (insert (format " %s: not an accessible directory\n"
+ (if server-use-tcp "ERROR" "WARNING"))))
+ (let ((val with-editor-emacsclient-executable)
+ (def (default-value 'with-editor-emacsclient-executable))
+ (fun (let ((warning-minimum-level :error)
+ (warning-minimum-log-level :error))
+ (with-editor-locate-emacsclient))))
+ (insert "magit-emacsclient-executable:\n"
+ (format " value: %s (%s)\n" val
+ (and val (with-editor-emacsclient-version val)))
+ (format " default: %s (%s)\n" def
+ (and def (with-editor-emacsclient-version def)))
+ (format " funcall: %s (%s)\n" fun
+ (and fun (with-editor-emacsclient-version fun)))))
+ (insert "path:\n"
+ (format " $PATH: %S\n" (getenv "PATH"))
+ (format " exec-path: %s\n" exec-path))
+ (insert (format " with-editor-emacsclient-path:\n"))
+ (--each (with-editor-emacsclient-path)
+ (insert (format " %s (%s)\n" it (car (file-attributes it))))
+ (when (file-directory-p it)
+ (dolist (exec (directory-files it nil "emacsclient"))
+ (insert (format " %s (%s)\n" exec
+ (with-editor-emacsclient-version exec))))))))
+
+(defconst with-editor-font-lock-keywords
+ '(("(\\(with-\\(?:git-\\)?editor\\)\\_>" (1 'font-lock-keyword-face))))
+(font-lock-add-keywords 'emacs-lisp-mode with-editor-font-lock-keywords)
+
+(provide 'with-editor)
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
+;;; with-editor.el ends here
--- /dev/null
+This is with-editor.info, produced by makeinfo version 5.2 from
+with-editor.texi.
+
+The library ‘with-editor’ makes it easy to use the Emacsclient as the
+‘$EDITOR’ of child processes, making sure they know how to call home.
+For remote processes a substitute is provided, which communicates with
+Emacs on standard output instead of using a socket as the Emacsclient
+does.
+
+ This library was written because Magit has to be able to do the above
+to allow the user to edit commit messages gracefully and to edit rebase
+sequences, which wouldn’t be possible at all otherwise.
+
+ Because other packages can benefit from such functionality, this
+library is made available as a separate package. It also defines some
+additional functionality which makes it useful even for end-users, who
+don’t use Magit or another package which uses it internally.
+
+ Copyright (C) 2015 Jonas Bernoulli <jonasNOSPAMbernoul.li>
+
+ You can redistribute this document and/or modify it under the terms
+ of the GNU General Public License as published by the Free Software
+ Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This document 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.
+INFO-DIR-SECTION Emacs
+START-INFO-DIR-ENTRY
+* With-Editor: (with-editor). Using the Emacsclient as $EDITOR.
+END-INFO-DIR-ENTRY
+
+\1f
+File: with-editor.info, Node: Top, Next: Using the With-Editor package, Up: (dir)
+
+With-Editor User Manual
+***********************
+
+The library ‘with-editor’ makes it easy to use the Emacsclient as the
+‘$EDITOR’ of child processes, making sure they know how to call home.
+For remote processes a substitute is provided, which communicates with
+Emacs on standard output instead of using a socket as the Emacsclient
+does.
+
+ This library was written because Magit has to be able to do the above
+to allow the user to edit commit messages gracefully and to edit rebase
+sequences, which wouldn’t be possible at all otherwise.
+
+ Because other packages can benefit from such functionality, this
+library is made available as a separate package. It also defines some
+additional functionality which makes it useful even for end-users, who
+don’t use Magit or another package which uses it internally.
+
+ Copyright (C) 2015 Jonas Bernoulli <jonasNOSPAMbernoul.li>
+
+ You can redistribute this document and/or modify it under the terms
+ of the GNU General Public License as published by the Free Software
+ Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This document 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.
+
+* Menu:
+
+* Using the With-Editor package::
+* Using With-Editor as a library::
+* Debugging::
+
+— The Detailed Node Listing —
+
+Using the With-Editor package
+
+* Configuring With-Editor::
+* Using With-Editor commands::
+
+
+\1f
+File: with-editor.info, Node: Using the With-Editor package, Next: Using With-Editor as a library, Prev: Top, Up: Top
+
+1 Using the With-Editor package
+*******************************
+
+The ‘With-Editor’ package is used internally by Magit when editing
+commit messages and rebase sequences. It also provides some commands
+and features which are useful by themselves, even if you don’t use
+Magit.
+
+ For information about using this library in you own package, see
+*note Using With-Editor as a library: Using With-Editor as a library.
+
+* Menu:
+
+* Configuring With-Editor::
+* Using With-Editor commands::
+
+\1f
+File: with-editor.info, Node: Configuring With-Editor, Next: Using With-Editor commands, Up: Using the With-Editor package
+
+1.1 Configuring With-Editor
+===========================
+
+With-Editor tries very hard to locate a suitable emacsclient executable,
+so ideally you should never have to customize the option
+‘with-editor-emacsclient-executable’. When it fails to do so, then the
+most likely reason is that someone found yet another way to package
+Emacs (most likely on OS X) without putting the executable on ‘$PATH’,
+and we have to add another kludge to find it anyway.
+
+ -- User Option: with-editor-emacsclient-executable
+
+ The emacsclient executable used as the editor by child process of
+ this Emacs instance. By using this executable, child processes can
+ call home to their parent process.
+
+ This option is automatically set at startup by looking in
+ ‘exec-path’, and other places where the executable could be
+ installed, to find the emacsclient executable most suitable for the
+ current emacs instance.
+
+ You should *not* customize this option permanently. If you have to
+ do it, then you should consider that a temporary kludge and inform
+ the Magit maintainer as described in *note Debugging: Debugging.
+
+ If With-Editor fails to find a suitable emacsclient on you system,
+ then this should be fixed for all users at once, by teaching
+ ‘with-editor-locate-emacsclient’ how to so on your system and
+ system like yours. Doing it this way has the advantage, that you
+ won’t have do it again every time you update Emacs, and that other
+ users who have installed Emacs the same way as you have, won’t have
+ to go through the same trouble.
+
+ Note that there also is a nuclear option; setting this variable to
+ ‘nil’ causes the "sleeping editor" described below to be used even
+ for local child processes. Obviously we don’t recommend that you
+ use this except in "emergencies", i.e. before we had a change to
+ add a kludge appropriate for you setup.
+
+ -- Function: with-editor-locate-emacsclient
+
+ The function used to set the initial value of the option
+ ‘with-editor-emacsclient-executable’. There’s a lot of voodoo
+ here.
+
+ The emacsclient cannot be used when using Tramp to run a process on a
+remote machine. (Theoretically it could, but that would be hard to
+setup, very fragile, and rather insecure).
+
+ With-Editor provides an alternative "editor" which can be used by
+remote processes in much the same way as local processes use an
+emacsclient executable. This alternative is known as the "sleeping
+editor" because it is implemented as a shell script which sleeps until
+it receives a signal.
+
+ -- User Option: with-editor-sleeping-editor
+
+ The sleeping editor is a shell script used as the editor of child
+ processes when the emacsclient executable cannot be used.
+
+ This fallback is used for asynchronous process started inside the
+ macro ‘with-editor’, when the process runs on a remote machine or
+ for local processes when ‘with-editor-emacsclient-executable’ is
+ ‘nil’.
+
+ Where the latter uses a socket to communicate with Emacs’ server,
+ this substitute prints edit requests to its standard output on
+ which a process filter listens for such requests. As such it is
+ not a complete substitute for a proper Emacsclient, it can only be
+ used as ‘$EDITOR’ of child process of the current Emacs instance.
+
+ It is unlikely that you should ever have to customize this option.
+
+\1f
+File: with-editor.info, Node: Using With-Editor commands, Prev: Configuring With-Editor, Up: Using the With-Editor package
+
+1.2 Using With-Editor commands
+==============================
+
+This section describes how to use the with-editor library _outside_ of
+Magit. You don’t need to know any of this just to create commits using
+Magit.
+
+ The commands ‘with-editor-async-shell-command’ and
+‘with-editor-shell-command’ are intended as drop in replacements for
+‘async-shell-command’ and ‘shell-command’. They automatically export
+‘$EDITOR’ making sure the executed command uses the current Emacs
+instance as "the editor". With a prefix argument these commands prompt
+for an alternative environment variable such as ‘$GIT_EDITOR’.
+
+ -- Command: with-editor-async-shell-command
+
+ Like ‘async-shell-command’, but the command is run with the current
+ Emacs instance exported as ‘$EDITOR’.
+
+ -- Command: with-editor-shell-command
+
+ Like ‘async-shell-command’, but the command is run with the current
+ Emacs instance exported as ‘$EDITOR’. This only has an effect if
+ the command is run asynchronously, i.e. when the command ends with
+ ‘&’.
+
+ To always use these variants add this to you init file:
+
+ (define-key (current-global-map)
+ [remap async-shell-command] 'with-editor-async-shell-command)
+ (define-key (current-global-map)
+ [remap shell-command] 'with-editor-shell-command)
+
+ Alternatively use the global ‘shell-command-with-editor-mode’.
+
+ -- Variable: shell-command-with-editor-mode
+
+ When this mode is active, then ‘$EDITOR’ is exported whenever
+ ultimately ‘shell-command’ is called to asynchronously run some
+ shell command. This affects most variants of that command, whether
+ they are defined in Emacs or in some third-party package.
+
+ The command ‘with-editor-export-editor’ exports ‘$EDITOR’ or another
+such environment variable in ‘shell-mode’, ‘term-mode’ and ‘eshell-mode’
+buffers. Use this Emacs command before executing a shell command which
+needs the editor set, or always arrange for the current Emacs instance
+to be used as editor by adding it to the appropriate mode hooks:
+
+ (add-hook 'shell-mode-hook 'with-editor-export-editor)
+ (add-hook 'term-mode-hook 'with-editor-export-editor)
+ (add-hook 'eshell-mode-hook 'with-editor-export-editor)
+
+ Some variants of this function exist; these two forms are equivalent:
+
+ (add-hook 'shell-mode-hook
+ (apply-partially 'with-editor-export-editor "GIT_EDITOR"))
+ (add-hook 'shell-mode-hook 'with-editor-export-git-editor)
+
+ -- Command: with-editor-export-editor
+
+ When invoked in a ‘shell-mode’, ‘term-mode’, or ‘eshell-mode’
+ buffer, this command teaches shell commands to use the current
+ Emacs instance as the editor, by exporting ‘$EDITOR’.
+
+ -- Command: with-editor-export-git-editor
+
+ Like ‘with-editor-export-editor’ but exports ‘$GIT_EDITOR’.
+
+ -- Command: with-editor-export-hg-editor
+
+ Like ‘with-editor-export-editor’ but exports ‘$HG_EDITOR’.
+
+\1f
+File: with-editor.info, Node: Using With-Editor as a library, Next: Debugging, Prev: Using the With-Editor package, Up: Top
+
+2 Using With-Editor as a library
+********************************
+
+This section describes how to use the with-editor library _outside_ of
+Magit to teach another package how to have its child processes call
+home, just like Magit does. You don’t need to know any of this just to
+create commits using Magit. You can also ignore this if you use
+with-editor outside of Magit, but only as an end-user.
+
+ For information about interactive use and options which affect both
+interactive and non-interactive use, see *note Using the With-Editor
+package: Using the With-Editor package.
+
+ -- Macro: with-editor &rest body
+
+ This macro arranges for the emacsclient or the sleeping editor to
+ be used as the editor of child processes, effectively teaching them
+ to call home to the current emacs instance when they require that
+ the user edits a file.
+
+ This is essentially done by establishing a local binding for
+ ‘process-environment’ and changing the value of the ‘$EDITOR’
+ environment variable. This affects all processes started by forms
+ inside BODY.
+
+ -- Function: with-editor-set-process-filter process filter
+
+ This function is like ‘set-process-filter’ but ensures that adding
+ the new FILTER does not remove the ‘with-editor-process-filter’.
+ This is done by wrapping the two filter functions using a lambda,
+ which becomes the actual filter. It calls
+ ‘with-editor-process-filter’ first, passing ‘t’ as
+ NO-STANDARD-FILTER. Then it calls FILTER.
+
+\1f
+File: with-editor.info, Node: Debugging, Prev: Using With-Editor as a library, Up: Top
+
+3 Debugging
+***********
+
+With-Editor tries very hard to locate a suitable emacsclient executable,
+and then sets option ‘with-editor-emacsclient-executable’ accordingly.
+In very rare cases this fails. When it does fail, then the most likely
+reason is that someone found yet another way to package Emacs (most
+likely on OS X) without putting the executable on ‘$PATH’, and we have
+to add another kludge to find it anyway.
+
+ If you are having problems using ‘with-editor’, e.g. you cannot
+commit in Magit, then please open a new issue at
+<https://github.com/magit/magit/issues> and provide information about
+your Emacs installation. Most importantly how did you install Emacs and
+what is the output of ‘M-x with-editor-debug’?
+
+
+\1f
+Tag Table:
+Node: Top\7f1540
+Node: Using the With-Editor package\7f3227
+Node: Configuring With-Editor\7f3843
+Node: Using With-Editor commands\7f7450
+Node: Using With-Editor as a library\7f10614
+Node: Debugging\7f12280
+\1f
+End Tag Table
+
+\1f
+Local Variables:
+coding: utf-8
+End: