From 92c3e19a468aeb0adab6818acaaad45a21a1e8b0 Mon Sep 17 00:00:00 2001
From: liding
Date: Mon, 23 Oct 2023 14:13:34 +0800
Subject: [PATCH] First release.
---
.gitattributes | 6 +
.gitignore | 0
.gitmodules | 45 +
LICENSE | 661 +++
README.md | 53 +
site-lisp/extensions-local/auto-save.el | 126 +
site-lisp/extensions-local/cmake-mode.el | 533 ++
site-lisp/extensions-local/company-ctags.el | 528 ++
.../extensions-local/dired-display-buffer.el | 87 +
.../extensions-local/dired-hacks-utils.el | 273 +
site-lisp/extensions-local/dired-narrow.el | 356 ++
site-lisp/extensions-local/dired-subtree.el | 784 +++
site-lisp/extensions-local/echo-keys.el | 126 +
site-lisp/extensions-local/evals.el | 21 +
site-lisp/extensions-local/force-indent.el | 81 +
site-lisp/extensions-local/frame-restore.el | 93 +
.../extensions-local/goto-last-change.el | 141 +
.../extensions-local/goto-line-preview.el | 124 +
.../extensions-local/highlight-indentation.el | 312 ++
.../extensions-local/highlight-parentheses.el | 157 +
site-lisp/extensions-local/lazy-load.el | 49 +
.../extensions-local/ld-buffer-operations.el | 72 +
site-lisp/extensions-local/ld-delete-block.el | 38 +
.../extensions-local/ld-file-operations.el | 14 +
.../extensions-local/ld-goto-cursor-stack.el | 39 +
site-lisp/extensions-local/ld-goto-simple.el | 30 +
.../ld-org-publish-project-desc.el | 106 +
.../extensions-local/ld-text-operations.el | 109 +
.../extensions-local/ld-toggle-one-window.el | 22 +
site-lisp/extensions-local/ld-tools.el | 167 +
site-lisp/extensions-local/neotree.el | 2228 ++++++++
.../extensions-local/scroll-next-window.el | 42 +
site-lisp/extensions-local/undo-tree.el | 4653 +++++++++++++++++
site-lisp/extensions-submodule/ace-window | 1 +
site-lisp/extensions-submodule/avy | 1 +
site-lisp/extensions-submodule/citre | 1 +
site-lisp/extensions-submodule/company-mode | 1 +
site-lisp/extensions-submodule/dash.el | 1 +
.../extensions-submodule/emacs-which-key | 1 +
site-lisp/extensions-submodule/go-mode.el | 1 +
site-lisp/extensions-submodule/jsonian | 1 +
site-lisp/extensions-submodule/lua-mode | 1 +
site-lisp/extensions-submodule/markdown-mode | 1 +
site-lisp/extensions-submodule/modus-themes | 1 +
.../extensions-submodule/multiple-cursors.el | 1 +
site-lisp/extensions-submodule/swiper | 1 +
site-lisp/extensions-submodule/web-mode | 1 +
site-lisp/extensions-submodule/yasnippet | 1 +
site-lisp/init-config/init-ace-window.el | 10 +
site-lisp/init-config/init-auto-save.el | 14 +
site-lisp/init-config/init-avy.el | 19 +
site-lisp/init-config/init-citre.el | 10 +
site-lisp/init-config/init-coding-system.el | 34 +
site-lisp/init-config/init-company-mode.el | 13 +
site-lisp/init-config/init-dired.el | 23 +
site-lisp/init-config/init-generic.el | 149 +
.../init-config/init-highlight-parentheses.el | 11 +
site-lisp/init-config/init-indent.el | 59 +
site-lisp/init-config/init-mode.el | 92 +
site-lisp/init-config/init-neotree.el | 12 +
site-lisp/init-config/init-org-todo.el | 37 +
site-lisp/init-config/init-org.el | 233 +
site-lisp/init-config/init-proxy.el | 45 +
site-lisp/init-config/init-session.el | 51 +
site-lisp/init-config/init-shortcut.el | 256 +
site-lisp/init-config/init-swiper.el | 34 +
site-lisp/init-config/init-theme.el | 82 +
site-lisp/init-config/init-time.el | 14 +
site-lisp/init-config/init-undo-tree.el | 19 +
site-lisp/init-config/init-which-key.el | 17 +
site-lisp/init-config/init-yasnippet.el | 17 +
site-lisp/init-config/init.el | 46 +
site-lisp/snippets/lua-mode/function | 6 +
site-lisp/snippets/org-mode/bloghead | 8 +
site-lisp/snippets/org-mode/notehead | 7 +
site-lisp/snippets/sh-mode/bang | 6 +
site-lisp/snippets/snippet-mode/cont | 5 +
site-lisp/snippets/snippet-mode/elisp | 5 +
site-lisp/snippets/snippet-mode/field | 6 +
site-lisp/snippets/snippet-mode/group | 5 +
site-lisp/snippets/snippet-mode/mirror | 6 +
site-lisp/snippets/snippet-mode/vars | 13 +
site-start.el | 51 +
83 files changed, 13506 insertions(+)
create mode 100644 .gitattributes
create mode 100644 .gitignore
create mode 100644 .gitmodules
create mode 100644 LICENSE
create mode 100644 README.md
create mode 100644 site-lisp/extensions-local/auto-save.el
create mode 100644 site-lisp/extensions-local/cmake-mode.el
create mode 100644 site-lisp/extensions-local/company-ctags.el
create mode 100644 site-lisp/extensions-local/dired-display-buffer.el
create mode 100644 site-lisp/extensions-local/dired-hacks-utils.el
create mode 100644 site-lisp/extensions-local/dired-narrow.el
create mode 100644 site-lisp/extensions-local/dired-subtree.el
create mode 100644 site-lisp/extensions-local/echo-keys.el
create mode 100644 site-lisp/extensions-local/evals.el
create mode 100644 site-lisp/extensions-local/force-indent.el
create mode 100644 site-lisp/extensions-local/frame-restore.el
create mode 100644 site-lisp/extensions-local/goto-last-change.el
create mode 100644 site-lisp/extensions-local/goto-line-preview.el
create mode 100644 site-lisp/extensions-local/highlight-indentation.el
create mode 100644 site-lisp/extensions-local/highlight-parentheses.el
create mode 100644 site-lisp/extensions-local/lazy-load.el
create mode 100644 site-lisp/extensions-local/ld-buffer-operations.el
create mode 100644 site-lisp/extensions-local/ld-delete-block.el
create mode 100644 site-lisp/extensions-local/ld-file-operations.el
create mode 100644 site-lisp/extensions-local/ld-goto-cursor-stack.el
create mode 100644 site-lisp/extensions-local/ld-goto-simple.el
create mode 100644 site-lisp/extensions-local/ld-org-publish-project-desc.el
create mode 100644 site-lisp/extensions-local/ld-text-operations.el
create mode 100644 site-lisp/extensions-local/ld-toggle-one-window.el
create mode 100644 site-lisp/extensions-local/ld-tools.el
create mode 100644 site-lisp/extensions-local/neotree.el
create mode 100644 site-lisp/extensions-local/scroll-next-window.el
create mode 100644 site-lisp/extensions-local/undo-tree.el
create mode 160000 site-lisp/extensions-submodule/ace-window
create mode 160000 site-lisp/extensions-submodule/avy
create mode 160000 site-lisp/extensions-submodule/citre
create mode 160000 site-lisp/extensions-submodule/company-mode
create mode 160000 site-lisp/extensions-submodule/dash.el
create mode 160000 site-lisp/extensions-submodule/emacs-which-key
create mode 160000 site-lisp/extensions-submodule/go-mode.el
create mode 160000 site-lisp/extensions-submodule/jsonian
create mode 160000 site-lisp/extensions-submodule/lua-mode
create mode 160000 site-lisp/extensions-submodule/markdown-mode
create mode 160000 site-lisp/extensions-submodule/modus-themes
create mode 160000 site-lisp/extensions-submodule/multiple-cursors.el
create mode 160000 site-lisp/extensions-submodule/swiper
create mode 160000 site-lisp/extensions-submodule/web-mode
create mode 160000 site-lisp/extensions-submodule/yasnippet
create mode 100644 site-lisp/init-config/init-ace-window.el
create mode 100644 site-lisp/init-config/init-auto-save.el
create mode 100644 site-lisp/init-config/init-avy.el
create mode 100644 site-lisp/init-config/init-citre.el
create mode 100644 site-lisp/init-config/init-coding-system.el
create mode 100644 site-lisp/init-config/init-company-mode.el
create mode 100644 site-lisp/init-config/init-dired.el
create mode 100644 site-lisp/init-config/init-generic.el
create mode 100644 site-lisp/init-config/init-highlight-parentheses.el
create mode 100644 site-lisp/init-config/init-indent.el
create mode 100644 site-lisp/init-config/init-mode.el
create mode 100644 site-lisp/init-config/init-neotree.el
create mode 100644 site-lisp/init-config/init-org-todo.el
create mode 100644 site-lisp/init-config/init-org.el
create mode 100644 site-lisp/init-config/init-proxy.el
create mode 100644 site-lisp/init-config/init-session.el
create mode 100644 site-lisp/init-config/init-shortcut.el
create mode 100644 site-lisp/init-config/init-swiper.el
create mode 100644 site-lisp/init-config/init-theme.el
create mode 100644 site-lisp/init-config/init-time.el
create mode 100644 site-lisp/init-config/init-undo-tree.el
create mode 100644 site-lisp/init-config/init-which-key.el
create mode 100644 site-lisp/init-config/init-yasnippet.el
create mode 100644 site-lisp/init-config/init.el
create mode 100644 site-lisp/snippets/lua-mode/function
create mode 100644 site-lisp/snippets/org-mode/bloghead
create mode 100644 site-lisp/snippets/org-mode/notehead
create mode 100644 site-lisp/snippets/sh-mode/bang
create mode 100644 site-lisp/snippets/snippet-mode/cont
create mode 100644 site-lisp/snippets/snippet-mode/elisp
create mode 100644 site-lisp/snippets/snippet-mode/field
create mode 100644 site-lisp/snippets/snippet-mode/group
create mode 100644 site-lisp/snippets/snippet-mode/mirror
create mode 100644 site-lisp/snippets/snippet-mode/vars
create mode 100644 site-start.el
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..a9c0e87
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,6 @@
+# Automatically normalize line endings (to LF) for all text-based files.
+* text=auto eol=lf
+
+# Do not modify line endings for binary files (which are sometimes auto
+# detected as text files by git).
+*.png binary
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..a074062
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,45 @@
+[submodule "site-lisp/extensions-submodule/go-mode.el"]
+ path = site-lisp/extensions-submodule/go-mode.el
+ url = git@github.com:dominikh/go-mode.el.git
+[submodule "site-lisp/extensions-submodule/jsonian"]
+ path = site-lisp/extensions-submodule/jsonian
+ url = git@github.com:iwahbe/jsonian.git
+[submodule "site-lisp/extensions-submodule/ace-window"]
+ path = site-lisp/extensions-submodule/ace-window
+ url = git@github.com:abo-abo/ace-window.git
+[submodule "site-lisp/extensions-submodule/dash.el"]
+ path = site-lisp/extensions-submodule/dash.el
+ url = git@github.com:magnars/dash.el.git
+[submodule "site-lisp/extensions-submodule/avy"]
+ path = site-lisp/extensions-submodule/avy
+ url = git@github.com:abo-abo/avy.git
+[submodule "site-lisp/extensions-submodule/emacs-which-key"]
+ path = site-lisp/extensions-submodule/emacs-which-key
+ url = git@github.com:justbur/emacs-which-key.git
+[submodule "site-lisp/extensions-submodule/lua-mode"]
+ path = site-lisp/extensions-submodule/lua-mode
+ url = git@github.com:immerrr/lua-mode.git
+[submodule "site-lisp/extensions-submodule/markdown-mode"]
+ path = site-lisp/extensions-submodule/markdown-mode
+ url = git@github.com:jrblevin/markdown-mode.git
+[submodule "site-lisp/extensions-submodule/modus-themes"]
+ path = site-lisp/extensions-submodule/modus-themes
+ url = git@github.com:protesilaos/modus-themes.git
+[submodule "site-lisp/extensions-submodule/swiper"]
+ path = site-lisp/extensions-submodule/swiper
+ url = git@github.com:abo-abo/swiper.git
+[submodule "site-lisp/extensions-submodule/web-mode"]
+ path = site-lisp/extensions-submodule/web-mode
+ url = git@github.com:fxbois/web-mode.git
+[submodule "site-lisp/extensions-submodule/yasnippet"]
+ path = site-lisp/extensions-submodule/yasnippet
+ url = git@github.com:joaotavora/yasnippet.git
+[submodule "site-lisp/extensions-submodule/multiple-cursors.el"]
+ path = site-lisp/extensions-submodule/multiple-cursors.el
+ url = git@github.com:magnars/multiple-cursors.el.git
+[submodule "site-lisp/extensions-submodule/citre"]
+ path = site-lisp/extensions-submodule/citre
+ url = git@github.com:universal-ctags/citre.git
+[submodule "site-lisp/extensions-submodule/company-mode"]
+ path = site-lisp/extensions-submodule/company-mode
+ url = git@github.com:company-mode/company-mode.git
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..0ad25db
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,661 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c080d6d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,53 @@
+# Install elisp files
+
+## Debian / Ubuntu
+
+Clone this repo to home path.
+
+Build config symlink to emacs directory:
+
+```bash
+sudo ln -s ~/ld-emacs/site-lisp /usr/share/emacs/ld
+```
+
+Copy site-start.el in emacs directory to start my config:
+
+```bash
+sudo cp ~/ld-emacs/site-start.el /usr/share/emacs/site-lisp/
+```
+
+Initialize submodules:
+
+```plaintext
+cd ~/ld-emacs && git submodule update --init --recursive
+```
+
+## Windows
+
+Clone this repo to `C:\Users\\AppData\Roaming\ld-emacs` and initialize submodules.
+
+Put the content below to `~/.emacs.d/init.el` (to `C:\Users\\AppData\Roaming\.emacs.d\init.el`).
+
+```lisp
+(load-file "~/ld-emacs/site-start.el")
+```
+
+# Install dependencies for extensions
+
+## Debian / Ubuntu
+
+```plaintext
+# for citre (ctags frontend)
+sudo apt install universal-ctags
+
+# for counsel-rg
+sudo apt install ripgrep
+```
+
+Note: Emacs will load ~/.profile for env variables at the start. According to the code in ~/.profile, emacs will not load ~/.bashrc. So if user wish to add some env variables, the code should be written to ~/.profile.
+
+## Windows
+
+Download Universal-ctags binary and add it to environment variable `Path`.
+
+Download the ripgrep for windows from [ripgrep](https://github.com/BurntSushi/ripgrep) and add the .exe file to env variable `Path`.
diff --git a/site-lisp/extensions-local/auto-save.el b/site-lisp/extensions-local/auto-save.el
new file mode 100644
index 0000000..de0c16d
--- /dev/null
+++ b/site-lisp/extensions-local/auto-save.el
@@ -0,0 +1,126 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+
+;;; Code:
+(defgroup auto-save nil
+ "Auto save file when emacs idle."
+ :group 'auto-save)
+
+(defcustom auto-save-idle 1
+ "The idle seconds to auto save file."
+ :type 'integer
+ :group 'auto-save)
+
+(defcustom auto-save-silent nil
+ "Nothing to dirty minibuffer if this option is non-nil."
+ :type 'boolean
+ :group 'auto-save)
+
+(defcustom auto-save-delete-trailing-whitespace nil
+ "Delete trailing whitespace when save if this option is non-nil.
+Note, this option is non-nil, will delete all training whitespace execpet current line,
+avoid delete current indent space when you programming."
+ :type 'boolean
+ :group 'auto-save)
+
+(defvar auto-save-disable-predicates
+ nil "disable auto save in these case.")
+
+;; Emacs' default auto-save is stupid to generate #foo# files!
+(setq make-backup-files nil)
+(setq auto-save-default nil)
+(setq create-lockfiles nil)
+
+(defun auto-save-buffers ()
+ (interactive)
+ (let ((autosave-buffer-list))
+ (ignore-errors
+ (save-current-buffer
+ (dolist (buf (buffer-list))
+ (set-buffer buf)
+ (when (and
+ ;; Buffer associate with a filename?
+ (buffer-file-name)
+ ;; Buffer is modifiable?
+ (buffer-modified-p)
+ ;; Yassnippet is not active?
+ (or (not (boundp 'yas--active-snippets))
+ (not yas--active-snippets))
+ ;; Company is not active?
+ (or (not (boundp 'company-candidates))
+ (not company-candidates))
+ ;; Corfu is not active?
+ (or (not (boundp 'corfu--total))
+ (zerop corfu--total))
+ ;; Org-capture is not active?
+ (not (eq (buffer-base-buffer (get-buffer (concat "CAPTURE-" (buffer-name))))
+ buf))
+ ;; tell auto-save don't save
+ (not (seq-some (lambda (predicate)
+ (funcall predicate))
+ auto-save-disable-predicates)))
+ (push (buffer-name) autosave-buffer-list)
+ (if auto-save-silent
+ ;; `inhibit-message' can shut up Emacs, but we want
+ ;; it doesn't clean up echo area during saving
+ (with-temp-message ""
+ (let ((inhibit-message t))
+ (basic-save-buffer)))
+ (basic-save-buffer))
+ ))
+ ;; Tell user when auto save files.
+ (unless auto-save-silent
+ (cond
+ ;; It's stupid tell user if nothing to save.
+ ((= (length autosave-buffer-list) 1)
+ (message "# Saved %s" (car autosave-buffer-list)))
+ ((> (length autosave-buffer-list) 1)
+ (message "# Saved %d files: %s"
+ (length autosave-buffer-list)
+ (mapconcat 'identity autosave-buffer-list ", ")))))
+ ))))
+
+(defun auto-save-delete-trailing-whitespace-except-current-line ()
+ (interactive)
+ (when auto-save-delete-trailing-whitespace
+ (let ((begin (line-beginning-position))
+ (end (point)))
+ (save-excursion
+ (when (< (point-min) begin)
+ (save-restriction
+ (narrow-to-region (point-min) (1- begin))
+ (delete-trailing-whitespace)))
+ (when (> (point-max) end)
+ (save-restriction
+ (narrow-to-region end (point-max))
+ (delete-trailing-whitespace)))))))
+
+(defvar auto-save-timer nil)
+
+(defun auto-save-set-timer ()
+ "Set the auto-save timer.
+Cancel any previous timer."
+ (auto-save-cancel-timer)
+ (setq auto-save-timer
+ (run-with-idle-timer auto-save-idle t 'auto-save-buffers)))
+
+(defun auto-save-cancel-timer ()
+ (when auto-save-timer
+ (cancel-timer auto-save-timer)
+ (setq auto-save-timer nil)))
+
+(defun auto-save-enable ()
+ (interactive)
+ (auto-save-set-timer)
+ (add-hook 'before-save-hook 'auto-save-delete-trailing-whitespace-except-current-line)
+ )
+
+(defun auto-save-disable ()
+ (interactive)
+ (auto-save-cancel-timer)
+ (remove-hook 'before-save-hook 'auto-save-delete-trailing-whitespace-except-current-line)
+ )
+
+(provide 'auto-save)
+
+;;; auto-save.el ends here
diff --git a/site-lisp/extensions-local/cmake-mode.el b/site-lisp/extensions-local/cmake-mode.el
new file mode 100644
index 0000000..0bb114d
--- /dev/null
+++ b/site-lisp/extensions-local/cmake-mode.el
@@ -0,0 +1,533 @@
+;;; cmake-mode.el --- major-mode for editing CMake sources
+
+;; Package-Requires: ((emacs "24.1"))
+;; Package-Version: 20220823.1201
+;; Package-Commit: 5936d4f2adeec64e0ff748b2c6c34f0436b19a97
+
+; Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+; file Copyright.txt or https://cmake.org/licensing for details.
+
+;------------------------------------------------------------------------------
+
+;;; Commentary:
+
+;; Provides syntax highlighting and indentation for CMakeLists.txt and
+;; *.cmake source files.
+;;
+;; Add this code to your .emacs file to use the mode:
+;;
+;; (setq load-path (cons (expand-file-name "/dir/with/cmake-mode") load-path))
+;; (require 'cmake-mode)
+
+;------------------------------------------------------------------------------
+
+;;; Code:
+;;
+;; cmake executable variable used to run cmake --help-command
+;; on commands in cmake-mode
+;;
+;; cmake-command-help Written by James Bigler
+;;
+
+(require 'rst)
+(require 'rx)
+
+(defcustom cmake-mode-cmake-executable "cmake"
+ "*The name of the cmake executable.
+
+This can be either absolute or looked up in $PATH. You can also
+set the path with these commands:
+ (setenv \"PATH\" (concat (getenv \"PATH\") \";C:\\\\Program Files\\\\CMake 2.8\\\\bin\"))
+ (setenv \"PATH\" (concat (getenv \"PATH\") \":/usr/local/cmake/bin\"))"
+ :type 'file
+ :group 'cmake)
+
+;; Keywords
+(defconst cmake-keywords-block-open '("BLOCK" "IF" "MACRO" "FOREACH" "ELSE" "ELSEIF" "WHILE" "FUNCTION"))
+(defconst cmake-keywords-block-close '("ENDBLOCK" "ENDIF" "ENDFOREACH" "ENDMACRO" "ELSE" "ELSEIF" "ENDWHILE" "ENDFUNCTION"))
+(defconst cmake-keywords
+ (let ((kwds (append cmake-keywords-block-open cmake-keywords-block-close nil)))
+ (delete-dups kwds)))
+
+;; Regular expressions used by line indentation function.
+;;
+(defconst cmake-regex-blank "^[ \t]*$")
+(defconst cmake-regex-comment "#.*")
+(defconst cmake-regex-paren-left "(")
+(defconst cmake-regex-paren-right ")")
+(defconst cmake-regex-closing-parens-line (concat "^[[:space:]]*\\("
+ cmake-regex-paren-right
+ "+\\)[[:space:]]*$"))
+(defconst cmake-regex-argument-quoted
+ (rx ?\" (* (or (not (any ?\" ?\\)) (and ?\\ anything))) ?\"))
+(defconst cmake-regex-argument-unquoted
+ (rx (or (not (any space "()#\"\\\n")) (and ?\\ nonl))
+ (* (or (not (any space "()#\\\n")) (and ?\\ nonl)))))
+(defconst cmake-regex-token
+ (rx-to-string `(group (or (regexp ,cmake-regex-comment)
+ ?\( ?\)
+ (regexp ,cmake-regex-argument-unquoted)
+ (regexp ,cmake-regex-argument-quoted)))))
+(defconst cmake-regex-indented
+ (rx-to-string `(and bol (* (group (or (regexp ,cmake-regex-token) (any space ?\n)))))))
+(defconst cmake-regex-block-open
+ (rx-to-string `(and symbol-start (or ,@(append cmake-keywords-block-open
+ (mapcar 'downcase cmake-keywords-block-open))) symbol-end)))
+(defconst cmake-regex-block-close
+ (rx-to-string `(and symbol-start (or ,@(append cmake-keywords-block-close
+ (mapcar 'downcase cmake-keywords-block-close))) symbol-end)))
+(defconst cmake-regex-close
+ (rx-to-string `(and bol (* space) (regexp ,cmake-regex-block-close)
+ (* space) (regexp ,cmake-regex-paren-left))))
+(defconst cmake-regex-token-paren-left (concat "^" cmake-regex-paren-left "$"))
+(defconst cmake-regex-token-paren-right (concat "^" cmake-regex-paren-right "$"))
+
+;------------------------------------------------------------------------------
+
+;; Line indentation helper functions
+
+(defun cmake-line-starts-inside-string ()
+ "Determine whether the beginning of the current line is in a string."
+ (save-excursion
+ (beginning-of-line)
+ (let ((parse-end (point)))
+ (goto-char (point-min))
+ (nth 3 (parse-partial-sexp (point) parse-end))
+ )
+ )
+ )
+
+(defun cmake-find-last-indented-line ()
+ "Move to the beginning of the last line that has meaningful indentation."
+ (let ((point-start (point))
+ region)
+ (forward-line -1)
+ (setq region (buffer-substring-no-properties (point) point-start))
+ (while (and (not (bobp))
+ (or (looking-at cmake-regex-blank)
+ (cmake-line-starts-inside-string)
+ (not (and (string-match cmake-regex-indented region)
+ (= (length region) (match-end 0))))))
+ (forward-line -1)
+ (setq region (buffer-substring-no-properties (point) point-start))
+ )
+ )
+ )
+
+;------------------------------------------------------------------------------
+
+;;
+;; Indentation increment.
+;;
+(defcustom cmake-tab-width 2
+ "Number of columns to indent cmake blocks"
+ :type 'integer
+ :group 'cmake)
+
+;;
+;; Line indentation function.
+;;
+(defun cmake-indent ()
+ "Indent current line as CMake code."
+ (interactive)
+ (unless (cmake-line-starts-inside-string)
+ (if (bobp)
+ (cmake-indent-line-to 0)
+ (let (cur-indent)
+ (save-excursion
+ (beginning-of-line)
+ (let ((point-start (point))
+ (closing-parens-only (looking-at cmake-regex-closing-parens-line))
+ (case-fold-search t) ;; case-insensitive
+ token)
+ ;; Search back for the last indented line.
+ (cmake-find-last-indented-line)
+ ;; Start with the indentation on this line.
+ (setq cur-indent (current-indentation))
+ (if closing-parens-only
+ (let ((open-parens 0))
+ (while (re-search-forward cmake-regex-token point-start t)
+ (setq token (match-string 0))
+ (cond
+ ((string-match cmake-regex-token-paren-left token)
+ (setq open-parens (+ open-parens 1)))
+ ((string-match cmake-regex-token-paren-right token)
+ (setq open-parens (- open-parens 1)))))
+ ;; Don't outdent if last indented line has open parens
+ (unless (> open-parens 0)
+ (setq cur-indent (- cur-indent cmake-tab-width))))
+ ;; Skip detailed analysis if last indented line is a 'closing
+ ;; parens only line'
+ (unless (looking-at cmake-regex-closing-parens-line)
+ ;; Search forward counting tokens that adjust indentation.
+ (while (re-search-forward cmake-regex-token point-start t)
+ (setq token (match-string 0))
+ (when (or (string-match cmake-regex-token-paren-left token)
+ (and (string-match cmake-regex-block-open token)
+ (looking-at (concat "[ \t]*" cmake-regex-paren-left))))
+ (setq cur-indent (+ cur-indent cmake-tab-width)))
+ (when (string-match cmake-regex-token-paren-right token)
+ (setq cur-indent (- cur-indent cmake-tab-width)))
+ ))
+ (goto-char point-start)
+ ;; If next token closes the block, decrease indentation
+ (when (looking-at cmake-regex-close)
+ (setq cur-indent (- cur-indent cmake-tab-width))
+ )
+ )
+ )
+ )
+ ;; Indent this line by the amount selected.
+ (cmake-indent-line-to (max cur-indent 0))
+ )
+ )
+ )
+ )
+
+(defun cmake-point-in-indendation ()
+ (string-match "^[ \\t]*$" (buffer-substring (point-at-bol) (point))))
+
+(defun cmake-indent-line-to (column)
+ "Indent the current line to COLUMN.
+If point is within the existing indentation it is moved to the end of
+the indentation. Otherwise it retains the same position on the line"
+ (if (cmake-point-in-indendation)
+ (indent-line-to column)
+ (save-excursion (indent-line-to column))))
+
+;------------------------------------------------------------------------------
+
+;;
+;; Helper functions for buffer
+;;
+(defun cmake-unscreamify-buffer ()
+ "Convert all CMake commands to lowercase in buffer."
+ (interactive)
+ (save-excursion
+ (goto-char (point-min))
+ (while (re-search-forward "^\\([ \t]*\\)\\_<\\(\\(?:\\w\\|\\s_\\)+\\)\\_>\\([ \t]*(\\)" nil t)
+ (replace-match
+ (concat
+ (match-string 1)
+ (downcase (match-string 2))
+ (match-string 3))
+ t))
+ )
+ )
+
+
+;------------------------------------------------------------------------------
+
+;;
+;; Navigation / marking by function or macro
+;;
+
+(defconst cmake--regex-defun-start
+ (rx line-start
+ (zero-or-more space)
+ (or "function" "macro")
+ (zero-or-more space)
+ "("))
+
+(defconst cmake--regex-defun-end
+ (rx line-start
+ (zero-or-more space)
+ "end"
+ (or "function" "macro")
+ (zero-or-more space)
+ "(" (zero-or-more (not-char ")")) ")"))
+
+(defun cmake-beginning-of-defun ()
+ "Move backward to the beginning of a CMake function or macro.
+
+Return t unless search stops due to beginning of buffer."
+ (interactive)
+ (when (not (region-active-p))
+ (push-mark))
+ (let ((case-fold-search t))
+ (when (re-search-backward cmake--regex-defun-start nil 'move)
+ t)))
+
+(defun cmake-end-of-defun ()
+ "Move forward to the end of a CMake function or macro.
+
+Return t unless search stops due to end of buffer."
+ (interactive)
+ (when (not (region-active-p))
+ (push-mark))
+ (let ((case-fold-search t))
+ (when (re-search-forward cmake--regex-defun-end nil 'move)
+ (forward-line)
+ t)))
+
+(defun cmake-mark-defun ()
+ "Mark the current CMake function or macro.
+
+This puts the mark at the end, and point at the beginning."
+ (interactive)
+ (cmake-end-of-defun)
+ (push-mark nil :nomsg :activate)
+ (cmake-beginning-of-defun))
+
+
+;------------------------------------------------------------------------------
+
+;;
+;; Keyword highlighting regex-to-face map.
+;;
+(defconst cmake-font-lock-keywords
+ `((,(rx-to-string `(and symbol-start
+ (or ,@cmake-keywords
+ ,@(mapcar #'downcase cmake-keywords))
+ symbol-end))
+ . font-lock-keyword-face)
+ (,(rx symbol-start (group (+ (or word (syntax symbol)))) (* blank) ?\()
+ 1 font-lock-function-name-face)
+ (,(rx "${" (group (+(any alnum "-_+/."))) "}")
+ 1 font-lock-variable-name-face t)
+ )
+ "Highlighting expressions for CMake mode.")
+
+;------------------------------------------------------------------------------
+
+(defun cmake--syntax-propertize-until-bracket-close (syntax)
+ ;; This function assumes that a previous search has matched the
+ ;; beginning of a bracket_comment or bracket_argument and that the
+ ;; second capture group has matched the equal signs between the two
+ ;; opening brackets
+ (let* ((mb (match-beginning 2))
+ (me (match-end 2))
+ (cb (format "]%s]" (buffer-substring mb me))))
+ (save-match-data
+ (if (search-forward cb end 'move)
+ (progn
+ (setq me (match-end 0))
+ (put-text-property
+ (1- me)
+ me
+ 'syntax-table
+ (string-to-syntax syntax)))
+ (setq me end)))
+ (put-text-property
+ (match-beginning 1)
+ me
+ 'syntax-multiline
+ t)))
+
+(defconst cmake--syntax-propertize-function
+ (syntax-propertize-rules
+ ("\\(#\\)\\[\\(=*\\)\\["
+ (1
+ (prog1 "!" (cmake--syntax-propertize-until-bracket-close "!"))))
+ ("\\(\\[\\)\\(=*\\)\\["
+ (1
+ (prog1 "|" (cmake--syntax-propertize-until-bracket-close "|"))))))
+
+;; Syntax table for this mode.
+(defvar cmake-mode-syntax-table nil
+ "Syntax table for CMake mode.")
+(or cmake-mode-syntax-table
+ (setq cmake-mode-syntax-table
+ (let ((table (make-syntax-table)))
+ (modify-syntax-entry ?\( "()" table)
+ (modify-syntax-entry ?\) ")(" table)
+ (modify-syntax-entry ?# "<" table)
+ (modify-syntax-entry ?\n ">" table)
+ (modify-syntax-entry ?$ "'" table)
+ table)))
+
+;;
+;; User hook entry point.
+;;
+(defvar cmake-mode-hook nil)
+
+;;------------------------------------------------------------------------------
+;; Mode definition.
+;;
+;;;###autoload
+(define-derived-mode cmake-mode prog-mode "CMake"
+ "Major mode for editing CMake source files."
+
+ ; Setup font-lock mode.
+ (set (make-local-variable 'font-lock-defaults) '(cmake-font-lock-keywords))
+ ; Setup indentation function.
+ (set (make-local-variable 'indent-line-function) 'cmake-indent)
+ ; Setup comment syntax.
+ (set (make-local-variable 'comment-start) "#")
+ ;; Setup syntax propertization
+ (set (make-local-variable 'syntax-propertize-function) cmake--syntax-propertize-function)
+ (add-hook 'syntax-propertize-extend-region-functions #'syntax-propertize-multiline nil t))
+
+;; Default cmake-mode key bindings
+(define-key cmake-mode-map "\e\C-a" #'cmake-beginning-of-defun)
+(define-key cmake-mode-map "\e\C-e" #'cmake-end-of-defun)
+(define-key cmake-mode-map "\e\C-h" #'cmake-mark-defun)
+
+
+; Help mode starts here
+
+
+;;;###autoload
+(defun cmake-command-run (type &optional topic buffer)
+ "Runs the command cmake with the arguments specified. The
+optional argument topic will be appended to the argument list."
+ (interactive "s")
+ (let* ((bufname (if buffer buffer (concat "*CMake" type (if topic "-") topic "*")))
+ (buffer (if (get-buffer bufname) (get-buffer bufname) (generate-new-buffer bufname)))
+ (command (concat cmake-mode-cmake-executable " " type " " topic))
+ ;; Turn of resizing of mini-windows for shell-command.
+ (resize-mini-windows nil)
+ )
+ (shell-command command buffer)
+ (save-selected-window
+ (select-window (display-buffer buffer 'not-this-window))
+ (cmake-mode)
+ (read-only-mode 1)
+ (view-mode 1))
+ )
+ )
+
+;;;###autoload
+(defun cmake-command-run-help (type &optional topic buffer)
+ "`cmake-command-run' but rendered in `rst-mode'."
+ (interactive "s")
+ (let* ((bufname (if buffer buffer (concat "*CMake" type (if topic "-") topic "*")))
+ (buffer (if (get-buffer bufname) (get-buffer bufname) (generate-new-buffer bufname)))
+ (command (concat cmake-mode-cmake-executable " " type " " topic))
+ ;; Turn of resizing of mini-windows for shell-command.
+ (resize-mini-windows nil)
+ )
+ (shell-command command buffer)
+ (save-selected-window
+ (select-window (display-buffer buffer 'not-this-window))
+ (rst-mode)
+ (read-only-mode 1)
+ (view-mode 1))
+ )
+ )
+
+;;;###autoload
+(defun cmake-help-list-commands ()
+ "Prints out a list of the cmake commands."
+ (interactive)
+ (cmake-command-run-help "--help-command-list")
+ )
+
+(defvar cmake-commands '() "List of available topics for --help-command.")
+(defvar cmake-help-command-history nil "Command read history.")
+(defvar cmake-modules '() "List of available topics for --help-module.")
+(defvar cmake-help-module-history nil "Module read history.")
+(defvar cmake-variables '() "List of available topics for --help-variable.")
+(defvar cmake-help-variable-history nil "Variable read history.")
+(defvar cmake-properties '() "List of available topics for --help-property.")
+(defvar cmake-help-property-history nil "Property read history.")
+(defvar cmake-help-complete-history nil "Complete help read history.")
+(defvar cmake-string-to-list-symbol
+ '(("command" cmake-commands cmake-help-command-history)
+ ("module" cmake-modules cmake-help-module-history)
+ ("variable" cmake-variables cmake-help-variable-history)
+ ("property" cmake-properties cmake-help-property-history)
+ ))
+
+(defun cmake-get-list (listname)
+ "If the value of LISTVAR is nil, run cmake --help-LISTNAME-list
+and store the result as a list in LISTVAR."
+ (let ((listvar (car (cdr (assoc listname cmake-string-to-list-symbol)))))
+ (if (not (symbol-value listvar))
+ (let ((temp-buffer-name "*CMake Temporary*"))
+ (save-window-excursion
+ (cmake-command-run-help (concat "--help-" listname "-list") nil temp-buffer-name)
+ (with-current-buffer temp-buffer-name
+ ; FIXME: Ignore first line if it is "cmake version ..." from CMake < 3.0.
+ (set listvar (split-string (buffer-substring-no-properties (point-min) (point-max)) "\n" t)))))
+ (symbol-value listvar)
+ ))
+ )
+
+(require 'thingatpt)
+(defun cmake-symbol-at-point ()
+ (let ((symbol (symbol-at-point)))
+ (and (not (null symbol))
+ (symbol-name symbol))))
+
+(defun cmake-help-type (type)
+ (let* ((default-entry (cmake-symbol-at-point))
+ (history (car (cdr (cdr (assoc type cmake-string-to-list-symbol)))))
+ (input (completing-read
+ (format "CMake %s: " type) ; prompt
+ (cmake-get-list type) ; completions
+ nil ; predicate
+ t ; require-match
+ default-entry ; initial-input
+ history
+ )))
+ (if (string= input "")
+ (error "No argument given")
+ input))
+ )
+
+;;;###autoload
+(defun cmake-help-command ()
+ "Prints out the help message for the command the cursor is on."
+ (interactive)
+ (cmake-command-run-help "--help-command" (cmake-help-type "command") "*CMake Help*"))
+
+;;;###autoload
+(defun cmake-help-module ()
+ "Prints out the help message for the module the cursor is on."
+ (interactive)
+ (cmake-command-run-help "--help-module" (cmake-help-type "module") "*CMake Help*"))
+
+;;;###autoload
+(defun cmake-help-variable ()
+ "Prints out the help message for the variable the cursor is on."
+ (interactive)
+ (cmake-command-run-help "--help-variable" (cmake-help-type "variable") "*CMake Help*"))
+
+;;;###autoload
+(defun cmake-help-property ()
+ "Prints out the help message for the property the cursor is on."
+ (interactive)
+ (cmake-command-run-help "--help-property" (cmake-help-type "property") "*CMake Help*"))
+
+;;;###autoload
+(defun cmake-help ()
+ "Queries for any of the four available help topics and prints out the appropriate page."
+ (interactive)
+ (let* ((default-entry (cmake-symbol-at-point))
+ (command-list (cmake-get-list "command"))
+ (variable-list (cmake-get-list "variable"))
+ (module-list (cmake-get-list "module"))
+ (property-list (cmake-get-list "property"))
+ (all-words (append command-list variable-list module-list property-list))
+ (input (completing-read
+ "CMake command/module/variable/property: " ; prompt
+ all-words ; completions
+ nil ; predicate
+ t ; require-match
+ default-entry ; initial-input
+ 'cmake-help-complete-history
+ )))
+ (if (string= input "")
+ (error "No argument given")
+ (if (member input command-list)
+ (cmake-command-run-help "--help-command" input "*CMake Help*")
+ (if (member input variable-list)
+ (cmake-command-run-help "--help-variable" input "*CMake Help*")
+ (if (member input module-list)
+ (cmake-command-run-help "--help-module" input "*CMake Help*")
+ (if (member input property-list)
+ (cmake-command-run-help "--help-property" input "*CMake Help*")
+ (error "Not a know help topic.") ; this really should not happen
+ ))))))
+ )
+
+;;;###autoload
+(progn
+ (add-to-list 'auto-mode-alist '("CMakeLists\\.txt\\'" . cmake-mode))
+ (add-to-list 'auto-mode-alist '("\\.cmake\\'" . cmake-mode)))
+
+; This file provides cmake-mode.
+(provide 'cmake-mode)
+
+;;; cmake-mode.el ends here
diff --git a/site-lisp/extensions-local/company-ctags.el b/site-lisp/extensions-local/company-ctags.el
new file mode 100644
index 0000000..2969402
--- /dev/null
+++ b/site-lisp/extensions-local/company-ctags.el
@@ -0,0 +1,528 @@
+;;; company-ctags.el --- Fastest company-mode completion backend for ctags -*- lexical-binding: t -*-
+
+;; Copyright (C) 2019,2020 Chen Bin
+
+;; Author: Chen Bin
+;; URL: https://github.com/redguardtoo/company-ctags
+;; Version: 0.0.7
+;; Keywords: convenience
+;; Package-Requires: ((emacs "25.1") (company "0.9.0"))
+
+;; This file is NOT 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 .
+
+;;; Commentary:
+
+;; This library completes code using tags file created by Ctags.
+;; It uses a much faster algorithm optimized for ctags.
+;; It takes only 9 seconds to load 300M tags file which is created by
+;; scanning the Linux Kernel code v5.3.1.
+;; After initial loading, this library will respond immediately
+;; when new tags file is created.
+;;
+;; Usage:
+;;
+;; Step 0, Make sure `company-mode' is already set up
+;; See http://company-mode.github.io/ for details.
+;;
+;; Step 1, insert below code into your configuration,
+;;
+;; (with-eval-after-load 'company
+;; (company-ctags-auto-setup))
+;;
+;; Step 2, Use Ctags to create tags file and enjoy.
+;;
+;; Tips:
+;;
+;; - Turn on `company-ctags-support-etags' to support tags
+;; file created by etags. But it will increase initial loading time.
+;;
+;; - Set `company-ctags-extra-tags-files' to load extra tags files,
+;;
+;; (setq company-ctags-extra-tags-files '("$HOME/TAGS" "/usr/include/TAGS"))
+;;
+;; - Set `company-ctags-fuzzy-match-p' to fuzzy match the candidates.
+;; The input could match any part of the candidate instead of the beginning of
+;; the candidate.
+;;
+;; - Set `company-ctags-ignore-case' to ignore case when fetching candidates
+;;
+;; - Use rusty-tags to generate tags file for Rust programming language.
+;; Add below code into ~/.emacs,
+;; (setq company-ctags-tags-file-name "rusty-tags.emacs")
+;;
+;; - Make sure CLI program diff is executable on Windows.
+;; It's optional but highly recommended. It can speed up tags file updating.
+;; This package uses diff through variable `diff-command'.
+;;
+;; - `company-ctags-debug-info' for debugging.
+;;
+
+;;; Code:
+
+(require 'find-file)
+(require 'company nil t)
+(require 'cl-lib)
+(require 'subr-x)
+
+(defgroup company-ctags nil
+ "Completion backend for ctags."
+ :group 'company)
+
+(defcustom company-ctags-use-main-table-list t
+ "Always search `tags-table-list' if set.
+If this is disabled, `company-ctags' will try to find the one table for each
+buffer automatically."
+ :type '(choice (const :tag "off" nil)
+ (const :tag "on" t)))
+
+(defcustom company-ctags-ignore-case nil
+ "Non-nil to ignore case in completion candidates."
+ :type 'boolean
+ :package-version '(company . "0.7.3"))
+
+(defcustom company-ctags-extra-tags-files nil
+ "List of extra tags files which are loaded only once.
+
+A typical format is,
+
+ (\"./TAGS\" \"/usr/include/TAGS\" \"$PROJECT/*/include/TAGS\")
+
+Environment variables can be inserted between slashes (`/').
+They will be replaced by their definitions. If a variable does
+not exist, it is replaced (silently) with an empty string."
+ :type '(repeat 'string))
+
+(defcustom company-ctags-quiet nil
+ "Be quiet and do not notify user tags file status."
+ :type 'boolean)
+
+(defcustom company-ctags-support-etags nil
+ "Support tags file created by etags.
+If t, it increases the loading time."
+ :type 'boolean)
+
+(defcustom company-ctags-everywhere nil
+ "Non-nil to offer completions in comments and strings.
+Set it to t or to a list of major modes."
+ :type '(choice (const :tag "Off" nil)
+ (const :tag "Any supported mode" t)
+ (repeat :tag "Some major modes"
+ (symbol :tag "Major mode")))
+ :package-version '(company . "0.9.0"))
+
+(defcustom company-ctags-check-tags-file-interval 30
+ "The interval (seconds) to check tags file.
+Default value is 30 seconds."
+ :type 'integer)
+
+(defcustom company-ctags-tags-file-name "TAGS"
+ "The name of tags file."
+ :type 'string)
+
+(defcustom company-ctags-tag-name-valid-characters
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$#@%_!*&1234567890"
+ "The characters of tag name. It's used for partition algorithm."
+ :type 'string)
+
+(defcustom company-ctags-fuzzy-match-p nil
+ "If t, fuzzy match the candidates.
+The input could match any part of the candidate instead of the beginning of
+the candidate."
+ :type 'boolean)
+
+(defvar company-ctags-modes
+ '(prog-mode
+ c-mode
+ objc-mode
+ c++-mode
+ java-mode
+ jde-mode
+ pascal-mode
+ perl-mode
+ python-mode
+ lua-mode
+ web-mode))
+
+(defvar company-backends) ; avoid compiling warning
+
+(defvar-local company-ctags-buffer-table-internal nil)
+
+(defvar company-ctags-tags-file-caches nil
+ "The cached tags files.")
+
+(defvar company-ctags-cached-candidates nil
+ "The cached candidates searched with certain prefix.")
+
+(defconst company-ctags-fast-pattern
+ "\177\\([^\177\001\n]+\\)\001"
+ "Pattern to extract tag name created by Ctags only.")
+
+(defconst company-ctags-slow-pattern
+ "\\([^\f\t\n\r()=,; ]*\\)[\f\t\n\r()=,; ]*\177\\\(?:\\([^\n\001]+\\)\001\\)?"
+ "Pattern to extract tag name created by Ctags/Etags.")
+
+(defun company-ctags-find-table ()
+ "Find tags file."
+ (let* ((file (expand-file-name
+ company-ctags-tags-file-name
+ (locate-dominating-file (or buffer-file-name
+ default-directory)
+ company-ctags-tags-file-name))))
+ (when (and file (file-regular-p file))
+ (list file))))
+
+(defun company-ctags-buffer-table ()
+ "Find buffer table."
+ (or (and company-ctags-use-main-table-list tags-table-list)
+ (or company-ctags-buffer-table-internal
+ (setq company-ctags-buffer-table-internal
+ (company-ctags-find-table)))))
+
+(defun company-ctags-char-in-string-p (character string)
+ "Test if CHARACTER is in STRING."
+ (let (rlt (i 0) (len (length string)))
+ (while (and (not rlt) (< i len))
+ (setq rlt (eq (elt string i) character))
+ (setq i (1+ i)))
+ rlt))
+
+(defun company-ctags-tag-name-character-p (character)
+ "Test if CHARACTER is in `company-ctags-tag-name-valid-characters'."
+ (company-ctags-char-in-string-p character
+ company-ctags-tag-name-valid-characters))
+
+(defmacro company-ctags-push-tagname (tagname tagname-dict)
+ "Push TAGNAME into TAGNAME-DICT."
+ `(let ((c (elt ,tagname 0)))
+ (when (company-ctags-tag-name-character-p c)
+ (push ,tagname (gethash c ,tagname-dict)))))
+
+(defun company-ctags-n-items (n tagnames)
+ "Return first N items of TAGNAMES."
+ (cond
+ ((<= (length tagnames) n)
+ tagnames)
+ (t
+ (let (rlt (i 0))
+ (while (< i n)
+ (push (nth i tagnames) rlt)
+ (setq i (1+ i)))
+ (push " ..." rlt)
+ (nreverse rlt)))))
+
+;;;###autoload
+(defun company-ctags-debug-info ()
+ "Print all debug information."
+ (interactive)
+ (let* ((caches company-ctags-tags-file-caches)
+ (keys (hash-table-keys caches)))
+ (message "* cache contents")
+ (dolist (k keys)
+ (let* ((h (gethash k caches))
+ (timestamp (plist-get h :timestamp))
+ (filesize (plist-get h :filesize))
+ (dict (plist-get h :tagname-dict))
+ (dict-keys (hash-table-keys dict)))
+ (message "** key=%s timestamp=%s filesize=%s\n" k timestamp filesize)
+ (dolist (dk dict-keys)
+ (let* ((items (company-ctags-n-items 4 (gethash dk dict))))
+ (when (> (length items) 0)
+ (message " %s: %s" (string dk) items))))))))
+
+(defun company-ctags-init-tagname-dict ()
+ "Initialize tagname dict."
+ (let* ((i 0)
+ (dict (make-hash-table))
+ (len (length company-ctags-tag-name-valid-characters)))
+ (while (< i len)
+ (puthash (elt company-ctags-tag-name-valid-characters i) '() dict)
+ (setq i (1+ i)))
+ dict))
+
+(defun company-ctags-parse-tags (text &optional dict)
+ "Extract tag names from TEXT.
+DICT is the existing lookup dictionary contains tag names.
+If it's nil, return a dictionary, or else return the existing dictionary."
+ (let* ((start 0)
+ (case-fold-search company-ctags-ignore-case))
+ (unless dict (setq dict (company-ctags-init-tagname-dict)))
+
+ ;; Code inside the loop should be optimized.
+ ;; Please avoid calling lisp function inside the loop.
+ (cond
+ (company-ctags-support-etags
+ ;; slow algorithm, need support both explicit and implicit tags name
+ (while (string-match company-ctags-slow-pattern text start)
+ (cond
+ ((match-beginning 2)
+ ;; There is an explicit tag name.
+ (company-ctags-push-tagname (substring text (match-beginning 2) (match-end 2))
+ dict))
+ (t
+ ;; No explicit tag name. Backtrack a little,
+ ;; and look for the implicit one.
+ (company-ctags-push-tagname (substring text (match-beginning 1) (match-end 1))
+ dict)))
+ (setq start (+ 4 (match-end 0)))))
+ (t
+ ;; fast algorithm, support explicit tags name only
+ (while (string-match company-ctags-fast-pattern text start)
+ (company-ctags-push-tagname (substring text (match-beginning 1) (match-end 1))
+ dict)
+ (setq start (+ 4 (match-end 0))))))
+
+ dict))
+
+(defun company-ctags-all-completions (string collection)
+ "Search match to STRING in COLLECTION to see if it begins with STRING.
+If `company-ctags-fuzzy-match-p' is t, check if the match contains STRING."
+ (let ((case-fold-search company-ctags-ignore-case))
+ (cond
+ (company-ctags-fuzzy-match-p
+ (let* (rlt)
+ ;; code should be efficient in side the this loop
+ (dolist (c collection)
+ (if (string-match string c) (push c rlt)))
+ rlt))
+ (t
+ (all-completions string collection)))))
+
+(defun company-ctags-fetch-by-first-char (c prefix tagname-dict)
+ "Fetch candidates by first character C of PREFIX from TAGNAME-DICT."
+ (let* ((rlt (company-ctags-all-completions prefix (gethash c tagname-dict))))
+ (when company-ctags-ignore-case
+ (let (c2 (offset (- ?a ?A)))
+ (cond
+ ((company-ctags-char-in-string-p c "abcdefghijklmnopqrstuvwxyz")
+ (setq c2 (- c offset)))
+
+ ((company-ctags-char-in-string-p c "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
+ (setq c2 (+ c offset))))
+
+ (when c2
+ (setq rlt (nconc rlt (company-ctags-all-completions prefix (gethash c2 tagname-dict)))))))
+ rlt))
+
+(defun company-ctags-all-candidates (prefix tagname-dict)
+ "Search for partial match to PREFIX in TAGNAME-DICT."
+ (cond
+ (company-ctags-fuzzy-match-p
+ (let* ((keys (hash-table-keys tagname-dict))
+ rlt)
+ ;; search all hash tables
+ ;; don't care the first character of prefix
+ (dolist (c keys)
+ (setq rlt (nconc rlt (company-ctags-fetch-by-first-char c prefix tagname-dict))))
+ rlt))
+ (t
+ (company-ctags-fetch-by-first-char (elt prefix 0) prefix tagname-dict))))
+
+(defun company-ctags-load-tags-file (file static-p &optional force no-diff-prog)
+ "Load tags from FILE.
+If STATIC-P is t, the corresponding tags file is read only once.
+If FORCE is t, tags file is read without `company-ctags-tags-file-caches'.
+If NO-DIFF-PROG is t, do NOT use diff on tags file.
+This function return t if any tag file is reloaded."
+ (let* (raw-content
+ (file-info (and company-ctags-tags-file-caches
+ (gethash file company-ctags-tags-file-caches)))
+ (use-diff (and (not no-diff-prog)
+ file-info
+ (plist-get file-info :raw-content)
+ (executable-find diff-command)))
+ tagname-dict
+ reloaded)
+
+ (when (or force
+ (not file-info)
+ (and
+ ;; the tags file is static and is already read into cache
+ ;; so don't read it again
+ ;; (not (plist-get file-info :static-p))
+ ;; time to expire cache from tags file
+ (> (- (float-time (current-time))
+ (plist-get file-info :timestamp))
+ company-ctags-check-tags-file-interval)
+ ;; When generating new tags file, file size could be
+ ;; temporarily smaller than cached file size.
+ ;; Don't reload tags file until new tags file is bigger.
+ (> (nth 7 (file-attributes file))
+ (plist-get file-info :filesize))))
+
+ ;; Read file content
+ (setq reloaded t)
+ (cond
+ (use-diff
+ ;; actually don't change raw-content attached to file-info
+ (setq raw-content (plist-get file-info :raw-content))
+
+ ;; use diff to find the new tags
+ (let (diff-output)
+ (with-temp-buffer
+ (insert (plist-get file-info :raw-content))
+ ;; when process finished, replace temp buffer with program output
+ (call-process-region (point-min) (point-max) diff-command t t nil "-ab" file "-")
+ (setq diff-output (buffer-string)))
+
+ ;; compare old and new tags file, extract tag names from diff output which
+ ;; should be merged with old tag names
+ (setq tagname-dict
+ (company-ctags-parse-tags diff-output
+ (plist-get file-info :tagname-dict)))))
+ (t
+ (unless company-ctags-quiet (message "Please be patient when loading %s" file))
+ (setq raw-content (with-temp-buffer
+ (insert-file-contents file)
+ (buffer-string)))
+ ;; collect all tag names
+ (setq tagname-dict (company-ctags-parse-tags raw-content))
+ (unless company-ctags-quiet (message "%s is loaded." file))))
+
+ ;; initialize hash table if needed
+ (unless company-ctags-tags-file-caches
+ (set 'company-ctags-tags-file-caches (make-hash-table :test #'equal)))
+
+ ;; finalize tags file info
+ (puthash file
+ ;; if the tags file is read only once, it will never be updated
+ ;; by `diff-command', so don't need store original raw content
+ ;; of tags file in order to save memory.
+ (list :raw-content (unless static-p raw-content)
+ :tagname-dict tagname-dict
+ :static-p static-p
+ :timestamp (float-time (current-time))
+ :filesize (nth 7 (file-attributes file)))
+ company-ctags-tags-file-caches))
+ reloaded))
+
+(defun company-ctags--test-cached-candidates (prefix)
+ "Test PREFIX in `company-ctags-cached-candidates'."
+ (let* ((cands company-ctags-cached-candidates)
+ (key (plist-get cands :key))
+ (keylen (length key))
+ (case-fold-search company-ctags-ignore-case))
+ ;; prefix is "hello" and cache's prefix "ell"
+ (and (>= (length prefix) keylen)
+ (if company-ctags-fuzzy-match-p (string-match key prefix)
+ ;; key is the beginning of prefix
+ (string= (substring prefix 0 keylen) key)))))
+
+(defun company-ctags--candidates (prefix)
+ "Get candidate with PREFIX."
+ (when (and prefix (> (length prefix) 0))
+ (let* ((file (and tags-file-name (file-truename tags-file-name)))
+ (completion-ignore-case company-ctags-ignore-case)
+ (all-tags-files (mapcar (lambda (f)
+ (file-truename f))
+ (delete-dups (append (if file (list file))
+ (company-ctags-buffer-table)))))
+ (extra-tags-files (ff-list-replace-env-vars company-ctags-extra-tags-files))
+ rlt)
+
+ ;; load tags files, maybe
+ (dolist (f all-tags-files)
+ (when (and f (file-exists-p f))
+ (when (company-ctags-load-tags-file f
+ nil ; primary tags file, not static
+ nil
+ nil)
+ ;; invalidate cached candidates if any tags file is reloaded
+ (setq company-ctags-cached-candidates nil))))
+
+ (when extra-tags-files
+ (dolist (f extra-tags-files)
+ (when (and f (file-exists-p f))
+ ;; tags file in `company-ctags-extra-tags-files' is read only once.
+ (company-ctags-load-tags-file f
+ t ; static tags file, read only once
+ nil
+ nil))))
+
+ (cond
+ ;; re-use cached candidates
+ ((and (not company-ctags-fuzzy-match-p)
+ company-ctags-cached-candidates
+ (company-ctags--test-cached-candidates prefix))
+
+ (let* ((cands (plist-get company-ctags-cached-candidates :cands)))
+ (setq rlt (company-ctags-all-completions prefix cands))))
+
+ ;; search candidates through tags files
+ (t
+ (dolist (f (nconc all-tags-files extra-tags-files))
+ (let* ((cache (gethash f company-ctags-tags-file-caches))
+ (tagname-dict (plist-get cache :tagname-dict)))
+ (when tagname-dict
+ (setq rlt (append rlt (company-ctags-all-candidates prefix tagname-dict))))))
+
+ ;; fuzzy algorithm don't use caching algorithm
+ (unless company-ctags-fuzzy-match-p
+ (setq company-ctags-cached-candidates
+ ;; clone the rlt into cache
+ (list :key prefix :cands (mapcar 'identity rlt))))))
+
+ ;; cleanup
+ (if rlt (delete-dups rlt)))))
+
+;;;###autoload
+(defun company-ctags (command &optional arg &rest ignored)
+ "Completion backend of for ctags. Execute COMMAND with ARG and IGNORED."
+ (interactive (list 'interactive))
+ (cl-case command
+ (interactive (company-begin-backend 'company-ctags))
+ (prefix (and (apply #'derived-mode-p company-ctags-modes)
+ (or (eq t company-ctags-everywhere)
+ (apply #'derived-mode-p company-ctags-everywhere)
+ (not (company-in-string-or-comment)))
+ (company-ctags-buffer-table)
+ (or (company-grab-symbol) 'stop)))
+ (candidates (company-ctags--candidates arg))
+ (location (let ((tags-table-list (company-ctags-buffer-table)))
+ (when (fboundp 'find-tag-noselect)
+ (save-excursion
+ (let ((buffer (find-tag-noselect arg)))
+ (cons buffer (with-current-buffer buffer (point))))))))
+ (ignore-case company-ctags-ignore-case)))
+
+;;;###autoload
+(defun company-ctags-replace-backend (backends)
+ "Replace `company-etags' with `company-ctags' in BACKENDS."
+ (let* (rlt)
+ (dolist (b backends)
+ (cond
+ ((eq b 'company-etags)
+ (push 'company-ctags rlt))
+ ((listp b)
+ (let* (children)
+ (dolist (c b)
+ (cond
+ ((eq c 'company-etags)
+ (push 'company-ctags children))
+ (t
+ (push c children))))
+ (push (nreverse children) rlt)))
+ (t
+ (push b rlt))))
+ (nreverse rlt)))
+
+;;;###autoload
+(defun company-ctags-auto-setup ()
+ "Set up `company-backends'."
+ (setq company-backends
+ (company-ctags-replace-backend company-backends)))
+
+(provide 'company-ctags)
+;;; company-ctags.el ends here
diff --git a/site-lisp/extensions-local/dired-display-buffer.el b/site-lisp/extensions-local/dired-display-buffer.el
new file mode 100644
index 0000000..4794543
--- /dev/null
+++ b/site-lisp/extensions-local/dired-display-buffer.el
@@ -0,0 +1,87 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+
+;;; Code:
+(defcustom dired-display-buffer-switch-window t
+ "Switch focus to the newly created buffer window. nil to disable."
+ :type 'boolean
+ )
+
+(defun ld-display-buffer (buffer-or-name alist direction &optional size pixelwise)
+ "BUFFER: The buffer that will be displayed.
+ALIST: See the doc-string of `display-buffer' for more information.
+DIRECTION: Must use one of these symbols: 'left 'right 'below 'above
+SIZE: See the doc-string for `split-window'.
+PIXELWISE: See the doc-string for `split-window'.
+There are three possibilities:
+- (1) If a window on the frame already displays the target buffer,
+then just reuse the same window.
+- (2) If there is already a window in the specified direction in relation
+to the selected window, then display the target buffer in said window.
+- (3) If there is no window in the specified direction, then create one
+in that direction and display the target buffer in said window."
+ (let* ((buffer
+ (if (bufferp buffer-or-name)
+ buffer-or-name
+ (get-buffer buffer-or-name)))
+ (window
+ (cond
+ ((get-buffer-window buffer (selected-frame)))
+ ((window-in-direction direction))
+ (t
+ (split-window (selected-window) size direction pixelwise)))))
+ ;; (window--display-buffer buffer window 'window alist display-buffer-mark-dedicated)
+ (window--display-buffer buffer window 'window alist)
+ (if dired-display-buffer-switch-window
+ (select-window window))
+ ))
+
+(defun dired-display-buffer (&optional direction alist)
+ "Display a dired-mode buffer or a file underneath point in a dired-mode buffer."
+ (interactive)
+ (let* ((file-or-dir (or (and (eq major-mode 'dired-mode) (dired-get-file-for-visit))
+ (read-directory-name "Directory: ")))
+ (buffer (find-file-noselect file-or-dir))
+ (direction
+ (if direction
+ direction
+ (let ((char (read-char-exclusive (concat
+ "["
+ (propertize "l" 'face '(:foreground "red"))
+ "]"
+ (propertize "eft" 'face '(:foreground "blue"))
+ " | ["
+ (propertize "r" 'face '(:foreground "red"))
+ "]"
+ (propertize "ight" 'face '(:foreground "blue"))
+ " | ["
+ (propertize "a" 'face '(:foreground "red"))
+ "]"
+ (propertize "bove" 'face '(:foreground "blue"))
+ " | ["
+ (propertize "b" 'face '(:foreground "red"))
+ "]"
+ (propertize "elow" 'face '(:foreground "blue"))))))
+ (cond
+ ((eq char ?l)
+ 'left)
+ ((eq char ?r)
+ 'right)
+ ((eq char ?a)
+ 'above)
+ ((eq char ?b)
+ 'below)
+ ;;; FIXME: @lawlist may add a loop similar to `org-capture'
+ ;;; whereby a new `read-char-exclusive' will be initiated if
+ ;;; a user did not initially choose a valid option (l/r/a/b).
+ (t
+ (let ((debug-on-quit nil)
+ (msg (concat "dired-display-buffer: "
+ "You did not select l/r/a/b "
+ "-- exiting.")))
+ (signal 'quit `(,msg)))))))))
+ (ld-display-buffer buffer alist direction)))
+
+(provide 'dired-display-buffer)
+
+;;; dired-display-buffer.el ends here
diff --git a/site-lisp/extensions-local/dired-hacks-utils.el b/site-lisp/extensions-local/dired-hacks-utils.el
new file mode 100644
index 0000000..d283312
--- /dev/null
+++ b/site-lisp/extensions-local/dired-hacks-utils.el
@@ -0,0 +1,273 @@
+;;; dired-hacks-utils.el --- Utilities and helpers for dired-hacks collection
+
+;; Copyright (C) 2014-2015 Matúš Goljer
+
+;; Author: Matúš Goljer
+;; Maintainer: Matúš Goljer
+;; Keywords: files
+;; Version: 0.0.1
+;; Created: 14th February 2014
+;; Package-Requires: ((dash "2.5.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 .
+
+;;; Commentary:
+
+;; Utilities and helpers for `dired-hacks' collection of dired
+;; improvements.
+
+;; This package also provides these interactive functions:
+;; * `dired-hacks-next-file' - go to next file, skipping empty and non-file lines
+;; * `dired-hacks-previous-file' - go to previous file, skipping empty
+;; and non-file lines
+;; * `dired-utils-format-information-line-mode' - Format the information
+;; (summary) line file sizes to be human readable (e.g. 1GB instead of 1048576).
+
+
+;; See https://github.com/Fuco1/dired-hacks for the entire collection
+
+;;; Code:
+
+(require 'dash)
+(require 'dired)
+
+(defgroup dired-hacks ()
+ "Collection of useful dired additions."
+ :group 'dired
+ :prefix "dired-hacks-")
+
+(defcustom dired-hacks-file-size-formatter #'file-size-human-readable
+ "The function used to format file sizes.
+
+See `dired-utils-format-file-sizes'."
+ :type 'function
+ :group 'dired-hacks)
+
+(defcustom dired-hacks-datetime-regexp
+ "\\sw\\sw\\sw....\\(?:[0-9][0-9]:[0-9][0-9]\\|.[0-9]\\{4\\}\\)"
+ "A regexp matching the date/time in the dired listing.
+
+It is used to determine where the filename starts. It should
+*not* match any characters after the last character of the
+timestamp. It is assumed that the timestamp is preceded and
+followed by at least one space character. You should only use
+shy groups (prefixed with ?:) because the first group is used by
+the font-lock to determine what portion of the name should be
+colored."
+ :type 'regexp
+ :group 'dired-hacks)
+
+(defalias 'dired-utils--string-trim
+ (if (and (require 'subr-x nil t)
+ (fboundp 'string-trim))
+ #'string-trim
+ (lambda (string)
+ (let ((s string))
+ (when (string-match "\\`[ \t\n\r]+" s)
+ (setq s (replace-match "" t t s)))
+ (when (string-match "[ \t\n\r]+\\'" s)
+ (setq s (replace-match "" t t s)))
+ s)))
+ "Trim STRING of trailing whitespace.
+
+\(fn STRING)")
+
+(defun dired-utils-get-filename (&optional localp)
+ "Like `dired-get-filename' but never signal an error.
+
+Optional arg LOCALP with value `no-dir' means don't include
+directory name in result."
+ (dired-get-filename localp t))
+
+(defun dired-utils-get-all-files (&optional localp)
+ "Return all files in this dired buffer as a list.
+
+LOCALP has same semantics as in `dired-get-filename'."
+ (save-excursion
+ (goto-char (point-min))
+ (let (r)
+ (while (= 0 (forward-line))
+ (--when-let (dired-utils-get-filename localp)
+ (push it r)))
+ (nreverse r))))
+
+(defconst dired-utils-file-attributes-keywords
+ '(:isdir :nlinks :uid :gid :atime :mtime :ctime :size :modes :gidchg :inode :devnum)
+ "List of keywords to map with `file-attributes'.")
+
+(defconst dired-utils-info-keywords
+ `(:name :issym :target ,@dired-utils-file-attributes-keywords)
+ "List of keywords available for `dired-utils-get-info'.")
+
+(defun dired-utils--get-keyword-info (keyword)
+ "Get file information about KEYWORD."
+ (let ((filename (dired-utils-get-filename)))
+ (cl-case keyword
+ (:name filename)
+ (:isdir (file-directory-p filename))
+ (:issym (and (file-symlink-p filename) t))
+ (:target (file-symlink-p filename))
+ (t
+ (nth (-elem-index keyword dired-utils-file-attributes-keywords)
+ (file-attributes filename))))))
+
+(defun dired-utils-get-info (&rest keywords)
+ "Query for info about the file at point.
+
+KEYWORDS is a list of attributes to query.
+
+When querying for one attribute, its value is returned. When
+querying for more than one, a list of results is returned.
+
+The available keywords are listed in
+`dired-utils-info-keywords'."
+ (let ((attributes (mapcar 'dired-utils--get-keyword-info keywords)))
+ (if (> (length attributes) 1)
+ attributes
+ (car attributes))))
+
+(defun dired-utils-goto-line (filename)
+ "Go to line describing FILENAME in listing.
+
+Should be absolute file name matched against
+`dired-get-filename'."
+ (goto-char (point-min))
+ (let (stop)
+ (while (and (not stop)
+ (= (forward-line) 0))
+ (when (equal filename (dired-utils-get-filename))
+ (setq stop t)
+ (dired-move-to-filename)))
+ stop))
+
+(defun dired-utils-match-filename-regexp (filename alist)
+ "Match FILENAME against each car in ALIST and return first matched cons.
+
+Each car in ALIST is a regular expression.
+
+The matching is done using `string-match-p'."
+ (let (match)
+ (--each-while alist (not match)
+ (when (string-match-p (car it) filename)
+ (setq match it)))
+ match))
+
+(defun dired-utils-match-filename-extension (filename alist)
+ "Match FILENAME against each car in ALIST and return first matched cons.
+
+Each car in ALIST is a string representing file extension
+*without* the delimiting dot."
+ (let (done)
+ (--each-while alist (not done)
+ (when (string-match-p (concat "\\." (regexp-quote (car it)) "\\'") filename)
+ (setq done it)))
+ done))
+
+(defun dired-utils-format-information-line ()
+ "Format the disk space on the Dired information line."
+ (save-excursion
+ (goto-char (point-min))
+ (forward-line)
+ (let ((inhibit-read-only t)
+ (limit (line-end-position)))
+ (while (re-search-forward "\\(?:directory\\|available\\) \\(\\<[0-9]+$\\>\\)" nil t)
+ (replace-match
+ (save-match-data
+ (propertize (dired-utils--string-trim
+ (funcall dired-hacks-file-size-formatter
+ (* 1024 (string-to-number (match-string 1))) t))
+ 'invisible 'dired-hide-details-information))
+ t nil nil 1)))))
+
+
+;;; Predicates
+(defun dired-utils-is-file-p ()
+ "Return non-nil if the line at point is a file or a directory."
+ (dired-utils-get-filename 'no-dir))
+
+(defun dired-utils-is-dir-p ()
+ "Return non-nil if the line at point is a directory."
+ (--when-let (dired-utils-get-filename)
+ (file-directory-p it)))
+
+
+;;; Interactive
+;; TODO: add wrap-around option
+(defun dired-hacks-next-file (&optional arg)
+ "Move point to the next file.
+
+Optional prefix ARG says how many lines to move; default is one
+line."
+ (interactive "p")
+ (unless arg (setq arg 1))
+ (if (< arg 0)
+ (dired-hacks-previous-file (- arg))
+ (--dotimes arg
+ (forward-line)
+ (while (and (or (not (dired-utils-is-file-p))
+ (get-text-property (point) 'invisible))
+ (= (forward-line) 0))))
+ (if (not (= (point) (point-max)))
+ (dired-move-to-filename)
+ (forward-line -1)
+ (dired-move-to-filename)
+ nil)))
+
+(defun dired-hacks-previous-file (&optional arg)
+ "Move point to the previous file.
+
+Optional prefix ARG says how many lines to move; default is one
+line."
+ (interactive "p")
+ (unless arg (setq arg 1))
+ (if (< arg 0)
+ (dired-hacks-next-file (- arg))
+ (--dotimes arg
+ (forward-line -1)
+ (while (and (or (not (dired-utils-is-file-p))
+ (get-text-property (point) 'invisible))
+ (= (forward-line -1) 0))))
+ (if (not (= (point) (point-min)))
+ (dired-move-to-filename)
+ (dired-hacks-next-file)
+ nil)))
+
+(defun dired-hacks-compare-files (file-a file-b)
+ "Test if two files FILE-A and FILE-B are the (probably) the same."
+ (interactive (let ((other-dir (dired-dwim-target-directory)))
+ (list (read-file-name "File A: " default-directory (car (dired-get-marked-files)) t)
+ (read-file-name "File B: " other-dir (with-current-buffer (cdr (assoc other-dir dired-buffers))
+ (car (dired-get-marked-files))) t))))
+ (let ((md5-a (with-temp-buffer
+ (shell-command (format "md5sum %s" file-a) (current-buffer))
+ (buffer-string)))
+ (md5-b (with-temp-buffer
+ (shell-command (format "md5sum %s" file-b) (current-buffer))
+ (buffer-string))))
+ (message "%s%sFiles are %s." md5-a md5-b
+ (if (equal (car (split-string md5-a))
+ (car (split-string md5-b)))
+ "probably the same" "different"))))
+
+(define-minor-mode dired-utils-format-information-line-mode
+ "Toggle formatting of disk space in the Dired information line."
+ :group 'dired-utils
+ :lighter ""
+ (if dired-utils-format-information-line-mode
+ (add-hook 'dired-after-readin-hook #'dired-utils-format-information-line)
+ (remove-hook 'dired-after-readin-hook #'dired-utils-format-information-line)))
+
+(provide 'dired-hacks-utils)
+
+;;; dired-hacks-utils.el ends here
diff --git a/site-lisp/extensions-local/dired-narrow.el b/site-lisp/extensions-local/dired-narrow.el
new file mode 100644
index 0000000..1c3eda2
--- /dev/null
+++ b/site-lisp/extensions-local/dired-narrow.el
@@ -0,0 +1,356 @@
+;;; dired-narrow.el --- Live-narrowing of search results for dired
+
+;; Copyright (C) 2014-2015 Matúš Goljer
+
+;; Author: Matúš Goljer
+;; Maintainer: Matúš Goljer
+;; Version: 0.0.1
+;; Created: 14th February 2014
+;; Package-Requires: ((dash "2.7.0") (dired-hacks-utils "0.0.1"))
+;; Keywords: files
+
+;; 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 .
+
+;;; Commentary:
+
+;; This package provides live filtering of files in dired buffers. In
+;; general, after calling the respective narrowing function you type a
+;; filter string into the minibuffer. After each change the changes
+;; automatically reflect in the buffer. Typing C-g will cancel the
+;; narrowing and restore the original view, typing RET will exit the
+;; live filtering mode and leave the dired buffer in the narrowed
+;; state. To bring it back to the original view, you can call
+;; `revert-buffer' (usually bound to `g').
+
+;; During the filtering process, several special functions are
+;; available. You can customize the binding by changing
+;; `dired-narrow-map'.
+
+;; * `dired-narrow-next-file' ( or C-n) - move the point to the
+;; next file
+;; * `dired-narrow-previous-file' ( or C-p) - move the point to the
+;; previous file
+;; * `dired-narrow-enter-directory' ( or C-j) - descend into the
+;; directory under point and immediately go back to narrowing mode
+
+;; You can customize what happens after exiting the live filtering
+;; mode by customizing `dired-narrow-exit-action'.
+
+;; These narrowing functions are provided:
+
+;; * `dired-narrow'
+;; * `dired-narrow-regexp'
+;; * `dired-narrow-fuzzy'
+
+;; You can also create your own narrowing functions quite easily. To
+;; define new narrowing function, use `dired-narrow--internal' and
+;; pass it an apropriate filter. The filter should take one argument
+;; which is the filter string from the minibuffer. It is then called
+;; at each line that describes a file with point at the beginning of
+;; the file name. If the filter returns nil, the file is removed from
+;; the view. As an inspiration, look at the built-in functions
+;; mentioned above.
+
+;; See https://github.com/Fuco1/dired-hacks for the entire collection.
+
+;;; Code:
+
+(require 'dash)
+(require 'dired-hacks-utils)
+
+(require 'delsel)
+
+(defgroup dired-narrow ()
+ "Live-narrowing of search results for dired."
+ :group 'dired-hacks
+ :prefix "dired-narrow-")
+
+(defvar dired-narrow-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd "") 'dired-narrow-previous-file)
+ (define-key map (kbd "") 'dired-narrow-next-file)
+ (define-key map (kbd "") 'dired-narrow-enter-directory)
+ (define-key map (kbd "C-p") 'dired-narrow-previous-file)
+ (define-key map (kbd "C-n") 'dired-narrow-next-file)
+ (define-key map (kbd "C-j") 'dired-narrow-enter-directory)
+ (define-key map (kbd "C-g") 'minibuffer-keyboard-quit)
+ (define-key map (kbd "RET") 'exit-minibuffer)
+ (define-key map (kbd "") 'exit-minibuffer)
+ map)
+ "Keymap used while `dired-narrow' is reading the pattern.")
+
+(defcustom dired-narrow-exit-action 'ignore
+ "Function to call after exiting minibuffer.
+
+Function takes no argument and is called with point over the file
+we should act on."
+ :type '(choice (const :tag "Open file under point" dired-narrow-find-file)
+ (function :tag "Use custom function."))
+ :group 'dired-narrow)
+
+(defcustom dired-narrow-exit-when-one-left nil
+ "If there is only one file left while narrowing,
+exit minibuffer and call `dired-narrow-exit-action'."
+ :type 'boolean
+ :group 'dired-narrow)
+
+(defcustom dired-narrow-enable-blinking t
+ "If non-nil, highlight the chosen file shortly.
+Only works when `dired-narrow-exit-when-one-left' is non-nil."
+ :type 'boolean
+ :group 'dired-narrow)
+
+(defcustom dired-narrow-blink-time 0.2
+ "How many seconds should a chosen file be highlighted."
+ :type 'number
+ :group 'dired-narrow)
+
+(defface dired-narrow-blink
+ '((t :background "#eadc62"
+ :foreground "black"))
+ "The face used to highlight a chosen file
+when `dired-narrow-exit-when-one-left' and `dired-narrow-enable-blinking' are true."
+ :group 'dired-narrow)
+
+
+;; Utils
+
+;; this is `gnus-remove-text-with-property'
+(defun dired-narrow--remove-text-with-property (prop)
+ "Delete all text in the current buffer with text property PROP."
+ (let ((start (point-min))
+ end)
+ (unless (get-text-property start prop)
+ (setq start (next-single-property-change start prop)))
+ (while start
+ (setq end (text-property-any start (point-max) prop nil))
+ (delete-region start (or end (point-max)))
+ (setq start (when end
+ (next-single-property-change start prop))))))
+
+(defvar dired-narrow-filter-function 'identity
+ "Filter function used to filter the dired view.")
+
+(defvar dired-narrow--current-file nil
+ "Value of point just before exiting minibuffer.")
+
+(defun dired-narrow--update (filter)
+ "Make the files not matching the FILTER invisible.
+ Return the count of visible files that are left after update."
+
+ (let ((inhibit-read-only t)
+ (visible-files-cnt 0))
+ (save-excursion
+ (goto-char (point-min))
+ ;; TODO: we might want to call this only if the filter gets less
+ ;; specialized.
+ (dired-narrow--restore)
+ (while (dired-hacks-next-file)
+ (if (funcall dired-narrow-filter-function filter)
+ (progn
+ (setq visible-files-cnt (1+ visible-files-cnt))
+ (when (fboundp 'dired-insert-set-properties)
+ (dired-insert-set-properties (line-beginning-position) (1+ (line-end-position)))))
+ (put-text-property (line-beginning-position) (1+ (line-end-position)) :dired-narrow t)
+ (put-text-property (line-beginning-position) (1+ (line-end-position)) 'invisible :dired-narrow))))
+ (unless (dired-hacks-next-file)
+ (dired-hacks-previous-file))
+ (unless (dired-utils-get-filename)
+ (dired-hacks-previous-file))
+ visible-files-cnt))
+
+(defun dired-narrow--restore ()
+ "Restore the invisible files of the current buffer."
+ (let ((inhibit-read-only t))
+ (remove-list-of-text-properties (point-min) (point-max)
+ '(invisible :dired-narrow))
+ (when (fboundp 'dired-insert-set-properties)
+ (dired-insert-set-properties (point-min) (point-max)))))
+
+
+(defun dired-narrow--blink-current-file ()
+ (let* ((beg (line-beginning-position))
+ (end (line-end-position))
+ (overlay (make-overlay beg end)))
+ (overlay-put overlay 'face 'dired-narrow-blink)
+ (redisplay)
+ (sleep-for dired-narrow-blink-time)
+ (discard-input)
+ (delete-overlay overlay)))
+
+
+;; Live filtering
+
+(defvar dired-narrow-buffer nil
+ "Dired buffer we are currently filtering.")
+
+(defvar dired-narrow--minibuffer-content ""
+ "Content of the minibuffer during narrowing.")
+
+(defun dired-narrow--minibuffer-setup ()
+ "Set up the minibuffer for live filtering."
+ (when dired-narrow-buffer
+ (add-hook 'post-command-hook 'dired-narrow--live-update nil :local)))
+
+(add-hook 'minibuffer-setup-hook 'dired-narrow--minibuffer-setup)
+
+(defun dired-narrow--live-update ()
+ "Update the dired buffer based on the contents of the minibuffer."
+ (when dired-narrow-buffer
+ (let ((current-filter (minibuffer-contents-no-properties))
+ visible-files-cnt)
+ (with-current-buffer dired-narrow-buffer
+ (setq visible-files-cnt
+ (unless (equal current-filter dired-narrow--minibuffer-content)
+ (dired-narrow--update current-filter)))
+
+ (setq dired-narrow--minibuffer-content current-filter)
+ (setq dired-narrow--current-file (dired-utils-get-filename))
+ (set-window-point (get-buffer-window (current-buffer)) (point))
+
+ (when (and dired-narrow-exit-when-one-left
+ visible-files-cnt
+ (= visible-files-cnt 1))
+ (when dired-narrow-enable-blinking
+ (dired-narrow--blink-current-file))
+ (exit-minibuffer))))))
+
+(defun dired-narrow--internal (filter-function)
+ "Narrow a dired buffer to the files matching a filter.
+
+The function FILTER-FUNCTION is called on each line: if it
+returns non-nil, the line is kept, otherwise it is removed. The
+function takes one argument, which is the current filter string
+read from minibuffer."
+ (let ((dired-narrow-buffer (current-buffer))
+ (dired-narrow-filter-function filter-function)
+ (disable-narrow nil))
+ (unwind-protect
+ (progn
+ (dired-narrow-mode 1)
+ (add-to-invisibility-spec :dired-narrow)
+ (setq disable-narrow (read-from-minibuffer
+ (pcase dired-narrow-filter-function
+ ('dired-narrow--regexp-filter
+ "Regex Filter:\s")
+ ('dired-narrow--fuzzy-filter
+ "Fuzzy Filter:\s")
+ (_ "Filter:\s"))
+ nil dired-narrow-map))
+ (let ((inhibit-read-only t))
+ (dired-narrow--remove-text-with-property :dired-narrow))
+ ;; If the file no longer exists, we can't do anything, so
+ ;; set to nil
+ (unless (dired-utils-goto-line dired-narrow--current-file)
+ (setq dired-narrow--current-file nil)))
+ (with-current-buffer dired-narrow-buffer
+ (unless disable-narrow (dired-narrow-mode -1))
+ (remove-from-invisibility-spec :dired-narrow)
+ (dired-narrow--restore))
+ (when (and disable-narrow
+ dired-narrow--current-file
+ dired-narrow-exit-action)
+ (funcall dired-narrow-exit-action))
+ (cond
+ ((equal disable-narrow "dired-narrow-enter-directory")
+ (dired-narrow--internal filter-function))))))
+
+
+;; Interactive
+
+(defun dired-narrow--regexp-filter (filter)
+ (condition-case nil
+ (string-match-p filter (dired-utils-get-filename 'no-dir))
+ ;; Return t if your regexp is incomplete/has errors, thus
+ ;; filtering nothing until you fix the regexp.
+ (invalid-regexp t)))
+
+;;;###autoload
+(defun dired-narrow-regexp ()
+ "Narrow a dired buffer to the files matching a regular expression."
+ (interactive)
+ (dired-narrow--internal 'dired-narrow--regexp-filter))
+
+(defun dired-narrow--string-filter (filter)
+ (let ((words (split-string filter " ")))
+ (--all? (save-excursion (search-forward it (line-end-position) t)) words)))
+
+(defun dired-narrow-next-file ()
+ "Move point to the next file."
+ (interactive)
+ (with-current-buffer dired-narrow-buffer
+ (dired-hacks-next-file)))
+
+(defun dired-narrow-previous-file ()
+ "Move point to the previous file."
+ (interactive)
+ (with-current-buffer dired-narrow-buffer
+ (dired-hacks-previous-file)))
+
+(defun dired-narrow-find-file ()
+ "Run `dired-find-file' or any remapped action on file under point."
+ (interactive)
+ (let ((function (or (command-remapping 'dired-find-file)
+ 'dired-find-file)))
+ (funcall function)))
+
+(defun dired-narrow-enter-directory ()
+ "Descend into directory under point and initiate narrowing."
+ (interactive)
+ (let ((inhibit-read-only t))
+ (erase-buffer)
+ (insert "dired-narrow-enter-directory"))
+ (exit-minibuffer))
+
+;;;###autoload
+(defun dired-narrow ()
+ "Narrow a dired buffer to the files matching a string.
+
+If the string contains spaces, then each word is matched against
+the file name separately. To succeed, all of them have to match
+but the order does not matter.
+
+For example \"foo bar\" matches filename \"bar-and-foo.el\"."
+ (interactive)
+ (dired-narrow--internal 'dired-narrow--string-filter))
+
+(defun dired-narrow--fuzzy-filter (filter)
+ (re-search-forward
+ (mapconcat 'regexp-quote
+ (mapcar 'char-to-string (string-to-list filter))
+ ".*")
+ (line-end-position) t))
+
+;;;###autoload
+(defun dired-narrow-fuzzy ()
+ "Narrow a dired buffer to the files matching a fuzzy string.
+
+A fuzzy string is constructed from the filter string by inserting
+\".*\" between each letter. This is then matched as regular
+expression against the file name."
+ (interactive)
+ (dired-narrow--internal 'dired-narrow--fuzzy-filter))
+
+(define-minor-mode dired-narrow-mode
+ "Minor mode for indicating when narrowing is in progress."
+ :lighter " dired-narrow")
+
+(defun dired-narrow--disable-on-revert ()
+ "Disable `dired-narrow-mode' after revert."
+ (dired-narrow-mode -1))
+
+(add-hook 'dired-after-readin-hook 'dired-narrow--disable-on-revert)
+
+(provide 'dired-narrow)
+;;; dired-narrow.el ends here
diff --git a/site-lisp/extensions-local/dired-subtree.el b/site-lisp/extensions-local/dired-subtree.el
new file mode 100644
index 0000000..87f0a69
--- /dev/null
+++ b/site-lisp/extensions-local/dired-subtree.el
@@ -0,0 +1,784 @@
+;;; dired-subtree.el --- Insert subdirectories in a tree-like fashion
+
+;; Copyright (C) 2014-2015 Matúš Goljer
+
+;; Author: Matúš Goljer
+;; Maintainer: Matúš Goljer
+;; Keywords: files
+;; Version: 0.0.1
+;; Created: 25th February 2014
+;; Package-Requires: ((dash "2.5.0") (dired-hacks-utils "0.0.1"))
+
+;; 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 .
+
+;;; Commentary:
+
+;; Introduction
+;; ------------
+
+;; The basic command to work with subdirectories in dired is `i',
+;; which inserts the subdirectory as a separate listing in the active
+;; dired buffer.
+
+;; This package defines function `dired-subtree-insert' which instead
+;; inserts the subdirectory directly below its line in the original
+;; listing, and indent the listing of subdirectory to resemble a
+;; tree-like structure (somewhat similar to tree(1) except the pretty
+;; graphics). The tree display is somewhat more intuitive than the
+;; default "flat" subdirectory manipulation provided by `i'.
+
+;; There are several presentation options and faces you can customize
+;; to change the way subtrees are displayed.
+
+;; You can further remove the unwanted lines from the subtree by using
+;; `k' command or some of the built-in "focusing" functions, such as
+;; `dired-subtree-only-*' (see list below).
+
+;; If you have the package `dired-filter', you can additionally filter
+;; the subtrees with global or local filters.
+
+;; A demo of basic functionality is available on youtube:
+;; https://www.youtube.com/watch?v=z26b8HKFsNE
+
+;; Interactive functions
+;; ---------------------
+
+;; Here's a list of available interactive functions. You can read
+;; more about each one by using the built-in documentation facilities
+;; of emacs. It is adviced to place bindings for these into a
+;; convenient prefix key map, for example C-,
+
+;; * `dired-subtree-insert'
+;; * `dired-subtree-remove'
+;; * `dired-subtree-toggle'
+;; * `dired-subtree-cycle'
+;; * `dired-subtree-revert'
+;; * `dired-subtree-narrow'
+;; * `dired-subtree-up'
+;; * `dired-subtree-down'
+;; * `dired-subtree-next-sibling'
+;; * `dired-subtree-previous-sibling'
+;; * `dired-subtree-beginning'
+;; * `dired-subtree-end'
+;; * `dired-subtree-mark-subtree'
+;; * `dired-subtree-unmark-subtree'
+;; * `dired-subtree-only-this-file'
+;; * `dired-subtree-only-this-directory'
+
+;; If you have package `dired-filter', additional command
+;; `dired-subtree-apply-filter' is available.
+
+;; See https://github.com/Fuco1/dired-hacks for the entire collection.
+
+;;; Code:
+
+(require 'dired-hacks-utils)
+(require 'dash)
+(require 'cl-lib)
+
+(defgroup dired-subtree ()
+ "Insert subdirectories in a tree-like fashion."
+ :group 'dired-hacks
+ :prefix "dired-subtree-")
+
+(defcustom dired-subtree-line-prefix " "
+ "A prefix put into each nested subtree.
+
+The prefix is repeated \"depth\" times.
+
+Alternatively, it can be a function taking one argument---the
+depth---that creates the prefix."
+ :type '(choice string function)
+ :group 'dired-subtree)
+
+(defcustom dired-subtree-line-prefix-face 'parents
+ "Specifies how the prefix is fontified."
+ :type '(radio
+ (const :tag "No face applied" nil)
+ (const :tag "Inherit from current subtree" subtree)
+ (const :tag "Inherit from all parents" parents))
+ :group 'dired-subtree)
+
+(defcustom dired-subtree-use-backgrounds t
+ "When non-nil, add a background face to a subtree listing."
+ :type 'boolean
+ :group 'dired-subtree)
+
+(defcustom dired-subtree-after-insert-hook ()
+ "Hook run at the end of `dired-subtree-insert'."
+ :type 'hook
+ :group 'dired-subtree)
+
+(defcustom dired-subtree-after-remove-hook ()
+ "Hook run at the end of `dired-subtree-remove'."
+ :type 'hook
+ :group 'dired-subtree)
+
+(defcustom dired-subtree-cycle-depth 3
+ "Default depth expanded by `dired-subtree-cycle'."
+ :type 'natnum
+ :group 'dired-subtree)
+
+(defcustom dired-subtree-ignored-regexp
+ (concat "^" (regexp-opt vc-directory-exclusion-list) "$")
+ "Matching directories will not be expanded in `dired-subtree-cycle'."
+ :type 'regexp
+ :group 'dired-subtree)
+
+(defgroup dired-subtree-faces ()
+ "Faces used in `dired-subtree'."
+ :group 'dired-subtree)
+
+(defface dired-subtree-depth-1-face
+ '((t (:background "#252e30")))
+ "Background for depth 1 subtrees"
+ :group 'dired-subtree-faces)
+
+(defface dired-subtree-depth-2-face
+ '((t (:background "#232a2b")))
+ "Background for depth 2 subtrees"
+ :group 'dired-subtree-faces)
+
+(defface dired-subtree-depth-3-face
+ '((t (:background "#212627")))
+ "Background for depth 3 subtrees"
+ :group 'dired-subtree-faces)
+
+(defface dired-subtree-depth-4-face
+ '((t (:background "#1e2223")))
+ "Background for depth 4 subtrees"
+ :group 'dired-subtree-faces)
+
+(defface dired-subtree-depth-5-face
+ '((t (:background "#1c1d1e")))
+ "Background for depth 5 subtrees"
+ :group 'dired-subtree-faces)
+
+(defface dired-subtree-depth-6-face
+ '((t (:background "#1a191a")))
+ "Background for depth 6 subtrees"
+ :group 'dired-subtree-faces)
+
+(defvar dired-subtree-overlays nil
+ "Subtree overlays in this buffer.")
+(make-variable-buffer-local 'dired-subtree-overlays)
+
+
+;;; Overlay manipulation
+;; Maybe we should abstract the overlay-foo into some subtree
+;; functions instead!!!
+
+(defun dired-subtree--remove-overlay (ov)
+ "Remove dired-subtree overlay OV."
+ (setq dired-subtree-overlays
+ (--remove (equal it ov) dired-subtree-overlays))
+ (delete-overlay ov))
+
+(defun dired-subtree--remove-overlays (ovs)
+ "Remove dired-subtree overlays OVS."
+ (mapc 'dired-subtree--remove-overlay ovs))
+
+(defun dired-subtree--cleanup-overlays ()
+ "Remove the `nil' values from `dired-subtree-overlays'."
+ (setq dired-subtree-overlays
+ (--remove (not (overlay-buffer it)) dired-subtree-overlays)))
+
+(defun dired-subtree--get-all-ovs ()
+ "Get all dired-subtree overlays in this buffer."
+ (--filter (overlay-get it 'dired-subtree-depth) (overlays-in (point-min) (point-max))))
+
+(defun dired-subtree--get-all-ovs-at-point (&optional p)
+ "Get all dired-subtree overlays at point P."
+ (setq p (or p (point)))
+ (--filter (overlay-get it 'dired-subtree-depth) (overlays-at (point))))
+
+(defun dired-subtree--get-ovs-in (&optional beg end)
+ "Get all dired-subtree overlays between BEG and END.
+
+BEG and END default to the region spanned by overlay at point."
+ (when (not beg)
+ (let ((ov (dired-subtree--get-ov)))
+ (setq beg (overlay-start ov))
+ (setq end (overlay-end ov))))
+ (--filter (and (overlay-get it 'dired-subtree-depth)
+ (>= (overlay-start it) beg)
+ (<= (overlay-end it) end))
+ (overlays-in (point-min) (point-max))))
+
+(defun dired-subtree--get-ov (&optional p)
+ "Get the parent subtree overlay at point."
+ (setq p (or p (point)))
+ (car (--sort (> (overlay-get it 'dired-subtree-depth)
+ (overlay-get other 'dired-subtree-depth))
+ (dired-subtree--get-all-ovs-at-point p))))
+
+(defun dired-subtree--get-depth (ov)
+ "Get subtree depth."
+ (or (and ov (overlay-get ov 'dired-subtree-depth)) 0))
+
+
+
+;;; helpers
+(defvar dired-subtree-preserve-properties '(dired-subtree-filter)
+ "Properties that should be preserved between read-ins.")
+
+(defun dired-subtree--after-readin (&optional subtrees)
+ "Insert the SUBTREES again after dired buffer has been reverted.
+
+If no SUBTREES are specified, use `dired-subtree-overlays'."
+ (-when-let (subtrees-to-process (or subtrees dired-subtree-overlays))
+ (let* ((ovs-by-depth (--sort (< (car it) (car other))
+ (--group-by (overlay-get it 'dired-subtree-depth)
+ subtrees-to-process)))
+ (sorted-ovs (--map (cons (car it)
+ (--map (-cons* it
+ (overlay-get it 'dired-subtree-name)
+ (-map (lambda (x) (cons x (overlay-get it x)))
+ dired-subtree-preserve-properties)) (cdr it)))
+ ovs-by-depth)))
+ ;; (depth (path1 ov1 (prop1 . value1) (prop2 . value2)) (path2 ...))
+ (--each sorted-ovs
+ (--each (cdr it)
+ (when (dired-utils-goto-line (cadr it))
+ (dired-subtree--remove-overlay (car it))
+ (dired-subtree-insert)
+ (let ((ov (dired-subtree--get-ov)))
+ (--each (cddr it)
+ (overlay-put ov (car it) (cdr it)))
+ (dired-subtree--filter-subtree ov))))))))
+
+(defun dired-subtree--after-insert ()
+ "After inserting the subtree, setup dired-details/dired-hide-details-mode."
+ (if (fboundp 'dired-insert-set-properties)
+ (let ((inhibit-read-only t)
+ (ov (dired-subtree--get-ov)))
+ (dired-insert-set-properties (overlay-start ov) (overlay-end ov)))
+ (when (featurep 'dired-details)
+ (dired-details-delete-overlays)
+ (dired-details-activate))))
+
+(add-hook 'dired-after-readin-hook 'dired-subtree--after-readin)
+
+(add-hook 'dired-subtree-after-insert-hook 'dired-subtree--after-insert)
+
+(defun dired-subtree--unmark ()
+ "Unmark a file without moving point."
+ (save-excursion (dired-unmark 1)))
+
+(defun dired-subtree--dired-line-is-directory-or-link-p ()
+ "Return non-nil if line under point is a directory or symlink"
+ ;; We've replaced `file-directory-p' with the regexp test to
+ ;; speed up filters over TRAMP. So long as dired/ls format
+ ;; doesn't change, we're good.
+ ;; 'd' for directories, 'l' for potential symlinks to directories.
+ (save-excursion (beginning-of-line) (looking-at "..[dl]")))
+
+(defun dired-subtree--is-expanded-p ()
+ "Return non-nil if directory under point is expanded."
+ (save-excursion
+ (when (dired-utils-get-filename)
+ (let ((depth (dired-subtree--get-depth (dired-subtree--get-ov))))
+ (dired-next-line 1)
+ (< depth (dired-subtree--get-depth (dired-subtree--get-ov)))))))
+
+(defmacro dired-subtree-with-subtree (&rest forms)
+ "Run FORMS on each file in this subtree."
+ (declare (debug (body)))
+ `(save-excursion
+ (dired-subtree-beginning)
+ ,@forms
+ (while (dired-subtree-next-sibling)
+ ,@forms)))
+
+
+;;;; Interactive
+
+;;;###autoload
+(defun dired-subtree-narrow ()
+ "Narrow the buffer to this subtree."
+ (interactive)
+ (-when-let (ov (dired-subtree--get-ov))
+ (narrow-to-region (overlay-start ov)
+ (overlay-end ov))))
+
+;;; Navigation
+
+;; make the arguments actually do something
+;;;###autoload
+(defun dired-subtree-up (&optional arg)
+ "Jump up one directory."
+ (interactive "p")
+ (-when-let (ov (dired-subtree--get-ov))
+ (goto-char (overlay-start ov))
+ (dired-previous-line 1)))
+
+;;;###autoload
+(defun dired-subtree-down (&optional arg)
+ "Jump down one directory."
+ (interactive "p")
+ (-when-let* ((p (point))
+ (ov (car (--sort
+ (< (overlay-start it)
+ (overlay-start other))
+ (--remove
+ (< (overlay-start it) p)
+ (dired-subtree--get-all-ovs))))))
+ (goto-char (overlay-start ov))
+ (dired-move-to-filename)))
+
+;;;###autoload
+(defun dired-subtree-next-sibling (&optional arg)
+ "Go to the next sibling."
+ (interactive "p")
+ (let ((current-ov (dired-subtree--get-ov)))
+ (dired-next-line 1)
+ (let ((new-ov (dired-subtree--get-ov)))
+ (cond
+ ((not (dired-utils-is-file-p))
+ nil)
+ ((< (dired-subtree--get-depth current-ov)
+ (dired-subtree--get-depth new-ov))
+ (goto-char (overlay-end new-ov))
+ (dired-move-to-filename)
+ t)
+ ((> (dired-subtree--get-depth current-ov)
+ (dired-subtree--get-depth new-ov))
+ ;; add option to either go to top or stay at the end
+ (dired-previous-line 1)
+ nil)
+ (t t)))))
+
+;;;###autoload
+(defun dired-subtree-previous-sibling (&optional arg)
+ "Go to the previous sibling."
+ (interactive "p")
+ (let ((current-ov (dired-subtree--get-ov)))
+ (dired-previous-line 1)
+ (let ((new-ov (dired-subtree--get-ov)))
+ (cond
+ ;; this will need better handlign if we have inserted
+ ;; subdirectories
+ ((not (dired-utils-is-file-p))
+ nil)
+ ((< (dired-subtree--get-depth current-ov)
+ (dired-subtree--get-depth new-ov))
+ (goto-char (overlay-start new-ov))
+ (dired-previous-line 1)
+ t)
+ ((> (dired-subtree--get-depth current-ov)
+ (dired-subtree--get-depth new-ov))
+ ;; add option to either go to top or stay at the end
+ (dired-next-line 1)
+ nil)
+ (t t)))))
+
+;;;###autoload
+(defun dired-subtree-beginning ()
+ "Go to the first file in this subtree."
+ (interactive)
+ (let ((ov (dired-subtree--get-ov)))
+ (if (not ov)
+ ;; do something when not in subtree
+ t
+ (goto-char (overlay-start ov))
+ (dired-move-to-filename))))
+
+;;;###autoload
+(defun dired-subtree-end ()
+ "Go to the first file in this subtree."
+ (interactive)
+ (let ((ov (dired-subtree--get-ov)))
+ (if (not ov)
+ ;; do something when not in subtree
+ t
+ (goto-char (overlay-end ov))
+ (dired-previous-line 1))))
+
+;;; Marking
+
+;;;###autoload
+(defun dired-subtree-mark-subtree (&optional all)
+ "Mark all files in this subtree.
+
+With prefix argument mark all the files in subdirectories
+recursively."
+ (interactive "P")
+ (save-excursion
+ (if all
+ (let ((beg (save-excursion
+ (dired-subtree-beginning)
+ (point)))
+ (end (save-excursion
+ (dired-subtree-end)
+ (point))))
+ (dired-mark-files-in-region
+ (progn (goto-char beg) (line-beginning-position))
+ (progn (goto-char end) (line-end-position))))
+ (dired-subtree-beginning)
+ (save-excursion (dired-mark 1))
+ (while (dired-subtree-next-sibling)
+ (save-excursion (dired-mark 1))))))
+
+;;;###autoload
+(defun dired-subtree-unmark-subtree (&optional all)
+ "Unmark all files in this subtree.
+
+With prefix argument unmark all the files in subdirectories
+recursively."
+ (interactive)
+ (let ((dired-marker-char ? ))
+ (dired-subtree-mark-subtree all)))
+
+;;; Insertion/deletion
+;;;###autoload
+(defun dired-subtree-revert ()
+ "Revert the subtree.
+
+This means reinserting the content of this subtree and all its
+children."
+ (interactive)
+ (let ((inhibit-read-only t)
+ (file-name (dired-utils-get-filename)))
+ (-when-let* ((ov (dired-subtree--get-ov))
+ (ovs (dired-subtree--get-ovs-in)))
+ (dired-subtree-up)
+ (delete-region (overlay-start ov) (overlay-end ov))
+ (dired-subtree--after-readin ovs)
+ (when file-name
+ (dired-utils-goto-line file-name)))))
+
+(defun dired-subtree--readin (dir-name)
+ "Read in the directory.
+
+Return a string suitable for insertion in `dired' buffer."
+ (with-temp-buffer
+ (insert-directory dir-name dired-listing-switches nil t)
+ (delete-char -1)
+ (goto-char (point-min))
+ (delete-region
+ (progn (beginning-of-line) (point))
+ (progn (forward-line
+ (if (save-excursion
+ (forward-line 1)
+ (end-of-line)
+ (looking-back "\\."))
+ 3 1)) (point)))
+ (insert " ")
+ (while (= (forward-line) 0)
+ (insert " "))
+ (delete-char -2)
+ (buffer-string)))
+
+;;;###autoload
+(defun dired-subtree-insert ()
+ "Insert subtree under this directory."
+ (interactive)
+ (when (and (dired-subtree--dired-line-is-directory-or-link-p)
+ (not (dired-subtree--is-expanded-p)))
+ (let* ((dir-name (dired-get-filename nil))
+ (listing (dired-subtree--readin (file-name-as-directory dir-name)))
+ beg end)
+ (read-only-mode -1)
+ (move-end-of-line 1)
+ ;; this is pretty ugly, I'm sure it can be done better
+ (save-excursion
+ (insert listing)
+ (setq end (+ (point) 2)))
+ (newline)
+ (setq beg (point))
+ (let ((inhibit-read-only t))
+ (remove-text-properties (1- beg) beg '(dired-filename)))
+ (let* ((ov (make-overlay beg end))
+ (parent (dired-subtree--get-ov (1- beg)))
+ (depth (or (and parent (1+ (overlay-get parent 'dired-subtree-depth)))
+ 1))
+ (face (intern (format "dired-subtree-depth-%d-face" depth))))
+ (when dired-subtree-use-backgrounds
+ (overlay-put ov 'face face))
+ ;; refactor this to some function
+ (overlay-put ov 'line-prefix
+ (if (stringp dired-subtree-line-prefix)
+ (if (not dired-subtree-use-backgrounds)
+ (apply 'concat (-repeat depth dired-subtree-line-prefix))
+ (cond
+ ((eq nil dired-subtree-line-prefix-face)
+ (apply 'concat
+ (-repeat depth dired-subtree-line-prefix)))
+ ((eq 'subtree dired-subtree-line-prefix-face)
+ (concat
+ dired-subtree-line-prefix
+ (propertize
+ (apply 'concat
+ (-repeat (1- depth) dired-subtree-line-prefix))
+ 'face face)))
+ ((eq 'parents dired-subtree-line-prefix-face)
+ (concat
+ dired-subtree-line-prefix
+ (apply 'concat
+ (--map
+ (propertize dired-subtree-line-prefix
+ 'face
+ (intern (format "dired-subtree-depth-%d-face" it)))
+ (number-sequence 1 (1- depth))))))))
+ (funcall dired-subtree-line-prefix depth)))
+ (overlay-put ov 'dired-subtree-name dir-name)
+ (overlay-put ov 'dired-subtree-parent parent)
+ (overlay-put ov 'dired-subtree-depth depth)
+ (overlay-put ov 'evaporate t)
+ (push ov dired-subtree-overlays))
+ (goto-char beg)
+ (dired-move-to-filename)
+ (read-only-mode 1)
+ (when (bound-and-true-p dired-filter-mode) (dired-filter-mode 1))
+ (run-hooks 'dired-subtree-after-insert-hook))))
+
+;;;###autoload
+(defun dired-subtree-remove ()
+ "Remove subtree at point."
+ (interactive)
+ (-when-let* ((ov (dired-subtree--get-ov))
+ (ovs (dired-subtree--get-ovs-in
+ (overlay-start ov)
+ (overlay-end ov))))
+ (let ((inhibit-read-only t))
+ (dired-subtree-up)
+ (delete-region (overlay-start ov)
+ (overlay-end ov))
+ (dired-subtree--remove-overlays ovs)))
+ (run-hooks 'dired-subtree-after-remove-hook))
+
+;;;###autoload
+(defun dired-subtree-toggle ()
+ "Insert subtree at point or remove it if it was not present."
+ (interactive)
+ (if (dired-subtree--is-expanded-p)
+ (progn
+ (dired-next-line 1)
+ (dired-subtree-remove)
+ ;; #175 fixes the case of the first line in dired when the
+ ;; cursor jumps to the header in dired rather then to the
+ ;; first file in buffer
+ (when (bobp)
+ (dired-next-line 1)))
+ (save-excursion (dired-subtree-insert))))
+
+(defun dired-subtree--insert-recursive (depth max-depth)
+ "Insert full subtree at point."
+ (save-excursion
+ (let ((name (dired-get-filename nil t)))
+ (when (and name (file-directory-p name)
+ (<= depth (or max-depth depth))
+ (or (= 1 depth)
+ (not (string-match-p dired-subtree-ignored-regexp
+ (file-name-nondirectory name)))))
+ (if (dired-subtree--is-expanded-p)
+ (dired-next-line 1)
+ (dired-subtree-insert))
+ (dired-subtree-end)
+ (dired-subtree--insert-recursive (1+ depth) max-depth)
+ (while (dired-subtree-previous-sibling)
+ (dired-subtree--insert-recursive (1+ depth) max-depth))))))
+
+(defvar dired-subtree--cycle-previous nil
+ "Remember previous action for `dired-subtree-cycle'")
+
+;;;###autoload
+(defun dired-subtree-cycle (&optional max-depth)
+ "Org-mode like cycle visibility:
+
+1) Show subtree
+2) Show subtree recursively (if previous command was cycle)
+3) Remove subtree
+
+Numeric prefix will set max depth"
+ (interactive "P")
+ (save-excursion
+ (cond
+ ;; prefix - show subtrees up to max-depth
+ (max-depth
+ (when (dired-subtree--is-expanded-p)
+ (dired-next-line 1)
+ (dired-subtree-remove))
+ (dired-subtree--insert-recursive 1 (if (integerp max-depth) max-depth nil))
+ (setq dired-subtree--cycle-previous :full))
+ ;; if directory is not expanded, expand one level
+ ((not (dired-subtree--is-expanded-p))
+ (dired-subtree-insert)
+ (setq dired-subtree--cycle-previous :insert))
+ ;; hide if previous command was not cycle or tree was fully expanded
+ ((or (not (eq last-command 'dired-subtree-cycle))
+ (eq dired-subtree--cycle-previous :full))
+ (dired-next-line 1)
+ (dired-subtree-remove)
+ (setq dired-subtree--cycle-previous :remove))
+ (t
+ (dired-subtree--insert-recursive 1 dired-subtree-cycle-depth)
+ (setq dired-subtree--cycle-previous :full)))))
+
+(defun dired-subtree--filter-up (keep-dir kill-siblings)
+ (save-excursion
+ (let (ov)
+ (save-excursion
+ (while (dired-subtree-up))
+ (dired-next-line 1)
+ (dired-subtree-mark-subtree t))
+ (if keep-dir
+ (dired-subtree-unmark-subtree)
+ (dired-subtree--unmark))
+ (while (and (dired-subtree-up)
+ (> (dired-subtree--get-depth (dired-subtree--get-ov)) 0))
+ (if (not kill-siblings)
+ (dired-subtree--unmark)
+ (dired-subtree--unmark)
+ (let ((here (point)))
+ (dired-subtree-with-subtree
+ (when (and (dired-subtree--is-expanded-p)
+ (/= (point) here))
+ (dired-subtree--unmark)
+ (save-excursion
+ (dired-next-line 1)
+ (dired-subtree-unmark-subtree t)))))))
+ (dired-do-kill-lines)
+ (dired-subtree--cleanup-overlays))))
+
+;;;###autoload
+(defun dired-subtree-only-this-file (&optional arg)
+ "Remove all the siblings on the route from this file to the top-most directory.
+
+With ARG non-nil, do not remove expanded directories in parents."
+ (interactive "P")
+ (dired-subtree--filter-up nil arg))
+
+;;;###autoload
+(defun dired-subtree-only-this-directory (&optional arg)
+ "Remove all the siblings on the route from this directory to the top-most directory.
+
+With ARG non-nil, do not remove expanded directories in parents."
+ (interactive "P")
+ (dired-subtree--filter-up t arg))
+
+;;; filtering
+(defun dired-subtree--filter-update-bs (ov)
+ "Update the local filter list.
+
+This function assumes that `dired-filter-stack' is dynamically
+bound to relevant value."
+ (let* ((filt (dired-filter--describe-filters))
+ (before-str (if (equal filt "") nil (concat " Local filters: " filt "\n"))))
+ (overlay-put ov 'before-string before-str)))
+
+(defun dired-subtree--filter-subtree (ov)
+ "Run the filter for this subtree.
+
+It is only safe to call this from readin.
+
+This depends on `dired-filter' package."
+ (when (featurep 'dired-filter)
+ (let ((dired-filter-stack (overlay-get ov 'dired-subtree-filter)))
+ (save-restriction
+ (widen)
+ (dired-subtree-narrow)
+ (dired-filter--expunge)
+ (dired-subtree--filter-update-bs ov)))))
+
+;;;###autoload
+(defun dired-subtree-apply-filter ()
+ "Push a local filter for this subtree.
+
+This depends on `dired-filter' package.
+
+It works exactly the same as global dired filters, only
+restricted to a subtree. The global filter is also applied to
+the subtree. The filter action is read from `dired-filter-map'."
+ (interactive)
+ (when (featurep 'dired-filter)
+ (-when-let (ov (dired-subtree--get-ov))
+ (let ((dired-filter-stack (overlay-get ov 'dired-subtree-filter))
+ (glob (current-global-map))
+ (loc (current-local-map))
+ cmd)
+ (cl-flet ((dired-filter--update
+ ()
+ (save-restriction
+ (overlay-put ov 'dired-subtree-filter dired-filter-stack)
+ (widen)
+ (dired-subtree-revert)
+ (dired-subtree--filter-update-bs ov))))
+ (unwind-protect
+ (progn
+ (use-global-map dired-filter-map)
+ (use-local-map nil)
+ (setq cmd (key-binding (read-key-sequence "Choose filter action: "))))
+ (use-global-map glob)
+ (use-local-map loc))
+ (let ((p (point))
+ (beg (overlay-start ov))
+ (current-file (dired-utils-get-filename)))
+ (unwind-protect
+ (call-interactively cmd)
+ (unless (dired-utils-goto-line current-file)
+ (goto-char beg)
+ (forward-line)
+ (goto-char (min p (1- (overlay-end (dired-subtree--get-ov)))))
+ (dired-move-to-filename)))))))))
+
+
+;;; Here we redefine a couple of functions from dired.el to make them
+;;; subtree-aware
+
+;; If the point is in a subtree, we need to provide a proper
+;; directory, not the one that would come from `dired-subdir-alist'.
+(defun dired-current-directory (&optional localp)
+ "Return the name of the subdirectory to which this line belongs.
+This returns a string with trailing slash, like `default-directory'.
+Optional argument means return a file name relative to `default-directory'."
+ (let ((here (point))
+ (alist (or dired-subdir-alist
+ ;; probably because called in a non-dired buffer
+ (error "No subdir-alist in %s" (current-buffer))))
+ elt dir)
+ (while alist
+ (setq elt (car alist)
+ dir (car elt)
+ ;; use `<=' (not `<') as subdir line is part of subdir
+ alist (if (<= (dired-get-subdir-min elt) here)
+ nil ; found
+ (cdr alist))))
+ ;; dired-subdir: modify dir here if we are in a "subtree" view
+ (-when-let (parent (dired-subtree--get-ov))
+ (setq dir (concat (overlay-get parent 'dired-subtree-name) "/")))
+ ;; end
+ (if localp
+ (dired-make-relative dir default-directory)
+ dir)))
+
+;; Since the tree-inserted directory is not in the dired-subdir-alist,
+;; we need to guard against nil.
+(defun dired-get-subdir ()
+ ;;"Return the subdir name on this line, or nil if not on a headerline."
+ ;; Look up in the alist whether this is a headerline.
+ (save-excursion
+ (let ((cur-dir (dired-current-directory)))
+ (beginning-of-line) ; alist stores b-o-l positions
+ (and (zerop (- (point)
+ (or (dired-get-subdir-min
+ (assoc cur-dir
+ dired-subdir-alist))
+ 0))) ;; dired-subtree: return zero if current
+ ;; dir is not in `dired-subdir-alist'.
+ cur-dir))))
+
+(provide 'dired-subtree)
+
+;;; dired-subtree.el ends here
diff --git a/site-lisp/extensions-local/echo-keys.el b/site-lisp/extensions-local/echo-keys.el
new file mode 100644
index 0000000..5b2cb5f
--- /dev/null
+++ b/site-lisp/extensions-local/echo-keys.el
@@ -0,0 +1,126 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+(require 'cl-lib)
+
+;;; Code:
+(defcustom echo-keys-last-record nil
+ "Last command processed by 'echo-keys'."
+ :type 'string
+ :group 'echo-keys)
+
+(defcustom echo-keys-last-record-count 0
+ "Number of times the `echo-keys-last-record` command was repeated."
+ :type 'integer
+ :group 'echo-keys)
+
+(defcustom echo-key-window-width 40
+ "Default width of the *echo-keys* window."
+ :type 'integer
+ :group 'echo-keys)
+
+(defcustom echo-key-password-protection nil
+ "Temporarily disable echo key for password input."
+ :type 'boolean
+ :group 'echo-keys)
+
+(defcustom echo-key-coallesce-repeats t
+ "If 't', show [ times].
+ If 'nil', show n lines."
+ :type 'boolean
+ :group 'echo-keys)
+
+(defun echo-keys ()
+ (let ((deactivate-mark deactivate-mark)
+ (keys (this-command-keys)))
+ (when (and keys
+ (not (eq (current-buffer) (get-buffer "*echo-keys*")))
+ (not echo-key-password-protection))
+ (save-excursion
+ (with-current-buffer (get-buffer-create "*echo-keys*")
+ (goto-char (point-max))
+ (if (eql this-command 'self-insert-command)
+ (let ((desc (key-description keys)))
+ (if (= 1 (length desc))
+ (insert desc)
+ (insert " " desc " "))
+ (setf echo-keys-last-record this-command
+ echo-keys-last-record-count 1))
+ (if (and echo-key-coallesce-repeats
+ (eql echo-keys-last-record this-command))
+ (progn
+ (incf echo-keys-last-record-count)
+ ;; update the last line
+ (forward-line -1)
+ (if (= 2 echo-keys-last-record-count)
+ (progn
+ (end-of-line)
+ (insert (format " [%d times]" echo-keys-last-record-count)))
+ (save-match-data
+ (when (re-search-forward " \\[\\([0-9]+\\) times\\]" nil t)
+ (delete-region (match-beginning 1) (match-end 1))
+ (goto-char (match-beginning 1))
+ (insert (format "%d" echo-keys-last-record-count)))))
+ (forward-line 1))
+ (progn
+ (insert (if (eq 'self-insert-command echo-keys-last-record)
+ "\n"
+ "")
+ (format "%-12s %s\n"
+ (key-description keys)
+ this-command))
+ (setf echo-keys-last-record this-command
+ echo-keys-last-record-count 1))))
+ (dolist (window (window-list))
+ (when (eq (window-buffer window) (current-buffer))
+ (with-selected-window window
+ ;; We need to use both to get the effect.
+ (set-window-point window (point))
+ (end-of-buffer)))))))))
+
+(defun toggle-echo-keys ()
+ "Toggle displaying the *echo-key* buffer."
+ (interactive)
+ (if (member 'echo-keys (default-value 'pre-command-hook))
+ (let ((echo-buffer (get-buffer "*echo-keys*")))
+ (remove-hook 'pre-command-hook 'echo-keys)
+ (dolist (window (window-list))
+ (when (eq (window-buffer window) echo-buffer)
+ (delete-window window))))
+ (progn
+ (delete-other-windows)
+ (split-window nil (- (window-width) echo-key-window-width) t)
+ (other-window 1)
+ (switch-to-buffer (get-buffer-create "*echo-keys*"))
+ (unless (eq major-mode 'echo-keys-mode)
+ (echo-keys-mode))
+ (toggle-truncate-lines +1)
+ (set-window-dedicated-p (selected-window) t)
+ (other-window 1)
+ (add-hook 'pre-command-hook 'echo-keys))))
+
+(defadvice echo-key--read-passwd--disable (before read-passwd)
+ (message "echo-key--read-passwd--disable")
+ (setf echo-key-password-protection t))
+
+(defadvice echo-key--read-passwd--enable (after read-passwd)
+ (message "echo-key--read-passwd--enable")
+ (setf echo-key-password-protection nil))
+
+(defun echo-keys-clean ()
+ "Erase the `*echo-keys*' buffer."
+ (interactive)
+ (with-current-buffer "*echo-keys*"
+ (erase-buffer)))
+
+(defvar echo-keys-mode-map
+ (let ((ek-mode-map (make-sparse-keymap)))
+ (define-key ek-mode-map (kbd "C-c e e") #'toggle-echo-keys)
+ (define-key ek-mode-map (kbd "C-c e c") #'echo-keys-clean)
+ ek-mode-map))
+
+(define-derived-mode echo-keys-mode fundamental-mode "Echo-keys"
+ "Major mode for echo-keys.")
+
+(provide 'echo-keys)
+
+;;; echo-keys.el ends here.
diff --git a/site-lisp/extensions-local/evals.el b/site-lisp/extensions-local/evals.el
new file mode 100644
index 0000000..f4c8ce8
--- /dev/null
+++ b/site-lisp/extensions-local/evals.el
@@ -0,0 +1,21 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+
+;;; Code:
+(defun ld-eval-elisp-to-next-line ()
+ "Replace the preceding sexp with its value."
+ (interactive)
+ (let ((value (eval (elisp--preceding-sexp))))
+ (newline-and-indent)
+ (insert (format "%S" value))))
+
+(defun ld-eval-elisp-and-replace ()
+ "Replace the preceding sexp with its value."
+ (interactive)
+ (let ((value (eval (elisp--preceding-sexp))))
+ (backward-kill-sexp)
+ (insert (format "%S" value))))
+
+(provide 'evals)
+
+;;; evals.el ends here
diff --git a/site-lisp/extensions-local/force-indent.el b/site-lisp/extensions-local/force-indent.el
new file mode 100644
index 0000000..08461c2
--- /dev/null
+++ b/site-lisp/extensions-local/force-indent.el
@@ -0,0 +1,81 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+
+;;; Code:
+
+;; use variable 'tab-width' value as indent size
+
+(defun force-indent-line ()
+ (let (col)
+ (save-excursion
+ (back-to-indentation)
+ (setq col (+ (current-column) tab-width))
+ (indent-line-to col))
+ (when (< (current-column) col)
+ (back-to-indentation))))
+
+(defun indent-line ()
+ (interactive)
+ (let ((bt (save-excursion
+ (back-to-indentation)
+ (current-column))))
+ (cond
+ ((< (current-column) bt)
+ (back-to-indentation))
+ ((looking-at "\\s-*\n")
+ (let ((col (save-excursion
+ (forward-line -1)
+ (back-to-indentation)
+ (current-column))))
+ (if (< (current-column) col)
+ (indent-line-to col)
+ (force-indent-line))))
+ (t
+ (force-indent-line)))))
+
+(defun un-indent-line ()
+ (interactive)
+ (let (col)
+ (save-excursion
+ (back-to-indentation)
+ (setq col (- (current-column) tab-width))
+ (when (>= col 0)
+ (indent-line-to col)))))
+
+(defun indent-region (start stop)
+ (interactive "r")
+ (setq stop (copy-marker stop))
+ (goto-char start)
+ (while (< (point) stop)
+ (unless (and (bolp) (eolp))
+ (force-indent-line))
+ (forward-line 1)))
+
+(defun un-indent-region (start stop)
+ (interactive "r")
+ (setq stop (copy-marker stop))
+ (goto-char start)
+ (while (< (point) stop)
+ (unless (and (bolp) (eolp))
+ (un-indent-line))
+ (forward-line 1)))
+
+(defun ld-indent ()
+ (interactive)
+ (if (use-region-p)
+ (save-excursion
+ (indent-region (region-beginning) (region-end))
+ (setq deactivate-mark nil))
+ (indent-line)))
+
+(defun ld-un-indent ()
+ (interactive)
+ (if (use-region-p)
+ (save-excursion
+ (un-indent-region (region-beginning) (region-end))
+ (setq deactivate-mark nil))
+ (un-indent-line)))
+
+(provide 'force-indent)
+
+;;; force-indent.el ends here
\ No newline at end of file
diff --git a/site-lisp/extensions-local/frame-restore.el b/site-lisp/extensions-local/frame-restore.el
new file mode 100644
index 0000000..df69ddc
--- /dev/null
+++ b/site-lisp/extensions-local/frame-restore.el
@@ -0,0 +1,93 @@
+;;; frame-restore.el --- save/restore frame size&position at shutdown/startup
+
+;; Copyright (C) 2002 by Free Software Foundation, Inc.
+
+;; Author: Patrick Anderson
+;; Version: 1.3
+
+;; 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:
+
+;;; ChangeLog
+;; 1.3: simplified install (now just copy one line to .emacs, and eval the next)
+;; 1.3: added checks for running in terminal
+;; 1.3: added checks for running on non-w32
+;; 1.2: added font save/restore
+;; 1.1: added more descriptive, correct installation docs
+
+;;;installation:
+;;1. put this file in your load path and add to your .emacs file (as the last thing) (without the semicolon):
+;;(require 'frame-restore)
+;;2. now evaluate the next line (don't uncomment it) [by putting the cursor at the end and pressing C-xC-e]
+;;(progn (require 'desktop) (customize-set-variable 'desktop-enable t) (desktop-save "~/") (require 'frame-restore))
+;;3. now change your font using S-down-mouse-1, adjust your frame size, then shutdown/restart emacs to test.
+;;once installed, i never have problems, but before that, it seems possible to get into strange states. if that happens try:
+;;1. shutdown emacs
+;;2. delete .emacs.desktop
+;;3. restart
+;;4. follow normal install
+;;since the font is stored here, don't also store it through a customization of the 'default' face. you may customize that face, just make sure the "Font Family" attribute box is unchecked.
+
+;;;Code:
+(require 'cl-lib)
+
+ ;this must be global - as that is how desktop-globals-to-save works
+ ;(defvar final-frame-params '((frame-parameter (selected-frame) 'font) 50 50 150 50 nil)) ;font, left, top, width, height, maximized
+(defvar final-frame-params '("-adobe-courier-medium-r-normal--*-120-*-*-m-*-iso8859-1" 50 50 150 50 nil)) ;font, left, top, width, height, maximized
+
+(if window-system
+ (add-hook 'after-init-hook
+ '(lambda()
+ "this is executed as emacs is coming up - _after_ final-frame-params have been read from `.emacs.desktop'."
+ (when desktop-enable
+ (desktop-load-default)
+ (desktop-read)
+ ;;now size and position frame according to the values read from disk
+ (set-default-font (first final-frame-params)) ;do font first - as it will goof with the frame size
+ (set-frame-size (selected-frame) (fourth final-frame-params) (fifth final-frame-params))
+ (set-frame-position (selected-frame) (max (eval (second final-frame-params)) 0) (max (eval (third final-frame-params)) 0))
+ (if (sixth final-frame-params)
+ (if (eq window-system 'w32)
+ (w32-send-sys-command ?\xf030)
+ ;else, do X something
+ ))))))
+
+(if window-system
+ (add-hook 'desktop-save-hook
+ '(lambda()
+ (let ((maximized (listp (frame-parameter (selected-frame) 'left))))
+ "this hook sets the fram size/pos vars before `desktop.el' writes them out to disk"
+ (if (eq window-system 'w32)
+ (w32-send-sys-command ?\xf120) ;restore the frame (so we can save the 'restored' size/pos)
+ ;else, do X something
+ )
+ ;;prepend our vars to the save list so `desktop.el' will save them out to disk
+ (setq desktop-globals-to-save (cons 'final-frame-params
+ desktop-globals-to-save))
+
+ (setq final-frame-params
+ (list
+ (frame-parameter (selected-frame) 'font)
+ (frame-parameter (selected-frame) 'left) ;x
+ (frame-parameter (selected-frame) 'top) ;y
+ (frame-width) ;width
+ (frame-height) ;height
+ maximized))))) ;if this frame param is a list, we're probably maximized (not guaranteed)
+ )
+
+(provide 'frame-restore)
+;;; frame-restore.el ends here
diff --git a/site-lisp/extensions-local/goto-last-change.el b/site-lisp/extensions-local/goto-last-change.el
new file mode 100644
index 0000000..524219f
--- /dev/null
+++ b/site-lisp/extensions-local/goto-last-change.el
@@ -0,0 +1,141 @@
+;;; goto-last-change.el --- Move point through buffer-undo-list positions
+
+;; Copyright © 2003 Kevin Rodgers
+
+;; Author: Kevin Rodgers
+;; Created: 17 Jun 2003
+;; Version: $Revision: 1.2 $
+;; Keywords: convenience
+;; RCS: $Id: goto-last-change.el,v 1.2 2003/07/30 17:43:47 kevinr Exp kevinr $
+
+;; Contributors:
+;; Attila Lendvai (line distance and auto marks)
+
+;; This program is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License as
+;; published by the Free Software Foundation; either version 2 of
+;; the License, or (at your option) any later version.
+
+;; This program is distributed in the hope that it will be
+;; useful, but WITHOUT ANY WARRANTY; without even the implied
+;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+;; PURPOSE. See the GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public
+;; License along with this program; if not, write to the Free
+;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+;; MA 02111-1307 USA
+
+;;; Commentary:
+
+;; After installing goto-last-change.el in a `load-path' directory and
+;; compiling it with `M-x byte-compile-file', load it with
+;; (require 'goto-last-change)
+;; or autoload it with
+;; (autoload 'goto-last-change "goto-last-change"
+;; "Set point to the position of the last change." t)
+;;
+;; You may also want to bind a key to `M-x goto-last-change', e.g.
+;; (global-set-key "\C-x\C-\\" 'goto-last-change)
+
+;; goto-last-change.el was written in response to to the following:
+;;
+;; From: Dan Jacobson
+;; Newsgroups: gnu.emacs.bug
+;; Subject: function to go to spot of last change
+;; Date: Sun, 15 Jun 2003 00:15:08 +0000 (UTC)
+;; Sender: news
+;; Message-ID:
+;; NNTP-Posting-Host: monty-python.gnu.org
+;;
+;;
+;; Why of course, a function to get the user to the spot of last changes
+;; in the current buffer(s?), that's what emacs must lack.
+;;
+;; How many times have you found yourself mosying [<-not in spell
+;; checker!?] thru a file when you wonder, where the heck was I just
+;; editing? Well, the best you can do is hit undo, ^F, and undo again,
+;; to get back. Hence the "burning need" for the additional function,
+;; which you might name the-jacobson-memorial-function, due to its brilliance.
+;; --
+;; http://jidanni.org/ Taiwan(04)25854780
+
+;;; Code:
+(provide 'goto-last-change)
+
+(or (fboundp 'last) ; Emacs 20
+ (require 'cl)) ; Emacs 19
+
+(defvar goto-last-change-undo nil
+ "The `buffer-undo-list' entry of the previous \\[goto-last-change] command.")
+(make-variable-buffer-local 'goto-last-change-undo)
+
+;;;###autoload
+(defun goto-last-change (&optional mark-point minimal-line-distance)
+ "Set point to the position of the last change.
+Consecutive calls set point to the position of the previous change.
+With a prefix arg (optional arg MARK-POINT non-nil), set mark so \
+\\[exchange-point-and-mark]
+will return point to the current position."
+ (interactive "P")
+ ;; (unless (buffer-modified-p)
+ ;; (error "Buffer not modified"))
+ (when (eq buffer-undo-list t)
+ (error "No undo information in this buffer"))
+ (when mark-point
+ (push-mark))
+ (unless minimal-line-distance
+ (setq minimal-line-distance 10))
+ (let ((position nil)
+ (undo-list (if (and (eq this-command last-command)
+ goto-last-change-undo)
+ (cdr (memq goto-last-change-undo buffer-undo-list))
+ buffer-undo-list))
+ undo)
+ (while (and undo-list
+ (or (not position)
+ (eql position (point))
+ (and minimal-line-distance
+ ;; The first invocation always goes to the last change, subsequent ones skip
+ ;; changes closer to (point) then minimal-line-distance.
+ (memq last-command '(goto-last-change
+ goto-last-change-with-auto-marks))
+ (< (count-lines (min position (point-max)) (point))
+ minimal-line-distance))))
+ (setq undo (car undo-list))
+ (cond ((and (consp undo) (integerp (car undo)) (integerp (cdr undo)))
+ ;; (BEG . END)
+ (setq position (cdr undo)))
+ ((and (consp undo) (stringp (car undo))) ; (TEXT . POSITION)
+ (setq position (abs (cdr undo))))
+ ((and (consp undo) (eq (car undo) t))) ; (t HIGH . LOW)
+ ((and (consp undo) (null (car undo)))
+ ;; (nil PROPERTY VALUE BEG . END)
+ (setq position (cdr (last undo))))
+ ((and (consp undo) (markerp (car undo)))) ; (MARKER . DISTANCE)
+ ((integerp undo)) ; POSITION
+ ((null undo)) ; nil
+ (t (error "Invalid undo entry: %s" undo)))
+ (setq undo-list (cdr undo-list)))
+ (cond (position
+ (setq goto-last-change-undo undo)
+ (goto-char (min position (point-max))))
+ ((and (eq this-command last-command)
+ goto-last-change-undo)
+ (setq goto-last-change-undo nil)
+ (error "No further undo information"))
+ (t
+ (setq goto-last-change-undo nil)
+ (error "Buffer not modified")))))
+
+(defun goto-last-change-with-auto-marks (&optional minimal-line-distance)
+ "Calls goto-last-change and sets the mark at only the first invocations
+in a sequence of invocations."
+ (interactive "P")
+ (goto-last-change (not (or (eq last-command 'goto-last-change-with-auto-marks)
+ (eq last-command t)))
+ minimal-line-distance))
+
+;; (global-set-key "\C-x\C-\\" 'goto-last-change)
+
+;;; goto-last-change.el ends here
diff --git a/site-lisp/extensions-local/goto-line-preview.el b/site-lisp/extensions-local/goto-line-preview.el
new file mode 100644
index 0000000..16d5575
--- /dev/null
+++ b/site-lisp/extensions-local/goto-line-preview.el
@@ -0,0 +1,124 @@
+;;; goto-line-preview.el --- Preview line when executing `goto-line` command -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2019-2023 Shen, Jen-Chieh
+;; Created date 2019-03-01 14:53:00
+
+;; Author: Shen, Jen-Chieh
+;; URL: https://github.com/emacs-vs/goto-line-preview
+;; Version: 0.1.1
+;; Package-Requires: ((emacs "25"))
+;; Keywords: convenience line navigation
+
+;; 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 .
+
+;;; Commentary:
+;;
+;; Preview line when executing `goto-line` command.
+;;
+
+;;; Code:
+
+(defgroup goto-line-preview nil
+ "Preview line when executing `goto-line` command."
+ :prefix "goto-line-preview-"
+ :group 'convenience
+ :group 'tools
+ :link '(url-link :tag "Repository" "https://github.com/emacs-vs/goto-line-preview"))
+
+(defcustom goto-line-preview-before-hook nil
+ "Hooks run before `goto-line-preview' is run."
+ :group 'goto-line-preview
+ :type 'hook)
+
+(defcustom goto-line-preview-after-hook nil
+ "Hooks run after `goto-line-preview' is run."
+ :group 'goto-line-preview
+ :type 'hook)
+
+(defvar goto-line-preview--prev-window nil
+ "Record down the previous window before we do preivew display.")
+
+(defvar goto-line-preview--prev-line-num nil
+ "Record down the previous line number before we do preivew display.")
+
+(defvar goto-line-preview--relative-p nil
+ "Flag to see if this command relative.")
+
+(defun goto-line-preview--do (line-num)
+ "Do goto LINE-NUM."
+ (save-selected-window
+ (select-window goto-line-preview--prev-window)
+ (goto-char (point-min))
+ (forward-line (1- line-num))))
+
+(defun goto-line-preview--do-preview ()
+ "Do the goto line preview action."
+ (save-selected-window
+ (when goto-line-preview--prev-window
+ (let ((line-num-str (thing-at-point 'line)))
+ (select-window goto-line-preview--prev-window)
+ (if line-num-str
+ (let ((line-num (string-to-number line-num-str)))
+ (when goto-line-preview--relative-p
+ (setq line-num (+ goto-line-preview--prev-line-num line-num)))
+ (unless (zerop line-num) (goto-line-preview--do line-num)))
+ (goto-line-preview--do goto-line-preview--prev-line-num))))))
+
+;;;###autoload
+(defun goto-line-preview ()
+ "Preview goto line."
+ (interactive)
+ (let ((goto-line-preview--prev-window (selected-window))
+ (window-point (window-point))
+ (goto-line-preview--prev-line-num (line-number-at-pos))
+ jumped)
+ (run-hooks 'goto-line-preview-before-hook)
+ (unwind-protect
+ (setq jumped (read-number
+ (let ((lines (line-number-at-pos (point-max))))
+ (format (if goto-line-preview--relative-p
+ "[%d] Goto line relative: (%d to %d) "
+ "[%d] Goto line: (%d to %d) ")
+ goto-line-preview--prev-line-num
+ (max 0 (min 1 lines))
+ lines))))
+ (if jumped
+ (with-current-buffer (window-buffer goto-line-preview--prev-window)
+ (unless (region-active-p) (push-mark window-point)))
+ (set-window-point goto-line-preview--prev-window window-point))
+ (run-hooks 'goto-line-preview-after-hook))))
+
+;;;###autoload
+(defun goto-line-preview-relative ()
+ "Preview goto line relative."
+ (interactive)
+ (let ((goto-line-preview--relative-p t))
+ (goto-line-preview)))
+
+;;;###autoload
+(define-obsolete-function-alias 'goto-line-preview-goto-line 'goto-line-preview "0.1.1")
+
+(defun goto-line-preview--minibuffer-setup ()
+ "Locally set up preview hooks for this minibuffer command."
+ (when (memq this-command '(goto-line-preview
+ goto-line-preview-goto-line
+ goto-line-preview-relative))
+ (add-hook 'post-command-hook #'goto-line-preview--do-preview nil t)))
+
+(add-hook 'minibuffer-setup-hook 'goto-line-preview--minibuffer-setup)
+
+(provide 'goto-line-preview)
+;;; goto-line-preview.el ends here
diff --git a/site-lisp/extensions-local/highlight-indentation.el b/site-lisp/extensions-local/highlight-indentation.el
new file mode 100644
index 0000000..bfe93b6
--- /dev/null
+++ b/site-lisp/extensions-local/highlight-indentation.el
@@ -0,0 +1,312 @@
+;;; highlight-indentation.el --- Minor modes for highlighting indentation
+;; Author: Anton Johansson - http://antonj.se
+;; Created: Dec 15 23:42:04 2010
+;; Version: 0.7.0
+;; URL: https://github.com/antonj/Highlight-Indentation-for-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:
+;; Customize `highlight-indentation-face', and
+;; `highlight-indentation-current-column-face' to suit your theme.
+
+;;; Code:
+
+(defgroup highlight-indentation nil
+ "Highlight Indentation"
+ :prefix "highlight-indentation-"
+ :group 'basic-faces)
+
+(defface highlight-indentation-face
+ ;; Fringe has non intrusive color in most color-themes
+ '((t :inherit fringe))
+ "Basic face for highlighting indentation guides."
+ :group 'highlight-indentation)
+
+(defcustom highlight-indentation-offset
+ (if (and (boundp 'standard-indent) standard-indent) standard-indent 2)
+ "Default indentation offset, used if no other can be found from
+ major mode. This value is always used by
+ `highlight-indentation-mode' if set buffer local. Set buffer
+ local with `highlight-indentation-set-offset'"
+ :type 'integer
+ :group 'highlight-indentation)
+
+(defcustom highlight-indentation-blank-lines nil
+ "Show indentation guides on blank lines. Experimental.
+Known issues:
+- Doesn't work well with completion popups that use overlays
+- Overlays on blank lines sometimes aren't cleaned up or updated perfectly
+ Can be refreshed by scrolling
+- Not yet implemented for highlight-indentation-current-column-mode
+- May not work perfectly near the bottom of the screen
+- Point appears after indent guides on blank lines"
+ :type 'boolean
+ :group 'highlight-indentation)
+
+(defvar highlight-indentation-overlay-priority 1)
+(defvar highlight-indentation-current-column-overlay-priority 2)
+
+(defconst highlight-indentation-hooks
+ '((after-change-functions (lambda (start end length)
+ (highlight-indentation-redraw-region
+ start end
+ 'highlight-indentation-overlay
+ 'highlight-indentation-put-overlays-region))
+ t t)
+ (window-scroll-functions (lambda (win start)
+ (highlight-indentation-redraw-window
+ win
+ 'highlight-indentation-overlay
+ 'highlight-indentation-put-overlays-region
+ start))
+ nil t)))
+
+(defun highlight-indentation-get-buffer-windows (&optional all-frames)
+ "Return a list of windows displaying the current buffer."
+ (get-buffer-window-list (current-buffer) 'no-minibuf all-frames))
+
+(defun highlight-indentation-delete-overlays-buffer (overlay)
+ "Delete all overlays in the current buffer."
+ (save-restriction
+ (widen)
+ (highlight-indentation-delete-overlays-region (point-min) (point-max) overlay)))
+
+(defun highlight-indentation-delete-overlays-region (start end overlay)
+ "Delete overlays between START and END."
+ (mapc #'(lambda (o)
+ (if (overlay-get o overlay) (delete-overlay o)))
+ (overlays-in start end)))
+
+(defun highlight-indentation-redraw-window (win overlay func &optional start)
+ "Redraw win starting from START."
+ (highlight-indentation-redraw-region (or start (window-start win)) (window-end win t) overlay func))
+
+(defun highlight-indentation-redraw-region (start end overlay func)
+ "Erase and read overlays between START and END."
+ (save-match-data
+ (save-excursion
+ (let ((inhibit-point-motion-hooks t)
+ (start (save-excursion (goto-char start) (beginning-of-line) (point)))
+
+ (end (save-excursion (goto-char end) (line-beginning-position 2))))
+ (highlight-indentation-delete-overlays-region start end overlay)
+ (funcall func start end overlay)))))
+
+(defun highlight-indentation-redraw-all-windows (overlay func &optional all-frames)
+ "Redraw the all windows showing the current buffer."
+ (dolist (win (highlight-indentation-get-buffer-windows all-frames))
+ (highlight-indentation-redraw-window win overlay func)))
+
+(defun highlight-indentation-put-overlays-region (start end overlay)
+ "Place overlays between START and END."
+ (goto-char end)
+ (let (o ;; overlay
+ (last-indent 0)
+ (last-char 0)
+ (pos (point))
+ (loop t))
+ (while (and loop
+ (>= pos start))
+ (save-excursion
+ (beginning-of-line)
+ (let ((c 0)
+ (cur-column (current-column)))
+ (while (and (setq c (char-after))
+ (integerp c)
+ (not (= 10 c)) ;; newline
+ (= 32 c)) ;; space
+ (when (= 0 (% cur-column highlight-indentation-offset))
+ (let ((p (point)))
+ (setq o (make-overlay p (+ p 1))))
+ (overlay-put o overlay t)
+ (overlay-put o 'priority highlight-indentation-overlay-priority)
+ (overlay-put o 'face 'highlight-indentation-face))
+ (forward-char)
+ (setq cur-column (current-column)))
+ (when (and highlight-indentation-blank-lines
+ (integerp c)
+ (or (= 10 c)
+ (= 13 c)))
+ (when (< cur-column last-indent)
+ (let ((column cur-column)
+ (s nil)
+ (show t)
+ num-spaces)
+ (while (< column last-indent)
+ (if (>= 0
+ (setq num-spaces
+ (%
+ (- last-indent column)
+ highlight-indentation-offset)))
+ (progn
+ (setq num-spaces (1- highlight-indentation-offset))
+ (setq show t))
+ (setq show nil))
+ (setq s (cons (concat
+ (if show
+ (propertize " "
+ 'face
+ 'highlight-indentation-face)
+ "")
+ (make-string num-spaces 32))
+ s))
+ (setq column (+ column num-spaces (if show 1 0))))
+ (setq s (apply 'concat (reverse s)))
+ (let ((p (point)))
+ (setq o (make-overlay p p)))
+ (overlay-put o overlay t)
+ (overlay-put o 'priority highlight-indentation-overlay-priority)
+ (overlay-put o 'after-string s))
+ (setq cur-column last-indent)))
+ (setq last-indent (* highlight-indentation-offset
+ (ceiling (/ (float cur-column)
+ highlight-indentation-offset))))))
+ (when (= pos start)
+ (setq loop nil))
+ (forward-line -1) ;; previous line
+ (setq pos (point)))))
+
+(defun highlight-indentation-guess-offset ()
+ "Get indentation offset of current buffer."
+ (cond ((and (eq major-mode 'python-mode) (boundp 'python-indent))
+ python-indent)
+ ((and (eq major-mode 'python-mode) (boundp 'py-indent-offset))
+ py-indent-offset)
+ ((and (eq major-mode 'python-mode) (boundp 'python-indent-offset))
+ python-indent-offset)
+ ((and (eq major-mode 'ruby-mode) (boundp 'ruby-indent-level))
+ ruby-indent-level)
+ ((and (eq major-mode 'scala-mode) (boundp 'scala-indent:step))
+ scala-indent:step)
+ ((and (eq major-mode 'scala-mode) (boundp 'scala-mode-indent:step))
+ scala-mode-indent:step)
+ ((and (or (eq major-mode 'scss-mode) (eq major-mode 'css-mode)) (boundp 'css-indent-offset))
+ css-indent-offset)
+ ((and (eq major-mode 'nxml-mode) (boundp 'nxml-child-indent))
+ nxml-child-indent)
+ ((and (eq major-mode 'coffee-mode) (boundp 'coffee-tab-width))
+ coffee-tab-width)
+ ((and (eq major-mode 'js-mode) (boundp 'js-indent-level))
+ js-indent-level)
+ ((and (eq major-mode 'js2-mode) (boundp 'js2-basic-offset))
+ js2-basic-offset)
+ ((and (fboundp 'derived-mode-class) (eq (derived-mode-class major-mode) 'sws-mode) (boundp 'sws-tab-width))
+ sws-tab-width)
+ ((and (eq major-mode 'web-mode) (boundp 'web-mode-markup-indent-offset))
+ web-mode-markup-indent-offset) ; other similar vars: web-mode-{css-indent,scripts}-offset
+ ((and (eq major-mode 'web-mode) (boundp 'web-mode-html-offset)) ; old var
+ web-mode-html-offset)
+ ((and (local-variable-p 'c-basic-offset) (boundp 'c-basic-offset))
+ c-basic-offset)
+ ((and (eq major-mode 'yaml-mode) (boundp 'yaml-indent-offset))
+ yaml-indent-offset)
+ ((and (eq major-mode 'elixir-mode) (boundp 'elixir-smie-indent-basic))
+ elixir-smie-indent-basic)
+ (t
+ (default-value 'highlight-indentation-offset))))
+
+;;;###autoload
+(define-minor-mode highlight-indentation-mode
+ "Highlight indentation minor mode highlights indentation based on spaces"
+ :lighter " ||"
+ (when (not highlight-indentation-mode) ;; OFF
+ (highlight-indentation-delete-overlays-buffer 'highlight-indentation-overlay)
+ (dolist (hook highlight-indentation-hooks)
+ (remove-hook (car hook) (nth 1 hook) (nth 3 hook))))
+
+ (when highlight-indentation-mode ;; ON
+ (when (not (local-variable-p 'highlight-indentation-offset))
+ (set (make-local-variable 'highlight-indentation-offset)
+ (highlight-indentation-guess-offset)))
+
+ ;; Setup hooks
+ (dolist (hook highlight-indentation-hooks)
+ (apply 'add-hook hook))
+ (highlight-indentation-redraw-all-windows 'highlight-indentation-overlay
+ 'highlight-indentation-put-overlays-region)))
+
+;;;###autoload
+(defun highlight-indentation-set-offset (offset)
+ "Set indentation offset locally in buffer, will prevent
+highlight-indentation from trying to guess indentation offset
+from major mode"
+ (interactive
+ (if (and current-prefix-arg (not (consp current-prefix-arg)))
+ (list (prefix-numeric-value current-prefix-arg))
+ (list (read-number "Indentation offset: "))))
+ (set (make-local-variable 'highlight-indentation-offset) offset)
+ (when highlight-indentation-mode
+ (highlight-indentation-mode)))
+
+;;; This minor mode will highlight the indentation of the current line
+;;; as a vertical bar (grey background color) aligned with the column of the
+;;; first character of the current line.
+(defface highlight-indentation-current-column-face
+ ;; Fringe has non intrusive color in most color-themes
+ '((t (:background "black")))
+ "Basic face for highlighting indentation guides."
+ :group 'highlight-indentation)
+
+(defconst highlight-indentation-current-column-hooks
+ '((post-command-hook (lambda ()
+ (highlight-indentation-redraw-all-windows 'highlight-indentation-current-column-overlay
+ 'highlight-indentation-current-column-put-overlays-region)) nil t)))
+
+(defun highlight-indentation-current-column-put-overlays-region (start end overlay)
+ "Place overlays between START and END."
+ (let (o ;; overlay
+ (last-indent 0)
+ (indent (save-excursion (back-to-indentation) (current-column)))
+ (pos start))
+ (goto-char start)
+ ;; (message "doing it %d" indent)
+ (while (< pos end)
+ (beginning-of-line)
+ (while (and (integerp (char-after))
+ (not (= 10 (char-after))) ;; newline
+ (= 32 (char-after))) ;; space
+ (when (= (current-column) indent)
+ (setq pos (point)
+ last-indent pos
+ o (make-overlay pos (+ pos 1)))
+ (overlay-put o overlay t)
+ (overlay-put o 'priority highlight-indentation-current-column-overlay-priority)
+ (overlay-put o 'face 'highlight-indentation-current-column-face))
+ (forward-char))
+ (forward-line) ;; Next line
+ (setq pos (point)))))
+
+;;;###autoload
+(define-minor-mode highlight-indentation-current-column-mode
+ "Highlight Indentation minor mode displays a vertical bar
+corresponding to the indentation of the current line"
+ :lighter " |"
+
+ (when (not highlight-indentation-current-column-mode) ;; OFF
+ (highlight-indentation-delete-overlays-buffer 'highlight-indentation-current-column-overlay)
+ (dolist (hook highlight-indentation-current-column-hooks)
+ (remove-hook (car hook) (nth 1 hook) (nth 3 hook))))
+
+ (when highlight-indentation-current-column-mode ;; ON
+ (when (not (local-variable-p 'highlight-indentation-offset))
+ (set (make-local-variable 'highlight-indentation-offset)
+ (highlight-indentation-guess-offset)))
+
+ ;; Setup hooks
+ (dolist (hook highlight-indentation-current-column-hooks)
+ (apply 'add-hook hook))
+ (highlight-indentation-redraw-all-windows 'highlight-indentation-current-column-overlay
+ 'highlight-indentation-current-column-put-overlays-region)))
+
+(provide 'highlight-indentation)
+
+;;; highlight-indentation.el ends here
diff --git a/site-lisp/extensions-local/highlight-parentheses.el b/site-lisp/extensions-local/highlight-parentheses.el
new file mode 100644
index 0000000..ba30756
--- /dev/null
+++ b/site-lisp/extensions-local/highlight-parentheses.el
@@ -0,0 +1,157 @@
+;;; highlight-parentheses.el --- highlight surrounding parentheses
+;;
+;; Copyright (C) 2007 Nikolaj Schumacher
+;;
+;; Author: Nikolaj Schumacher
+;; Version: 1.0
+;; Keywords: faces, matching
+;; URL: http://nschum.de/src/emacs/highlight-parentheses/
+;; Compatibility: GNU Emacs 22.x
+;;
+;; 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.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+;;
+;;; Commentary:
+;;
+;; Add the following to your .emacs file:
+;; (require 'highlight-parentheses)
+;;
+;; Enable `highlight-symbol-mode'.
+;;
+;;; Changes Log:
+;;
+;; 2007-07-30 (1.0)
+;; Added background highlighting and faces.
+;;
+;; 2007-05-15 (0.9.1)
+;; Support for defcustom. Changed from vector to list.
+;;
+;; 2007-04-26 (0.9)
+;; Initial Release.
+;;
+;;; Code:
+
+(eval-when-compile (require 'cl))
+
+(defgroup highlight-parentheses nil
+ "Highlight surrounding parentheses"
+ :group 'faces
+ :group 'matching)
+
+(defvar hl-paren-overlays nil
+ "This buffers currently active overlays.")
+(make-variable-buffer-local 'hl-paren-overlays)
+
+(defcustom hl-paren-colors
+ '("firebrick1" "IndianRed4" "IndianRed")
+ "*List of colors for the highlighted parentheses.
+The list starts with the the inside parentheses and moves outwards."
+ :type '(repeat color)
+ :group 'highlight-parentheses)
+
+(defcustom hl-paren-background-colors nil
+ "*List of colors for the background highlighted parentheses.
+The list starts with the the inside parentheses and moves outwards."
+ :type '(repeat color)
+ :group 'highlight-parentheses)
+
+(defface hl-paren-face nil
+ "*Face used for highlighting parentheses.
+Color attributes might be overriden by `hl-paren-colors' and
+`hl-paren-background-colors'."
+ :group 'highlight-parentheses)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defvar hl-paren-last-point 0
+ "The last point for which parentheses were highlighted.
+This is used to prevent analyzing the same context over and over.")
+(make-variable-buffer-local 'hl-paren-last-point)
+
+(defun hl-paren-highlight ()
+ "Highlight the parentheses around point."
+ (unless (= (point) hl-paren-last-point)
+ (save-excursion
+ (let ((pos (point))
+ (match-pos (point))
+ (level -1)
+ (max (1- (length hl-paren-overlays))))
+ (while (and match-pos (< level max))
+ (setq match-pos
+ (when (setq pos (cadr (syntax-ppss pos)))
+ (ignore-errors (scan-sexps pos 1))))
+ (when match-pos
+ (hl-paren-put-overlay (cl-incf level) pos 'hl-paren-face)
+ (hl-paren-put-overlay (cl-incf level) (1- match-pos) 'hl-paren-face)))
+ (while (< level max)
+ (hl-paren-put-overlay (cl-incf level) nil nil))))
+ (setq hl-paren-last-point (point))))
+
+(defun hl-paren-put-overlay (n pos face)
+ "Move or create the N'th overlay so its shown at POS."
+ (let ((ov (elt hl-paren-overlays n)) end)
+ (if (null pos)
+ (when ov
+ (delete-overlay ov)
+ (aset hl-paren-overlays n nil))
+ (if (atom pos)
+ (setq end (1+ pos))
+ (setq end (cdr pos))
+ (setq pos (car pos)))
+ (if ov
+ (move-overlay ov pos end)
+ (let ((face-attributes (face-attr-construct face))
+ (color-value (nth (/ n 2) hl-paren-colors))
+ (background-value (nth (/ n 2) hl-paren-background-colors)))
+ (when color-value
+ (let ((attribute (memq :foreground face-attributes)))
+ (if attribute
+ (setcar (cdr attribute) color-value)
+ (push color-value face-attributes)
+ (push :foreground face-attributes))))
+ (when background-value
+ (let ((attribute (memq :background face-attributes)))
+ (if attribute
+ (setcar (cdr attribute) background-value)
+ (push background-value face-attributes)
+ (push :background face-attributes))))
+ (setq ov (make-overlay pos end))
+ (aset hl-paren-overlays n ov)
+ (overlay-put ov 'face face-attributes))))))
+
+;;;###autoload
+(define-minor-mode highlight-parentheses-mode
+ "Minor mode to highlight the surrounding parentheses."
+ :init-value nil
+ :lighter " hl-p"
+ :keymap nil
+ (if highlight-parentheses-mode
+ (progn
+ (setq hl-paren-overlays
+ (make-vector (* 2 (max (length hl-paren-colors)
+ (length hl-paren-background-colors))) nil))
+ (add-hook 'post-command-hook 'hl-paren-highlight nil t))
+ (let (ov)
+ (dotimes (i (length hl-paren-overlays))
+ (when (setq ov (elt hl-paren-overlays i))
+ (delete-overlay ov))))
+ (kill-local-variable 'hl-paren-overlays)
+ (kill-local-variable 'hl-paren-point)
+ (remove-hook 'post-command-hook 'hl-paren-highlight t)))
+
+(provide 'highlight-parentheses)
+
+;;; highlight-parentheses.el ends here
diff --git a/site-lisp/extensions-local/lazy-load.el b/site-lisp/extensions-local/lazy-load.el
new file mode 100644
index 0000000..555e6a5
--- /dev/null
+++ b/site-lisp/extensions-local/lazy-load.el
@@ -0,0 +1,49 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+
+;;; Code:
+(defun lazy-load-global-keys (key-alist filename &optional key-prefix)
+ (lazy-load-set-keys key-alist nil key-prefix)
+ (dolist (element key-alist)
+ (setq fun (cdr element))
+ (autoload fun filename nil t)))
+
+(defun lazy-load-local-keys (key-alist keymap filename &optional key-prefix)
+ (lazy-load-set-keys key-alist keymap key-prefix)
+ (dolist (element key-alist)
+ (setq fun (cdr element))
+ (autoload fun filename nil t)))
+
+(defun lazy-load-set-keys (key-alist &optional keymap key-prefix)
+ "This function is to little type when define key binding.
+`KEYMAP' is a add keymap for some binding, default is `current-global-map'.
+`KEY-ALIST' is a alist contain main-key and command.
+`KEY-PREFIX' is a add prefix for some binding, default is nil."
+ (let (key def)
+ (or keymap (setq keymap (current-global-map)))
+ (if key-prefix
+ (setq key-prefix (concat key-prefix " "))
+ (setq key-prefix ""))
+ (dolist (element key-alist)
+ (setq key (car element))
+ (setq def (cdr element))
+ (cond ((stringp key) (setq key (read-kbd-macro (concat key-prefix key))))
+ ((vectorp key) nil)
+ (t (signal 'wrong-type-argument (list 'array key))))
+ (define-key keymap key def))))
+
+(defun lazy-load-unset-keys (key-list &optional keymap)
+ "This function is to little type when unset key binding.
+`KEYMAP' is add keymap for some binding, default is `current-global-map'
+`KEY-LIST' is list contain key."
+ (let (key)
+ (or keymap (setq keymap (current-global-map)))
+ (dolist (key key-list)
+ (cond ((stringp key) (setq key (read-kbd-macro (concat key))))
+ ((vectorp key) nil)
+ (t (signal 'wrong-type-argument (list 'array key))))
+ (define-key keymap key nil))))
+
+(provide 'lazy-load)
+
+;;; lazy-load.el ends here
diff --git a/site-lisp/extensions-local/ld-buffer-operations.el b/site-lisp/extensions-local/ld-buffer-operations.el
new file mode 100644
index 0000000..426084e
--- /dev/null
+++ b/site-lisp/extensions-local/ld-buffer-operations.el
@@ -0,0 +1,72 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+
+;;; Code:
+(defun ld-indent-buffer ()
+ "Automatic format current buffer."
+ (interactive)
+ (cond
+ ((derived-mode-p 'python-mode)
+ (message "Don't indent python buffer. It will mess up the code syntax."))
+ ((derived-mode-p 'yaml-mode)
+ (message "Don't indent yaml buffer. It will mess up the code syntax."))
+ (t
+ (save-excursion
+ (indent-region (point-min) (point-max) nil)
+ (delete-trailing-whitespace)
+ (untabify (point-min) (point-max))))))
+
+; ---
+
+(defun ld-rename-file-and-buffer ()
+ "Rename current buffer and if the buffer is visiting a file, rename it too."
+ (interactive)
+ (let ((filename (buffer-file-name)))
+ (if (not (and filename (file-exists-p filename)))
+ (rename-buffer (read-from-minibuffer "New name: " (buffer-name)))
+ (let* ((new-name (read-file-name "New name: " (file-name-directory filename)))
+ (containing-dir (file-name-directory new-name)))
+ (make-directory containing-dir t)
+ (cond
+ ((vc-backend filename) (vc-rename-file filename new-name))
+ (t
+ (rename-file filename new-name t)
+ (set-visited-file-name new-name t t)))))))
+
+; ---
+
+(defun ld-delete-file-and-buffer ()
+ "Kill the current buffer and deletes the file it is visiting."
+ (interactive)
+ (let ((filename (buffer-file-name)))
+ (when filename
+ (if (vc-backend filename)
+ (vc-delete-file filename)
+ (when (y-or-n-p (format "Sure to delete %s? " filename))
+ (delete-file filename delete-by-moving-to-trash)
+ (message "Deleted file %s" filename)
+ (kill-buffer))))))
+
+; ---
+
+(defun ld-revert-buffer-no-confirm ()
+ "Revert buffer without confirmation."
+ (interactive)
+ (revert-buffer :ignore-auto :noconfirm))
+
+; ---
+
+(defun ld-unmark-all-buffers ()
+ "Unmark all have marked buffers."
+ (interactive)
+ (let ((current-element (current-buffer)))
+ (save-excursion
+ (dolist (element (buffer-list))
+ (set-buffer element)
+ (deactivate-mark)))
+ (switch-to-buffer current-element)
+ (deactivate-mark)))
+
+(provide 'ld-buffer-operations)
+
+;;; ld-buffer-operations.el ends here
diff --git a/site-lisp/extensions-local/ld-delete-block.el b/site-lisp/extensions-local/ld-delete-block.el
new file mode 100644
index 0000000..494a9ed
--- /dev/null
+++ b/site-lisp/extensions-local/ld-delete-block.el
@@ -0,0 +1,38 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+(require 'subword)
+
+;;; Code:
+(defun ld-delete-one-block-forward ()
+ (interactive)
+ (if (eobp)
+ (message "End of buffer")
+ (let* ((syntax-move-point
+ (save-excursion
+ (skip-syntax-forward (string (char-syntax (char-after))))
+ (point)
+ ))
+ (subword-move-point
+ (save-excursion
+ (subword-forward)
+ (point))))
+ (kill-region (point) (min syntax-move-point subword-move-point)))))
+
+(defun ld-delete-one-block-backward ()
+ (interactive)
+ (if (bobp)
+ (message "Beginning of buffer")
+ (let* ((syntax-move-point
+ (save-excursion
+ (skip-syntax-backward (string (char-syntax (char-before))))
+ (point)
+ ))
+ (subword-move-point
+ (save-excursion
+ (subword-backward)
+ (point))))
+ (kill-region (point) (max syntax-move-point subword-move-point)))))
+
+(provide 'ld-delete-block)
+
+;;; ld-delete-block.el ends here
\ No newline at end of file
diff --git a/site-lisp/extensions-local/ld-file-operations.el b/site-lisp/extensions-local/ld-file-operations.el
new file mode 100644
index 0000000..0b08aad
--- /dev/null
+++ b/site-lisp/extensions-local/ld-file-operations.el
@@ -0,0 +1,14 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+
+;;; Code:
+(defun ld-find-file-in-root (file)
+ "Find file with root."
+ (interactive "fFind file as sudo: ")
+ (require 'tramp)
+ (tramp-cleanup-all-connections)
+ (find-file (concat "/sudo:root@localhost:" file)))
+
+(provide 'ld-file-operations)
+
+;;; ld-file-operations.el ends here
diff --git a/site-lisp/extensions-local/ld-goto-cursor-stack.el b/site-lisp/extensions-local/ld-goto-cursor-stack.el
new file mode 100644
index 0000000..0d065c1
--- /dev/null
+++ b/site-lisp/extensions-local/ld-goto-cursor-stack.el
@@ -0,0 +1,39 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+
+;;; Code:
+(defvar ld-cursor-position-stack nil
+ "Cursor position stack.")
+
+(defun ld-cursor-position-1-store ()
+ "Remember current position and setup."
+ (interactive)
+ (point-to-register 8)
+ (message "Have remember one position"))
+
+(defun ld-cursor-position-1-jump ()
+ "Jump to latest position and setup."
+ (interactive)
+ (let ((tmp (point-marker)))
+ (jump-to-register 8)
+ (set-register 8 tmp))
+ (message "Have back to remember position"))
+
+(defun ld-cursor-position-stack-push ()
+ "Push current point in stack."
+ (interactive)
+ (message "Location marked.")
+ (setq ld-cursor-position-stack (cons (list (current-buffer) (point)) ld-cursor-position-stack)))
+
+(defun ld-cursor-position-stack-pop ()
+ "Pop point from stack."
+ (interactive)
+ (if (null ld-cursor-position-stack)
+ (message "Stack is empty.")
+ (switch-to-buffer (caar ld-cursor-position-stack))
+ (goto-char (cadar ld-cursor-position-stack))
+ (setq ld-cursor-position-stack (cdr ld-cursor-position-stack))))
+
+(provide 'ld-goto-cursor-stack)
+
+;;; ld-goto-cursor-stack.el ends here
diff --git a/site-lisp/extensions-local/ld-goto-simple.el b/site-lisp/extensions-local/ld-goto-simple.el
new file mode 100644
index 0000000..11afc39
--- /dev/null
+++ b/site-lisp/extensions-local/ld-goto-simple.el
@@ -0,0 +1,30 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+
+;;; Code:
+(defun ld-goto-column (number)
+ "Untabify, and go to a column NUMBER within the current line (0 is beginning of the line)."
+ (interactive "nColumn number: ")
+ (move-to-column number t))
+
+; ---
+
+(defun ld-goto-percent-text (percent)
+ "Move the cursor to the character,
+ which is % far from the top character."
+ (interactive "n(text) Goto percent: ")
+ (goto-char (/ (* percent (point-max)) 100)))
+
+; ---
+
+(defun ld-goto-percent-line (percent)
+ "Move the cursor to the line,
+ which is % far from the top line."
+ (interactive "n(line) Goto percent: ")
+ (goto-line (/ (* percent (count-lines (point-min) (point-max)))
+ 100)))
+
+(provide 'ld-goto-simple)
+
+;;; ld-goto-simple.el ends here
+
diff --git a/site-lisp/extensions-local/ld-org-publish-project-desc.el b/site-lisp/extensions-local/ld-org-publish-project-desc.el
new file mode 100644
index 0000000..cd21e2b
--- /dev/null
+++ b/site-lisp/extensions-local/ld-org-publish-project-desc.el
@@ -0,0 +1,106 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+
+;;; Code:
+(setq
+ org-publish-project-alist
+ (let* ((ld-site-path "~/Documents/ld_org_article/")
+ (ld-site-pub-path "~/Public/ld_org_article_publish/")
+ (get-content (lambda (x)
+ (with-temp-buffer
+ (insert-file-contents (concat ld-site-path x))
+ (buffer-string))))
+ (ld-site-postamble (funcall get-content "template/postamble.html"))
+ (ld-site-preamble (funcall get-content "template/preamble.html"))
+ (ld-site-head (funcall get-content "template/head.html")))
+ `(
+ ("blog"
+ :base-directory ,(concat ld-site-path "article/blog/")
+ :base-extension "org"
+ :publishing-directory ,(concat ld-site-pub-path "article/blog/")
+ :publishing-function org-html-publish-to-html
+ :recursive t
+ :headline-levels 4
+
+ :auto-sitemap t
+ :sitemap-filename "sitemap-index.org"
+ :sitemap-title "blog"
+
+ :html-doctype "html5"
+ :html-head ,ld-site-head
+ :html-preamble ,ld-site-preamble
+ :html-postamble ,ld-site-postamble
+ ;; :htmlized-source t
+
+ :with-toc t
+ )
+ ("wiki"
+ :base-directory ,(concat ld-site-path "article/wiki/")
+ :base-extension "org"
+ :publishing-directory ,(concat ld-site-pub-path "article/wiki/")
+ :publishing-function org-html-publish-to-html
+ :recursive t
+ :headline-levels 4
+
+ :auto-sitemap t
+ :sitemap-filename "sitemap-index.org"
+ :sitemap-title "wiki"
+
+ :html-doctype "html5"
+ :html-head ,ld-site-head
+ :html-preamble ,ld-site-preamble
+ :html-postamble ,ld-site-postamble
+ ;; :htmlized-source t
+
+ :with-toc t
+ )
+ ("translation"
+ :base-directory ,(concat ld-site-path "article/translation/")
+ :base-extension "org"
+ :publishing-directory ,(concat ld-site-pub-path "article/translation/")
+ :publishing-function org-html-publish-to-html
+ :recursive t
+ :headline-levels 4
+
+ :auto-sitemap t
+ :sitemap-filename "sitemap-index.org"
+ :sitemap-title "translation"
+
+ :html-doctype "html5"
+ :html-head ,ld-site-head
+ :html-preamble ,ld-site-preamble
+ :html-postamble ,ld-site-postamble
+ ;; :htmlized-source t
+
+ :with-toc t
+ )
+ ("site"
+ :base-directory ,(concat ld-site-path "article/site/")
+ :base-extension "org"
+ :publishing-directory ,(concat ld-site-pub-path "article/site/")
+ :publishing-function org-html-publish-to-html
+ :recursive t
+ :headline-levels 4
+
+ :html-doctype "html5"
+ :html-head ,ld-site-head
+ :html-preamble ,ld-site-preamble
+ :html-postamble ,ld-site-postamble
+ ;; :htmlized-source t
+
+ :with-toc nil
+ )
+ ("static"
+ :base-directory ,(concat ld-site-path "article_static/")
+ ;; :base-extension "css\\|js\\|ico\\|png\\|jpg\\|gif\\|zip\\|7z\\|rar\\|pdf"
+ :base-extension ".*"
+ :publishing-directory ,(concat ld-site-pub-path "/article_static")
+ :publishing-function org-publish-attachment
+ :recursive t
+ )
+ ("all" :components ("blog" "wiki" "site" "translation" "static"))
+ )))
+
+(provide 'ld-org-publish-project-desc)
+
+;;; ld-org-publish-project-desc.el ends here
diff --git a/site-lisp/extensions-local/ld-text-operations.el b/site-lisp/extensions-local/ld-text-operations.el
new file mode 100644
index 0000000..f08c12b
--- /dev/null
+++ b/site-lisp/extensions-local/ld-text-operations.el
@@ -0,0 +1,109 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+
+;;; Code:
+; --- move lines
+(defun ld-move-text-internal (arg)
+ (cond
+ ((and mark-active transient-mark-mode)
+ (if (> (point) (mark))
+ (exchange-point-and-mark))
+ (let ((column (current-column))
+ (text (delete-and-extract-region (point) (mark))))
+ (forward-line arg)
+ (move-to-column column t)
+ (set-mark (point))
+ (insert text)
+ (exchange-point-and-mark)
+ (setq deactivate-mark nil)))
+ (t
+ (beginning-of-line)
+ (when (or (> arg 0) (not (bobp)))
+ (forward-line)
+ (when (or (< arg 0) (not (eobp)))
+ (transpose-lines arg))
+ (forward-line -1)))))
+
+(defun ld-move-text-down (arg)
+ "Move region (transient-mark-mode active) or current line
+ arg lines down."
+ (interactive "*p")
+ (ld-move-text-internal arg))
+
+(defun ld-move-text-up (arg)
+ "Move region (transient-mark-mode active) or current line
+ arg lines up."
+ (interactive "*p")
+ (ld-move-text-internal (- arg)))
+
+; --- duplicate line
+(defun ld-get-positions-of-line-or-region ()
+ "Return positions (beg . end) of the current line or region."
+ (let (beg end)
+ (if (and mark-active (> (point) (mark)))
+ (exchange-point-and-mark))
+ (setq beg (line-beginning-position))
+ (if mark-active
+ (exchange-point-and-mark))
+ (setq end (line-end-position))
+ (cons beg end)))
+
+(defun ld-duplicate-current-line-or-region (arg)
+ "Duplicates the current line or region ARG times.
+If there's no region, the current line will be duplicated. However, if
+there's a region, all lines that region covers will be duplicated."
+ (interactive "p")
+ (pcase-let* ((origin (point))
+ (`(,beg . ,end) (ld-get-positions-of-line-or-region))
+ (region (buffer-substring-no-properties beg end)))
+ (dotimes (_i arg)
+ (goto-char end)
+ (unless (use-region-p)
+ (newline))
+ (insert region)
+ (setq end (point)))
+ (goto-char (+ origin (* (length region) arg) arg))))
+
+(defun ld-duplicate-and-comment-current-line-or-region (arg)
+ "Duplicates and comments the current line or region ARG times.
+If there's no region, the current line will be duplicated. However, if
+there's a region, all lines that region covers will be duplicated."
+ (interactive "p")
+ (pcase-let* ((origin (point))
+ (`(,beg . ,end) (ld-get-positions-of-line-or-region))
+ (region (buffer-substring-no-properties beg end)))
+ (comment-or-uncomment-region beg end)
+ (setq end (line-end-position))
+ (dotimes (_ arg)
+ (goto-char end)
+ (unless (use-region-p)
+ (newline))
+ (insert region)
+ (setq end (point)))
+ (goto-char (+ origin (* (length region) arg) arg))))
+
+; ---
+
+(defun ld-delete-current-line ()
+ "Delete (not kill) the current line."
+ (interactive)
+ (save-excursion
+ (delete-region
+ (progn (forward-visible-line 0) (point))
+ (progn (forward-visible-line 1) (point)))))
+
+; ---
+
+(defun ld-mark-line ()
+ "Mark one whole line, similar to `mark-paragraph'."
+ (interactive)
+ (beginning-of-line)
+ (if mark-active
+ (exchange-point-and-mark)
+ (push-mark nil nil t))
+ (forward-line)
+ (exchange-point-and-mark))
+
+(provide 'ld-text-operations)
+
+;;; ld-text-operations.el ends here
diff --git a/site-lisp/extensions-local/ld-toggle-one-window.el b/site-lisp/extensions-local/ld-toggle-one-window.el
new file mode 100644
index 0000000..0690222
--- /dev/null
+++ b/site-lisp/extensions-local/ld-toggle-one-window.el
@@ -0,0 +1,22 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+
+;;; Code:
+(defvar ld-toggle-one-window-config-of-window nil
+ "The window configuration used for `toggle-one-window'.")
+
+(defun ld-toggle-one-window ()
+ "Toggle between window layout and one window."
+ (interactive)
+ (if (equal (length (cl-remove-if #'window-dedicated-p (window-list))) 1)
+ (if toggle-one-window-config-of-window
+ (progn
+ (set-window-configuration toggle-one-window-config-of-window)
+ (setq toggle-one-window-config-of-window nil))
+ (message "No other windows exist."))
+ (setq toggle-one-window-config-of-window (current-window-configuration))
+ (delete-other-windows)))
+
+(provide 'ld-toggle-one-window)
+
+;;; ld-toggle-one-window.el ends here
\ No newline at end of file
diff --git a/site-lisp/extensions-local/ld-tools.el b/site-lisp/extensions-local/ld-tools.el
new file mode 100644
index 0000000..ae80331
--- /dev/null
+++ b/site-lisp/extensions-local/ld-tools.el
@@ -0,0 +1,167 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+
+;;; Code:
+(defun insert-line-number (beg end &optional start-line)
+ "Insert line numbers into buffer."
+ (interactive "r")
+ (save-excursion
+ (let ((max (count-lines beg end))
+ (line (or start-line 1))
+ (counter 1))
+ (goto-char beg)
+ (while (<= counter max)
+ (insert (format "%0d " line))
+ (beginning-of-line 2)
+ (cl-incf line)
+ (cl-incf counter)))))
+
+(defun insert-line-number+ ()
+ "Insert line number into buffer."
+ (interactive)
+ (if mark-active
+ (insert-line-number (region-beginning) (region-end) (read-number "Start line: "))
+ (insert-line-number (point-min) (point-max))))
+
+(defun strip-blank-lines()
+ "Strip all blank lines in select area of buffer,
+if not select any area, then strip all blank lines of buffer."
+ (interactive)
+ (strip-regular-expression-string "^[ \t]*\n")
+ (message "Blanks line striped. ^_^"))
+
+(defun strip-line-number()
+ "Strip all line number in select area of buffer,
+if not select any area, then strip all line number of buffer."
+ (interactive)
+ (strip-regular-expression-string "^[0-9]+ ")
+ (message "Line number striped. ^_^"))
+
+(defun strip-regular-expression-string (regular-expression)
+ "Strip all string that match REGULAR-EXPRESSION in select area of buffer.
+If not select any area, then strip current buffer"
+ (interactive)
+ (let ((begin (point-min))
+ (end (point-max)))
+ (if mark-active
+ (setq begin (region-beginning)
+ end (region-end)))
+ (save-excursion
+ (goto-char end)
+ (while (and (> (point) begin)
+ (re-search-backward regular-expression nil t))
+ (replace-match "" t t)))))
+
+(defun indent-comment-buffer ()
+ "Indent comment of buffer."
+ (interactive)
+ (indent-comment-region (point-min) (point-max)))
+
+(defun indent-comment-region (start end)
+ "Indent region."
+ (interactive "r")
+ (save-excursion
+ (setq end (copy-marker end))
+ (goto-char start)
+ (while (< (point) end)
+ (if (comment-search-forward end t)
+ (comment-indent)
+ (goto-char end)))))
+
+(defun capitalize-one-char (arg)
+ "Change the letter pointed by the cursor to uppercase."
+ (interactive "P")
+ (upcase-region (point) (+ (point) (or arg 1)))
+ (forward-char (or arg 1)))
+
+(defun lowercase-one-char (arg)
+ "Change the letter pointed by the cursor to lowercase."
+ (interactive "P")
+ (downcase-region (point) (+ (point) (or arg 1)))
+ (forward-char (or arg 1)))
+
+(defun delete-chars-hungry-forward (&optional reverse)
+ "Delete chars forward use `hungry' style.
+Optional argument REVERSE default is delete forward, if reverse is non-nil delete backward."
+ (delete-region
+ (point)
+ (progn
+ (if reverse
+ (skip-chars-backward " \t\n\r")
+ (skip-chars-forward " \t\n\r"))
+ (point))))
+
+(defun delete-chars-hungry-backward ()
+ "Delete chars backward use `hungry' style."
+ (delete-chars-hungry-forward t))
+
+(defun reverse-chars-in-region (start end)
+ "Reverse the region character by character without reversing lines."
+ (interactive "r")
+ (let ((str (buffer-substring start end)))
+ (delete-region start end)
+ (dolist (line (split-string str "\n"))
+ (let ((chars (mapcar (lambda (c)
+ (or (matching-paren c)
+ c))
+ (reverse (append line nil)))))
+ (when chars
+ (apply 'insert chars))
+ (newline)))))
+
+(defun underline-line-with (char)
+ "Insert some char below at current line."
+ (interactive "cType one char: ")
+ (save-excursion
+ (let ((length (- (point-at-eol) (point-at-bol))))
+ (end-of-line)
+ (insert "\n")
+ (insert (make-string length char)))))
+
+(defun prettyfy-string (string &optional after)
+ "Strip starting and ending whitespace and pretty `STRING'.
+Replace any chars after AFTER with '...'.
+Argument STRING the string that need pretty."
+ (let ((replace-map (list
+ (cons "^[ \t]*" "")
+ (cons "[ \t]*$" "")
+ (cons (concat "^\\(.\\{"
+ (or (number-to-string after) "10")
+ "\\}\\).*")
+ "\\1..."))))
+ (dolist (replace replace-map)
+ (when (string-match (car replace) string)
+ (setq string (replace-match (cdr replace) nil nil string))))
+ string))
+
+(defun forward-button-with-line-begin ()
+ "Move to next button with line begin."
+ (interactive)
+ (call-interactively 'forward-button)
+ (while (not (bolp))
+ (call-interactively 'forward-button)))
+
+(defun backward-button-with-line-begin ()
+ "Move to previous button with line begin."
+ (interactive)
+ (call-interactively 'backward-button)
+ (while (not (bolp))
+ (call-interactively 'backward-button)))
+
+(defun only-comment-p ()
+ "Return t if current line only contains comment. Otherwise return nil."
+ (interactive)
+ (save-excursion
+ (beginning-of-line)
+ (if (search-forward comment-start (line-end-position) t)
+ (progn
+ (backward-char (length comment-start))
+ (equal (point)
+ (progn
+ (back-to-indentation)
+ (point))))
+ nil)))
+
+(provide 'ld-tools)
+
+;;; ld-tools.el ends here
diff --git a/site-lisp/extensions-local/neotree.el b/site-lisp/extensions-local/neotree.el
new file mode 100644
index 0000000..4a303f1
--- /dev/null
+++ b/site-lisp/extensions-local/neotree.el
@@ -0,0 +1,2228 @@
+;;; neotree.el --- A tree plugin like NerdTree for Vim
+
+;; Copyright (C) 2014 jaypei
+
+;; Author: jaypei
+;; URL: https://github.com/jaypei/emacs-neotree
+;; Version: 0.6.0
+;; Package-Requires: ((cl-lib "0.5"))
+
+;; 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 .
+
+;;; Commentary:
+
+;; To use this file, put something like the following in your
+;; ~/.emacs:
+;;
+;; (add-to-list 'load-path "/directory/containing/neotree/")
+;; (require 'neotree)
+;;
+;; Type M-x neotree to start.
+;;
+;; To set options for NeoTree, type M-x customize, then select
+;; Applications, NeoTree.
+;;
+
+;;; Code:
+
+(require 'cl-lib)
+
+;;
+;; Constants
+;;
+
+(defconst neo-buffer-name " *NeoTree*"
+ "Name of the buffer where neotree shows directory contents.")
+
+(defconst neo-dir
+ (expand-file-name (if load-file-name
+ (file-name-directory load-file-name)
+ default-directory)))
+
+(defconst neo-header-height 5)
+
+(eval-and-compile
+
+ ;; Added in Emacs 24.3
+ (unless (fboundp 'user-error)
+ (defalias 'user-error 'error))
+
+ ;; Added in Emacs 24.3 (mirrors/emacs@b335efc3).
+ (unless (fboundp 'setq-local)
+ (defmacro setq-local (var val)
+ "Set variable VAR to value VAL in current buffer."
+ (list 'set (list 'make-local-variable (list 'quote var)) val)))
+
+ ;; Added in Emacs 24.3 (mirrors/emacs@b335efc3).
+ (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))
+ (list 'progn (list 'defvar var val docstring)
+ (list 'make-variable-buffer-local (list 'quote var))))))
+
+;; Add autoload function for vc (#153).
+(autoload 'vc-responsible-backend "vc.elc")
+
+;;
+;; Macros
+;;
+
+(defmacro neo-util--to-bool (obj)
+ "If OBJ is non-nil, return t, else return nil."
+ `(and ,obj t))
+
+(defmacro neo-global--with-buffer (&rest body)
+ "Execute the forms in BODY with global NeoTree buffer."
+ (declare (indent 0) (debug t))
+ `(let ((neotree-buffer (neo-global--get-buffer)))
+ (unless (null neotree-buffer)
+ (with-current-buffer neotree-buffer
+ ,@body))))
+
+(defmacro neo-global--with-window (&rest body)
+ "Execute the forms in BODY with global NeoTree window."
+ (declare (indent 0) (debug t))
+ `(save-selected-window
+ (neo-global--select-window)
+ ,@body))
+
+(defmacro neo-global--when-window (&rest body)
+ "Execute the forms in BODY when selected window is NeoTree window."
+ (declare (indent 0) (debug t))
+ `(when (eq (selected-window) neo-global--window)
+ ,@body))
+
+(defmacro neo-global--switch-to-buffer ()
+ "Switch to NeoTree buffer."
+ `(let ((neotree-buffer (neo-global--get-buffer)))
+ (unless (null neotree-buffer)
+ (switch-to-buffer neotree-buffer))))
+
+(defmacro neo-buffer--with-editing-buffer (&rest body)
+ "Execute BODY in neotree buffer without read-only restriction."
+ `(let (rlt)
+ (neo-global--with-buffer
+ (setq buffer-read-only nil)
+ (setq rlt (progn ,@body))
+ (setq buffer-read-only t))
+ rlt))
+
+(defmacro neo-buffer--with-resizable-window (&rest body)
+ "Execute BODY in neotree window without `window-size-fixed' restriction."
+ `(let (rlt)
+ (neo-global--with-buffer
+ (neo-buffer--unlock-width))
+ (setq rlt (progn ,@body))
+ (neo-global--with-buffer
+ (neo-buffer--lock-width))
+ rlt))
+
+(defmacro neotree-make-executor (&rest fn-form)
+ "Make an open event handler, FN-FORM is event handler form."
+ (let* ((get-args-fn
+ (lambda (sym) (or (plist-get fn-form sym) (lambda (&rest _)))))
+ (file-fn (funcall get-args-fn :file-fn))
+ (dir-fn (funcall get-args-fn :dir-fn)))
+ `(lambda (&optional arg)
+ (interactive "P")
+ (neo-global--select-window)
+ (neo-buffer--execute arg ,file-fn ,dir-fn))))
+
+
+;;
+;; Customization
+;;
+
+(defgroup neotree nil
+ "Options for neotree."
+ :prefix "neo-"
+ :group 'files)
+
+(defgroup neotree-vc-options nil
+ "Neotree-VC customizations."
+ :prefix "neo-vc-"
+ :group 'neotree
+ :link '(info-link "(neotree)Configuration"))
+
+(defgroup neotree-confirmations nil
+ "Neotree confirmation customizations."
+ :prefix "neo-confirm-"
+ :group 'neotree)
+
+(defcustom neo-window-position 'left
+ "*The position of NeoTree window."
+ :group 'neotree
+ :type '(choice (const left)
+ (const right)))
+
+(defcustom neo-display-action '(neo-default-display-fn)
+ "*Action to use for displaying NeoTree window.
+If you change the action so it doesn't use
+`neo-default-display-fn', then other variables such as
+`neo-window-position' won't be respected when opening NeoTree
+window."
+ :type 'sexp
+ :group 'neotree)
+
+(defcustom neo-create-file-auto-open nil
+ "*If non-nil, the file will auto open when created."
+ :type 'boolean
+ :group 'neotree)
+
+(defcustom neo-banner-message nil
+ "*The banner message of neotree window."
+ :type 'string
+ :group 'neotree)
+
+(defcustom neo-show-updir-line t
+ "*If non-nil, show the updir line (..)."
+ :type 'boolean
+ :group 'neotree)
+
+(defcustom neo-show-slash-for-folder t
+ "*If non-nil, show the slash at the end of folder (folder/)"
+ :type 'boolean
+ :group 'neotree)
+
+(defcustom neo-reset-size-on-open nil
+ "*If non-nil, the width of the noetree window will be reseted every time a file is open."
+ :type 'boolean
+ :group 'neotree)
+
+(defcustom neo-theme 'classic
+ "*The tree style to display.
+`classic' use icon to display, it only it suitable for GUI mode.
+`ascii' is the simplest style, it will use +/- to display the fold state,
+it suitable for terminal.
+`arrow' use unicode arrow.
+`nerd' use the nerdtree indentation mode and arrow."
+ :group 'neotree
+ :type '(choice (const classic)
+ (const ascii)
+ (const arrow)
+ (const icons)
+ (const nerd)))
+
+(defcustom neo-mode-line-type 'neotree
+ "*The mode-line type to display, `default' is a non-modified mode-line, \
+`neotree' is a compact mode-line that shows useful information about the
+ current node like the parent directory and the number of nodes,
+`custom' uses the format stored in `neo-mode-line-custom-format',
+`none' hide the mode-line."
+ :group 'neotree
+ :type '(choice (const default)
+ (const neotree)
+ (const custom)
+ (const none)))
+
+(defcustom neo-mode-line-custom-format nil
+ "*If `neo-mode-line-type' is set to `custom', this variable specifiy \
+the mode-line format."
+ :type 'sexp
+ :group 'neotree)
+
+(defcustom neo-smart-open nil
+ "*If non-nil, every time when the neotree window is opened, it will try to find current file and jump to node."
+ :type 'boolean
+ :group 'neotree)
+
+(defcustom neo-show-hidden-files nil
+ "*If non-nil, the hidden files are shown by default."
+ :type 'boolean
+ :group 'neotree)
+
+(defcustom neo-autorefresh nil
+ "*If non-nil, the neotree buffer will auto refresh."
+ :type 'boolean
+ :group 'neotree)
+
+(defcustom neo-window-width 25
+ "*Specifies the width of the NeoTree window."
+ :type 'integer
+ :group 'neotree)
+
+(defcustom neo-window-fixed-size t
+ "*If the neotree windows is fixed, it won't be resize when rebalance windows."
+ :type 'boolean
+ :group 'neotree)
+
+(defcustom neo-keymap-style 'default
+ "*The default keybindings for neotree-mode-map."
+ :group 'neotree
+ :type '(choice (const default)
+ (const concise)))
+
+(defcustom neo-cwd-line-style 'text
+ "*The default header style."
+ :group 'neotree
+ :type '(choice (const text)
+ (const button)))
+
+(defcustom neo-help-echo-style 'default
+ "The message NeoTree displays when the mouse moves onto nodes.
+`default' means the node name is displayed if it has a
+width (including the indent) larger than `neo-window-width', and
+`none' means NeoTree doesn't display any messages."
+ :group 'neotree
+ :type '(choice (const default)
+ (const none)))
+
+(defcustom neo-click-changes-root nil
+ "*If non-nil, clicking on a directory will change the current root to the directory."
+ :type 'boolean
+ :group 'neotree)
+
+(defcustom neo-auto-indent-point nil
+ "*If non-nil the point is autmotically put on the first letter of a node."
+ :type 'boolean
+ :group 'neotree)
+
+(defcustom neo-hidden-regexp-list
+ '("^\\." "\\.pyc$" "~$" "^#.*#$" "\\.elc$" "\\.o$")
+ "*The regexp list matching hidden files."
+ :type '(repeat (choice regexp))
+ :group 'neotree)
+
+(defcustom neo-enter-hook nil
+ "Functions to run if enter node occured."
+ :type 'hook
+ :group 'neotree)
+
+(defcustom neo-after-create-hook nil
+ "Hooks called after creating the neotree buffer."
+ :type 'hook
+ :group 'neotree)
+
+(defcustom neo-vc-integration nil
+ "If non-nil, show VC status."
+ :group 'neotree-vc
+ :type '(set (const :tag "Use different faces" face)
+ (const :tag "Use different characters" char)))
+
+(defcustom neo-vc-state-char-alist
+ '((up-to-date . ?\s)
+ (edited . ?E)
+ (added . ?+)
+ (removed . ?-)
+ (missing . ?!)
+ (needs-merge . ?M)
+ (conflict . ?!)
+ (unlocked-changes . ?!)
+ (needs-update . ?U)
+ (ignored . ?\s)
+ (user . ?U)
+ (unregistered . ?\s)
+ (nil . ?\s))
+ "Alist of vc-states to indicator characters.
+This variable is used in `neo-vc-for-node' when
+`neo-vc-integration' contains `char'."
+ :group 'neotree-vc
+ :type '(alist :key-type symbol
+ :value-type character))
+
+(defcustom neo-confirm-change-root 'yes-or-no-p
+ "Confirmation asking for permission to change root if file was not found in root path."
+ :type '(choice (function-item :tag "Verbose" yes-or-no-p)
+ (function-item :tag "Succinct" y-or-n-p)
+ (function-item :tag "Off" off-p))
+ :group 'neotree-confirmations)
+
+(defcustom neo-confirm-create-file 'yes-or-no-p
+ "Confirmation asking whether *NeoTree* should create a file."
+ :type '(choice (function-item :tag "Verbose" yes-or-no-p)
+ (function-item :tag "Succinct" y-or-n-p)
+ (function-item :tag "Off" off-p))
+ :group 'neotree-confirmations)
+
+(defcustom neo-confirm-create-directory 'yes-or-no-p
+ "Confirmation asking whether *NeoTree* should create a directory."
+ :type '(choice (function-item :tag "Verbose" yes-or-no-p)
+ (function-item :tag "Succinct" y-or-n-p)
+ (function-item :tag "Off" off-p))
+ :group 'neotree-confirmations)
+
+(defcustom neo-confirm-delete-file 'yes-or-no-p
+ "Confirmation asking whether *NeoTree* should delete the file."
+ :type '(choice (function-item :tag "Verbose" yes-or-no-p)
+ (function-item :tag "Succinct" y-or-n-p)
+ (function-item :tag "Off" off-p))
+ :group 'neotree-confirmations)
+
+(defcustom neo-confirm-delete-directory-recursively 'yes-or-no-p
+ "Confirmation asking whether the directory should be deleted recursively."
+ :type '(choice (function-item :tag "Verbose" yes-or-no-p)
+ (function-item :tag "Succinct" y-or-n-p)
+ (function-item :tag "Off" off-p))
+ :group 'neotree-confirmations)
+
+(defcustom neo-confirm-kill-buffers-for-files-in-directory 'yes-or-no-p
+ "Confirmation asking whether *NeoTree* should kill buffers for the directory in question."
+ :type '(choice (function-item :tag "Verbose" yes-or-no-p)
+ (function-item :tag "Succinct" y-or-n-p)
+ (function-item :tag "Off" off-p))
+ :group 'neotree-confirmations)
+
+(defcustom neo-toggle-window-keep-p nil
+ "If not nil, not switch to *NeoTree* buffer when executing `neotree-toggle'."
+ :type 'boolean
+ :group 'neotree)
+
+(defcustom neo-force-change-root t
+ "If not nil, do not prompt when switching root."
+ :type 'boolean
+ :group 'neotree)
+
+(defcustom neo-filepath-sort-function 'string<
+ "Function to be called when sorting neotree nodes."
+ :type '(symbol (const :tag "Normal" string<)
+ (const :tag "Sort Hidden at Bottom" neo-sort-hidden-last)
+ (function :tag "Other"))
+ :group 'neotree)
+
+(defcustom neo-default-system-application "xdg-open"
+ "*Name of the application that is used to open a file under point.
+By default it is xdg-open."
+ :type 'string
+ :group 'neotree)
+
+(defcustom neo-hide-cursor nil
+ "If not nil, hide cursor in NeoTree buffer and turn on line higlight."
+ :type 'boolean
+ :group 'neotree)
+
+;;
+;; Faces
+;;
+
+(defface neo-banner-face
+ '((((background dark)) (:foreground "lightblue" :weight bold))
+ (t (:foreground "DarkMagenta")))
+ "*Face used for the banner in neotree buffer."
+ :group 'neotree :group 'font-lock-highlighting-faces)
+(defvar neo-banner-face 'neo-banner-face)
+
+(defface neo-header-face
+ '((((background dark)) (:foreground "White"))
+ (t (:foreground "DarkMagenta")))
+ "*Face used for the header in neotree buffer."
+ :group 'neotree :group 'font-lock-highlighting-faces)
+(defvar neo-header-face 'neo-header-face)
+
+(defface neo-root-dir-face
+ '((((background dark)) (:foreground "lightblue" :weight bold))
+ (t (:foreground "DarkMagenta")))
+ "*Face used for the root dir in neotree buffer."
+ :group 'neotree :group 'font-lock-highlighting-faces)
+(defvar neo-root-dir-face 'neo-root-dir-face)
+
+(defface neo-dir-link-face
+ '((((background dark)) (:foreground "DeepSkyBlue"))
+ (t (:foreground "MediumBlue")))
+ "*Face used for expand sign [+] in neotree buffer."
+ :group 'neotree :group 'font-lock-highlighting-faces)
+(defvar neo-dir-link-face 'neo-dir-link-face)
+
+(defface neo-file-link-face
+ '((((background dark)) (:foreground "White"))
+ (t (:foreground "Black")))
+ "*Face used for open file/dir in neotree buffer."
+ :group 'neotree :group 'font-lock-highlighting-faces)
+(defvar neo-file-link-face 'neo-file-link-face)
+
+(defface neo-button-face
+ '((t (:underline nil)))
+ "*Face used for open file/dir in neotree buffer."
+ :group 'neotree :group 'font-lock-highlighting-faces)
+(defvar neo-button-face 'neo-button-face)
+
+(defface neo-expand-btn-face
+ '((((background dark)) (:foreground "SkyBlue"))
+ (t (:foreground "DarkCyan")))
+ "*Face used for open file/dir in neotree buffer."
+ :group 'neotree :group 'font-lock-highlighting-faces)
+(defvar neo-expand-btn-face 'neo-expand-btn-face)
+
+(defface neo-vc-default-face
+ '((((background dark)) (:foreground "White"))
+ (t (:foreground "Black")))
+ "*Face used for unknown files in the neotree buffer.
+Used only when \(vc-state node\) returns nil."
+ :group 'neotree-vc :group 'font-lock-highlighting-faces)
+(defvar neo-vc-default-face 'neo-vc-default-face)
+
+(defface neo-vc-user-face
+ '((t (:foreground "Red" :slant italic)))
+ "*Face used for user-locked files in the neotree buffer."
+ :group 'neotree-vc :group 'font-lock-highlighting-faces)
+(defvar neo-vc-user-face 'neo-vc-user-face)
+
+(defface neo-vc-up-to-date-face
+ '((((background dark)) (:foreground "LightGray"))
+ (t (:foreground "DarkGray")))
+ "*Face used for vc-up-to-date files in the neotree buffer."
+ :group 'neotree-vc :group 'font-lock-highlighting-faces)
+(defvar neo-vc-up-to-date-face 'neo-vc-up-to-date-face)
+
+(defface neo-vc-edited-face
+ '((((background dark)) (:foreground "Magenta"))
+ (t (:foreground "DarkMagenta")))
+ "*Face used for vc-edited files in the neotree buffer."
+ :group 'neotree-vc :group 'font-lock-highlighting-faces)
+(defvar neo-vc-edited-face 'neo-vc-edited-face)
+
+(defface neo-vc-needs-update-face
+ '((t (:underline t)))
+ "*Face used for vc-needs-update files in the neotree buffer."
+ :group 'neotree-vc :group 'font-lock-highlighting-faces)
+(defvar neo-vc-needs-update-face 'neo-vc-needs-update-face)
+
+(defface neo-vc-needs-merge-face
+ '((((background dark)) (:foreground "Red1"))
+ (t (:foreground "Red3")))
+ "*Face used for vc-needs-merge files in the neotree buffer."
+ :group 'neotree-vc :group 'font-lock-highlighting-faces)
+(defvar neo-vc-needs-merge-face 'neo-vc-needs-merge-face)
+
+(defface neo-vc-unlocked-changes-face
+ '((t (:foreground "Red" :background "Blue")))
+ "*Face used for vc-unlocked-changes files in the neotree buffer."
+ :group 'neotree-vc :group 'font-lock-highlighting-faces)
+(defvar neo-vc-unlocked-changes-face 'neo-vc-unlocked-changes-face)
+
+(defface neo-vc-added-face
+ '((((background dark)) (:foreground "LightGreen"))
+ (t (:foreground "DarkGreen")))
+ "*Face used for vc-added files in the neotree buffer."
+ :group 'neotree-vc :group 'font-lock-highlighting-faces)
+(defvar neo-vc-added-face 'neo-vc-added-face)
+
+(defface neo-vc-removed-face
+ '((t (:strike-through t)))
+ "*Face used for vc-removed files in the neotree buffer."
+ :group 'neotree-vc :group 'font-lock-highlighting-faces)
+(defvar neo-vc-removed-face 'neo-vc-removed-face)
+
+(defface neo-vc-conflict-face
+ '((((background dark)) (:foreground "Red1"))
+ (t (:foreground "Red3")))
+ "*Face used for vc-conflict files in the neotree buffer."
+ :group 'neotree-vc :group 'font-lock-highlighting-faces)
+(defvar neo-vc-conflict-face 'neo-vc-conflict-face)
+
+(defface neo-vc-missing-face
+ '((((background dark)) (:foreground "Red1"))
+ (t (:foreground "Red3")))
+ "*Face used for vc-missing files in the neotree buffer."
+ :group 'neotree-vc :group 'font-lock-highlighting-faces)
+(defvar neo-vc-missing-face 'neo-vc-missing-face)
+
+(defface neo-vc-ignored-face
+ '((((background dark)) (:foreground "DarkGrey"))
+ (t (:foreground "LightGray")))
+ "*Face used for vc-ignored files in the neotree buffer."
+ :group 'neotree-vc :group 'font-lock-highlighting-faces)
+(defvar neo-vc-ignored-face 'neo-vc-ignored-face)
+
+(defface neo-vc-unregistered-face
+ nil
+ "*Face used for vc-unregistered files in the neotree buffer."
+ :group 'neotree-vc :group 'font-lock-highlighting-faces)
+(defvar neo-vc-unregistered-face 'neo-vc-unregistered-face)
+
+;;
+;; Variables
+;;
+
+(defvar neo-global--buffer nil)
+
+(defvar neo-global--window nil)
+
+(defvar neo-global--autorefresh-timer nil)
+
+(defvar neo-mode-line-format
+ (list
+ '(:eval
+ (let* ((fname (neo-buffer--get-filename-current-line))
+ (current (if fname fname neo-buffer--start-node))
+ (parent (if fname (file-name-directory current) current))
+ (nodes (neo-buffer--get-nodes parent))
+ (dirs (car nodes))
+ (files (cdr nodes))
+ (ndirs (length dirs))
+ (nfiles (length files))
+ (index
+ (when fname
+ (1+ (if (file-directory-p current)
+ (neo-buffer--get-node-index current dirs)
+ (+ ndirs (neo-buffer--get-node-index current files)))))))
+ (neo-mode-line--compute-format parent index ndirs nfiles))))
+ "Neotree mode-line displaying information on the current node.
+This mode-line format is used if `neo-mode-line-type' is set to `neotree'")
+
+(defvar-local neo-buffer--start-node nil
+ "Start node(i.e. directory) for the window.")
+
+(defvar-local neo-buffer--start-line nil
+ "Index of the start line of the root.")
+
+(defvar-local neo-buffer--cursor-pos (cons nil 1)
+ "To save the cursor position.
+The car of the pair will store fullpath, and cdr will store line number.")
+
+(defvar-local neo-buffer--last-window-pos (cons nil 1)
+ "To save the scroll position for NeoTree window.")
+
+(defvar-local neo-buffer--show-hidden-file-p nil
+ "Show hidden nodes in tree.")
+
+(defvar-local neo-buffer--expanded-node-list nil
+ "A list of expanded dir nodes.")
+
+(defvar-local neo-buffer--node-list nil
+ "The model of current NeoTree buffer.")
+
+(defvar-local neo-buffer--node-list-1 nil
+ "The model of current NeoTree buffer (temp).")
+
+;;
+;; Major mode definitions
+;;
+
+(defvar neotree-file-button-keymap
+ (let ((map (make-sparse-keymap)))
+ (define-key map [mouse-2]
+ (neotree-make-executor
+ :file-fn 'neo-open-file))
+ map)
+ "Keymap for file-node button.")
+
+(defvar neotree-dir-button-keymap
+ (let ((map (make-sparse-keymap)))
+ (define-key map [mouse-2]
+ (neotree-make-executor :dir-fn 'neo-open-dir))
+ map)
+ "Keymap for dir-node button.")
+
+(defvar neotree-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd "TAB") (neotree-make-executor
+ :dir-fn 'neo-open-dir))
+ (define-key map (kbd "RET") (neotree-make-executor
+ :file-fn 'neo-open-file
+ :dir-fn 'neo-open-dir))
+ (define-key map (kbd "|") (neotree-make-executor
+ :file-fn 'neo-open-file-vertical-split))
+ (define-key map (kbd "-") (neotree-make-executor
+ :file-fn 'neo-open-file-horizontal-split))
+ (define-key map (kbd "a") (neotree-make-executor
+ :file-fn 'neo-open-file-ace-window))
+ (define-key map (kbd "d") (neotree-make-executor
+ :dir-fn 'neo-open-dired))
+ (define-key map (kbd "O") (neotree-make-executor
+ :dir-fn 'neo-open-dir-recursive))
+ (define-key map (kbd "SPC") 'neotree-quick-look)
+ (define-key map (kbd "g") 'neotree-refresh)
+ (define-key map (kbd "q") 'neotree-hide)
+ (define-key map (kbd "p") 'neotree-previous-line)
+ (define-key map (kbd "C-p") 'neotree-previous-line)
+ (define-key map (kbd "n") 'neotree-next-line)
+ (define-key map (kbd "C-n") 'neotree-next-line)
+ (define-key map (kbd "A") 'neotree-stretch-toggle)
+ (define-key map (kbd "U") 'neotree-select-up-node)
+ (define-key map (kbd "D") 'neotree-select-down-node)
+ (define-key map (kbd "H") 'neotree-hidden-file-toggle)
+ (define-key map (kbd "S") 'neotree-select-previous-sibling-node)
+ (define-key map (kbd "s") 'neotree-select-next-sibling-node)
+ (define-key map (kbd "o") 'neotree-open-file-in-system-application)
+ (define-key map (kbd "C-x C-f") 'find-file-other-window)
+ (define-key map (kbd "C-x 1") 'neotree-empty-fn)
+ (define-key map (kbd "C-x 2") 'neotree-empty-fn)
+ (define-key map (kbd "C-x 3") 'neotree-empty-fn)
+ (define-key map (kbd "C-c C-f") 'find-file-other-window)
+ (define-key map (kbd "C-c C-c") 'neotree-change-root)
+ (define-key map (kbd "C-c c") 'neotree-dir)
+ (define-key map (kbd "C-c C-a") 'neotree-collapse-all)
+ (cond
+ ((eq neo-keymap-style 'default)
+ (define-key map (kbd "C-c C-n") 'neotree-create-node)
+ (define-key map (kbd "C-c C-d") 'neotree-delete-node)
+ (define-key map (kbd "C-c C-r") 'neotree-rename-node)
+ (define-key map (kbd "C-c C-p") 'neotree-copy-node))
+ ((eq neo-keymap-style 'concise)
+ (define-key map (kbd "C") 'neotree-change-root)
+ (define-key map (kbd "c") 'neotree-create-node)
+ (define-key map (kbd "+") 'neotree-create-node)
+ (define-key map (kbd "d") 'neotree-delete-node)
+ (define-key map (kbd "r") 'neotree-rename-node)
+ (define-key map (kbd "e") 'neotree-enter)))
+ map)
+ "Keymap for `neotree-mode'.")
+
+(define-derived-mode neotree-mode special-mode "NeoTree"
+ "A major mode for displaying the directory tree in text mode."
+ (setq indent-tabs-mode nil ; only spaces
+ buffer-read-only t ; read only
+ truncate-lines -1
+ neo-buffer--show-hidden-file-p neo-show-hidden-files)
+ (when neo-hide-cursor
+ (progn
+ (setq cursor-type nil)
+ (hl-line-mode +1)))
+ (pcase neo-mode-line-type
+ (`neotree
+ (setq-local mode-line-format neo-mode-line-format)
+ (add-hook 'post-command-hook 'force-mode-line-update nil t))
+ (`none (setq-local mode-line-format nil))
+ (`custom
+ (setq-local mode-line-format neo-mode-line-custom-format)
+ (add-hook 'post-command-hook 'force-mode-line-update nil t))
+ (_ nil))
+ ;; fix for electric-indent-mode
+ ;; for emacs 24.4
+ (if (fboundp 'electric-indent-local-mode)
+ (electric-indent-local-mode -1)
+ ;; for emacs 24.3 or less
+ (add-hook 'electric-indent-functions
+ (lambda (arg) 'no-indent) nil 'local))
+ (when neo-auto-indent-point
+ (add-hook 'post-command-hook 'neo-hook--node-first-letter nil t)))
+
+;;
+;; Global methods
+;;
+
+(defun neo-global--window-exists-p ()
+ "Return non-nil if neotree window exists."
+ (and (not (null (window-buffer neo-global--window)))
+ (eql (window-buffer neo-global--window) (neo-global--get-buffer))))
+
+(defun neo-global--select-window ()
+ "Select the NeoTree window."
+ (interactive)
+ (let ((window (neo-global--get-window t)))
+ (select-window window)))
+
+(defun neo-global--get-window (&optional auto-create-p)
+ "Return the neotree window if it exists, else return nil.
+But when the neotree window does not exist and AUTO-CREATE-P is non-nil,
+it will create the neotree window and return it."
+ (unless (neo-global--window-exists-p)
+ (setf neo-global--window nil))
+ (when (and (null neo-global--window)
+ auto-create-p)
+ (setq neo-global--window
+ (neo-global--create-window)))
+ neo-global--window)
+
+(defun neo-default-display-fn (buffer _alist)
+ "Display BUFFER to the left or right of the root window.
+The side is decided according to `neo-window-position'.
+The root window is the root window of the selected frame.
+_ALIST is ignored."
+ (let ((window-pos (if (eq neo-window-position 'left) 'left 'right)))
+ (display-buffer-in-side-window buffer `((side . ,window-pos)))))
+
+(defun neo-global--create-window ()
+ "Create global neotree window."
+ (let ((window nil)
+ (buffer (neo-global--get-buffer t)))
+ (setq window
+ (select-window
+ (display-buffer buffer neo-display-action)))
+ (neo-window--init window buffer)
+ (neo-global--attach)
+ (neo-global--reset-width)
+ window))
+
+(defun neo-global--get-buffer (&optional init-p)
+ "Return the global neotree buffer if it exists.
+If INIT-P is non-nil and global NeoTree buffer not exists, then create it."
+ (unless (equal (buffer-name neo-global--buffer)
+ neo-buffer-name)
+ (setf neo-global--buffer nil))
+ (when (and init-p
+ (null neo-global--buffer))
+ (save-window-excursion
+ (setq neo-global--buffer
+ (neo-buffer--create))))
+ neo-global--buffer)
+
+(defun neo-global--file-in-root-p (path)
+ "Return non-nil if PATH in root dir."
+ (neo-global--with-buffer
+ (and (not (null neo-buffer--start-node))
+ (neo-path--file-in-directory-p path neo-buffer--start-node))))
+
+(defun neo-global--alone-p ()
+ "Check whether the global neotree window is alone with some other window."
+ (let ((windows (window-list)))
+ (and (= (length windows)
+ 2)
+ (member neo-global--window windows))))
+
+(defun neo-global--do-autorefresh ()
+ "Do auto refresh."
+ (interactive)
+ (when (and neo-autorefresh (neo-global--window-exists-p)
+ (buffer-file-name))
+ (neotree-refresh t)))
+
+(defun neo-global--open ()
+ "Show the NeoTree window."
+ (let ((valid-start-node-p nil))
+ (neo-global--with-buffer
+ (setf valid-start-node-p (neo-buffer--valid-start-node-p)))
+ (if (not valid-start-node-p)
+ (neo-global--open-dir (neo-path--get-working-dir))
+ (neo-global--get-window t))))
+
+(defun neo-global--open-dir (path)
+ "Show the NeoTree window, and change root to PATH."
+ (neo-global--get-window t)
+ (neo-global--with-buffer
+ (neo-buffer--change-root path)))
+
+(defun neo-global--open-and-find (path)
+ "Quick select node which specified PATH in NeoTree."
+ (let ((npath path)
+ root-dir)
+ (when (null npath)
+ (throw 'invalid-path "Invalid path to select."))
+ (setq root-dir (if (file-directory-p npath)
+ npath (neo-path--updir npath)))
+ (when (or (not (neo-global--window-exists-p))
+ (not (neo-global--file-in-root-p npath)))
+ (neo-global--open-dir root-dir))
+ (neo-global--with-window
+ (neo-buffer--select-file-node npath t))))
+
+(defun neo-global--select-mru-window (arg)
+ "Create or find a window to select when open a file node.
+The description of ARG is in `neotree-enter'."
+ (when (eq (safe-length (window-list)) 1)
+ (neo-buffer--with-resizable-window
+ (split-window-horizontally)))
+ (when neo-reset-size-on-open
+ (neo-global--when-window
+ (neo-window--zoom 'minimize)))
+ ;; select target window
+ (cond
+ ;; select window with winum
+ ((and (integerp arg)
+ (bound-and-true-p winum-mode)
+ (fboundp 'winum-select-window-by-number))
+ (winum-select-window-by-number arg))
+ ;; select window with window numbering
+ ((and (integerp arg)
+ (boundp 'window-numbering-mode)
+ (symbol-value window-numbering-mode)
+ (fboundp 'select-window-by-number))
+ (select-window-by-number arg))
+ ;; open node in a new vertically split window
+ ((and (stringp arg) (string= arg "a")
+ (fboundp 'ace-select-window))
+ (ace-select-window))
+ ((and (stringp arg) (string= arg "|"))
+ (select-window (get-mru-window))
+ (split-window-right)
+ (windmove-right))
+ ;; open node in a new horizontally split window
+ ((and (stringp arg) (string= arg "-"))
+ (select-window (get-mru-window))
+ (split-window-below)
+ (windmove-down)))
+ ;; open node in last active window
+ (select-window (get-mru-window)))
+
+(defun neo-global--detach ()
+ "Detach the global neotree buffer."
+ (when neo-global--autorefresh-timer
+ (cancel-timer neo-global--autorefresh-timer))
+ (neo-global--with-buffer
+ (neo-buffer--unlock-width))
+ (setq neo-global--buffer nil)
+ (setq neo-global--window nil))
+
+(defun neo-global--attach ()
+ "Attach the global neotree buffer"
+ (when neo-global--autorefresh-timer
+ (cancel-timer neo-global--autorefresh-timer))
+ (when neo-autorefresh
+ (setq neo-global--autorefresh-timer
+ (run-with-idle-timer 2 10 'neo-global--do-autorefresh)))
+ (setq neo-global--buffer (get-buffer neo-buffer-name))
+ (setq neo-global--window (get-buffer-window
+ neo-global--buffer))
+ (neo-global--with-buffer
+ (neo-buffer--lock-width))
+ (run-hook-with-args 'neo-after-create-hook '(window)))
+
+(defun neo-global--set-window-width (width)
+ "Set neotree window width to WIDTH."
+ (neo-global--with-window
+ (neo-buffer--with-resizable-window
+ (neo-util--set-window-width (selected-window) width))))
+
+(defun neo-global--reset-width ()
+ "Set neotree window width to `neo-window-width'."
+ (neo-global--set-window-width neo-window-width))
+
+;;
+;; Advices
+;;
+
+(defadvice mouse-drag-vertical-line
+ (around neotree-drag-vertical-line (start-event) activate)
+ "Drag and drop is not affected by the lock."
+ (neo-buffer--with-resizable-window
+ ad-do-it))
+
+(defadvice balance-windows
+ (around neotree-balance-windows activate)
+ "Fix neotree inhibits balance-windows."
+ (if (neo-global--window-exists-p)
+ (let (old-width)
+ (neo-global--with-window
+ (setq old-width (window-width)))
+ (neo-buffer--with-resizable-window
+ ad-do-it)
+ (neo-global--with-window
+ (neo-global--set-window-width old-width)))
+ ad-do-it))
+
+(eval-after-load 'popwin
+ '(progn
+ (defadvice popwin:create-popup-window
+ (around neotree/popwin-popup-buffer activate)
+ (let ((neo-exists-p (neo-global--window-exists-p)))
+ (when neo-exists-p
+ (neo-global--detach))
+ ad-do-it
+ (when neo-exists-p
+ (neo-global--attach)
+ (neo-global--reset-width))))
+
+ (defadvice popwin:close-popup-window
+ (around neotree/popwin-close-popup-window activate)
+ (let ((neo-exists-p (neo-global--window-exists-p)))
+ (when neo-exists-p
+ (neo-global--detach))
+ ad-do-it
+ (when neo-exists-p
+ (neo-global--attach)
+ (neo-global--reset-width))))))
+
+;;
+;; Hooks
+;;
+
+(defun neo-hook--node-first-letter ()
+ "Move point to the first letter of the current node."
+ (when (or (eq this-command 'next-line)
+ (eq this-command 'previous-line))
+ (neo-point-auto-indent)))
+
+;;
+;; Util methods
+;;
+
+(defun neo-util--filter (condp lst)
+ "Apply CONDP to elements of LST keeping those that return non-nil.
+
+Example:
+ (neo-util--filter 'symbolp '(a \"b\" 3 d4))
+ => (a d4)
+
+This procedure does not work when CONDP is the `null' function."
+ (delq nil
+ (mapcar (lambda (x) (and (funcall condp x) x)) lst)))
+
+(defun neo-util--find (where which)
+ "Find element of the list WHERE matching predicate WHICH."
+ (catch 'found
+ (dolist (elt where)
+ (when (funcall which elt)
+ (throw 'found elt)))
+ nil))
+
+(defun neo-util--make-printable-string (string)
+ "Strip newline character from STRING, like 'Icon\n'."
+ (replace-regexp-in-string "\n" "" string))
+
+(defun neo-util--walk-dir (path)
+ "Return the subdirectories and subfiles of the PATH."
+ (let* ((full-path (neo-path--file-truename path)))
+ (condition-case nil
+ (directory-files
+ path 'full directory-files-no-dot-files-regexp)
+ ('file-error
+ (message "Walk directory %S failed." path)
+ nil))))
+
+(defun neo-util--hidden-path-filter (node)
+ "A filter function, if the NODE can not match each item in \
+`neo-hidden-regexp-list', return t."
+ (if (not neo-buffer--show-hidden-file-p)
+ (let ((shortname (neo-path--file-short-name node)))
+ (null (neo-util--filter
+ (lambda (x) (not (null (string-match-p x shortname))))
+ neo-hidden-regexp-list)))
+ node))
+
+(defun neo-str--trim-left (s)
+ "Remove whitespace at the beginning of S."
+ (if (string-match "\\`[ \t\n\r]+" s)
+ (replace-match "" t t s)
+ s))
+
+(defun neo-str--trim-right (s)
+ "Remove whitespace at the end of S."
+ (if (string-match "[ \t\n\r]+\\'" s)
+ (replace-match "" t t s)
+ s))
+
+(defun neo-str--trim (s)
+ "Remove whitespace at the beginning and end of S."
+ (neo-str--trim-left (neo-str--trim-right s)))
+
+(defun neo-path--expand-name (path &optional current-dir)
+ (expand-file-name (or (if (file-name-absolute-p path) path)
+ (let ((r-path path))
+ (setq r-path (substitute-in-file-name r-path))
+ (setq r-path (expand-file-name r-path current-dir))
+ r-path))))
+
+(defun neo-path--shorten (path len)
+ "Shorten a given PATH to a specified LEN.
+This is needed for paths, which are to long for the window to display
+completely. The function cuts of the first part of the path to remain
+the last folder (the current one)."
+ (let ((result
+ (if (> (length path) len)
+ (concat "<" (substring path (- (- len 2))))
+ path)))
+ (when result
+ (decode-coding-string result 'utf-8))))
+
+(defun neo-path--insert-chroot-button (label path face)
+ (insert-button
+ label
+ 'action '(lambda (x) (neotree-change-root))
+ 'follow-link t
+ 'face face
+ 'neo-full-path path))
+
+(defun neo-path--insert-header-buttonized (path)
+ "Shortens the PATH to (window-body-width) and displays any \
+visible remains as buttons that, when clicked, navigate to that
+parent directory."
+ (let* ((dirs (reverse (cl-maplist 'identity (reverse (split-string path "/" :omitnulls)))))
+ (last (car-safe (car-safe (last dirs)))))
+ (neo-path--insert-chroot-button "/" "/" 'neo-root-dir-face)
+ (dolist (dir dirs)
+ (if (string= (car dir) last)
+ (neo-buffer--insert-with-face last 'neo-root-dir-face)
+ (neo-path--insert-chroot-button
+ (concat (car dir) "/")
+ (apply 'neo-path--join (cons "/" (reverse dir)))
+ 'neo-root-dir-face))))
+ ;;shorten the line if need be
+ (when (> (current-column) (window-body-width))
+ (forward-char (- (window-body-width)))
+ (delete-region (point-at-bol) (point))
+ (let* ((button (button-at (point)))
+ (path (if button (overlay-get button 'neo-full-path) "/")))
+ (neo-path--insert-chroot-button "<" path 'neo-root-dir-face))
+ (end-of-line)))
+
+(defun neo-path--updir (path)
+ (let ((r-path (neo-path--expand-name path)))
+ (if (and (> (length r-path) 0)
+ (equal (substring r-path -1) "/"))
+ (setq r-path (substring r-path 0 -1)))
+ (if (eq (length r-path) 0)
+ (setq r-path "/"))
+ (directory-file-name
+ (file-name-directory r-path))))
+
+(defun neo-path--join (root &rest dirs)
+ "Joins a series of directories together with ROOT and DIRS.
+Like Python's os.path.join,
+ (neo-path--join \"/tmp\" \"a\" \"b\" \"c\") => /tmp/a/b/c ."
+ (or (if (not dirs) root)
+ (let ((tdir (car dirs))
+ (epath nil))
+ (setq epath
+ (or (if (equal tdir ".") root)
+ (if (equal tdir "..") (neo-path--updir root))
+ (neo-path--expand-name tdir root)))
+ (apply 'neo-path--join
+ epath
+ (cdr dirs)))))
+
+(defun neo-path--file-short-name (file)
+ "Base file/directory name by FILE.
+Taken from http://lists.gnu.org/archive/html/emacs-devel/2011-01/msg01238.html"
+ (or (if (string= file "/") "/")
+ (neo-util--make-printable-string (file-name-nondirectory (directory-file-name file)))))
+
+(defun neo-path--file-truename (path)
+ (let ((rlt (file-truename path)))
+ (if (not (null rlt))
+ (progn
+ (if (and (file-directory-p rlt)
+ (> (length rlt) 0)
+ (not (equal (substring rlt -1) "/")))
+ (setq rlt (concat rlt "/")))
+ rlt)
+ nil)))
+
+(defun neo-path--has-subfile-p (dir)
+ "To determine whether a directory(DIR) contain files."
+ (and (file-exists-p dir)
+ (file-directory-p dir)
+ (neo-util--walk-dir dir)
+ t))
+
+(defun neo-path--match-path-directory (path)
+ (let ((true-path (neo-path--file-truename path))
+ (rlt-path nil))
+ (setq rlt-path
+ (catch 'rlt
+ (if (file-directory-p true-path)
+ (throw 'rlt true-path))
+ (setq true-path
+ (file-name-directory true-path))
+ (if (file-directory-p true-path)
+ (throw 'rlt true-path))))
+ (if (not (null rlt-path))
+ (setq rlt-path (neo-path--join "." rlt-path "./")))
+ rlt-path))
+
+(defun neo-path--get-working-dir ()
+ "Return a directory name of the current buffer."
+ (file-name-as-directory (file-truename default-directory)))
+
+(defun neo-path--strip (path)
+ "Remove whitespace at the end of PATH."
+ (let* ((rlt (neo-str--trim path))
+ (pos (string-match "[\\\\/]+\\'" rlt)))
+ (when pos
+ (setq rlt (replace-match "" t t rlt))
+ (when (eq (length rlt) 0)
+ (setq rlt "/")))
+ rlt))
+
+(defun neo-path--path-equal-p (path1 path2)
+ "Return non-nil if pathes PATH1 and PATH2 are the same path."
+ (string-equal (neo-path--strip path1)
+ (neo-path--strip path2)))
+
+(defun neo-path--file-equal-p (file1 file2)
+ "Return non-nil if files FILE1 and FILE2 name the same file.
+If FILE1 or FILE2 does not exist, the return value is unspecified."
+ (unless (or (null file1)
+ (null file2))
+ (let ((nfile1 (neo-path--strip file1))
+ (nfile2 (neo-path--strip file2)))
+ (file-equal-p nfile1 nfile2))))
+
+(defun neo-path--file-in-directory-p (file dir)
+ "Return non-nil if FILE is in DIR or a subdirectory of DIR.
+A directory is considered to be \"in\" itself.
+Return nil if DIR is not an existing directory."
+ (let ((nfile (neo-path--strip file))
+ (ndir (neo-path--strip dir)))
+ (setq ndir (concat ndir "/"))
+ (file-in-directory-p nfile ndir)))
+
+(defun neo-util--kill-buffers-for-path (path)
+ "Kill all buffers for files in PATH."
+ (let ((buffer (find-buffer-visiting path)))
+ (when buffer
+ (kill-buffer buffer)))
+ (dolist (filename (directory-files path t directory-files-no-dot-files-regexp))
+ (let ((buffer (find-buffer-visiting filename)))
+ (when buffer
+ (kill-buffer buffer))
+ (when (and
+ (file-directory-p filename)
+ (neo-path--has-subfile-p filename))
+ (neo-util--kill-buffers-for-path filename)))))
+
+(defun neo-util--set-window-width (window n)
+ "Make WINDOW N columns width."
+ (let ((w (max n window-min-width)))
+ (unless (null window)
+ (if (> (window-width) w)
+ (shrink-window-horizontally (- (window-width) w))
+ (if (< (window-width) w)
+ (enlarge-window-horizontally (- w (window-width))))))))
+
+(defun neo-point-auto-indent ()
+ "Put the point on the first letter of the current node."
+ (when (neo-buffer--get-filename-current-line)
+ (beginning-of-line 1)
+ (re-search-forward "[^-\s+]" (line-end-position 1) t)
+ (backward-char 1)))
+
+(defun off-p (msg)
+ "Returns true regardless of message value in the argument."
+ t)
+
+(defun neo-sort-hidden-last (x y)
+ "Sort normally but with hidden files last."
+ (let ((x-hidden (neo-filepath-hidden-p x))
+ (y-hidden (neo-filepath-hidden-p y)))
+ (cond
+ ((and x-hidden (not y-hidden))
+ nil)
+ ((and (not x-hidden) y-hidden)
+ t)
+ (t
+ (string< x y)))))
+
+(defun neo-filepath-hidden-p (node)
+ "Return whether or not node is a hidden path."
+ (let ((shortname (neo-path--file-short-name node)))
+ (neo-util--filter
+ (lambda (x) (not (null (string-match-p x shortname))))
+ neo-hidden-regexp-list)))
+
+(defun neo-get-unsaved-buffers-from-projectile ()
+ "Return list of unsaved buffers from projectile buffers."
+ (interactive)
+ (let ((rlist '())
+ (rtag t))
+ (condition-case nil
+ (projectile-project-buffers)
+ (error (setq rtag nil)))
+ (when (and rtag (fboundp 'projectile-project-buffers))
+ (dolist (buf (projectile-project-buffers))
+ (with-current-buffer buf
+ (if (and (buffer-modified-p) buffer-file-name)
+ (setq rlist (cons (buffer-file-name) rlist))
+ ))))
+ rlist))
+
+;;
+;; Buffer methods
+;;
+
+(defun neo-buffer--newline-and-begin ()
+ "Insert new line."
+ (newline)
+ (beginning-of-line))
+
+(defun neo-buffer--get-icon (name)
+ "Get image by NAME."
+ (let ((icon-path (neo-path--join neo-dir "icons"))
+ image)
+ (setq image (create-image
+ (neo-path--join icon-path (concat name ".xpm"))
+ 'xpm nil :ascent 'center :mask '(heuristic t)))
+ image))
+
+(defun neo-buffer--insert-fold-symbol (name &optional node-name)
+ "Write icon by NAME, the icon style affected by neo-theme.
+`open' write opened folder icon.
+`close' write closed folder icon.
+`leaf' write leaf icon.
+Optional NODE-NAME is used for the `icons' theme"
+ (let ((n-insert-image (lambda (n)
+ (insert-image (neo-buffer--get-icon n))))
+ (n-insert-symbol (lambda (n)
+ (neo-buffer--insert-with-face
+ n 'neo-expand-btn-face))))
+ (cond
+ ((and (display-graphic-p) (equal neo-theme 'classic))
+ (or (and (equal name 'open) (funcall n-insert-image "open"))
+ (and (equal name 'close) (funcall n-insert-image "close"))
+ (and (equal name 'leaf) (funcall n-insert-image "leaf"))))
+ ((equal neo-theme 'arrow)
+ (or (and (equal name 'open) (funcall n-insert-symbol "▾"))
+ (and (equal name 'close) (funcall n-insert-symbol "▸"))))
+ ((equal neo-theme 'nerd)
+ (or (and (equal name 'open) (funcall n-insert-symbol "▾ "))
+ (and (equal name 'close) (funcall n-insert-symbol "▸ "))
+ (and (equal name 'leaf) (funcall n-insert-symbol " "))))
+ ((and (display-graphic-p) (equal neo-theme 'icons))
+ (unless (require 'all-the-icons nil 'noerror)
+ (error "Package `all-the-icons' isn't installed"))
+ (setq-local tab-width 1)
+ (or (and (equal name 'open) (insert (all-the-icons-icon-for-dir-with-chevron (directory-file-name node-name) "down")))
+ (and (equal name 'close) (insert (all-the-icons-icon-for-dir-with-chevron (directory-file-name node-name) "right")))
+ (and (equal name 'leaf) (insert (format "\t\t\t%s\t" (all-the-icons-icon-for-file node-name))))))
+ (t
+ (or (and (equal name 'open) (funcall n-insert-symbol "- "))
+ (and (equal name 'close) (funcall n-insert-symbol "+ ")))))))
+
+(defun neo-buffer--save-cursor-pos (&optional node-path line-pos)
+ "Save cursor position.
+If NODE-PATH and LINE-POS is nil, it will be save the current line node position."
+ (let ((cur-node-path nil)
+ (cur-line-pos nil)
+ (ws-wind (selected-window))
+ (ws-pos (window-start)))
+ (setq cur-node-path (if node-path
+ node-path
+ (neo-buffer--get-filename-current-line)))
+ (setq cur-line-pos (if line-pos
+ line-pos
+ (line-number-at-pos)))
+ (setq neo-buffer--cursor-pos (cons cur-node-path cur-line-pos))
+ (setq neo-buffer--last-window-pos (cons ws-wind ws-pos))))
+
+(defun neo-buffer--goto-cursor-pos ()
+ "Jump to saved cursor position."
+ (let ((line-pos nil)
+ (node (car neo-buffer--cursor-pos))
+ (line-pos (cdr neo-buffer--cursor-pos))
+ (ws-wind (car neo-buffer--last-window-pos))
+ (ws-pos (cdr neo-buffer--last-window-pos)))
+ (catch 'line-pos-founded
+ (unless (null node)
+ (setq line-pos 0)
+ (mapc
+ (lambda (x)
+ (setq line-pos (1+ line-pos))
+ (unless (null x)
+ (when (neo-path--path-equal-p x node)
+ (throw 'line-pos-founded line-pos))))
+ neo-buffer--node-list))
+ (setq line-pos (cdr neo-buffer--cursor-pos))
+ (throw 'line-pos-founded line-pos))
+ ;; goto line
+ (goto-char (point-min))
+ (neo-buffer--forward-line (1- line-pos))
+ ;; scroll window
+ (when (equal (selected-window) ws-wind)
+ (set-window-start ws-wind ws-pos t))))
+
+(defun neo-buffer--node-list-clear ()
+ "Clear node list."
+ (setq neo-buffer--node-list nil))
+
+(defun neo-buffer--node-list-set (line-num path)
+ "Set value in node list.
+LINE-NUM is the index of node list.
+PATH is value."
+ (let ((node-list-length (length neo-buffer--node-list))
+ (node-index line-num))
+ (when (null node-index)
+ (setq node-index (line-number-at-pos)))
+ (when (< node-list-length node-index)
+ (setq neo-buffer--node-list
+ (vconcat neo-buffer--node-list
+ (make-vector (- node-index node-list-length) nil))))
+ (aset neo-buffer--node-list (1- node-index) path))
+ neo-buffer--node-list)
+
+(defun neo-buffer--insert-with-face (content face)
+ (let ((pos-start (point)))
+ (insert content)
+ (set-text-properties pos-start
+ (point)
+ (list 'face face))))
+
+(defun neo-buffer--valid-start-node-p ()
+ (and (not (null neo-buffer--start-node))
+ (file-accessible-directory-p neo-buffer--start-node)))
+
+(defun neo-buffer--create ()
+ "Create and switch to NeoTree buffer."
+ (switch-to-buffer
+ (generate-new-buffer-name neo-buffer-name))
+ (neotree-mode)
+ ;; disable linum-mode
+ (when (and (boundp 'linum-mode)
+ (not (null linum-mode)))
+ (linum-mode -1))
+ ;; Use inside helm window in NeoTree
+ ;; Refs https://github.com/jaypei/emacs-neotree/issues/226
+ (setq-local helm-split-window-inside-p t)
+ (current-buffer))
+
+(defun neo-buffer--insert-banner ()
+ (unless (null neo-banner-message)
+ (let ((start (point)))
+ (insert neo-banner-message)
+ (set-text-properties start (point) '(face neo-banner-face)))
+ (neo-buffer--newline-and-begin)))
+
+(defun neo-buffer--insert-root-entry (node)
+ (neo-buffer--node-list-set nil node)
+ (cond ((eq neo-cwd-line-style 'button)
+ (neo-path--insert-header-buttonized node))
+ (t
+ (neo-buffer--insert-with-face (neo-path--shorten node (window-body-width))
+ 'neo-root-dir-face)))
+ (neo-buffer--newline-and-begin)
+ (when neo-show-updir-line
+ (neo-buffer--insert-fold-symbol 'close node)
+ (insert-button ".."
+ 'action '(lambda (x) (neotree-change-root))
+ 'follow-link t
+ 'face neo-dir-link-face
+ 'neo-full-path (neo-path--updir node))
+ (neo-buffer--newline-and-begin)))
+
+(defun neo-buffer--help-echo-message (node-name)
+ (cond
+ ((eq neo-help-echo-style 'default)
+ (if (<= (+ (current-column) (string-width node-name))
+ neo-window-width)
+ nil
+ node-name))
+ (t nil)))
+
+(defun neo-buffer--insert-dir-entry (node depth expanded)
+ (let ((node-short-name (neo-path--file-short-name node)))
+ (insert-char ?\s (* (- depth 1) 2)) ; indent
+ (when (memq 'char neo-vc-integration)
+ (insert-char ?\s 2))
+ (neo-buffer--insert-fold-symbol
+ (if expanded 'open 'close) node)
+ (insert-button (if neo-show-slash-for-folder (concat node-short-name "/") node-short-name)
+ 'follow-link t
+ 'face neo-dir-link-face
+ 'neo-full-path node
+ 'keymap neotree-dir-button-keymap
+ 'help-echo (neo-buffer--help-echo-message node-short-name))
+ (neo-buffer--node-list-set nil node)
+ (neo-buffer--newline-and-begin)))
+
+(defun neo-buffer--insert-file-entry (node depth)
+ (let ((node-short-name (neo-path--file-short-name node))
+ (vc (when neo-vc-integration (neo-vc-for-node node))))
+ (insert-char ?\s (* (- depth 1) 2)) ; indent
+ (when (memq 'char neo-vc-integration)
+ (insert-char (car vc))
+ (insert-char ?\s))
+ (neo-buffer--insert-fold-symbol 'leaf node-short-name)
+ (insert-button node-short-name
+ 'follow-link t
+ 'face (if (memq 'face neo-vc-integration)
+ (cdr vc)
+ neo-file-link-face)
+ 'neo-full-path node
+ 'keymap neotree-file-button-keymap
+ 'help-echo (neo-buffer--help-echo-message node-short-name))
+ (neo-buffer--node-list-set nil node)
+ (neo-buffer--newline-and-begin)))
+
+(defun neo-vc-for-node (node)
+ (let* ((backend (ignore-errors
+ (vc-responsible-backend node)))
+ (vc-state (when backend (vc-state node backend))))
+ (cons (cdr (assoc vc-state neo-vc-state-char-alist))
+ (cl-case vc-state
+ (up-to-date neo-vc-up-to-date-face)
+ (edited neo-vc-edited-face)
+ (needs-update neo-vc-needs-update-face)
+ (needs-merge neo-vc-needs-merge-face)
+ (unlocked-changes neo-vc-unlocked-changes-face)
+ (added neo-vc-added-face)
+ (removed neo-vc-removed-face)
+ (conflict neo-vc-conflict-face)
+ (missing neo-vc-missing-face)
+ (ignored neo-vc-ignored-face)
+ (unregistered neo-vc-unregistered-face)
+ (user neo-vc-user-face)
+ (otherwise neo-vc-default-face)))))
+
+(defun neo-buffer--get-nodes (path)
+ (let* ((nodes (neo-util--walk-dir path))
+ (comp neo-filepath-sort-function)
+ (nodes (neo-util--filter 'neo-util--hidden-path-filter nodes)))
+ (cons (sort (neo-util--filter 'file-directory-p nodes) comp)
+ (sort (neo-util--filter #'(lambda (f) (not (file-directory-p f))) nodes) comp))))
+
+(defun neo-buffer--get-node-index (node nodes)
+ "Return the index of NODE in NODES.
+
+NODES can be a list of directory or files.
+Return nil if NODE has not been found in NODES."
+ (let ((i 0)
+ (l (length nodes))
+ (cur (car nodes))
+ (rest (cdr nodes)))
+ (while (and cur (not (equal cur node)))
+ (setq i (1+ i))
+ (setq cur (car rest))
+ (setq rest (cdr rest)))
+ (if (< i l) i)))
+
+(defun neo-buffer--expanded-node-p (node)
+ "Return non-nil if NODE is expanded."
+ (neo-util--to-bool
+ (neo-util--find
+ neo-buffer--expanded-node-list
+ #'(lambda (x) (equal x node)))))
+
+(defun neo-buffer--set-expand (node do-expand)
+ "Set the expanded state of the NODE to DO-EXPAND.
+Return the new expand state for NODE (t for expanded, nil for collapsed)."
+ (if (not do-expand)
+ (setq neo-buffer--expanded-node-list
+ (neo-util--filter
+ #'(lambda (x) (not (equal node x)))
+ neo-buffer--expanded-node-list))
+ (push node neo-buffer--expanded-node-list))
+ do-expand)
+
+(defun neo-buffer--toggle-expand (node)
+ (neo-buffer--set-expand node (not (neo-buffer--expanded-node-p node))))
+
+(defun neo-buffer--insert-tree (path depth)
+ (if (eq depth 1)
+ (neo-buffer--insert-root-entry path))
+ (let* ((contents (neo-buffer--get-nodes path))
+ (nodes (car contents))
+ (leafs (cdr contents))
+ (default-directory path))
+ (dolist (node nodes)
+ (let ((expanded (neo-buffer--expanded-node-p node)))
+ (neo-buffer--insert-dir-entry
+ node depth expanded)
+ (if expanded (neo-buffer--insert-tree (concat node "/") (+ depth 1)))))
+ (dolist (leaf leafs)
+ (neo-buffer--insert-file-entry leaf depth))))
+
+(defun neo-buffer--refresh (save-pos-p &optional non-neotree-buffer)
+ "Refresh the NeoTree buffer.
+If SAVE-POS-P is non-nil, it will be auto save current line number."
+ (let ((start-node neo-buffer--start-node))
+ (unless start-node
+ (setq start-node default-directory))
+ (neo-buffer--with-editing-buffer
+ ;; save context
+ (when save-pos-p
+ (neo-buffer--save-cursor-pos))
+ (when non-neotree-buffer
+ (setq neo-buffer--start-node start-node))
+ ;; starting refresh
+ (erase-buffer)
+ (neo-buffer--node-list-clear)
+ (neo-buffer--insert-banner)
+ (setq neo-buffer--start-line neo-header-height)
+ (neo-buffer--insert-tree start-node 1))
+ ;; restore context
+ (neo-buffer--goto-cursor-pos)))
+
+(defun neo-buffer--post-move ()
+ "Reset current directory when position moved."
+ (funcall
+ (neotree-make-executor
+ :file-fn
+ '(lambda (path _)
+ (setq default-directory (neo-path--updir btn-full-path)))
+ :dir-fn
+ '(lambda (path _)
+ (setq default-directory (file-name-as-directory path))))))
+
+(defun neo-buffer--get-button-current-line ()
+ "Return the first button in current line."
+ (let* ((btn-position nil)
+ (pos-line-start (line-beginning-position))
+ (pos-line-end (line-end-position))
+ ;; NOTE: cannot find button when the button
+ ;; at beginning of the line
+ (current-button (or (button-at (point))
+ (button-at pos-line-start))))
+ (if (null current-button)
+ (progn
+ (setf btn-position
+ (catch 'ret-button
+ (let* ((next-button (next-button pos-line-start))
+ (pos-btn nil))
+ (if (null next-button) (throw 'ret-button nil))
+ (setf pos-btn (overlay-start next-button))
+ (if (> pos-btn pos-line-end) (throw 'ret-button nil))
+ (throw 'ret-button pos-btn))))
+ (if (null btn-position)
+ nil
+ (setf current-button (button-at btn-position)))))
+ current-button))
+
+(defun neo-buffer--get-filename-current-line (&optional default)
+ "Return filename for first button in current line.
+If there is no button in current line, then return DEFAULT."
+ (let ((btn (neo-buffer--get-button-current-line)))
+ (if (not (null btn))
+ (button-get btn 'neo-full-path)
+ default)))
+
+(defun neo-buffer--lock-width ()
+ "Lock the width size for NeoTree window."
+ (if neo-window-fixed-size
+ (setq window-size-fixed 'width)))
+
+(defun neo-buffer--unlock-width ()
+ "Unlock the width size for NeoTree window."
+ (setq window-size-fixed nil))
+
+(defun neo-buffer--rename-node ()
+ "Rename current node as another path."
+ (interactive)
+ (let* ((current-path (neo-buffer--get-filename-current-line))
+ (buffer (find-buffer-visiting current-path))
+ to-path
+ msg)
+ (unless (null current-path)
+ (setq msg (format "Rename [%s] to: " (neo-path--file-short-name current-path)))
+ (setq to-path (read-file-name msg (file-name-directory current-path)))
+ (if buffer
+ (with-current-buffer buffer
+ (set-visited-file-name to-path nil t)))
+ (if (vc-registered current-path)
+ (vc-rename-file current-path to-path)
+ (rename-file current-path to-path 1))
+ (neo-buffer--refresh t)
+ (message "Rename successful."))))
+
+(defun neo-buffer--copy-node ()
+ "Copies current node as another path."
+ (interactive)
+ (let* ((current-path (neo-buffer--get-filename-current-line))
+ (buffer (find-buffer-visiting current-path))
+ to-path
+ msg)
+ (unless (null current-path)
+ (setq msg (format "Copy [%s] to: " (neo-path--file-short-name current-path)))
+ (setq to-path (read-file-name msg (file-name-directory current-path)))
+ (if (file-directory-p current-path)
+ (copy-directory current-path to-path)
+ (copy-file current-path to-path))
+ (neo-buffer--refresh t)
+ (message "Copy successful."))))
+
+(defun neo-buffer--select-file-node (file &optional recursive-p)
+ "Select the node that corresponds to the FILE.
+If RECURSIVE-P is non nil, find files will recursively."
+ (let ((efile file)
+ (iter-curr-dir nil)
+ (file-node-find-p nil)
+ (file-node-list nil))
+ (unless (file-name-absolute-p efile)
+ (setq efile (expand-file-name efile)))
+ (setq iter-curr-dir efile)
+ (catch 'return
+ (while t
+ (setq iter-curr-dir (neo-path--updir iter-curr-dir))
+ (push iter-curr-dir file-node-list)
+ (when (neo-path--file-equal-p iter-curr-dir neo-buffer--start-node)
+ (setq file-node-find-p t)
+ (throw 'return nil))
+ (let ((niter-curr-dir (file-remote-p iter-curr-dir 'localname)))
+ (unless niter-curr-dir
+ (setq niter-curr-dir iter-curr-dir))
+ (when (neo-path--file-equal-p niter-curr-dir "/")
+ (setq file-node-find-p nil)
+ (throw 'return nil)))))
+ (when file-node-find-p
+ (dolist (p file-node-list)
+ (neo-buffer--set-expand p t))
+ (neo-buffer--save-cursor-pos file)
+ (neo-buffer--refresh nil))))
+
+(defun neo-buffer--change-root (root-dir)
+ "Change the tree root to ROOT-DIR."
+ (let ((path root-dir)
+ start-path)
+ (unless (and (file-exists-p path)
+ (file-directory-p path))
+ (throw 'error "The path is not a valid directory."))
+ (setq start-path (expand-file-name (substitute-in-file-name path)))
+ (setq neo-buffer--start-node start-path)
+ (cd start-path)
+ (neo-buffer--save-cursor-pos path nil)
+ (neo-buffer--refresh nil)))
+
+(defun neo-buffer--get-nodes-for-select-down-node (path)
+ "Return the node list for the down dir selection."
+ (if path
+ (when (file-name-directory path)
+ (if (neo-buffer--expanded-node-p path)
+ (neo-buffer--get-nodes path)
+ (neo-buffer--get-nodes (file-name-directory path))))
+ (neo-buffer--get-nodes (file-name-as-directory neo-buffer--start-node))))
+
+(defun neo-buffer--get-nodes-for-sibling (path)
+ "Return the node list for the sibling selection. Return nil of no nodes can
+be found.
+The returned list is a directory list if path is a directory, otherwise it is
+a file list."
+ (when path
+ (let ((nodes (neo-buffer--get-nodes (file-name-directory path))))
+ (if (file-directory-p path)
+ (car nodes)
+ (cdr nodes)))))
+
+(defun neo-buffer--sibling (path &optional previous)
+ "Return the next sibling of node PATH.
+If PREVIOUS is non-nil the previous sibling is returned."
+ (let* ((nodes (neo-buffer--get-nodes-for-sibling path)))
+ (when nodes
+ (let ((i (neo-buffer--get-node-index path nodes))
+ (l (length nodes)))
+ (if i (nth (mod (+ i (if previous -1 1)) l) nodes))))))
+
+(defun neo-buffer--execute (arg &optional file-fn dir-fn)
+ "Define the behaviors for keyboard event.
+ARG is the parameter for command.
+If FILE-FN is non-nil, it will executed when a file node.
+If DIR-FN is non-nil, it will executed when a dir node."
+ (interactive "P")
+ (let* ((btn-full-path (neo-buffer--get-filename-current-line))
+ is-file-p
+ enter-fn)
+ (unless (null btn-full-path)
+ (setq is-file-p (not (file-directory-p btn-full-path))
+ enter-fn (if is-file-p file-fn dir-fn))
+ (unless (null enter-fn)
+ (funcall enter-fn btn-full-path arg)
+ (run-hook-with-args
+ 'neo-enter-hook
+ (if is-file-p 'file 'directory)
+ btn-full-path
+ arg)))
+ btn-full-path))
+
+(defun neo-buffer--set-show-hidden-file-p (show-p)
+ "If SHOW-P is non-nil, show hidden nodes in tree."
+ (setq neo-buffer--show-hidden-file-p show-p)
+ (neo-buffer--refresh t))
+
+(defun neo-buffer--forward-line (n)
+ "Move N lines forward in NeoTree buffer."
+ (forward-line (or n 1))
+ (neo-buffer--post-move))
+
+;;
+;; Mode-line methods
+;;
+
+(defun neo-mode-line--compute-format (parent index ndirs nfiles)
+ "Return a formated string to be used in the `neotree' mode-line."
+ (let* ((nall (+ ndirs nfiles))
+ (has-dirs (> ndirs 0))
+ (has-files (> nfiles 0))
+ (msg-index (when index (format "[%s/%s] " index nall)))
+ (msg-ndirs (when has-dirs (format (if has-files " (D:%s" " (D:%s)") ndirs)))
+ (msg-nfiles (when has-files (format (if has-dirs " F:%s)" " (F:%s)") nfiles)))
+ (msg-directory (file-name-nondirectory (directory-file-name parent)))
+ (msg-directory-max-length (- (window-width)
+ (length msg-index)
+ (length msg-ndirs)
+ (length msg-nfiles))))
+ (setq msg-directory (if (<= (length msg-directory) msg-directory-max-length)
+ msg-directory
+ (concat (substring msg-directory
+ 0 (- msg-directory-max-length 3))
+ "...")))
+ (propertize
+ (decode-coding-string (concat msg-index msg-directory msg-ndirs msg-nfiles) 'utf-8)
+ 'help-echo (decode-coding-string parent 'utf-8))))
+
+;;
+;; Window methods
+;;
+
+(defun neo-window--init (window buffer)
+ "Make WINDOW a NeoTree window.
+NeoTree buffer is BUFFER."
+ (neo-buffer--with-resizable-window
+ (switch-to-buffer buffer)
+ (set-window-parameter window 'no-delete-other-windows t)
+ (set-window-dedicated-p window t))
+ window)
+
+(defun neo-window--zoom (method)
+ "Zoom the NeoTree window, the METHOD should one of these options:
+'maximize 'minimize 'zoom-in 'zoom-out."
+ (neo-buffer--unlock-width)
+ (cond
+ ((eq method 'maximize)
+ (maximize-window))
+ ((eq method 'minimize)
+ (neo-util--set-window-width (selected-window) neo-window-width))
+ ((eq method 'zoom-in)
+ (shrink-window-horizontally 2))
+ ((eq method 'zoom-out)
+ (enlarge-window-horizontally 2)))
+ (neo-buffer--lock-width))
+
+(defun neo-window--minimize-p ()
+ "Return non-nil when the NeoTree window is minimize."
+ (<= (window-width) neo-window-width))
+
+;;
+;; Interactive functions
+;;
+
+(defun neotree-next-line (&optional count)
+ "Move next line in NeoTree buffer.
+Optional COUNT argument, moves COUNT lines down."
+ (interactive "p")
+ (neo-buffer--forward-line (or count 1)))
+
+(defun neotree-previous-line (&optional count)
+ "Move previous line in NeoTree buffer.
+Optional COUNT argument, moves COUNT lines up."
+ (interactive "p")
+ (neo-buffer--forward-line (- (or count 1))))
+
+;;;###autoload
+(defun neotree-find (&optional path default-path)
+ "Quick select node which specified PATH in NeoTree.
+If path is nil and no buffer file name, then use DEFAULT-PATH,"
+ (interactive)
+ (let* ((ndefault-path (if default-path default-path
+ (neo-path--get-working-dir)))
+ (npath (if path path
+ (or (buffer-file-name) ndefault-path)))
+ (do-open-p nil))
+ (if (and (not neo-force-change-root)
+ (not (neo-global--file-in-root-p npath))
+ (neo-global--window-exists-p))
+ (setq do-open-p (funcall neo-confirm-change-root "File not found in root path, do you want to change root?"))
+ (setq do-open-p t))
+ (when do-open-p
+ (neo-global--open-and-find npath))
+ (when neo-auto-indent-point
+ (neo-point-auto-indent)))
+ (neo-global--select-window))
+
+(defun neotree-click-changes-root-toggle ()
+ "Toggle the variable neo-click-changes-root.
+If true, clicking on a directory will change the current root to
+the directory instead of showing the directory contents."
+ (interactive)
+ (setq neo-click-changes-root (not neo-click-changes-root)))
+
+(defun neo-open-dir (full-path &optional arg)
+ "Toggle fold a directory node.
+
+FULL-PATH is the path of the directory.
+ARG is ignored."
+ (if neo-click-changes-root
+ (neotree-change-root)
+ (progn
+ (let ((new-state (neo-buffer--toggle-expand full-path)))
+ (neo-buffer--refresh t)
+ (when neo-auto-indent-point
+ (when new-state (forward-line 1))
+ (neo-point-auto-indent))))))
+
+
+(defun neo--expand-recursive (path state)
+ "Set the state of children recursively.
+
+The children of PATH will have state STATE."
+ (let ((children (car (neo-buffer--get-nodes path) )))
+ (dolist (node children)
+ (neo-buffer--set-expand node state)
+ (neo--expand-recursive node state ))))
+
+(defun neo-open-dir-recursive (full-path &optional arg)
+ "Toggle fold a directory node recursively.
+
+The children of the node will also be opened recursively.
+FULL-PATH is the path of the directory.
+ARG is ignored."
+ (if neo-click-changes-root
+ (neotree-change-root)
+ (let ((new-state (neo-buffer--toggle-expand full-path))
+ (children (car (neo-buffer--get-nodes full-path))))
+ (dolist (node children)
+ (neo-buffer--set-expand node new-state)
+ (neo--expand-recursive node new-state))
+ (neo-buffer--refresh t))))
+
+(defun neo-open-dired (full-path &optional arg)
+ "Open file or directory node in `dired-mode'.
+
+FULL-PATH is the path of node.
+ARG is same as `neo-open-file'."
+ (neo-global--select-mru-window arg)
+ (dired full-path))
+
+(defun neo-open-file (full-path &optional arg)
+ "Open a file node.
+
+FULL-PATH is the file path you want to open.
+If ARG is an integer then the node is opened in a window selected via
+`winum' or`window-numbering' (if available) according to the passed number.
+If ARG is `|' then the node is opened in new vertically split window.
+If ARG is `-' then the node is opened in new horizontally split window."
+ (neo-global--select-mru-window arg)
+ (find-file full-path))
+
+(defun neo-open-file-vertical-split (full-path arg)
+ "Open the current node is a vertically split window.
+FULL-PATH and ARG are the same as `neo-open-file'."
+ (neo-open-file full-path "|"))
+
+(defun neo-open-file-horizontal-split (full-path arg)
+ "Open the current node is horizontally split window.
+FULL-PATH and ARG are the same as `neo-open-file'."
+ (neo-open-file full-path "-"))
+
+(defun neo-open-file-ace-window (full-path arg)
+ "Open the current node in a window chosen by ace-window.
+FULL-PATH and ARG are the same as `neo-open-file'."
+ (neo-open-file full-path "a"))
+
+(defun neotree-open-file-in-system-application ()
+ "Open a file under point in the system application."
+ (interactive)
+ (call-process neo-default-system-application nil 0 nil
+ (neo-buffer--get-filename-current-line)))
+
+(defun neotree-change-root ()
+ "Change root to current node dir.
+If current node is a file, then it will do nothing.
+If cannot find any node in current line, it equivalent to using `neotree-dir'."
+ (interactive)
+ (neo-global--select-window)
+ (let ((btn-full-path (neo-buffer--get-filename-current-line)))
+ (if (null btn-full-path)
+ (call-interactively 'neotree-dir)
+ (neo-global--open-dir btn-full-path))))
+
+(defun neotree-select-up-node ()
+ "Select the parent directory of the current node. Change the root if
+necessary. "
+ (interactive)
+ (neo-global--select-window)
+ (let* ((btn-full-path (neo-buffer--get-filename-current-line))
+ (btn-parent-dir (if btn-full-path (file-name-directory btn-full-path)))
+ (root-slash (file-name-as-directory neo-buffer--start-node)))
+ (cond
+ ((equal btn-parent-dir root-slash) (neo-global--open-dir root-slash))
+ (btn-parent-dir (neotree-find btn-parent-dir))
+ (t (neo-global--open-dir (file-name-directory
+ (directory-file-name root-slash)))))))
+
+(defun neotree-select-down-node ()
+ "Select an expanded directory or content directory according to the
+current node, in this order:
+- select the first expanded child node if the current node has one
+- select the content of current node if it is expanded
+- select the next expanded sibling if the current node is not expanded."
+ (interactive)
+ (let* ((btn-full-path (neo-buffer--get-filename-current-line))
+ (path (if btn-full-path btn-full-path neo-buffer--start-node))
+ (nodes (neo-buffer--get-nodes-for-select-down-node path)))
+ (when nodes
+ (if (or (equal path neo-buffer--start-node)
+ (neo-buffer--expanded-node-p path))
+ ;; select the first expanded child node
+ (let ((expanded-dir (catch 'break
+ (dolist (node (car nodes))
+ (if (neo-buffer--expanded-node-p node)
+ (throw 'break node)))
+ nil)))
+ (if expanded-dir
+ (neotree-find expanded-dir)
+ ;; select the directory content if needed
+ (let ((dirs (car nodes))
+ (files (cdr nodes)))
+ (if (> (length dirs) 0)
+ (neotree-find (car dirs))
+ (when (> (length files) 0)
+ (neotree-find (car files)))))))
+ ;; select the next expanded sibling
+ (let ((sibling (neo-buffer--sibling path)))
+ (while (and (not (neo-buffer--expanded-node-p sibling))
+ (not (equal sibling path)))
+ (setq sibling (neo-buffer--sibling sibling)))
+ (when (not (string< sibling path))
+ ;; select next expanded sibling
+ (neotree-find sibling)))))))
+
+(defun neotree-select-next-sibling-node ()
+ "Select the next sibling of current node.
+If the current node is the last node then the first node is selected."
+ (interactive)
+ (let ((sibling (neo-buffer--sibling (neo-buffer--get-filename-current-line))))
+ (when sibling (neotree-find sibling))))
+
+(defun neotree-select-previous-sibling-node ()
+ "Select the previous sibling of current node.
+If the current node is the first node then the last node is selected."
+ (interactive)
+ (let ((sibling (neo-buffer--sibling (neo-buffer--get-filename-current-line) t)))
+ (when sibling (neotree-find sibling))))
+
+(defun neotree-create-node (filename)
+ "Create a file or directory use specified FILENAME in current node."
+ (interactive
+ (let* ((current-dir (neo-buffer--get-filename-current-line neo-buffer--start-node))
+ (current-dir (neo-path--match-path-directory current-dir))
+ (filename (read-file-name "Filename:" current-dir)))
+ (if (file-directory-p filename)
+ (setq filename (concat filename "/")))
+ (list filename)))
+ (catch 'rlt
+ (let ((is-file nil))
+ (when (= (length filename) 0)
+ (throw 'rlt nil))
+ (setq is-file (not (equal (substring filename -1) "/")))
+ (when (file-exists-p filename)
+ (message "File %S already exists." filename)
+ (throw 'rlt nil))
+ (when (and is-file
+ (funcall neo-confirm-create-file (format "Do you want to create file %S ?"
+ filename)))
+ ;; ensure parent directory exist before saving
+ (mkdir (substring filename 0 (+ 1 (cl-position ?/ filename :from-end t))) t)
+ ;; NOTE: create a empty file
+ (write-region "" nil filename)
+ (neo-buffer--save-cursor-pos filename)
+ (neo-buffer--refresh nil)
+ (if neo-create-file-auto-open
+ (find-file-other-window filename)))
+ (when (and (not is-file)
+ (funcall neo-confirm-create-directory (format "Do you want to create directory %S?"
+ filename)))
+ (mkdir filename t)
+ (neo-buffer--save-cursor-pos filename)
+ (neo-buffer--refresh nil)))))
+
+(defun neotree-delete-node ()
+ "Delete current node."
+ (interactive)
+ (let* ((filename (neo-buffer--get-filename-current-line))
+ (buffer (find-buffer-visiting filename))
+ (deleted-p nil)
+ (trash delete-by-moving-to-trash))
+ (catch 'end
+ (if (null filename) (throw 'end nil))
+ (if (not (file-exists-p filename)) (throw 'end nil))
+ (if (not (funcall neo-confirm-delete-file (format "Do you really want to delete %S?"
+ filename)))
+ (throw 'end nil))
+ (if (file-directory-p filename)
+ ;; delete directory
+ (progn
+ (unless (neo-path--has-subfile-p filename)
+ (delete-directory filename nil trash)
+ (setq deleted-p t)
+ (throw 'end nil))
+ (when (funcall neo-confirm-delete-directory-recursively
+ (format "%S is a directory, delete it recursively?"
+ filename))
+ (when (funcall neo-confirm-kill-buffers-for-files-in-directory
+ (format "kill buffers for files in directory %S?"
+ filename))
+ (neo-util--kill-buffers-for-path filename))
+ (delete-directory filename t trash)
+ (setq deleted-p t)))
+ ;; delete file
+ (progn
+ (delete-file filename trash)
+ (when buffer
+ (kill-buffer-ask buffer))
+ (setq deleted-p t))))
+ (when deleted-p
+ (message "%S deleted." filename)
+ (neo-buffer--refresh t))
+ filename))
+
+(defun neotree-rename-node ()
+ "Rename current node."
+ (interactive)
+ (neo-buffer--rename-node))
+
+(defun neotree-copy-node ()
+ "Copy current node."
+ (interactive)
+ (neo-buffer--copy-node))
+
+(defun neotree-hidden-file-toggle ()
+ "Toggle show hidden files."
+ (interactive)
+ (neo-buffer--set-show-hidden-file-p (not neo-buffer--show-hidden-file-p)))
+
+(defun neotree-empty-fn ()
+ "Used to bind the empty function to the shortcut."
+ (interactive))
+
+(defun neotree-refresh (&optional is-auto-refresh)
+ "Refresh the NeoTree buffer."
+ (interactive)
+ (if (eq (current-buffer) (neo-global--get-buffer))
+ (neo-buffer--refresh t)
+ (save-excursion
+ (let ((cw (selected-window))) ;; save current window
+ (if is-auto-refresh
+ (let ((origin-buffer-file-name (buffer-file-name)))
+ (when (and (fboundp 'projectile-project-p)
+ (projectile-project-p)
+ (fboundp 'projectile-project-root))
+ (neo-global--open-dir (projectile-project-root))
+ (neotree-find (projectile-project-root)))
+ (neotree-find origin-buffer-file-name))
+ (neo-buffer--refresh t t))
+ (recenter)
+ (when (or is-auto-refresh neo-toggle-window-keep-p)
+ (select-window cw))))))
+
+(defun neotree-stretch-toggle ()
+ "Make the NeoTree window toggle maximize/minimize."
+ (interactive)
+ (neo-global--with-window
+ (if (neo-window--minimize-p)
+ (neo-window--zoom 'maximize)
+ (neo-window--zoom 'minimize))))
+
+(defun neotree-collapse-all ()
+ (interactive)
+ "Collapse all expanded folders in the neotree buffer"
+ (setq list-of-expanded-folders neo-buffer--expanded-node-list)
+ (dolist (folder list-of-expanded-folders)
+ (neo-buffer--toggle-expand folder)
+ (neo-buffer--refresh t)
+ )
+ )
+;;;###autoload
+(defun neotree-projectile-action ()
+ "Integration with `Projectile'.
+
+Usage:
+ (setq projectile-switch-project-action 'neotree-projectile-action).
+
+When running `projectile-switch-project' (C-c p p), `neotree' will change root
+automatically."
+ (interactive)
+ (cond
+ ((fboundp 'projectile-project-root)
+ (neotree-dir (projectile-project-root)))
+ (t
+ (error "Projectile is not available"))))
+
+;;;###autoload
+(defun neotree-toggle ()
+ "Toggle show the NeoTree window."
+ (interactive)
+ (if (neo-global--window-exists-p)
+ (neotree-hide)
+ (neotree-show)))
+
+;;;###autoload
+(defun neotree-show ()
+ "Show the NeoTree window."
+ (interactive)
+ (let ((cw (selected-window))
+ (path (buffer-file-name))) ;; save current window and buffer
+ (if neo-smart-open
+ (progn
+ (when (and (fboundp 'projectile-project-p)
+ (projectile-project-p)
+ (fboundp 'projectile-project-root))
+ (neotree-dir (projectile-project-root)))
+ (neotree-find path))
+ (neo-global--open))
+ (neo-global--select-window)
+ (when neo-toggle-window-keep-p
+ (select-window cw))))
+
+;;;###autoload
+(defun neotree-hide ()
+ "Close the NeoTree window."
+ (interactive)
+ (if (neo-global--window-exists-p)
+ (delete-window neo-global--window)))
+
+;;;###autoload
+(defun neotree-dir (path)
+ "Show the NeoTree window, and change root to PATH."
+ (interactive "DDirectory: ")
+ (neo-global--open-dir path)
+ (neo-global--select-window))
+
+;;;###autoload
+(defalias 'neotree 'neotree-show "Show the NeoTree window.")
+
+;;
+;; backward compatible
+;;
+
+(defun neo-bc--make-obsolete-message (from to)
+ (message "Warning: `%S' is obsolete. Use `%S' instead." from to))
+
+(defun neo-buffer--enter-file (path)
+ (neo-bc--make-obsolete-message 'neo-buffer--enter-file 'neo-open-file))
+
+(defun neo-buffer--enter-dir (path)
+ (neo-bc--make-obsolete-message 'neo-buffer--enter-dir 'neo-open-dir))
+
+(defun neotree-enter (&optional arg)
+ "NeoTree typical open event.
+ARG are the same as `neo-open-file'."
+ (interactive "P")
+ (neo-buffer--execute arg 'neo-open-file 'neo-open-dir))
+
+(defun neotree-quick-look (&optional arg)
+ "Quick Look like NeoTree open event.
+ARG are the same as `neo-open-file'."
+ (interactive "P")
+ (neotree-enter arg)
+ (neo-global--select-window))
+
+(defun neotree-enter-vertical-split ()
+ "NeoTree open event, file node will opened in new vertically split window."
+ (interactive)
+ (neo-buffer--execute nil 'neo-open-file-vertical-split 'neo-open-dir))
+
+(defun neotree-enter-horizontal-split ()
+ "NeoTree open event, file node will opened in new horizontally split window."
+ (interactive)
+ (neo-buffer--execute nil 'neo-open-file-horizontal-split 'neo-open-dir))
+
+(defun neotree-enter-ace-window ()
+ "NeoTree open event, file node will be opened in window chosen by ace-window."
+ (interactive)
+ (neo-buffer--execute nil 'neo-open-file-ace-window 'neo-open-dir))
+
+(defun neotree-copy-filepath-to-yank-ring ()
+ "Neotree convenience interactive function: file node path will be added to the kill ring."
+ (interactive)
+ (kill-new (neo-buffer--get-filename-current-line)))
+
+(defun neotree-split-window-sensibly (&optional window)
+ "An neotree-version of split-window-sensibly,
+which is used to fix issue #209.
+(setq split-window-preferred-function 'neotree-split-window-sensibly)"
+ (let ((window (or window (selected-window))))
+ (or (split-window-sensibly window)
+ (and (get-buffer-window neo-buffer-name)
+ (not (window-minibuffer-p window))
+ ;; If WINDOW is the only window on its frame
+ ;; (or only include Neo window) and is not the
+ ;; minibuffer window, try to split it vertically disregarding
+ ;; the value of `split-height-threshold'.
+ (let ((split-height-threshold 0))
+ (when (window-splittable-p window)
+ (with-selected-window window
+ (split-window-below))))))))
+
+(provide 'neotree)
+;;; neotree.el ends here
+
diff --git a/site-lisp/extensions-local/scroll-next-window.el b/site-lisp/extensions-local/scroll-next-window.el
new file mode 100644
index 0000000..c4db9b7
--- /dev/null
+++ b/site-lisp/extensions-local/scroll-next-window.el
@@ -0,0 +1,42 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+
+;;; Code:
+;'up' to see previous content
+;'down' to see further content
+(defun scroll-next-window-up ()
+ (interactive)
+ (scroll-next-window-internal "up"))
+
+(defun scroll-next-window-down ()
+ (interactive)
+ (scroll-next-window-internal "down"))
+
+(defun scroll-next-window-up-line ()
+ (interactive)
+ (scroll-next-window-internal "up" 1))
+
+(defun scroll-next-window-down-line ()
+ (interactive)
+ (scroll-next-window-internal "down" 1))
+
+(defun scroll-next-window-internal (direction &optional line)
+ (save-excursion
+ ;; Switch to next window.
+ (other-window 1)
+ ;; Do scroll operation.
+ (ignore-errors
+ (if (string-equal direction "up")
+ (if line
+ (scroll-up line)
+ (scroll-up))
+ (if line
+ (scroll-down line)
+ (scroll-down))))
+ ;; Switch back to current window.
+ (other-window -1)
+ ))
+
+(provide 'scroll-next-window)
+
+;;; scroll-next-window.el ends here
diff --git a/site-lisp/extensions-local/undo-tree.el b/site-lisp/extensions-local/undo-tree.el
new file mode 100644
index 0000000..9919fec
--- /dev/null
+++ b/site-lisp/extensions-local/undo-tree.el
@@ -0,0 +1,4653 @@
+;;; undo-tree.el --- Treat undo history as a tree -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2009-2020 Free Software Foundation, Inc
+
+;; Author: Toby Cubitt
+;; Maintainer: Toby Cubitt
+;; Version: 0.7.4
+;; Keywords: convenience, files, undo, redo, history, tree
+;; URL: http://www.dr-qubit.org/emacs.php
+;; Repository: http://www.dr-qubit.org/git/undo-tree.git
+
+;; This file is 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 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 GNU Emacs. If not, see .
+
+
+;;; Commentary:
+;;
+;; Emacs has a powerful undo system. Unlike the standard undo/redo system in
+;; most software, it allows you to recover *any* past state of a buffer
+;; (whereas the standard undo/redo system can lose past states as soon as you
+;; redo). However, this power comes at a price: many people find Emacs' undo
+;; system confusing and difficult to use, spawning a number of packages that
+;; replace it with the less powerful but more intuitive undo/redo system.
+;;
+;; Both the loss of data with standard undo/redo, and the confusion of Emacs'
+;; undo, stem from trying to treat undo history as a linear sequence of
+;; changes. It's not. The `undo-tree-mode' provided by this package replaces
+;; Emacs' undo system with a system that treats undo history as what it is: a
+;; branching tree of changes. This simple idea allows the more intuitive
+;; behaviour of the standard undo/redo system to be combined with the power of
+;; never losing any history. An added side bonus is that undo history can in
+;; some cases be stored more efficiently, allowing more changes to accumulate
+;; before Emacs starts discarding history.
+;;
+;; The only downside to this more advanced yet simpler undo system is that it
+;; was inspired by Vim. But, after all, most successful religions steal the
+;; best ideas from their competitors!
+;;
+;;
+;; Installation
+;; ============
+;;
+;; This package has only been tested with Emacs versions 24 and CVS. It should
+;; work in Emacs versions 22 and 23 too, but will not work without
+;; modifications in earlier versions of Emacs.
+;;
+;; To install `undo-tree-mode', make sure this file is saved in a directory in
+;; your `load-path', and add the line:
+;;
+;; (require 'undo-tree)
+;;
+;; to your .emacs file. Byte-compiling undo-tree.el is recommended (e.g. using
+;; "M-x byte-compile-file" from within emacs).
+;;
+;; If you want to replace the standard Emacs' undo system with the
+;; `undo-tree-mode' system in all buffers, you can enable it globally by
+;; adding:
+;;
+;; (global-undo-tree-mode)
+;;
+;; to your .emacs file.
+;;
+;;
+;; Quick-Start
+;; ===========
+;;
+;; If you're the kind of person who likes to jump in the car and drive,
+;; without bothering to first figure out whether the button on the left dips
+;; the headlights or operates the ejector seat (after all, you'll soon figure
+;; it out when you push it), then here's the minimum you need to know:
+;;
+;; `undo-tree-mode' and `global-undo-tree-mode'
+;; Enable undo-tree mode (either in the current buffer or globally).
+;;
+;; C-_ C-/ (`undo-tree-undo')
+;; Undo changes.
+;;
+;; M-_ C-? (`undo-tree-redo')
+;; Redo changes.
+;;
+;; `undo-tree-switch-branch'
+;; Switch undo-tree branch.
+;; (What does this mean? Better press the button and see!)
+;;
+;; C-x u (`undo-tree-visualize')
+;; Visualize the undo tree.
+;; (Better try pressing this button too!)
+;;
+;; C-x r u (`undo-tree-save-state-to-register')
+;; Save current buffer state to register.
+;;
+;; C-x r U (`undo-tree-restore-state-from-register')
+;; Restore buffer state from register.
+;;
+;;
+;;
+;; In the undo-tree visualizer:
+;;
+;; p C-p (`undo-tree-visualize-undo')
+;; Undo changes.
+;;
+;; n C-n (`undo-tree-visualize-redo')
+;; Redo changes.
+;;
+;; b C-b (`undo-tree-visualize-switch-branch-left')
+;; Switch to previous undo-tree branch.
+;;
+;; f C-f (`undo-tree-visualize-switch-branch-right')
+;; Switch to next undo-tree branch.
+;;
+;; C- M-{ (`undo-tree-visualize-undo-to-x')
+;; Undo changes up to last branch point.
+;;
+;; C- M-} (`undo-tree-visualize-redo-to-x')
+;; Redo changes down to next branch point.
+;;
+;; n C-n (`undo-tree-visualize-redo')
+;; Redo changes.
+;;
+;; (`undo-tree-visualizer-mouse-set')
+;; Set state to node at mouse click.
+;;
+;; t (`undo-tree-visualizer-toggle-timestamps')
+;; Toggle display of time-stamps.
+;;
+;; d (`undo-tree-visualizer-toggle-diff')
+;; Toggle diff display.
+;;
+;; s (`undo-tree-visualizer-selection-mode')
+;; Toggle keyboard selection mode.
+;;
+;; q (`undo-tree-visualizer-quit')
+;; Quit undo-tree-visualizer.
+;;
+;; C-q (`undo-tree-visualizer-abort')
+;; Abort undo-tree-visualizer.
+;;
+;; , <
+;; Scroll left.
+;;
+;; . >
+;; Scroll right.
+;;
+;; M-v
+;; Scroll up.
+;;
+;; C-v
+;; Scroll down.
+;;
+;;
+;;
+;; In visualizer selection mode:
+;;
+;; p C-p (`undo-tree-visualizer-select-previous')
+;; Select previous node.
+;;
+;; n C-n (`undo-tree-visualizer-select-next')
+;; Select next node.
+;;
+;; b C-b (`undo-tree-visualizer-select-left')
+;; Select left sibling node.
+;;
+;; f C-f (`undo-tree-visualizer-select-right')
+;; Select right sibling node.
+;;
+;; M-v
+;; Select node 10 above.
+;;
+;; C-v
+;; Select node 10 below.
+;;
+;; (`undo-tree-visualizer-set')
+;; Set state to selected node and exit selection mode.
+;;
+;; s (`undo-tree-visualizer-mode')
+;; Exit selection mode.
+;;
+;; t (`undo-tree-visualizer-toggle-timestamps')
+;; Toggle display of time-stamps.
+;;
+;; d (`undo-tree-visualizer-toggle-diff')
+;; Toggle diff display.
+;;
+;; q (`undo-tree-visualizer-quit')
+;; Quit undo-tree-visualizer.
+;;
+;; C-q (`undo-tree-visualizer-abort')
+;; Abort undo-tree-visualizer.
+;;
+;; , <
+;; Scroll left.
+;;
+;; . >
+;; Scroll right.
+;;
+;;
+;;
+;; Persistent undo history:
+;;
+;; Note: Requires Emacs version 24.3 or higher.
+;;
+;; `undo-tree-auto-save-history' (variable)
+;; automatically save and restore undo-tree history along with buffer
+;; (disabled by default)
+;;
+;; `undo-tree-save-history' (command)
+;; manually save undo history to file
+;;
+;; `undo-tree-load-history' (command)
+;; manually load undo history from file
+;;
+;;
+;;
+;; Compressing undo history:
+;;
+;; Undo history files cannot grow beyond the maximum undo tree size, which
+;; is limited by `undo-limit', `undo-strong-limit' and
+;; `undo-outer-limit'. Nevertheless, undo history files can grow quite
+;; large. If you want to automatically compress undo history, add the
+;; following advice to your .emacs file (replacing ".gz" with the filename
+;; extension of your favourite compression algorithm):
+;;
+;; (defadvice undo-tree-make-history-save-file-name
+;; (after undo-tree activate)
+;; (setq ad-return-value (concat ad-return-value ".gz")))
+;;
+;;
+;;
+;;
+;; Undo Systems
+;; ============
+;;
+;; To understand the different undo systems, it's easiest to consider an
+;; example. Imagine you make a few edits in a buffer. As you edit, you
+;; accumulate a history of changes, which we might visualize as a string of
+;; past buffer states, growing downwards:
+;;
+;; o (initial buffer state)
+;; |
+;; |
+;; o (first edit)
+;; |
+;; |
+;; o (second edit)
+;; |
+;; |
+;; x (current buffer state)
+;;
+;;
+;; Now imagine that you undo the last two changes. We can visualize this as
+;; rewinding the current state back two steps:
+;;
+;; o (initial buffer state)
+;; |
+;; |
+;; x (current buffer state)
+;; |
+;; |
+;; o
+;; |
+;; |
+;; o
+;;
+;;
+;; However, this isn't a good representation of what Emacs' undo system
+;; does. Instead, it treats the undos as *new* changes to the buffer, and adds
+;; them to the history:
+;;
+;; o (initial buffer state)
+;; |
+;; |
+;; o (first edit)
+;; |
+;; |
+;; o (second edit)
+;; |
+;; |
+;; x (buffer state before undo)
+;; |
+;; |
+;; o (first undo)
+;; |
+;; |
+;; x (second undo)
+;;
+;;
+;; Actually, since the buffer returns to a previous state after an undo,
+;; perhaps a better way to visualize it is to imagine the string of changes
+;; turning back on itself:
+;;
+;; (initial buffer state) o
+;; |
+;; |
+;; (first edit) o x (second undo)
+;; | |
+;; | |
+;; (second edit) o o (first undo)
+;; | /
+;; |/
+;; o (buffer state before undo)
+;;
+;; Treating undos as new changes might seem a strange thing to do. But the
+;; advantage becomes clear as soon as we imagine what happens when you edit
+;; the buffer again. Since you've undone a couple of changes, new edits will
+;; branch off from the buffer state that you've rewound to. Conceptually, it
+;; looks like this:
+;;
+;; o (initial buffer state)
+;; |
+;; |
+;; o
+;; |\
+;; | \
+;; o x (new edit)
+;; |
+;; |
+;; o
+;;
+;; The standard undo/redo system only lets you go backwards and forwards
+;; linearly. So as soon as you make that new edit, it discards the old
+;; branch. Emacs' undo just keeps adding changes to the end of the string. So
+;; the undo history in the two systems now looks like this:
+;;
+;; Undo/Redo: Emacs' undo
+;;
+;; o o
+;; | |
+;; | |
+;; o o o
+;; .\ | |\
+;; . \ | | \
+;; . x (new edit) o o |
+;; (discarded . | / |
+;; branch) . |/ |
+;; . o |
+;; |
+;; |
+;; x (new edit)
+;;
+;; Now, what if you change your mind about those undos, and decide you did
+;; like those other changes you'd made after all? With the standard undo/redo
+;; system, you're lost. There's no way to recover them, because that branch
+;; was discarded when you made the new edit.
+;;
+;; However, in Emacs' undo system, those old buffer states are still there in
+;; the undo history. You just have to rewind back through the new edit, and
+;; back through the changes made by the undos, until you reach them. Of
+;; course, since Emacs treats undos (even undos of undos!) as new changes,
+;; you're really weaving backwards and forwards through the history, all the
+;; time adding new changes to the end of the string as you go:
+;;
+;; o
+;; |
+;; |
+;; o o o (undo new edit)
+;; | |\ |\
+;; | | \ | \
+;; o o | | o (undo the undo)
+;; | / | | |
+;; |/ | | |
+;; (trying to get o | | x (undo the undo)
+;; to this state) | /
+;; |/
+;; o
+;;
+;; So far, this is still reasonably intuitive to use. It doesn't behave so
+;; differently to standard undo/redo, except that by going back far enough you
+;; can access changes that would be lost in standard undo/redo.
+;;
+;; However, imagine that after undoing as just described, you decide you
+;; actually want to rewind right back to the initial state. If you're lucky,
+;; and haven't invoked any command since the last undo, you can just keep on
+;; undoing until you get back to the start:
+;;
+;; (trying to get o x (got there!)
+;; to this state) | |
+;; | |
+;; o o o o (keep undoing)
+;; | |\ |\ |
+;; | | \ | \ |
+;; o o | | o o (keep undoing)
+;; | / | | | /
+;; |/ | | |/
+;; (already undid o | | o (got this far)
+;; to this state) | /
+;; |/
+;; o
+;;
+;; But if you're unlucky, and you happen to have moved the point (say) after
+;; getting to the state labelled "got this far", then you've "broken the undo
+;; chain". Hold on to something solid, because things are about to get
+;; hairy. If you try to undo now, Emacs thinks you're trying to undo the
+;; undos! So to get back to the initial state you now have to rewind through
+;; *all* the changes, including the undos you just did:
+;;
+;; (trying to get o x (finally got there!)
+;; to this state) | |
+;; | |
+;; o o o o o o
+;; | |\ |\ |\ |\ |
+;; | | \ | \ | \ | \ |
+;; o o | | o o | | o o
+;; | / | | | / | | | /
+;; |/ | | |/ | | |/
+;; (already undid o | | o<. | | o
+;; to this state) | / : | /
+;; |/ : |/
+;; o : o
+;; :
+;; (got this far, but
+;; broke the undo chain)
+;;
+;; Confused?
+;;
+;; In practice you can just hold down the undo key until you reach the buffer
+;; state that you want. But whatever you do, don't move around in the buffer
+;; to *check* that you've got back to where you want! Because you'll break the
+;; undo chain, and then you'll have to traverse the entire string of undos
+;; again, just to get back to the point at which you broke the
+;; chain. Undo-in-region and commands such as `undo-only' help to make using
+;; Emacs' undo a little easier, but nonetheless it remains confusing for many
+;; people.
+;;
+;;
+;; So what does `undo-tree-mode' do? Remember the diagram we drew to represent
+;; the history we've been discussing (make a few edits, undo a couple of them,
+;; and edit again)? The diagram that conceptually represented our undo
+;; history, before we started discussing specific undo systems? It looked like
+;; this:
+;;
+;; o (initial buffer state)
+;; |
+;; |
+;; o
+;; |\
+;; | \
+;; o x (current state)
+;; |
+;; |
+;; o
+;;
+;; Well, that's *exactly* what the undo history looks like to
+;; `undo-tree-mode'. It doesn't discard the old branch (as standard undo/redo
+;; does), nor does it treat undos as new changes to be added to the end of a
+;; linear string of buffer states (as Emacs' undo does). It just keeps track
+;; of the tree of branching changes that make up the entire undo history.
+;;
+;; If you undo from this point, you'll rewind back up the tree to the previous
+;; state:
+;;
+;; o
+;; |
+;; |
+;; x (undo)
+;; |\
+;; | \
+;; o o
+;; |
+;; |
+;; o
+;;
+;; If you were to undo again, you'd rewind back to the initial state. If on
+;; the other hand you redo the change, you'll end up back at the bottom of the
+;; most recent branch:
+;;
+;; o (undo takes you here)
+;; |
+;; |
+;; o (start here)
+;; |\
+;; | \
+;; o x (redo takes you here)
+;; |
+;; |
+;; o
+;;
+;; So far, this is just like the standard undo/redo system. But what if you
+;; want to return to a buffer state located on a previous branch of the
+;; history? Since `undo-tree-mode' keeps the entire history, you simply need
+;; to tell it to switch to a different branch, and then redo the changes you
+;; want:
+;;
+;; o
+;; |
+;; |
+;; o (start here, but switch
+;; |\ to the other branch)
+;; | \
+;; (redo) o o
+;; |
+;; |
+;; (redo) x
+;;
+;; Now you're on the other branch, if you undo and redo changes you'll stay on
+;; that branch, moving up and down through the buffer states located on that
+;; branch. Until you decide to switch branches again, of course.
+;;
+;; Real undo trees might have multiple branches and sub-branches:
+;;
+;; o
+;; ____|______
+;; / \
+;; o o
+;; ____|__ __|
+;; / | \ / \
+;; o o o o x
+;; | |
+;; / \ / \
+;; o o o o
+;;
+;; Trying to imagine what Emacs' undo would do as you move about such a tree
+;; will likely frazzle your brain circuits! But in `undo-tree-mode', you're
+;; just moving around this undo history tree. Most of the time, you'll
+;; probably only need to stay on the most recent branch, in which case it
+;; behaves like standard undo/redo, and is just as simple to understand. But
+;; if you ever need to recover a buffer state on a different branch, the
+;; possibility of switching between branches and accessing the full undo
+;; history is still there.
+;;
+;;
+;;
+;; The Undo-Tree Visualizer
+;; ========================
+;;
+;; Actually, it gets better. You don't have to imagine all these tree
+;; diagrams, because `undo-tree-mode' includes an undo-tree visualizer which
+;; draws them for you! In fact, it draws even better diagrams: it highlights
+;; the node representing the current buffer state, it highlights the current
+;; branch, and you can toggle the display of time-stamps (by hitting "t") and
+;; a diff of the undo changes (by hitting "d"). (There's one other tiny
+;; difference: the visualizer puts the most recent branch on the left rather
+;; than the right.)
+;;
+;; Bring up the undo tree visualizer whenever you want by hitting "C-x u".
+;;
+;; In the visualizer, the usual keys for moving up and down a buffer instead
+;; move up and down the undo history tree (e.g. the up and down arrow keys, or
+;; "C-n" and "C-p"). The state of the "parent" buffer (the buffer whose undo
+;; history you are visualizing) is updated as you move around the undo tree in
+;; the visualizer. If you reach a branch point in the visualizer, the usual
+;; keys for moving forward and backward in a buffer instead switch branch
+;; (e.g. the left and right arrow keys, or "C-f" and "C-b").
+;;
+;; Clicking with the mouse on any node in the visualizer will take you
+;; directly to that node, resetting the state of the parent buffer to the
+;; state represented by that node.
+;;
+;; You can also select nodes directly using the keyboard, by hitting "s" to
+;; toggle selection mode. The usual motion keys now allow you to move around
+;; the tree without changing the parent buffer. Hitting will reset the
+;; state of the parent buffer to the state represented by the currently
+;; selected node.
+;;
+;; It can be useful to see how long ago the parent buffer was in the state
+;; represented by a particular node in the visualizer. Hitting "t" in the
+;; visualizer toggles the display of time-stamps for all the nodes. (Note
+;; that, because of the way `undo-tree-mode' works, these time-stamps may be
+;; somewhat later than the true times, especially if it's been a long time
+;; since you last undid any changes.)
+;;
+;; To get some idea of what changes are represented by a given node in the
+;; tree, it can be useful to see a diff of the changes. Hit "d" in the
+;; visualizer to toggle a diff display. This normally displays a diff between
+;; the current state and the previous one, i.e. it shows you the changes that
+;; will be applied if you undo (move up the tree). However, the diff display
+;; really comes into its own in the visualizer's selection mode (see above),
+;; where it instead shows a diff between the current state and the currently
+;; selected state, i.e. it shows you the changes that will be applied if you
+;; reset to the selected state.
+;;
+;; (Note that the diff is generated by the Emacs `diff' command, and is
+;; displayed using `diff-mode'. See the corresponding customization groups if
+;; you want to customize the diff display.)
+;;
+;; Finally, hitting "q" will quit the visualizer, leaving the parent buffer in
+;; whatever state you ended at. Hitting "C-q" will abort the visualizer,
+;; returning the parent buffer to whatever state it was originally in when the
+;; visualizer was invoked.
+;;
+;;
+;;
+;; Undo-in-Region
+;; ==============
+;;
+;; Emacs allows a very useful and powerful method of undoing only selected
+;; changes: when a region is active, only changes that affect the text within
+;; that region will be undone. With the standard Emacs undo system, changes
+;; produced by undoing-in-region naturally get added onto the end of the
+;; linear undo history:
+;;
+;; o
+;; |
+;; | x (second undo-in-region)
+;; o |
+;; | |
+;; | o (first undo-in-region)
+;; o |
+;; | /
+;; |/
+;; o
+;;
+;; You can of course redo these undos-in-region as usual, by undoing the
+;; undos:
+;;
+;; o
+;; |
+;; | o_
+;; o | \
+;; | | |
+;; | o o (undo the undo-in-region)
+;; o | |
+;; | / |
+;; |/ |
+;; o x (undo the undo-in-region)
+;;
+;;
+;; In `undo-tree-mode', undo-in-region works much the same way: when there's
+;; an active region, undoing only undoes changes that affect that region. In
+;; `undo-tree-mode', redoing when there's an active region similarly only
+;; redoes changes that affect that region.
+;;
+;; However, the way these undo- and redo-in-region changes are recorded in the
+;; undo history is quite different. The good news is, you don't need to
+;; understand this to use undo- and redo-in-region in `undo-tree-mode' - just
+;; go ahead and use them! They'll probably work as you expect. But if you're
+;; masochistic enough to want to understand conceptually what's happening to
+;; the undo tree as you undo- and redo-in-region, then read on...
+;;
+;;
+;; Undo-in-region creates a new branch in the undo history. The new branch
+;; consists of an undo step that undoes some of the changes that affect the
+;; current region, and another step that undoes the remaining changes needed
+;; to rejoin the previous undo history.
+;;
+;; Previous undo history Undo-in-region
+;;
+;; o o
+;; | |
+;; | |
+;; | |
+;; o o
+;; | |
+;; | |
+;; | |
+;; o o_
+;; | | \
+;; | | x (undo-in-region)
+;; | | |
+;; x o o
+;;
+;; As long as you don't change the active region after undoing-in-region,
+;; continuing to undo-in-region extends the new branch, pulling more changes
+;; that affect the current region into an undo step immediately above your
+;; current location in the undo tree, and pushing the point at which the new
+;; branch is attached further up the tree:
+;;
+;; First undo-in-region Second undo-in-region
+;;
+;; o o
+;; | |
+;; | |
+;; | |
+;; o o_
+;; | | \
+;; | | x (undo-in-region)
+;; | | |
+;; o_ o |
+;; | \ | |
+;; | x | o
+;; | | | |
+;; o o o o
+;;
+;; Redoing takes you back down the undo tree, as usual (as long as you haven't
+;; changed the active region after undoing-in-region, it doesn't matter if it
+;; is still active):
+;;
+;; o
+;; |
+;; |
+;; |
+;; o_
+;; | \
+;; | o
+;; | |
+;; o |
+;; | |
+;; | o (redo)
+;; | |
+;; o x (redo)
+;;
+;;
+;; What about redo-in-region? Obviously, redo-in-region only makes sense if
+;; you have already undone some changes, so that there are some changes to
+;; redo! Redoing-in-region splits off a new branch of the undo history below
+;; your current location in the undo tree. This time, the new branch consists
+;; of a first redo step that redoes some of the redo changes that affect the
+;; current region, followed by *all* the remaining redo changes.
+;;
+;; Previous undo history Redo-in-region
+;;
+;; o o
+;; | |
+;; | |
+;; | |
+;; x o_
+;; | | \
+;; | | x (redo-in-region)
+;; | | |
+;; o o |
+;; | | |
+;; | | |
+;; | | |
+;; o o o
+;;
+;; As long as you don't change the active region after redoing-in-region,
+;; continuing to redo-in-region extends the new branch, pulling more redo
+;; changes into a redo step immediately below your current location in the
+;; undo tree.
+;;
+;; First redo-in-region Second redo-in-region
+;;
+;; o o
+;; | |
+;; | |
+;; | |
+;; o_ o_
+;; | \ | \
+;; | x | o
+;; | | | |
+;; o | o |
+;; | | | |
+;; | | | x (redo-in-region)
+;; | | | |
+;; o o o o
+;;
+;; Note that undo-in-region and redo-in-region only ever add new changes to
+;; the undo tree, they *never* modify existing undo history. So you can always
+;; return to previous buffer states by switching to a previous branch of the
+;; tree.
+
+
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'diff)
+(require 'gv)
+
+
+
+;;; =====================================================================
+;;; Compatibility hacks for older Emacsen
+
+;; `characterp' isn't defined in Emacs versions < 23
+(unless (fboundp 'characterp)
+ (defalias 'characterp 'char-valid-p))
+
+;; `region-active-p' isn't defined in Emacs versions < 23
+(unless (fboundp 'region-active-p)
+ (defun region-active-p () (and transient-mark-mode mark-active)))
+
+
+;; `registerv' defstruct isn't defined in Emacs versions < 24
+(unless (fboundp 'registerv-make)
+ (defmacro registerv-make (data &rest _dummy) data))
+
+(unless (fboundp 'registerv-data)
+ (defmacro registerv-data (data) data))
+
+
+;; `diff-no-select' and `diff-file-local-copy' aren't defined in Emacs
+;; versions < 24 (copied and adapted from Emacs 24)
+(unless (fboundp 'diff-no-select)
+ (defun diff-no-select (old new &optional switches no-async buf)
+ ;; Noninteractive helper for creating and reverting diff buffers
+ (unless (bufferp new) (setq new (expand-file-name new)))
+ (unless (bufferp old) (setq old (expand-file-name old)))
+ (or switches (setq switches diff-switches)) ; If not specified, use default.
+ (unless (listp switches) (setq switches (list switches)))
+ (or buf (setq buf (get-buffer-create "*Diff*")))
+ (let* ((old-alt (diff-file-local-copy old))
+ (new-alt (diff-file-local-copy new))
+ (command
+ (mapconcat 'identity
+ `(,diff-command
+ ;; Use explicitly specified switches
+ ,@switches
+ ,@(mapcar #'shell-quote-argument
+ (nconc
+ (when (or old-alt new-alt)
+ (list "-L" (if (stringp old)
+ old (prin1-to-string old))
+ "-L" (if (stringp new)
+ new (prin1-to-string new))))
+ (list (or old-alt old)
+ (or new-alt new)))))
+ " "))
+ (thisdir default-directory))
+ (with-current-buffer buf
+ (setq buffer-read-only t)
+ (buffer-disable-undo (current-buffer))
+ (let ((inhibit-read-only t))
+ (erase-buffer))
+ (buffer-enable-undo (current-buffer))
+ (diff-mode)
+ (set (make-local-variable 'revert-buffer-function)
+ (lambda (_ignore-auto _noconfirm)
+ (diff-no-select old new switches no-async (current-buffer))))
+ (setq default-directory thisdir)
+ (let ((inhibit-read-only t))
+ (insert command "\n"))
+ (if (and (not no-async) (fboundp 'start-process))
+ (let ((proc (start-process "Diff" buf shell-file-name
+ shell-command-switch command)))
+ (set-process-filter proc 'diff-process-filter)
+ (set-process-sentinel
+ proc (lambda (proc _msg)
+ (with-current-buffer (process-buffer proc)
+ (diff-sentinel (process-exit-status proc))
+ (if old-alt (delete-file old-alt))
+ (if new-alt (delete-file new-alt))))))
+ ;; Async processes aren't available.
+ (let ((inhibit-read-only t))
+ (diff-sentinel
+ (call-process shell-file-name nil buf nil
+ shell-command-switch command))
+ (if old-alt (delete-file old-alt))
+ (if new-alt (delete-file new-alt)))))
+ buf)))
+
+(unless (fboundp 'diff-file-local-copy)
+ (defun diff-file-local-copy (file-or-buf)
+ (if (bufferp file-or-buf)
+ (with-current-buffer file-or-buf
+ (let ((tempfile (make-temp-file "buffer-content-")))
+ (write-region nil nil tempfile nil 'nomessage)
+ tempfile))
+ (file-local-copy file-or-buf))))
+
+
+;; `user-error' isn't defined in Emacs < 24.3
+(unless (fboundp 'user-error)
+ (defalias 'user-error 'error)
+ ;; prevent debugger being called on user errors
+ (add-to-list 'debug-ignored-errors "^No further undo information")
+ (add-to-list 'debug-ignored-errors "^No further redo information")
+ (add-to-list 'debug-ignored-errors "^No further redo information for region"))
+
+
+
+
+
+;;; =====================================================================
+;;; Global variables and customization options
+
+(defvar buffer-undo-tree nil
+ "Tree of undo entries in current buffer.")
+(put 'buffer-undo-tree 'permanent-local t)
+(make-variable-buffer-local 'buffer-undo-tree)
+
+
+(defgroup undo-tree nil
+ "Tree undo/redo."
+ :group 'undo)
+
+
+(defcustom undo-tree-limit 80000000
+ "Value of `undo-limit' used in `undo-tree-mode'.
+
+If `undo-limit' is larger than `undo-tree-limit', the larger of
+the two values will be used.
+
+See also `undo-tree-strong-limit' and `undo-tree-outer-limit'.
+
+Setting this to nil prevents `undo-tree-mode' ever discarding
+undo history. (As far as possible. In principle, it is still
+possible for Emacs to discard undo history behind
+`undo-tree-mode's back.) USE THIS SETTING AT YOUR OWN RISK! Emacs
+may crash if undo history exceeds Emacs' available memory. This
+is particularly risky if `undo-tree-auto-save-history' is
+enabled, as in that case undo history is preserved even between
+Emacs sessions."
+ :group 'undo-tree
+ :type '(choice integer (const nil)))
+
+
+(defcustom undo-tree-strong-limit 120000000
+ "Value of `undo-strong-limit' used in `undo-tree-mode'.
+
+If `undo-strong-limit' is larger than `undo-tree-strong-limit'
+the larger of the two values will be used."
+ :group 'undo-tree
+ :type 'integer)
+
+
+(defcustom undo-tree-outer-limit 360000000
+ "Value of `undo-outer-limit' used in `undo-tree-mode'.
+
+If `undo-outer-limit' is larger than `undo-tree-outer-limit' the
+larger of the two values will be used."
+ :group 'undo-tree
+ :type 'integer)
+
+
+(defcustom undo-tree-mode-lighter " Undo-Tree"
+ "Lighter displayed in mode line
+when `undo-tree-mode' is enabled."
+ :group 'undo-tree
+ :type 'string)
+
+
+(defcustom undo-tree-incompatible-major-modes '(term-mode)
+ "List of major-modes in which `undo-tree-mode' should not be enabled.
+\(See `turn-on-undo-tree-mode'.\)"
+ :group 'undo-tree
+ :type '(repeat symbol))
+
+
+(defcustom undo-tree-enable-undo-in-region nil
+ "When non-nil, enable undo-in-region.
+
+When undo-in-region is enabled, undoing or redoing when the
+region is active (in `transient-mark-mode') or with a prefix
+argument (not in `transient-mark-mode') only undoes changes
+within the current region."
+ :group 'undo-tree
+ :type 'boolean)
+
+
+(defcustom undo-tree-auto-save-history nil
+ "When non-nil, `undo-tree-mode' will save undo history to file
+when a buffer is saved to file.
+
+It will automatically load undo history when a buffer is loaded
+from file, if an undo save file exists.
+
+By default, undo-tree history is saved to a file called
+\"..~undo-tree~\" in the same directory as the
+file itself. To save under a different directory, customize
+`undo-tree-history-directory-alist' (see the documentation for
+that variable for details).
+
+WARNING! `undo-tree-auto-save-history' will not work properly in
+Emacs versions prior to 24.3, so it cannot be enabled via
+the customization interface in versions earlier than that one. To
+ignore this warning and enable it regardless, set
+`undo-tree-auto-save-history' to a non-nil value outside of
+customize."
+ :group 'undo-tree
+ :type (if (version-list-< (version-to-list emacs-version) '(24 3))
+ '(choice (const :tag "" nil))
+ 'boolean))
+
+
+(defcustom undo-tree-history-directory-alist nil
+ "Alist of filename patterns and undo history directory names.
+Each element looks like (REGEXP . DIRECTORY). Undo history for
+files with names matching REGEXP will be saved in DIRECTORY.
+DIRECTORY may be relative or absolute. If it is absolute, so
+that all matching files are backed up into the same directory,
+the file names in this directory will be the full name of the
+file backed up with all directory separators changed to `!' to
+prevent clashes. This will not work correctly if your filesystem
+truncates the resulting name.
+
+For the common case of all backups going into one directory, the
+alist should contain a single element pairing \".\" with the
+appropriate directory name.
+
+If this variable is nil, or it fails to match a filename, the
+backup is made in the original file's directory.
+
+On MS-DOS filesystems without long names this variable is always
+ignored."
+ :group 'undo-tree
+ :type '(repeat (cons (regexp :tag "Regexp matching filename")
+ (directory :tag "Undo history directory name"))))
+
+
+
+(defcustom undo-tree-visualizer-relative-timestamps t
+ "When non-nil, display times relative to current time
+when displaying time stamps in visualizer.
+
+Otherwise, display absolute times."
+ :group 'undo-tree
+ :type 'boolean)
+
+
+(defcustom undo-tree-visualizer-timestamps nil
+ "When non-nil, display time-stamps by default
+in undo-tree visualizer.
+
+\\You can always toggle time-stamps on and off \
+using \\[undo-tree-visualizer-toggle-timestamps], regardless of the
+setting of this variable."
+ :group 'undo-tree
+ :type 'boolean)
+
+
+(defcustom undo-tree-visualizer-diff nil
+ "When non-nil, display diff by default in undo-tree visualizer.
+
+\\You can always toggle the diff display \
+using \\[undo-tree-visualizer-toggle-diff], regardless of the
+setting of this variable."
+ :group 'undo-tree
+ :type 'boolean)
+
+
+(defcustom undo-tree-visualizer-lazy-drawing 100
+ "When non-nil, use lazy undo-tree drawing in visualizer.
+
+Setting this to a number causes the visualizer to switch to lazy
+drawing when the number of nodes in the tree is larger than this
+value.
+
+Lazy drawing means that only the visible portion of the tree will
+be drawn initially, and the tree will be extended later as
+needed. For the most part, the only visible effect of this is to
+significantly speed up displaying the visualizer for very large
+trees.
+
+There is one potential negative effect of lazy drawing. Other
+branches of the tree will only be drawn once the node from which
+they branch off becomes visible. So it can happen that certain
+portions of the tree that would be shown with lazy drawing
+disabled, will not be drawn immediately when it is
+enabled. However, this effect is quite rare in practice."
+ :group 'undo-tree
+ :type '(choice (const :tag "never" nil)
+ (const :tag "always" t)
+ (integer :tag "> size")))
+
+
+(defvar undo-tree-pre-save-element-functions '()
+ "Special hook to modify undo-tree elements prior to saving.
+Each function on this hook is called in turn on each undo element
+in the tree by `undo-tree-save-history' prior to writing the undo
+history to file. It should return either nil, which removes that
+undo element from the saved history, or a replacement element to
+use instead (which should be identical to the original element if
+that element should be saved unchanged).")
+
+
+(defvar undo-tree-post-load-element-functions '()
+ "Special hook to modify undo-tree undo elements after loading.
+Each function on this hook is called in turn on each undo element
+in the tree by `undo-tree-load-history' after loading the undo
+history from file. It should return either nil, which removes that
+undo element from the loaded history, or a replacement element to
+use instead (which should be identical to the original element if
+that element should be loaded unchanged).")
+
+
+(defface undo-tree-visualizer-default-face
+ '((((class color)) :foreground "gray"))
+ "Face used to draw undo-tree in visualizer."
+ :group 'undo-tree)
+
+(defface undo-tree-visualizer-current-face
+ '((((class color)) :foreground "red"))
+ "Face used to highlight current undo-tree node in visualizer."
+ :group 'undo-tree)
+
+(defface undo-tree-visualizer-active-branch-face
+ '((((class color) (background dark))
+ (:foreground "white" :weight bold))
+ (((class color) (background light))
+ (:foreground "black" :weight bold)))
+ "Face used to highlight active undo-tree branch in visualizer."
+ :group 'undo-tree)
+
+(defface undo-tree-visualizer-register-face
+ '((((class color)) :foreground "yellow"))
+ "Face used to highlight undo-tree nodes saved to a register
+in visualizer."
+ :group 'undo-tree)
+
+(defface undo-tree-visualizer-unmodified-face
+ '((((class color)) :foreground "cyan"))
+ "Face used to highlight nodes corresponding to unmodified buffers
+in visualizer."
+ :group 'undo-tree)
+
+
+(defvar undo-tree-visualizer-parent-buffer nil
+ "Parent buffer in visualizer.")
+(put 'undo-tree-visualizer-parent-buffer 'permanent-local t)
+(make-variable-buffer-local 'undo-tree-visualizer-parent-buffer)
+
+;; stores modification time of parent buffer's file, if any
+(defvar undo-tree-visualizer-parent-mtime nil)
+(put 'undo-tree-visualizer-parent-mtime 'permanent-local t)
+(make-variable-buffer-local 'undo-tree-visualizer-parent-mtime)
+
+;; stores current horizontal spacing needed for drawing undo-tree
+(defvar undo-tree-visualizer-spacing nil)
+(put 'undo-tree-visualizer-spacing 'permanent-local t)
+(make-variable-buffer-local 'undo-tree-visualizer-spacing)
+
+;; calculate horizontal spacing required for drawing tree with current
+;; settings
+(defsubst undo-tree-visualizer-calculate-spacing ()
+ (if undo-tree-visualizer-timestamps
+ (if undo-tree-visualizer-relative-timestamps 9 13)
+ 3))
+
+;; holds node that was current when visualizer was invoked
+(defvar undo-tree-visualizer-initial-node nil)
+(put 'undo-tree-visualizer-initial-node 'permanent-local t)
+(make-variable-buffer-local 'undo-tree-visualizer-initial-node)
+
+;; holds currently selected node in visualizer selection mode
+(defvar undo-tree-visualizer-selected-node nil)
+(put 'undo-tree-visualizer-selected-node 'permanent-local t)
+(make-variable-buffer-local 'undo-tree-visualizer-selected)
+
+;; used to store nodes at edge of currently drawn portion of tree
+(defvar undo-tree-visualizer-needs-extending-down nil)
+(put 'undo-tree-visualizer-needs-extending-down 'permanent-local t)
+(make-variable-buffer-local 'undo-tree-visualizer-needs-extending-down)
+(defvar undo-tree-visualizer-needs-extending-up nil)
+(put 'undo-tree-visualizer-needs-extending-up 'permanent-local t)
+(make-variable-buffer-local 'undo-tree-visualizer-needs-extending-up)
+
+;; dynamically bound to t when undoing from visualizer, to inhibit
+;; `undo-tree-kill-visualizer' hook function in parent buffer
+(defvar undo-tree-inhibit-kill-visualizer nil)
+
+;; can be let-bound to a face name, used in drawing functions
+(defvar undo-tree-insert-face nil)
+
+;; visualizer buffer names
+(defconst undo-tree-visualizer-buffer-name " *undo-tree*")
+(defconst undo-tree-diff-buffer-name "*undo-tree Diff*")
+
+
+
+
+;;; =================================================================
+;;; Default keymaps
+
+(defvar undo-tree-map nil
+ "Keymap used in undo-tree-mode.")
+
+(unless undo-tree-map
+ (let ((map (make-sparse-keymap)))
+ ;; remap `undo' and `undo-only' to `undo-tree-undo'
+ (define-key map [remap undo] 'undo-tree-undo)
+ (define-key map [remap undo-only] 'undo-tree-undo)
+ ;; bind standard undo bindings (since these match redo counterparts)
+ (define-key map (kbd "C-/") 'undo-tree-undo)
+ (define-key map "\C-_" 'undo-tree-undo)
+ ;; redo doesn't exist normally, so define our own keybindings
+ (define-key map (kbd "C-?") 'undo-tree-redo)
+ (define-key map (kbd "M-_") 'undo-tree-redo)
+ ;; just in case something has defined `redo'...
+ (define-key map [remap redo] 'undo-tree-redo)
+ ;; we use "C-x u" for the undo-tree visualizer
+ (define-key map (kbd "\C-x u") 'undo-tree-visualize)
+ ;; bind register commands
+ (define-key map (kbd "C-x r u") 'undo-tree-save-state-to-register)
+ (define-key map (kbd "C-x r U") 'undo-tree-restore-state-from-register)
+ ;; set keymap
+ (setq undo-tree-map map)))
+
+
+(defvar undo-tree-visualizer-mode-map nil
+ "Keymap used in undo-tree visualizer.")
+
+(unless undo-tree-visualizer-mode-map
+ (let ((map (make-sparse-keymap)))
+ ;; vertical motion keys undo/redo
+ (define-key map [remap previous-line] 'undo-tree-visualize-undo)
+ (define-key map [remap next-line] 'undo-tree-visualize-redo)
+ (define-key map [up] 'undo-tree-visualize-undo)
+ (define-key map "p" 'undo-tree-visualize-undo)
+ (define-key map "\C-p" 'undo-tree-visualize-undo)
+ (define-key map [down] 'undo-tree-visualize-redo)
+ (define-key map "n" 'undo-tree-visualize-redo)
+ (define-key map "\C-n" 'undo-tree-visualize-redo)
+ ;; horizontal motion keys switch branch
+ (define-key map [remap forward-char]
+ 'undo-tree-visualize-switch-branch-right)
+ (define-key map [remap backward-char]
+ 'undo-tree-visualize-switch-branch-left)
+ (define-key map [right] 'undo-tree-visualize-switch-branch-right)
+ (define-key map "f" 'undo-tree-visualize-switch-branch-right)
+ (define-key map "\C-f" 'undo-tree-visualize-switch-branch-right)
+ (define-key map [left] 'undo-tree-visualize-switch-branch-left)
+ (define-key map "b" 'undo-tree-visualize-switch-branch-left)
+ (define-key map "\C-b" 'undo-tree-visualize-switch-branch-left)
+ ;; paragraph motion keys undo/redo to significant points in tree
+ (define-key map [remap backward-paragraph] 'undo-tree-visualize-undo-to-x)
+ (define-key map [remap forward-paragraph] 'undo-tree-visualize-redo-to-x)
+ (define-key map "\M-{" 'undo-tree-visualize-undo-to-x)
+ (define-key map "\M-}" 'undo-tree-visualize-redo-to-x)
+ (define-key map [C-up] 'undo-tree-visualize-undo-to-x)
+ (define-key map [C-down] 'undo-tree-visualize-redo-to-x)
+ ;; mouse sets buffer state to node at click
+ (define-key map [mouse-1] 'undo-tree-visualizer-mouse-set)
+ ;; toggle timestamps
+ (define-key map "t" 'undo-tree-visualizer-toggle-timestamps)
+ ;; toggle diff
+ (define-key map "d" 'undo-tree-visualizer-toggle-diff)
+ ;; toggle selection mode
+ (define-key map "s" 'undo-tree-visualizer-selection-mode)
+ ;; horizontal scrolling may be needed if the tree is very wide
+ (define-key map "," 'undo-tree-visualizer-scroll-left)
+ (define-key map "." 'undo-tree-visualizer-scroll-right)
+ (define-key map "<" 'undo-tree-visualizer-scroll-left)
+ (define-key map ">" 'undo-tree-visualizer-scroll-right)
+ ;; vertical scrolling may be needed if the tree is very tall
+ (define-key map [next] 'undo-tree-visualizer-scroll-up)
+ (define-key map [prior] 'undo-tree-visualizer-scroll-down)
+ ;; quit/abort visualizer
+ (define-key map "q" 'undo-tree-visualizer-quit)
+ (define-key map "\C-q" 'undo-tree-visualizer-abort)
+ ;; set keymap
+ (setq undo-tree-visualizer-mode-map map)))
+
+
+(defvar undo-tree-visualizer-selection-mode-map nil
+ "Keymap used in undo-tree visualizer selection mode.")
+
+(unless undo-tree-visualizer-selection-mode-map
+ (let ((map (make-sparse-keymap)))
+ ;; vertical motion keys move up and down tree
+ (define-key map [remap previous-line]
+ 'undo-tree-visualizer-select-previous)
+ (define-key map [remap next-line]
+ 'undo-tree-visualizer-select-next)
+ (define-key map [up] 'undo-tree-visualizer-select-previous)
+ (define-key map "p" 'undo-tree-visualizer-select-previous)
+ (define-key map "\C-p" 'undo-tree-visualizer-select-previous)
+ (define-key map [down] 'undo-tree-visualizer-select-next)
+ (define-key map "n" 'undo-tree-visualizer-select-next)
+ (define-key map "\C-n" 'undo-tree-visualizer-select-next)
+ ;; vertical scroll keys move up and down quickly
+ (define-key map [next]
+ (lambda () (interactive) (undo-tree-visualizer-select-next 10)))
+ (define-key map [prior]
+ (lambda () (interactive) (undo-tree-visualizer-select-previous 10)))
+ ;; horizontal motion keys move to left and right siblings
+ (define-key map [remap forward-char] 'undo-tree-visualizer-select-right)
+ (define-key map [remap backward-char] 'undo-tree-visualizer-select-left)
+ (define-key map [right] 'undo-tree-visualizer-select-right)
+ (define-key map "f" 'undo-tree-visualizer-select-right)
+ (define-key map "\C-f" 'undo-tree-visualizer-select-right)
+ (define-key map [left] 'undo-tree-visualizer-select-left)
+ (define-key map "b" 'undo-tree-visualizer-select-left)
+ (define-key map "\C-b" 'undo-tree-visualizer-select-left)
+ ;; horizontal scroll keys move left or right quickly
+ (define-key map ","
+ (lambda () (interactive) (undo-tree-visualizer-select-left 10)))
+ (define-key map "."
+ (lambda () (interactive) (undo-tree-visualizer-select-right 10)))
+ (define-key map "<"
+ (lambda () (interactive) (undo-tree-visualizer-select-left 10)))
+ (define-key map ">"
+ (lambda () (interactive) (undo-tree-visualizer-select-right 10)))
+ ;; sets buffer state to node at point
+ (define-key map "\r" 'undo-tree-visualizer-set)
+ ;; mouse selects node at click
+ (define-key map [mouse-1] 'undo-tree-visualizer-mouse-select)
+ ;; toggle diff
+ (define-key map "d" 'undo-tree-visualizer-selection-toggle-diff)
+ ;; set keymap
+ (setq undo-tree-visualizer-selection-mode-map map)))
+
+
+
+
+;;; =====================================================================
+;;; Undo-tree data structure
+
+(cl-defstruct
+ (undo-tree
+ :named
+ (:constructor nil)
+ (:constructor make-undo-tree
+ (&aux
+ (root (undo-tree-make-node nil nil))
+ (current root)
+ (size 0)
+ (count 0)
+ (object-pool (make-hash-table :test 'eq :weakness 'value))))
+ (:copier nil))
+ root current size count object-pool)
+
+(defun undo-tree-copy (tree)
+ ;; Return a copy of undo-tree TREE.
+ (unwind-protect
+ (let ((new (make-undo-tree)))
+ (undo-tree-decircle tree)
+ (let ((max-lisp-eval-depth (* 100 (undo-tree-count tree)))
+ (max-specpdl-size (* 100 (undo-tree-count tree))))
+ (setf (undo-tree-root new)
+ (undo-tree-node-copy (undo-tree-root tree)
+ new (undo-tree-current tree))))
+ (setf (undo-tree-size new)
+ (undo-tree-size tree))
+ (setf (undo-tree-count new)
+ (undo-tree-count tree))
+ (setf (undo-tree-object-pool new)
+ (copy-hash-table (undo-tree-object-pool tree)))
+ (undo-tree-recircle new)
+ new)
+ (undo-tree-recircle tree)))
+
+
+(cl-defstruct
+ (undo-tree-node
+ (:type vector) ; create unnamed struct
+ (:constructor nil)
+ (:constructor undo-tree-make-node
+ (previous undo
+ &optional redo
+ &aux
+ (timestamp (current-time))
+ (branch 0)))
+ (:constructor undo-tree-make-node-backwards
+ (next-node undo
+ &optional redo
+ &aux
+ (next (list next-node))
+ (timestamp (current-time))
+ (branch 0)))
+ (:constructor undo-tree-make-empty-node ())
+ (:copier nil))
+ previous next undo redo timestamp branch meta-data)
+
+
+(defmacro undo-tree-node-p (n)
+ (let ((len (length (undo-tree-make-node nil nil))))
+ `(and (vectorp ,n) (= (length ,n) ,len))))
+
+(defun undo-tree-node-copy (node &optional tree current)
+ ;; Return a copy of undo-tree NODE, sans previous link or meta-data.
+ ;; If TREE and CURRENT are supplied, set (undo-tree-current TREE) to the
+ ;; copy of CURRENT node, if found.
+ (let* ((new (undo-tree-make-empty-node))
+ (stack (list (cons node new)))
+ n)
+ (while (setq n (pop stack))
+ (setf (undo-tree-node-undo (cdr n))
+ (copy-tree (undo-tree-node-undo (car n)) 'copy-vectors))
+ (setf (undo-tree-node-redo (cdr n))
+ (copy-tree (undo-tree-node-redo (car n)) 'copy-vectors))
+ (setf (undo-tree-node-timestamp (cdr n))
+ (copy-sequence (undo-tree-node-timestamp (car n))))
+ (setf (undo-tree-node-branch (cdr n))
+ (undo-tree-node-branch (car n)))
+ (setf (undo-tree-node-next (cdr n))
+ (mapcar (lambda (_) (undo-tree-make-empty-node))
+ (make-list (length (undo-tree-node-next (car n))) nil)))
+ ;; set (undo-tree-current TREE) to copy if we've found CURRENT
+ (when (and tree (eq (car n) current))
+ (setf (undo-tree-current tree) (cdr n)))
+ ;; recursively copy next nodes
+ (let ((next0 (undo-tree-node-next (car n)))
+ (next1 (undo-tree-node-next (cdr n))))
+ (while (and next0 next1)
+ (push (cons (pop next0) (pop next1)) stack))))
+ new))
+
+
+(cl-defstruct
+ (undo-tree-region-data
+ (:type vector) ; create unnamed struct
+ (:constructor nil)
+ (:constructor undo-tree-make-region-data
+ (&optional undo-beginning undo-end
+ redo-beginning redo-end))
+ (:constructor undo-tree-make-undo-region-data
+ (undo-beginning undo-end))
+ (:constructor undo-tree-make-redo-region-data
+ (redo-beginning redo-end))
+ (:copier nil))
+ undo-beginning undo-end redo-beginning redo-end)
+
+
+(defmacro undo-tree-region-data-p (r)
+ (let ((len (length (undo-tree-make-region-data))))
+ `(and (vectorp ,r) (= (length ,r) ,len))))
+
+(defmacro undo-tree-node-clear-region-data (node)
+ `(setf (undo-tree-node-meta-data ,node)
+ (delq nil
+ (delq :region
+ (plist-put (undo-tree-node-meta-data ,node)
+ :region nil)))))
+
+
+(defmacro undo-tree-node-undo-beginning (node)
+ `(let ((r (plist-get (undo-tree-node-meta-data ,node) :region)))
+ (when (undo-tree-region-data-p r)
+ (undo-tree-region-data-undo-beginning r))))
+
+(defmacro undo-tree-node-undo-end (node)
+ `(let ((r (plist-get (undo-tree-node-meta-data ,node) :region)))
+ (when (undo-tree-region-data-p r)
+ (undo-tree-region-data-undo-end r))))
+
+(defmacro undo-tree-node-redo-beginning (node)
+ `(let ((r (plist-get (undo-tree-node-meta-data ,node) :region)))
+ (when (undo-tree-region-data-p r)
+ (undo-tree-region-data-redo-beginning r))))
+
+(defmacro undo-tree-node-redo-end (node)
+ `(let ((r (plist-get (undo-tree-node-meta-data ,node) :region)))
+ (when (undo-tree-region-data-p r)
+ (undo-tree-region-data-redo-end r))))
+
+
+(gv-define-setter undo-tree-node-undo-beginning (val node)
+ `(let ((r (plist-get (undo-tree-node-meta-data ,node) :region)))
+ (unless (undo-tree-region-data-p r)
+ (setf (undo-tree-node-meta-data ,node)
+ (plist-put (undo-tree-node-meta-data ,node) :region
+ (setq r (undo-tree-make-region-data)))))
+ (setf (undo-tree-region-data-undo-beginning r) ,val)))
+
+(gv-define-setter undo-tree-node-undo-end (val node)
+ `(let ((r (plist-get (undo-tree-node-meta-data ,node) :region)))
+ (unless (undo-tree-region-data-p r)
+ (setf (undo-tree-node-meta-data ,node)
+ (plist-put (undo-tree-node-meta-data ,node) :region
+ (setq r (undo-tree-make-region-data)))))
+ (setf (undo-tree-region-data-undo-end r) ,val)))
+
+(gv-define-setter undo-tree-node-redo-beginning (val node)
+ `(let ((r (plist-get (undo-tree-node-meta-data ,node) :region)))
+ (unless (undo-tree-region-data-p r)
+ (setf (undo-tree-node-meta-data ,node)
+ (plist-put (undo-tree-node-meta-data ,node) :region
+ (setq r (undo-tree-make-region-data)))))
+ (setf (undo-tree-region-data-redo-beginning r) ,val)))
+
+(gv-define-setter undo-tree-node-redo-end (val node)
+ `(let ((r (plist-get (undo-tree-node-meta-data ,node) :region)))
+ (unless (undo-tree-region-data-p r)
+ (setf (undo-tree-node-meta-data ,node)
+ (plist-put (undo-tree-node-meta-data ,node) :region
+ (setq r (undo-tree-make-region-data)))))
+ (setf (undo-tree-region-data-redo-end r) ,val)))
+
+
+
+(cl-defstruct
+ (undo-tree-visualizer-data
+ (:type vector) ; create unnamed struct
+ (:constructor nil)
+ (:constructor undo-tree-make-visualizer-data
+ (&optional lwidth cwidth rwidth marker))
+ (:copier nil))
+ lwidth cwidth rwidth marker)
+
+
+(defmacro undo-tree-visualizer-data-p (v)
+ (let ((len (length (undo-tree-make-visualizer-data))))
+ `(and (vectorp ,v) (= (length ,v) ,len))))
+
+(defun undo-tree-node-clear-visualizer-data (node)
+ (let ((plist (undo-tree-node-meta-data node)))
+ (if (eq (car plist) :visualizer)
+ (setf (undo-tree-node-meta-data node) (nthcdr 2 plist))
+ (while (and plist (not (eq (cadr plist) :visualizer)))
+ (setq plist (cdr plist)))
+ (if plist (setcdr plist (nthcdr 3 plist))))))
+
+(defmacro undo-tree-node-lwidth (node)
+ `(let ((v (plist-get (undo-tree-node-meta-data ,node) :visualizer)))
+ (when (undo-tree-visualizer-data-p v)
+ (undo-tree-visualizer-data-lwidth v))))
+
+(defmacro undo-tree-node-cwidth (node)
+ `(let ((v (plist-get (undo-tree-node-meta-data ,node) :visualizer)))
+ (when (undo-tree-visualizer-data-p v)
+ (undo-tree-visualizer-data-cwidth v))))
+
+(defmacro undo-tree-node-rwidth (node)
+ `(let ((v (plist-get (undo-tree-node-meta-data ,node) :visualizer)))
+ (when (undo-tree-visualizer-data-p v)
+ (undo-tree-visualizer-data-rwidth v))))
+
+(defmacro undo-tree-node-marker (node)
+ `(let ((v (plist-get (undo-tree-node-meta-data ,node) :visualizer)))
+ (when (undo-tree-visualizer-data-p v)
+ (undo-tree-visualizer-data-marker v))))
+
+
+(gv-define-setter undo-tree-node-lwidth (val node)
+ `(let ((v (plist-get (undo-tree-node-meta-data ,node) :visualizer)))
+ (unless (undo-tree-visualizer-data-p v)
+ (setf (undo-tree-node-meta-data ,node)
+ (plist-put (undo-tree-node-meta-data ,node) :visualizer
+ (setq v (undo-tree-make-visualizer-data)))))
+ (setf (undo-tree-visualizer-data-lwidth v) ,val)))
+
+(gv-define-setter undo-tree-node-cwidth (val node)
+ `(let ((v (plist-get (undo-tree-node-meta-data ,node) :visualizer)))
+ (unless (undo-tree-visualizer-data-p v)
+ (setf (undo-tree-node-meta-data ,node)
+ (plist-put (undo-tree-node-meta-data ,node) :visualizer
+ (setq v (undo-tree-make-visualizer-data)))))
+ (setf (undo-tree-visualizer-data-cwidth v) ,val)))
+
+(gv-define-setter undo-tree-node-rwidth (val node)
+ `(let ((v (plist-get (undo-tree-node-meta-data ,node) :visualizer)))
+ (unless (undo-tree-visualizer-data-p v)
+ (setf (undo-tree-node-meta-data ,node)
+ (plist-put (undo-tree-node-meta-data ,node) :visualizer
+ (setq v (undo-tree-make-visualizer-data)))))
+ (setf (undo-tree-visualizer-data-rwidth v) ,val)))
+
+(gv-define-setter undo-tree-node-marker (val node)
+ `(let ((v (plist-get (undo-tree-node-meta-data ,node) :visualizer)))
+ (unless (undo-tree-visualizer-data-p v)
+ (setf (undo-tree-node-meta-data ,node)
+ (plist-put (undo-tree-node-meta-data ,node) :visualizer
+ (setq v (undo-tree-make-visualizer-data)))))
+ (setf (undo-tree-visualizer-data-marker v) ,val)))
+
+
+
+(cl-defstruct
+ (undo-tree-register-data
+ (:type vector)
+ (:constructor nil)
+ (:constructor undo-tree-make-register-data (buffer node)))
+ buffer node)
+
+(defun undo-tree-register-data-p (data)
+ (and (vectorp data)
+ (= (length data) 2)
+ (undo-tree-node-p (undo-tree-register-data-node data))))
+
+(defun undo-tree-register-data-print-func (data)
+ (princ (format "an undo-tree state for buffer %s"
+ (undo-tree-register-data-buffer data))))
+
+(defmacro undo-tree-node-register (node)
+ `(plist-get (undo-tree-node-meta-data ,node) :register))
+
+(gv-define-setter undo-tree-node-register (val node)
+ `(setf (undo-tree-node-meta-data ,node)
+ (plist-put (undo-tree-node-meta-data ,node) :register ,val)))
+
+
+
+
+;;; =====================================================================
+;;; Basic undo-tree data structure functions
+
+(defun undo-tree-grow (undo)
+ "Add an UNDO node to current branch of `buffer-undo-tree'."
+ (let* ((current (undo-tree-current buffer-undo-tree))
+ (new (undo-tree-make-node current undo)))
+ (push new (undo-tree-node-next current))
+ (setf (undo-tree-current buffer-undo-tree) new)))
+
+
+(defun undo-tree-grow-backwards (node undo &optional redo)
+ "Add new node *above* undo-tree NODE, and return new node.
+Note that this will overwrite NODE's \"previous\" link, so should
+only be used on a detached NODE, never on nodes that are already
+part of `buffer-undo-tree'."
+ (let ((new (undo-tree-make-node-backwards node undo redo)))
+ (setf (undo-tree-node-previous node) new)
+ new))
+
+
+(defun undo-tree-splice-node (node splice)
+ "Splice NODE into undo tree, below node SPLICE.
+Note that this will overwrite NODE's \"next\" and \"previous\"
+links, so should only be used on a detached NODE, never on nodes
+that are already part of `buffer-undo-tree'."
+ (setf (undo-tree-node-next node) (undo-tree-node-next splice)
+ (undo-tree-node-branch node) (undo-tree-node-branch splice)
+ (undo-tree-node-previous node) splice
+ (undo-tree-node-next splice) (list node)
+ (undo-tree-node-branch splice) 0)
+ (dolist (n (undo-tree-node-next node))
+ (setf (undo-tree-node-previous n) node)))
+
+
+(defun undo-tree-snip-node (node)
+ "Snip NODE out of undo tree."
+ (let* ((parent (undo-tree-node-previous node))
+ position p)
+ ;; if NODE is only child, replace parent's next links with NODE's
+ (if (= (length (undo-tree-node-next parent)) 0)
+ (setf (undo-tree-node-next parent) (undo-tree-node-next node)
+ (undo-tree-node-branch parent) (undo-tree-node-branch node))
+ ;; otherwise...
+ (setq position (undo-tree-position node (undo-tree-node-next parent)))
+ (cond
+ ;; if active branch used do go via NODE, set parent's branch to active
+ ;; branch of NODE
+ ((= (undo-tree-node-branch parent) position)
+ (setf (undo-tree-node-branch parent)
+ (+ position (undo-tree-node-branch node))))
+ ;; if active branch didn't go via NODE, update parent's branch to point
+ ;; to same node as before
+ ((> (undo-tree-node-branch parent) position)
+ (cl-incf (undo-tree-node-branch parent)
+ (1- (length (undo-tree-node-next node))))))
+ ;; replace NODE in parent's next list with NODE's entire next list
+ (if (= position 0)
+ (setf (undo-tree-node-next parent)
+ (nconc (undo-tree-node-next node)
+ (cdr (undo-tree-node-next parent))))
+ (setq p (nthcdr (1- position) (undo-tree-node-next parent)))
+ (setcdr p (nconc (undo-tree-node-next node) (cddr p)))))
+ ;; update previous links of NODE's children
+ (dolist (n (undo-tree-node-next node))
+ (setf (undo-tree-node-previous n) parent))))
+
+
+(defun undo-tree-mapc (--undo-tree-mapc-function-- node)
+ ;; Apply FUNCTION to NODE and to each node below it.
+ (let ((stack (list node))
+ n)
+ (while (setq n (pop stack))
+ (funcall --undo-tree-mapc-function-- n)
+ (setq stack (append (undo-tree-node-next n) stack)))))
+
+
+(defmacro undo-tree-num-branches ()
+ "Return number of branches at current undo tree node."
+ '(length (undo-tree-node-next (undo-tree-current buffer-undo-tree))))
+
+
+(defun undo-tree-position (node list)
+ "Find the first occurrence of NODE in LIST.
+Return the index of the matching item, or nil of not found.
+Comparison is done with `eq'."
+ (let ((i 0))
+ (catch 'found
+ (while (progn
+ (when (eq node (car list)) (throw 'found i))
+ (cl-incf i)
+ (setq list (cdr list))))
+ nil)))
+
+
+(defvar *undo-tree-id-counter* 0)
+(make-variable-buffer-local '*undo-tree-id-counter*)
+
+(defmacro undo-tree-generate-id ()
+ ;; Generate a new, unique id (uninterned symbol).
+ ;; The name is made by appending a number to "undo-tree-id".
+ ;; (Copied from CL package `gensym'.)
+ `(let ((num (prog1 *undo-tree-id-counter*
+ (cl-incf *undo-tree-id-counter*))))
+ (make-symbol (format "undo-tree-id%d" num))))
+
+
+(defun undo-tree-decircle (undo-tree)
+ ;; Nullify PREVIOUS links of UNDO-TREE nodes, to make UNDO-TREE data
+ ;; structure non-circular.
+ (undo-tree-mapc
+ (lambda (node)
+ (dolist (n (undo-tree-node-next node))
+ (setf (undo-tree-node-previous n) nil)))
+ (undo-tree-root undo-tree)))
+
+
+(defun undo-tree-recircle (undo-tree)
+ ;; Recreate PREVIOUS links of UNDO-TREE nodes, to restore circular UNDO-TREE
+ ;; data structure.
+ (undo-tree-mapc
+ (lambda (node)
+ (dolist (n (undo-tree-node-next node))
+ (setf (undo-tree-node-previous n) node)))
+ (undo-tree-root undo-tree)))
+
+
+
+
+;;; =====================================================================
+;;; Undo list and undo changeset utility functions
+
+(defmacro undo-list-marker-elt-p (elt)
+ `(markerp (car-safe ,elt)))
+
+(defmacro undo-list-GCd-marker-elt-p (elt)
+ ;; Return t if ELT is a marker element whose marker has been moved to the
+ ;; object-pool, so may potentially have been garbage-collected.
+ ;; Note: Valid marker undo elements should be uniquely identified as cons
+ ;; cells with a symbol in the car (replacing the marker), and a number in
+ ;; the cdr. However, to guard against future changes to undo element
+ ;; formats, we perform an additional redundant check on the symbol name.
+ `(and (car-safe ,elt)
+ (symbolp (car ,elt))
+ (let ((str (symbol-name (car ,elt))))
+ (and (> (length str) 12)
+ (string= (substring str 0 12) "undo-tree-id")))
+ (numberp (cdr-safe ,elt))))
+
+
+(defun undo-tree-move-GC-elts-to-pool (elt)
+ ;; Move elements that can be garbage-collected into `buffer-undo-tree'
+ ;; object pool, substituting a unique id that can be used to retrieve them
+ ;; later. (Only markers require this treatment currently.)
+ (when (undo-list-marker-elt-p elt)
+ (let ((id (undo-tree-generate-id)))
+ (puthash id (car elt) (undo-tree-object-pool buffer-undo-tree))
+ (setcar elt id))))
+
+
+(defun undo-tree-restore-GC-elts-from-pool (elt)
+ ;; Replace object id's in ELT with corresponding objects from
+ ;; `buffer-undo-tree' object pool and return modified ELT, or return nil if
+ ;; any object in ELT has been garbage-collected.
+ (if (undo-list-GCd-marker-elt-p elt)
+ (when (setcar elt (gethash (car elt)
+ (undo-tree-object-pool buffer-undo-tree)))
+ elt)
+ elt))
+
+
+(defun undo-list-clean-GCd-elts (undo-list)
+ ;; Remove object id's from UNDO-LIST that refer to elements that have been
+ ;; garbage-collected. UNDO-LIST is modified by side-effect.
+ (while (undo-list-GCd-marker-elt-p (car undo-list))
+ (unless (gethash (caar undo-list)
+ (undo-tree-object-pool buffer-undo-tree))
+ (setq undo-list (cdr undo-list))))
+ (let ((p undo-list))
+ (while (cdr p)
+ (when (and (undo-list-GCd-marker-elt-p (cadr p))
+ (null (gethash (car (cadr p))
+ (undo-tree-object-pool buffer-undo-tree))))
+ (setcdr p (cddr p)))
+ (setq p (cdr p))))
+ undo-list)
+
+
+(defun undo-list-found-canary-p (undo-list)
+ (or (eq (car undo-list) 'undo-tree-canary)
+ (and (null (car undo-list))
+ (eq (cadr undo-list) 'undo-tree-canary))))
+
+
+(defmacro undo-list-pop-changeset (undo-list &optional discard-pos)
+ ;; Pop changeset from `undo-list'. If DISCARD-POS is non-nil, discard
+ ;; any position entries from changeset.
+ `(when (and ,undo-list (not (undo-list-found-canary-p ,undo-list)))
+ (let (changeset)
+ ;; discard initial undo boundary(ies)
+ (while (null (car ,undo-list)) (setq ,undo-list (cdr ,undo-list)))
+ ;; pop elements up to next undo boundary, discarding position entries
+ ;; if DISCARD-POS is non-nil
+ (while (null changeset)
+ (while (and ,undo-list (car ,undo-list)
+ (not (undo-list-found-canary-p ,undo-list)))
+ (if (and ,discard-pos (integerp (car ,undo-list)))
+ (setq ,undo-list (cdr ,undo-list))
+ (push (pop ,undo-list) changeset)
+ (undo-tree-move-GC-elts-to-pool (car changeset)))))
+ (nreverse changeset))))
+
+
+(defun undo-tree-copy-list (undo-list)
+ ;; Return a deep copy of first changeset in `undo-list'. Object id's are
+ ;; replaced by corresponding objects from `buffer-undo-tree' object-pool.
+ (let (copy p)
+ ;; if first element contains an object id, replace it with object from
+ ;; pool, discarding element entirely if it's been GC'd
+ (while (and undo-list (null copy))
+ (setq copy
+ (undo-tree-restore-GC-elts-from-pool (pop undo-list))))
+ (when copy
+ (setq copy (list copy)
+ p copy)
+ ;; copy remaining elements, replacing object id's with objects from
+ ;; pool, or discarding them entirely if they've been GC'd
+ (while undo-list
+ (when (setcdr p (undo-tree-restore-GC-elts-from-pool
+ (undo-copy-list-1 (pop undo-list))))
+ (setcdr p (list (cdr p)))
+ (setq p (cdr p))))
+ copy)))
+
+
+(defvar undo-tree-gc-flag nil)
+
+(defun undo-tree-post-gc ()
+ (setq undo-tree-gc-flag t))
+
+
+(defun undo-list-transfer-to-tree ()
+ ;; Transfer entries accumulated in `undo-list' to `buffer-undo-tree'.
+
+ ;; `undo-list-transfer-to-tree' should never be called when undo is disabled
+ ;; (i.e. `buffer-undo-tree' is t)
+ (cl-assert (not (eq buffer-undo-tree t)))
+
+ ;; if `buffer-undo-tree' is empty, create initial undo-tree
+ (when (null buffer-undo-tree) (setq buffer-undo-tree (make-undo-tree)))
+
+ ;; garbage-collect then repeatedly try to deep-copy `buffer-undo-list' until
+ ;; we succeed without GC running, in an attempt to mitigate race conditions
+ ;; with garbage collector corrupting undo history (is this even a thing?!)
+ (unless (or (null buffer-undo-list)
+ (undo-list-found-canary-p buffer-undo-list))
+ (garbage-collect))
+ (let (undo-list changeset)
+ (setq undo-tree-gc-flag t)
+ (while undo-tree-gc-flag
+ (setq undo-tree-gc-flag nil
+ undo-list (copy-tree buffer-undo-list)))
+ (setq buffer-undo-list '(nil undo-tree-canary))
+
+ ;; create new node from first changeset in `undo-list', save old
+ ;; `buffer-undo-tree' current node, and make new node the current node
+ (when (setq changeset (undo-list-pop-changeset undo-list))
+ (let* ((node (undo-tree-make-node nil changeset))
+ (splice (undo-tree-current buffer-undo-tree))
+ (size (undo-list-byte-size (undo-tree-node-undo node)))
+ (count 1))
+ (setf (undo-tree-current buffer-undo-tree) node)
+ ;; grow tree fragment backwards using `undo-list' changesets
+ (while (setq changeset (undo-list-pop-changeset undo-list))
+ (setq node (undo-tree-grow-backwards node changeset))
+ (cl-incf size (undo-list-byte-size (undo-tree-node-undo node)))
+ (cl-incf count))
+
+ ;; if no undo history has been discarded from `undo-list' since last
+ ;; transfer, splice new tree fragment onto end of old
+ ;; `buffer-undo-tree' current node
+ (if (undo-list-found-canary-p undo-list)
+ (progn
+ (setf (undo-tree-node-previous node) splice)
+ (push node (undo-tree-node-next splice))
+ (setf (undo-tree-node-branch splice) 0)
+ (cl-incf (undo-tree-size buffer-undo-tree) size)
+ (cl-incf (undo-tree-count buffer-undo-tree) count))
+
+ ;; if undo history has been discarded, replace entire
+ ;; `buffer-undo-tree' with new tree fragment
+ (unless (= (undo-tree-size buffer-undo-tree) 0)
+ (message "Undo history discarded by Emacs (see `undo-limit') - rebuilding undo-tree"))
+ (setq node (undo-tree-grow-backwards node nil))
+ (setf (undo-tree-root buffer-undo-tree) node)
+ (setf (undo-tree-size buffer-undo-tree) size)
+ (setf (undo-tree-count buffer-undo-tree) count)
+ (setq undo-list '(nil undo-tree-canary))))))
+
+ ;; discard undo history if necessary
+ (undo-tree-discard-history))
+
+
+(defun undo-list-byte-size (undo-list)
+ ;; Return size (in bytes) of UNDO-LIST
+ (let ((size 0))
+ (dolist (elt undo-list)
+ (cl-incf size 8) ; cons cells use up 8 bytes
+ (when (stringp (car-safe elt))
+ (cl-incf size (string-bytes (car elt)))))
+ size))
+
+
+
+(defun undo-list-rebuild-from-tree ()
+ "Rebuild `buffer-undo-list' from information in `buffer-undo-tree'."
+ (unless (eq buffer-undo-list t)
+ (undo-list-transfer-to-tree)
+ (setq buffer-undo-list nil)
+ (when buffer-undo-tree
+ (let ((stack (list (list (undo-tree-root buffer-undo-tree)))))
+ (push (sort (mapcar 'identity (undo-tree-node-next (caar stack)))
+ (lambda (a b)
+ (time-less-p (undo-tree-node-timestamp a)
+ (undo-tree-node-timestamp b))))
+ stack)
+ ;; Traverse tree in depth-and-oldest-first order, but add undo records
+ ;; on the way down, and redo records on the way up.
+ (while (or (car stack)
+ (not (eq (car (nth 1 stack))
+ (undo-tree-current buffer-undo-tree))))
+ (if (car stack)
+ (progn
+ (setq buffer-undo-list
+ (append (undo-tree-node-undo (caar stack))
+ buffer-undo-list))
+ (undo-boundary)
+ (push (sort (mapcar 'identity
+ (undo-tree-node-next (caar stack)))
+ (lambda (a b)
+ (time-less-p (undo-tree-node-timestamp a)
+ (undo-tree-node-timestamp b))))
+ stack))
+ (pop stack)
+ (setq buffer-undo-list
+ (append (undo-tree-node-redo (caar stack))
+ buffer-undo-list))
+ (undo-boundary)
+ (pop (car stack))))))))
+
+
+
+
+;;; =====================================================================
+;;; History discarding utility functions
+
+(defun undo-tree-oldest-leaf (node)
+ ;; Return oldest leaf node below NODE.
+ (while (undo-tree-node-next node)
+ (setq node
+ (car (sort (mapcar 'identity (undo-tree-node-next node))
+ (lambda (a b)
+ (time-less-p (undo-tree-node-timestamp a)
+ (undo-tree-node-timestamp b)))))))
+ node)
+
+
+(defun undo-tree-discard-node (node)
+ ;; Discard NODE from `buffer-undo-tree', and return next in line for
+ ;; discarding.
+
+ ;; don't discard current node
+ (unless (eq node (undo-tree-current buffer-undo-tree))
+
+ ;; discarding root node...
+ (if (eq node (undo-tree-root buffer-undo-tree))
+ (cond
+ ;; should always discard branches before root
+ ((> (length (undo-tree-node-next node)) 1)
+ (error "Trying to discard undo-tree root which still\
+ has multiple branches"))
+ ;; don't discard root if current node is only child
+ ((eq (car (undo-tree-node-next node))
+ (undo-tree-current buffer-undo-tree))
+ nil)
+ ;; discard root
+ (t
+ ;; clear any register referring to root
+ (let ((r (undo-tree-node-register node)))
+ (when (and r (eq (get-register r) node))
+ (set-register r nil)))
+ ;; make child of root into new root
+ (setq node (setf (undo-tree-root buffer-undo-tree)
+ (car (undo-tree-node-next node))))
+ ;; update undo-tree size
+ (cl-decf (undo-tree-size buffer-undo-tree)
+ (+ (undo-list-byte-size (undo-tree-node-undo node))
+ (undo-list-byte-size (undo-tree-node-redo node))))
+ (cl-decf (undo-tree-count buffer-undo-tree))
+ ;; discard new root's undo data and PREVIOUS link
+ (setf (undo-tree-node-undo node) nil
+ (undo-tree-node-redo node) nil
+ (undo-tree-node-previous node) nil)
+ ;; if new root has branches, or new root is current node, next node
+ ;; to discard is oldest leaf, otherwise it's new root
+ (if (or (> (length (undo-tree-node-next node)) 1)
+ (eq (car (undo-tree-node-next node))
+ (undo-tree-current buffer-undo-tree)))
+ (undo-tree-oldest-leaf node)
+ node)))
+
+ ;; discarding leaf node...
+ (let* ((parent (undo-tree-node-previous node))
+ (current (nth (undo-tree-node-branch parent)
+ (undo-tree-node-next parent))))
+ ;; clear any register referring to the discarded node
+ (let ((r (undo-tree-node-register node)))
+ (when (and r (eq (get-register r) node))
+ (set-register r nil)))
+ ;; update undo-tree size
+ (cl-decf (undo-tree-size buffer-undo-tree)
+ (+ (undo-list-byte-size (undo-tree-node-undo node))
+ (undo-list-byte-size (undo-tree-node-redo node))))
+ (cl-decf (undo-tree-count buffer-undo-tree))
+ ;; discard leaf
+ (setf (undo-tree-node-next parent)
+ (delq node (undo-tree-node-next parent))
+ (undo-tree-node-branch parent)
+ (undo-tree-position current (undo-tree-node-next parent)))
+ ;; if parent has branches, or parent is current node, next node to
+ ;; discard is oldest leaf, otherwise it's the parent itself
+ (if (or (eq parent (undo-tree-current buffer-undo-tree))
+ (and (undo-tree-node-next parent)
+ (or (not (eq parent (undo-tree-root buffer-undo-tree)))
+ (> (length (undo-tree-node-next parent)) 1))))
+ (undo-tree-oldest-leaf parent)
+ parent)))))
+
+
+
+(defun undo-tree-discard-history ()
+ "Discard undo history until we're within memory usage limits
+set by `undo-limit', `undo-strong-limit' and `undo-outer-limit'."
+
+ (when (> (undo-tree-size buffer-undo-tree) undo-limit)
+ ;; if there are no branches off root, first node to discard is root;
+ ;; otherwise it's leaf node at botom of oldest branch
+ (let ((node (if (> (length (undo-tree-node-next
+ (undo-tree-root buffer-undo-tree))) 1)
+ (undo-tree-oldest-leaf (undo-tree-root buffer-undo-tree))
+ (undo-tree-root buffer-undo-tree)))
+ discarded)
+
+ ;; discard nodes until memory use is within `undo-strong-limit'
+ (while (and node
+ (> (undo-tree-size buffer-undo-tree) undo-strong-limit))
+ (setq node (undo-tree-discard-node node)
+ discarded t))
+
+ ;; discard nodes until next node to discard would bring memory use
+ ;; within `undo-limit'
+ (while (and node
+ ;; check first if last discard has brought us within
+ ;; `undo-limit', in case we can avoid more expensive
+ ;; `undo-strong-limit' calculation
+ ;; Note: this assumes undo-strong-limit > undo-limit;
+ ;; if not, effectively undo-strong-limit = undo-limit
+ (> (undo-tree-size buffer-undo-tree) undo-limit)
+ (> (- (undo-tree-size buffer-undo-tree)
+ ;; if next node to discard is root, the memory we
+ ;; free-up comes from discarding changesets from its
+ ;; only child...
+ (if (eq node (undo-tree-root buffer-undo-tree))
+ (+ (undo-list-byte-size
+ (undo-tree-node-undo
+ (car (undo-tree-node-next node))))
+ (undo-list-byte-size
+ (undo-tree-node-redo
+ (car (undo-tree-node-next node)))))
+ ;; ...otherwise, it comes from discarding changesets
+ ;; from along with the node itself
+ (+ (undo-list-byte-size (undo-tree-node-undo node))
+ (undo-list-byte-size (undo-tree-node-redo node)))
+ ))
+ undo-limit))
+ (setq node (undo-tree-discard-node node)
+ discarded t))
+
+ (when discarded
+ (message "Undo history discarded by undo-tree (see `undo-tree-limit')"))
+
+ ;; if we're still over the `undo-outer-limit', discard entire history
+ (when (and undo-outer-limit
+ (> (undo-tree-size buffer-undo-tree) undo-outer-limit))
+ ;; query first if `undo-ask-before-discard' is set
+ (if undo-ask-before-discard
+ (when (yes-or-no-p
+ (format
+ "Buffer `%s' undo info is %d bytes long; discard it? "
+ (buffer-name) (undo-tree-size buffer-undo-tree)))
+ (setq buffer-undo-tree nil))
+ ;; otherwise, discard and display warning
+ (display-warning
+ '(undo discard-info)
+ (concat
+ (format "Buffer `%s' undo info was %d bytes long.\n"
+ (buffer-name) (undo-tree-size buffer-undo-tree))
+ "The undo info was discarded because it exceeded\
+ `undo-outer-limit'.
+
+This is normal if you executed a command that made a huge change
+to the buffer. In that case, to prevent similar problems in the
+future, set `undo-outer-limit' to a value that is large enough to
+cover the maximum size of normal changes you expect a single
+command to make, but not so large that it might exceed the
+maximum memory allotted to Emacs.
+
+If you did not execute any such command, the situation is
+probably due to a bug and you should report it.
+
+You can disable the popping up of this buffer by adding the entry
+\(undo discard-info) to the user option `warning-suppress-types',
+which is defined in the `warnings' library.\n")
+ :warning)
+ (setq buffer-undo-tree nil)))
+
+ ;; if currently displaying the visualizer, redraw it
+ (when (and buffer-undo-tree
+ discarded
+ (or (eq major-mode 'undo-tree-visualizer-mode)
+ undo-tree-visualizer-parent-buffer
+ (get-buffer undo-tree-visualizer-buffer-name)))
+ (let ((undo-tree buffer-undo-tree))
+ (with-current-buffer undo-tree-visualizer-buffer-name
+ (undo-tree-draw-tree undo-tree)
+ (when undo-tree-visualizer-diff (undo-tree-visualizer-update-diff)))))
+ )))
+
+
+
+
+;;; =====================================================================
+;;; Visualizer utility functions
+
+(defun undo-tree-compute-widths (node)
+ "Recursively compute widths for nodes below NODE."
+ (let ((stack (list node))
+ res)
+ (while stack
+ ;; try to compute widths for node at top of stack
+ (if (undo-tree-node-p
+ (setq res (undo-tree-node-compute-widths (car stack))))
+ ;; if computation fails, it returns a node whose widths still need
+ ;; computing, which we push onto the stack
+ (push res stack)
+ ;; otherwise, store widths and remove it from stack
+ (setf (undo-tree-node-lwidth (car stack)) (aref res 0)
+ (undo-tree-node-cwidth (car stack)) (aref res 1)
+ (undo-tree-node-rwidth (car stack)) (aref res 2))
+ (pop stack)))))
+
+
+(defun undo-tree-node-compute-widths (node)
+ ;; Compute NODE's left-, centre-, and right-subtree widths. Returns widths
+ ;; (in a vector) if successful. Otherwise, returns a node whose widths need
+ ;; calculating before NODE's can be calculated.
+ (let ((num-children (length (undo-tree-node-next node)))
+ (lwidth 0) (cwidth 0) (rwidth 0) p)
+ (catch 'need-widths
+ (cond
+ ;; leaf nodes have 0 width
+ ((= 0 num-children)
+ (setf cwidth 1
+ (undo-tree-node-lwidth node) 0
+ (undo-tree-node-cwidth node) 1
+ (undo-tree-node-rwidth node) 0))
+
+ ;; odd number of children
+ ((= (mod num-children 2) 1)
+ (setq p (undo-tree-node-next node))
+ ;; compute left-width
+ (dotimes (_ (/ num-children 2))
+ (if (undo-tree-node-lwidth (car p))
+ (cl-incf lwidth (+ (undo-tree-node-lwidth (car p))
+ (undo-tree-node-cwidth (car p))
+ (undo-tree-node-rwidth (car p))))
+ ;; if child's widths haven't been computed, return that child
+ (throw 'need-widths (car p)))
+ (setq p (cdr p)))
+ (if (undo-tree-node-lwidth (car p))
+ (cl-incf lwidth (undo-tree-node-lwidth (car p)))
+ (throw 'need-widths (car p)))
+ ;; centre-width is inherited from middle child
+ (setf cwidth (undo-tree-node-cwidth (car p)))
+ ;; compute right-width
+ (cl-incf rwidth (undo-tree-node-rwidth (car p)))
+ (setq p (cdr p))
+ (dotimes (_ (/ num-children 2))
+ (if (undo-tree-node-lwidth (car p))
+ (cl-incf rwidth (+ (undo-tree-node-lwidth (car p))
+ (undo-tree-node-cwidth (car p))
+ (undo-tree-node-rwidth (car p))))
+ (throw 'need-widths (car p)))
+ (setq p (cdr p))))
+
+ ;; even number of children
+ (t
+ (setq p (undo-tree-node-next node))
+ ;; compute left-width
+ (dotimes (_ (/ num-children 2))
+ (if (undo-tree-node-lwidth (car p))
+ (cl-incf lwidth (+ (undo-tree-node-lwidth (car p))
+ (undo-tree-node-cwidth (car p))
+ (undo-tree-node-rwidth (car p))))
+ (throw 'need-widths (car p)))
+ (setq p (cdr p)))
+ ;; centre-width is 0 when number of children is even
+ (setq cwidth 0)
+ ;; compute right-width
+ (dotimes (_ (/ num-children 2))
+ (if (undo-tree-node-lwidth (car p))
+ (cl-incf rwidth (+ (undo-tree-node-lwidth (car p))
+ (undo-tree-node-cwidth (car p))
+ (undo-tree-node-rwidth (car p))))
+ (throw 'need-widths (car p)))
+ (setq p (cdr p)))))
+
+ ;; return left-, centre- and right-widths
+ (vector lwidth cwidth rwidth))))
+
+
+(defun undo-tree-clear-visualizer-data (tree)
+ ;; Clear visualizer data below NODE.
+ (undo-tree-mapc
+ (lambda (n) (undo-tree-node-clear-visualizer-data n))
+ (undo-tree-root tree)))
+
+
+(defun undo-tree-node-unmodified-p (node &optional mtime)
+ ;; Return non-nil if NODE corresponds to a buffer state that once upon a
+ ;; time was unmodified. If a file modification time MTIME is specified,
+ ;; return non-nil if the corresponding buffer state really is unmodified.
+ (let (changeset ntime)
+ (setq changeset
+ (or (undo-tree-node-redo node)
+ (and (setq changeset (car (undo-tree-node-next node)))
+ (undo-tree-node-undo changeset)))
+ ntime
+ (catch 'found
+ (dolist (elt changeset)
+ (when (and (consp elt) (eq (car elt) t) (consp (cdr elt))
+ (throw 'found (cdr elt)))))))
+ (and ntime
+ (or (null mtime)
+ ;; high-precision timestamps
+ (if (listp (cdr ntime))
+ (equal ntime mtime)
+ ;; old-style timestamps
+ (and (= (car ntime) (car mtime))
+ (= (cdr ntime) (cadr mtime))))))))
+
+
+
+
+;;; =====================================================================
+;;; Undo-in-region utility functions
+
+;; `undo-elt-in-region' uses this as a dynamically-scoped variable
+(defvar undo-adjusted-markers nil)
+
+
+(defun undo-tree-pull-undo-in-region-branch (start end)
+ ;; Pull out entries from undo changesets to create a new undo-in-region
+ ;; branch, which undoes changeset entries lying between START and END first,
+ ;; followed by remaining entries from the changesets, before rejoining the
+ ;; existing undo tree history. Repeated calls will, if appropriate, extend
+ ;; the current undo-in-region branch rather than creating a new one.
+
+ ;; if we're just reverting the last redo-in-region, we don't need to
+ ;; manipulate the undo tree at all
+ (if (undo-tree-reverting-redo-in-region-p start end)
+ t ; return t to indicate success
+
+ ;; We build the `region-changeset' and `delta-list' lists forwards, using
+ ;; pointers `r' and `d' to the penultimate element of the list. So that we
+ ;; don't have to treat the first element differently, we prepend a dummy
+ ;; leading nil to the lists, and have the pointers point to that
+ ;; initially.
+ ;; Note: using '(nil) instead of (list nil) in the `let*' results in
+ ;; errors when the code is byte-compiled, presumably because the
+ ;; Lisp reader generates a single cons, and that same cons gets used
+ ;; each call.
+ (let* ((region-changeset (list nil))
+ (r region-changeset)
+ (delta-list (list nil))
+ (d delta-list)
+ (node (undo-tree-current buffer-undo-tree))
+ (repeated-undo-in-region
+ (undo-tree-repeated-undo-in-region-p start end))
+ undo-adjusted-markers ; `undo-elt-in-region' expects this
+ fragment splice original-fragment original-splice original-current
+ got-visible-elt undo-list elt)
+
+ ;; --- initialisation ---
+ (cond
+ ;; if this is a repeated undo in the same region, start pulling changes
+ ;; from NODE at which undo-in-region branch is attached, and detatch
+ ;; the branch, using it as initial FRAGMENT of branch being constructed
+ (repeated-undo-in-region
+ (setq original-current node
+ fragment (car (undo-tree-node-next node))
+ splice node)
+ ;; undo up to node at which undo-in-region branch is attached
+ ;; (recognizable as first node with more than one branch)
+ (let ((mark-active nil))
+ (while (= (length (undo-tree-node-next node)) 1)
+ (undo-tree-undo-1)
+ (setq fragment node
+ node (undo-tree-current buffer-undo-tree))))
+ (when (eq splice node) (setq splice nil))
+ ;; detatch undo-in-region branch
+ (setf (undo-tree-node-next node)
+ (delq fragment (undo-tree-node-next node))
+ (undo-tree-node-previous fragment) nil
+ original-fragment fragment
+ original-splice node))
+
+ ;; if this is a new undo-in-region, initial FRAGMENT is a copy of all
+ ;; nodes below the current one in the active branch
+ ((undo-tree-node-next node)
+ (setq fragment (undo-tree-make-node nil nil)
+ splice fragment)
+ (while (setq node (nth (undo-tree-node-branch node)
+ (undo-tree-node-next node)))
+ (push (undo-tree-make-node
+ splice
+ (undo-copy-list (undo-tree-node-undo node))
+ (undo-copy-list (undo-tree-node-redo node)))
+ (undo-tree-node-next splice))
+ (setq splice (car (undo-tree-node-next splice))))
+ (setq fragment (car (undo-tree-node-next fragment))
+ splice nil
+ node (undo-tree-current buffer-undo-tree))))
+
+
+ ;; --- pull undo-in-region elements into branch ---
+ ;; work backwards up tree, pulling out undo elements within region until
+ ;; we've got one that undoes a visible change (insertion or deletion)
+ (catch 'abort
+ (while (and (not got-visible-elt) node (undo-tree-node-undo node))
+ ;; we cons a dummy nil element on the front of the changeset so that
+ ;; we can conveniently remove the first (real) element from the
+ ;; changeset if we need to; the leading nil is removed once we're
+ ;; done with this changeset
+ (setq undo-list (cons nil (undo-copy-list (undo-tree-node-undo node)))
+ elt (cadr undo-list))
+ (if fragment
+ (progn
+ (setq fragment (undo-tree-grow-backwards fragment undo-list))
+ (unless splice (setq splice fragment)))
+ (setq fragment (undo-tree-make-node nil undo-list))
+ (setq splice fragment))
+
+ (while elt
+ (cond
+ ;; keep elements within region
+ ((undo-elt-in-region elt start end)
+ ;; set flag if kept element is visible (insertion or deletion)
+ (when (and (consp elt)
+ (or (stringp (car elt)) (integerp (car elt))))
+ (setq got-visible-elt t))
+ ;; adjust buffer positions in elements previously undone before
+ ;; kept element, as kept element will now be undone first
+ (undo-tree-adjust-elements-to-elt splice elt)
+ ;; move kept element to undo-in-region changeset, adjusting its
+ ;; buffer position as it will now be undone first
+ (setcdr r (list (undo-tree-apply-deltas elt (cdr delta-list))))
+ (setq r (cdr r))
+ (setcdr undo-list (cddr undo-list)))
+
+ ;; discard "was unmodified" elements
+ ;; FIXME: deal properly with these
+ ((and (consp elt) (eq (car elt) t))
+ (setcdr undo-list (cddr undo-list)))
+
+ ;; if element crosses region, we can't pull any more elements
+ ((undo-elt-crosses-region elt start end)
+ ;; if we've found a visible element, it must be earlier in
+ ;; current node's changeset; stop pulling elements (null
+ ;; `undo-list' and non-nil `got-visible-elt' cause loop to exit)
+ (if got-visible-elt
+ (setq undo-list nil)
+ ;; if we haven't found a visible element yet, pulling
+ ;; undo-in-region branch has failed
+ (setq region-changeset nil)
+ (throw 'abort t)))
+
+ ;; if rejecting element, add its delta (if any) to the list
+ (t
+ (let ((delta (undo-delta elt)))
+ (when (/= 0 (cdr delta))
+ (setcdr d (list delta))
+ (setq d (cdr d))))
+ (setq undo-list (cdr undo-list))))
+
+ ;; process next element of current changeset
+ (setq elt (cadr undo-list)))
+
+ ;; if there are remaining elements in changeset, remove dummy nil
+ ;; from front
+ (if (cadr (undo-tree-node-undo fragment))
+ (pop (undo-tree-node-undo fragment))
+ ;; otherwise, if we've kept all elements in changeset, discard
+ ;; empty changeset
+ (when (eq splice fragment) (setq splice nil))
+ (setq fragment (car (undo-tree-node-next fragment))))
+ ;; process changeset from next node up the tree
+ (setq node (undo-tree-node-previous node))))
+
+ ;; pop dummy nil from front of `region-changeset'
+ (setq region-changeset (cdr region-changeset))
+
+
+ ;; --- integrate branch into tree ---
+ ;; if no undo-in-region elements were found, restore undo tree
+ (if (null region-changeset)
+ (when original-current
+ (push original-fragment (undo-tree-node-next original-splice))
+ (setf (undo-tree-node-branch original-splice) 0
+ (undo-tree-node-previous original-fragment) original-splice)
+ (let ((mark-active nil))
+ (while (not (eq (undo-tree-current buffer-undo-tree)
+ original-current))
+ (undo-tree-redo-1)))
+ nil) ; return nil to indicate failure
+
+ ;; otherwise...
+ ;; need to undo up to node where new branch will be attached, to
+ ;; ensure redo entries are populated, and then redo back to where we
+ ;; started
+ (let ((mark-active nil)
+ (current (undo-tree-current buffer-undo-tree)))
+ (while (not (eq (undo-tree-current buffer-undo-tree) node))
+ (undo-tree-undo-1))
+ (while (not (eq (undo-tree-current buffer-undo-tree) current))
+ (undo-tree-redo-1)))
+
+ (cond
+ ;; if there's no remaining fragment, just create undo-in-region node
+ ;; and attach it to parent of last node from which elements were
+ ;; pulled
+ ((null fragment)
+ (setq fragment (undo-tree-make-node node region-changeset))
+ (push fragment (undo-tree-node-next node))
+ (setf (undo-tree-node-branch node) 0)
+ ;; set current node to undo-in-region node
+ (setf (undo-tree-current buffer-undo-tree) fragment))
+
+ ;; if no splice point has been set, add undo-in-region node to top of
+ ;; fragment and attach it to parent of last node from which elements
+ ;; were pulled
+ ((null splice)
+ (setq fragment (undo-tree-grow-backwards fragment region-changeset))
+ (push fragment (undo-tree-node-next node))
+ (setf (undo-tree-node-branch node) 0
+ (undo-tree-node-previous fragment) node)
+ ;; set current node to undo-in-region node
+ (setf (undo-tree-current buffer-undo-tree) fragment))
+
+ ;; if fragment contains nodes, attach fragment to parent of last node
+ ;; from which elements were pulled, and splice in undo-in-region node
+ (t
+ (setf (undo-tree-node-previous fragment) node)
+ (push fragment (undo-tree-node-next node))
+ (setf (undo-tree-node-branch node) 0)
+ ;; if this is a repeated undo-in-region, then we've left the current
+ ;; node at the original splice-point; we need to set the current
+ ;; node to the equivalent node on the undo-in-region branch and redo
+ ;; back to where we started
+ (when repeated-undo-in-region
+ (setf (undo-tree-current buffer-undo-tree)
+ (undo-tree-node-previous original-fragment))
+ (let ((mark-active nil))
+ (while (not (eq (undo-tree-current buffer-undo-tree) splice))
+ (undo-tree-redo-1 nil 'preserve-undo))))
+ ;; splice new undo-in-region node into fragment
+ (setq node (undo-tree-make-node nil region-changeset))
+ (undo-tree-splice-node node splice)
+ ;; set current node to undo-in-region node
+ (setf (undo-tree-current buffer-undo-tree) node)))
+
+ ;; update undo-tree size
+ (setq node (undo-tree-node-previous fragment))
+ (while (progn
+ (and (setq node (car (undo-tree-node-next node)))
+ (not (eq node original-fragment))
+ (cl-incf (undo-tree-count buffer-undo-tree))
+ (cl-incf (undo-tree-size buffer-undo-tree)
+ (+ (undo-list-byte-size (undo-tree-node-undo node))
+ (undo-list-byte-size (undo-tree-node-redo node)))))))
+ t) ; indicate undo-in-region branch was successfully pulled
+ )))
+
+
+
+(defun undo-tree-pull-redo-in-region-branch (start end)
+ ;; Pull out entries from redo changesets to create a new redo-in-region
+ ;; branch, which redoes changeset entries lying between START and END first,
+ ;; followed by remaining entries from the changesets. Repeated calls will,
+ ;; if appropriate, extend the current redo-in-region branch rather than
+ ;; creating a new one.
+
+ ;; if we're just reverting the last undo-in-region, we don't need to
+ ;; manipulate the undo tree at all
+ (if (undo-tree-reverting-undo-in-region-p start end)
+ t ; return t to indicate success
+
+ ;; We build the `region-changeset' and `delta-list' lists forwards, using
+ ;; pointers `r' and `d' to the penultimate element of the list. So that we
+ ;; don't have to treat the first element differently, we prepend a dummy
+ ;; leading nil to the lists, and have the pointers point to that
+ ;; initially.
+ ;; Note: using '(nil) instead of (list nil) in the `let*' causes bizarre
+ ;; errors when the code is byte-compiled, where parts of the lists
+ ;; appear to survive across different calls to this function. An
+ ;; obscure byte-compiler bug, perhaps?
+ (let* ((region-changeset (list nil))
+ (r region-changeset)
+ (delta-list (list nil))
+ (d delta-list)
+ (node (undo-tree-current buffer-undo-tree))
+ (repeated-redo-in-region
+ (undo-tree-repeated-redo-in-region-p start end))
+ undo-adjusted-markers ; `undo-elt-in-region' expects this
+ fragment splice got-visible-elt redo-list elt)
+
+ ;; --- inisitalisation ---
+ (cond
+ ;; if this is a repeated redo-in-region, detach fragment below current
+ ;; node
+ (repeated-redo-in-region
+ (when (setq fragment (car (undo-tree-node-next node)))
+ (setf (undo-tree-node-previous fragment) nil
+ (undo-tree-node-next node)
+ (delq fragment (undo-tree-node-next node)))))
+ ;; if this is a new redo-in-region, initial fragment is a copy of all
+ ;; nodes below the current one in the active branch
+ ((undo-tree-node-next node)
+ (setq fragment (undo-tree-make-node nil nil)
+ splice fragment)
+ (while (setq node (nth (undo-tree-node-branch node)
+ (undo-tree-node-next node)))
+ (push (undo-tree-make-node
+ splice nil
+ (undo-copy-list (undo-tree-node-redo node)))
+ (undo-tree-node-next splice))
+ (setq splice (car (undo-tree-node-next splice))))
+ (setq fragment (car (undo-tree-node-next fragment)))))
+
+
+ ;; --- pull redo-in-region elements into branch ---
+ ;; work down fragment, pulling out redo elements within region until
+ ;; we've got one that redoes a visible change (insertion or deletion)
+ (setq node fragment)
+ (catch 'abort
+ (while (and (not got-visible-elt) node (undo-tree-node-redo node))
+ ;; we cons a dummy nil element on the front of the changeset so that
+ ;; we can conveniently remove the first (real) element from the
+ ;; changeset if we need to; the leading nil is removed once we're
+ ;; done with this changeset
+ (setq redo-list (push nil (undo-tree-node-redo node))
+ elt (cadr redo-list))
+ (while elt
+ (cond
+ ;; keep elements within region
+ ((undo-elt-in-region elt start end)
+ ;; set flag if kept element is visible (insertion or deletion)
+ (when (and (consp elt)
+ (or (stringp (car elt)) (integerp (car elt))))
+ (setq got-visible-elt t))
+ ;; adjust buffer positions in elements previously redone before
+ ;; kept element, as kept element will now be redone first
+ (undo-tree-adjust-elements-to-elt fragment elt t)
+ ;; move kept element to redo-in-region changeset, adjusting its
+ ;; buffer position as it will now be redone first
+ (setcdr r (list (undo-tree-apply-deltas elt (cdr delta-list) -1)))
+ (setq r (cdr r))
+ (setcdr redo-list (cddr redo-list)))
+
+ ;; discard "was unmodified" elements
+ ;; FIXME: deal properly with these
+ ((and (consp elt) (eq (car elt) t))
+ (setcdr redo-list (cddr redo-list)))
+
+ ;; if element crosses region, we can't pull any more elements
+ ((undo-elt-crosses-region elt start end)
+ ;; if we've found a visible element, it must be earlier in
+ ;; current node's changeset; stop pulling elements (null
+ ;; `redo-list' and non-nil `got-visible-elt' cause loop to exit)
+ (if got-visible-elt
+ (setq redo-list nil)
+ ;; if we haven't found a visible element yet, pulling
+ ;; redo-in-region branch has failed
+ (setq region-changeset nil)
+ (throw 'abort t)))
+
+ ;; if rejecting element, add its delta (if any) to the list
+ (t
+ (let ((delta (undo-delta elt)))
+ (when (/= 0 (cdr delta))
+ (setcdr d (list delta))
+ (setq d (cdr d))))
+ (setq redo-list (cdr redo-list))))
+
+ ;; process next element of current changeset
+ (setq elt (cadr redo-list)))
+
+ ;; if there are remaining elements in changeset, remove dummy nil
+ ;; from front
+ (if (cadr (undo-tree-node-redo node))
+ (pop (undo-tree-node-undo node))
+ ;; otherwise, if we've kept all elements in changeset, discard
+ ;; empty changeset
+ (if (eq fragment node)
+ (setq fragment (car (undo-tree-node-next fragment)))
+ (undo-tree-snip-node node)))
+ ;; process changeset from next node in fragment
+ (setq node (car (undo-tree-node-next node)))))
+
+ ;; pop dummy nil from front of `region-changeset'
+ (setq region-changeset (cdr region-changeset))
+
+
+ ;; --- integrate branch into tree ---
+ (setq node (undo-tree-current buffer-undo-tree))
+ ;; if no redo-in-region elements were found, restore undo tree
+ (if (null (car region-changeset))
+ (when (and repeated-redo-in-region fragment)
+ (push fragment (undo-tree-node-next node))
+ (setf (undo-tree-node-branch node) 0
+ (undo-tree-node-previous fragment) node)
+ nil) ; return nil to indicate failure
+
+ ;; otherwise, add redo-in-region node to top of fragment, and attach
+ ;; it below current node
+ (setq fragment
+ (if fragment
+ (undo-tree-grow-backwards fragment nil region-changeset)
+ (undo-tree-make-node nil nil region-changeset)))
+ (push fragment (undo-tree-node-next node))
+ (setf (undo-tree-node-branch node) 0
+ (undo-tree-node-previous fragment) node)
+ ;; update undo-tree size
+ (unless repeated-redo-in-region
+ (setq node fragment)
+ (while (and (setq node (car (undo-tree-node-next node)))
+ (cl-incf (undo-tree-count buffer-undo-tree))
+ (cl-incf (undo-tree-size buffer-undo-tree)
+ (undo-list-byte-size
+ (undo-tree-node-redo node))))))
+ (cl-incf (undo-tree-size buffer-undo-tree)
+ (undo-list-byte-size (undo-tree-node-redo fragment)))
+ t) ; indicate redo-in-region branch was successfully pulled
+ )))
+
+
+
+(defun undo-tree-adjust-elements-to-elt (node undo-elt &optional below)
+ "Adjust buffer positions of undo elements, starting at NODE's
+and going up the tree (or down the active branch if BELOW is
+non-nil) and through the nodes' undo elements until we reach
+UNDO-ELT. UNDO-ELT must appear somewhere in the undo changeset
+of either NODE itself or some node above it in the tree."
+ (let ((delta (list (undo-delta undo-elt)))
+ (undo-list (undo-tree-node-undo node)))
+ ;; adjust elements until we reach UNDO-ELT
+ (while (and (car undo-list)
+ (not (eq (car undo-list) undo-elt)))
+ (setcar undo-list
+ (undo-tree-apply-deltas (car undo-list) delta -1))
+ ;; move to next undo element in list, or to next node if we've run out
+ ;; of elements
+ (unless (car (setq undo-list (cdr undo-list)))
+ (if below
+ (setq node (nth (undo-tree-node-branch node)
+ (undo-tree-node-next node)))
+ (setq node (undo-tree-node-previous node)))
+ (setq undo-list (undo-tree-node-undo node))))))
+
+
+
+(defun undo-tree-apply-deltas (undo-elt deltas &optional sgn)
+ ;; Apply DELTAS in order to UNDO-ELT, multiplying deltas by SGN
+ ;; (only useful value for SGN is -1).
+ (let (position offset)
+ (dolist (delta deltas)
+ (setq position (car delta)
+ offset (* (cdr delta) (or sgn 1)))
+ (cond
+ ;; POSITION
+ ((integerp undo-elt)
+ (when (>= undo-elt position)
+ (setq undo-elt (- undo-elt offset))))
+ ;; nil (or any other atom)
+ ((atom undo-elt))
+ ;; (TEXT . POSITION)
+ ((stringp (car undo-elt))
+ (let ((text-pos (abs (cdr undo-elt)))
+ (point-at-end (< (cdr undo-elt) 0)))
+ (if (>= text-pos position)
+ (setcdr undo-elt (* (if point-at-end -1 1)
+ (- text-pos offset))))))
+ ;; (BEGIN . END)
+ ((integerp (car undo-elt))
+ (when (>= (car undo-elt) position)
+ (setcar undo-elt (- (car undo-elt) offset))
+ (setcdr undo-elt (- (cdr undo-elt) offset))))
+ ;; (nil PROPERTY VALUE BEG . END)
+ ((null (car undo-elt))
+ (let ((tail (nthcdr 3 undo-elt)))
+ (when (>= (car tail) position)
+ (setcar tail (- (car tail) offset))
+ (setcdr tail (- (cdr tail) offset)))))
+ ))
+ undo-elt))
+
+
+
+(defun undo-tree-repeated-undo-in-region-p (start end)
+ ;; Return non-nil if undo-in-region between START and END is a repeated
+ ;; undo-in-region
+ (let ((node (undo-tree-current buffer-undo-tree)))
+ (and (setq node
+ (nth (undo-tree-node-branch node) (undo-tree-node-next node)))
+ (eq (undo-tree-node-undo-beginning node) start)
+ (eq (undo-tree-node-undo-end node) end))))
+
+
+(defun undo-tree-repeated-redo-in-region-p (start end)
+ ;; Return non-nil if undo-in-region between START and END is a repeated
+ ;; undo-in-region
+ (let ((node (undo-tree-current buffer-undo-tree)))
+ (and (eq (undo-tree-node-redo-beginning node) start)
+ (eq (undo-tree-node-redo-end node) end))))
+
+
+;; Return non-nil if undo-in-region between START and END is simply
+;; reverting the last redo-in-region
+(defalias 'undo-tree-reverting-undo-in-region-p
+ 'undo-tree-repeated-undo-in-region-p)
+
+
+;; Return non-nil if redo-in-region between START and END is simply
+;; reverting the last undo-in-region
+(defalias 'undo-tree-reverting-redo-in-region-p
+ 'undo-tree-repeated-redo-in-region-p)
+
+
+
+
+;;; =====================================================================
+;;; Undo-tree commands
+
+(defvar undo-tree-timer nil)
+
+;;;###autoload
+(define-minor-mode undo-tree-mode
+ "Toggle undo-tree mode.
+With no argument, this command toggles the mode.
+A positive prefix argument turns the mode on.
+A negative prefix argument turns it off.
+
+Undo-tree-mode replaces Emacs' standard undo feature with a more
+powerful yet easier to use version, that treats the undo history
+as what it is: a tree.
+
+The following keys are available in `undo-tree-mode':
+
+ \\{undo-tree-map}
+
+Within the undo-tree visualizer, the following keys are available:
+
+ \\{undo-tree-visualizer-mode-map}"
+
+ :init-value nil ; init value
+ :lighter undo-tree-mode-lighter ; lighter
+ :keymap undo-tree-map ; keymap
+
+ (cond
+ (undo-tree-mode ; enabling `undo-tree-mode'
+ (set (make-local-variable 'undo-limit)
+ (if undo-tree-limit
+ (max undo-limit undo-tree-limit)
+ most-positive-fixnum))
+ (set (make-local-variable 'undo-strong-limit)
+ (if undo-tree-limit
+ (max undo-strong-limit undo-tree-strong-limit)
+ most-positive-fixnum))
+ (set (make-local-variable 'undo-outer-limit) ; null `undo-outer-limit' means no limit
+ (when (and undo-tree-limit undo-outer-limit undo-outer-limit)
+ (max undo-outer-limit undo-tree-outer-limit)))
+ (when (null undo-tree-limit)
+ (setq undo-tree-timer
+ (run-with-idle-timer 5 'repeat #'undo-list-transfer-to-tree)))
+ (add-hook 'post-gc-hook #'undo-tree-post-gc nil))
+
+ (t ; disabling `undo-tree-mode'
+ ;; rebuild `buffer-undo-list' from tree so Emacs undo can work
+ (undo-list-rebuild-from-tree)
+ (setq buffer-undo-tree nil)
+ (remove-hook 'post-gc-hook #'undo-tree-post-gc 'local)
+ (when (timerp undo-tree-timer) (cancel-timer undo-tree-timer))
+ (kill-local-variable 'undo-limit)
+ (kill-local-variable 'undo-strong-limit)
+ (kill-local-variable 'undo-outer-limit))))
+
+
+(defun turn-on-undo-tree-mode (&optional print-message)
+ "Enable `undo-tree-mode' in the current buffer, when appropriate.
+Some major modes implement their own undo system, which should
+not normally be overridden by `undo-tree-mode'. This command does
+not enable `undo-tree-mode' in such buffers. If you want to force
+`undo-tree-mode' to be enabled regardless, use (undo-tree-mode 1)
+instead.
+
+The heuristic used to detect major modes in which
+`undo-tree-mode' should not be used is to check whether either
+the `undo' command has been remapped, or the default undo
+keybindings (C-/ and C-_) have been overridden somewhere other
+than in the global map. In addition, `undo-tree-mode' will not be
+enabled if the buffer's `major-mode' appears in
+`undo-tree-incompatible-major-modes'."
+ (interactive "p")
+ (if (or (key-binding [remap undo])
+ (undo-tree-overridden-undo-bindings-p)
+ (memq major-mode undo-tree-incompatible-major-modes))
+ (when print-message
+ (message "Buffer does not support undo-tree-mode;\
+ undo-tree-mode NOT enabled"))
+ (undo-tree-mode 1)))
+
+
+(defun undo-tree-overridden-undo-bindings-p ()
+ "Returns t if default undo bindings are overridden, nil otherwise.
+Checks if either of the default undo key bindings (\"C-/\" or
+\"C-_\") are overridden in the current buffer by any keymap other
+than the global one. (So global redefinitions of the default undo
+key bindings do not count.)"
+ (let ((binding1 (lookup-key (current-global-map) [?\C-/]))
+ (binding2 (lookup-key (current-global-map) [?\C-_])))
+ (global-set-key [?\C-/] 'undo)
+ (global-set-key [?\C-_] 'undo)
+ (unwind-protect
+ (or (and (key-binding [?\C-/])
+ (not (eq (key-binding [?\C-/]) 'undo)))
+ (and (key-binding [?\C-_])
+ (not (eq (key-binding [?\C-_]) 'undo))))
+ (global-set-key [?\C-/] binding1)
+ (global-set-key [?\C-_] binding2))))
+
+
+;;;###autoload
+(define-globalized-minor-mode global-undo-tree-mode
+ undo-tree-mode turn-on-undo-tree-mode)
+
+
+
+(defun undo-tree-undo (&optional arg)
+ "Undo changes.
+Repeat this command to undo more changes.
+A numeric ARG serves as a repeat count.
+
+In Transient Mark mode when the mark is active, only undo changes
+within the current region. Similarly, when not in Transient Mark
+mode, just \\[universal-argument] as an argument limits undo to
+changes within the current region."
+ (interactive "*P")
+ (unless undo-tree-mode
+ (user-error "Undo-tree mode not enabled in buffer"))
+ ;; throw error if undo is disabled in buffer
+ (when (eq buffer-undo-list t)
+ (user-error "No undo information in this buffer"))
+ (undo-tree-undo-1 arg)
+ ;; inform user if at branch point
+ (when (> (undo-tree-num-branches) 1) (message "Undo branch point!")))
+
+
+(defun undo-tree-undo-1 (&optional arg preserve-redo preserve-timestamps)
+ ;; Internal undo function. An active mark in `transient-mark-mode', or
+ ;; non-nil ARG otherwise, enables undo-in-region. Non-nil PRESERVE-REDO
+ ;; causes the existing redo record to be preserved, rather than replacing it
+ ;; with the new one generated by undoing. Non-nil PRESERVE-TIMESTAMPS
+ ;; disables updating of timestamps in visited undo-tree nodes. (This latter
+ ;; should *only* be used when temporarily visiting another undo state and
+ ;; immediately returning to the original state afterwards. Otherwise, it
+ ;; could cause history-discarding errors.)
+ (let ((undo-in-progress t)
+ (undo-in-region (and undo-tree-enable-undo-in-region
+ (or (region-active-p)
+ (and arg (not (numberp arg))))))
+ pos current)
+ ;; transfer entries accumulated in `buffer-undo-list' to
+ ;; `buffer-undo-tree'
+ (undo-list-transfer-to-tree)
+
+ (dotimes (_ (or (and (numberp arg) (prefix-numeric-value arg)) 1))
+ ;; check if at top of undo tree
+ (unless (undo-tree-node-previous (undo-tree-current buffer-undo-tree))
+ (user-error "No further undo information"))
+
+ ;; if region is active, or a non-numeric prefix argument was supplied,
+ ;; try to pull out a new branch of changes affecting the region
+ (when (and undo-in-region
+ (not (undo-tree-pull-undo-in-region-branch
+ (region-beginning) (region-end))))
+ (user-error "No further undo information for region"))
+
+ ;; remove any GC'd elements from node's undo list
+ (setq current (undo-tree-current buffer-undo-tree))
+ (cl-decf (undo-tree-size buffer-undo-tree)
+ (undo-list-byte-size (undo-tree-node-undo current)))
+ (setf (undo-tree-node-undo current)
+ (undo-list-clean-GCd-elts (undo-tree-node-undo current)))
+ (cl-incf (undo-tree-size buffer-undo-tree)
+ (undo-list-byte-size (undo-tree-node-undo current)))
+ ;; undo one record from undo tree
+ (when undo-in-region
+ (setq pos (set-marker (make-marker) (point)))
+ (set-marker-insertion-type pos t))
+ (primitive-undo 1 (undo-tree-copy-list (undo-tree-node-undo current)))
+ (undo-boundary)
+
+ ;; if preserving old redo record, discard new redo entries that
+ ;; `primitive-undo' has added to `buffer-undo-list', and remove any GC'd
+ ;; elements from node's redo list
+ (if preserve-redo
+ (progn
+ (undo-list-pop-changeset buffer-undo-list)
+ (cl-decf (undo-tree-size buffer-undo-tree)
+ (undo-list-byte-size (undo-tree-node-redo current)))
+ (setf (undo-tree-node-redo current)
+ (undo-list-clean-GCd-elts (undo-tree-node-redo current)))
+ (cl-incf (undo-tree-size buffer-undo-tree)
+ (undo-list-byte-size (undo-tree-node-redo current))))
+ ;; otherwise, record redo entries that `primitive-undo' has added to
+ ;; `buffer-undo-list' in current node's redo record, replacing
+ ;; existing entry if one already exists
+ (cl-decf (undo-tree-size buffer-undo-tree)
+ (undo-list-byte-size (undo-tree-node-redo current)))
+ (setf (undo-tree-node-redo current)
+ (undo-list-pop-changeset buffer-undo-list 'discard-pos))
+ (cl-incf (undo-tree-size buffer-undo-tree)
+ (undo-list-byte-size (undo-tree-node-redo current))))
+
+ ;; rewind current node and update timestamp
+ (setf (undo-tree-current buffer-undo-tree)
+ (undo-tree-node-previous (undo-tree-current buffer-undo-tree)))
+ (unless preserve-timestamps
+ (setf (undo-tree-node-timestamp (undo-tree-current buffer-undo-tree))
+ (current-time)))
+
+ ;; if undoing-in-region, record current node, region and direction so we
+ ;; can tell if undo-in-region is repeated, and re-activate mark if in
+ ;; `transient-mark-mode'; if not, erase any leftover data
+ (if (not undo-in-region)
+ (undo-tree-node-clear-region-data current)
+ (goto-char pos)
+ ;; note: we deliberately want to store the region information in the
+ ;; node *below* the now current one
+ (setf (undo-tree-node-undo-beginning current) (region-beginning)
+ (undo-tree-node-undo-end current) (region-end))
+ (set-marker pos nil)))
+
+ ;; undo deactivates mark unless undoing-in-region
+ (setq deactivate-mark (not undo-in-region))))
+
+
+
+(defun undo-tree-redo (&optional arg)
+ "Redo changes. A numeric ARG serves as a repeat count.
+
+In Transient Mark mode when the mark is active, only redo changes
+within the current region. Similarly, when not in Transient Mark
+mode, just \\[universal-argument] as an argument limits redo to
+changes within the current region."
+ (interactive "*P")
+ (unless undo-tree-mode
+ (user-error "Undo-tree mode not enabled in buffer"))
+ ;; throw error if undo is disabled in buffer
+ (when (eq buffer-undo-list t)
+ (user-error "No undo information in this buffer"))
+ (undo-tree-redo-1 arg)
+ ;; inform user if at branch point
+ (when (> (undo-tree-num-branches) 1) (message "Undo branch point!")))
+
+
+(defun undo-tree-redo-1 (&optional arg preserve-undo preserve-timestamps)
+ ;; Internal redo function. An active mark in `transient-mark-mode', or
+ ;; non-nil ARG otherwise, enables undo-in-region. Non-nil PRESERVE-UNDO
+ ;; causes the existing redo record to be preserved, rather than replacing it
+ ;; with the new one generated by undoing. Non-nil PRESERVE-TIMESTAMPS
+ ;; disables updating of timestamps in visited undo-tree nodes. (This latter
+ ;; should *only* be used when temporarily visiting another undo state and
+ ;; immediately returning to the original state afterwards. Otherwise, it
+ ;; could cause history-discarding errors.)
+ (let ((undo-in-progress t)
+ (redo-in-region (and undo-tree-enable-undo-in-region
+ (or (region-active-p)
+ (and arg (not (numberp arg))))))
+ pos current)
+ ;; transfer entries accumulated in `buffer-undo-list' to
+ ;; `buffer-undo-tree'
+ (undo-list-transfer-to-tree)
+
+ (dotimes (_ (or (and (numberp arg) (prefix-numeric-value arg)) 1))
+ ;; check if at bottom of undo tree
+ (when (null (undo-tree-node-next (undo-tree-current buffer-undo-tree)))
+ (user-error "No further redo information"))
+
+ ;; if region is active, or a non-numeric prefix argument was supplied,
+ ;; try to pull out a new branch of changes affecting the region
+ (when (and redo-in-region
+ (not (undo-tree-pull-redo-in-region-branch
+ (region-beginning) (region-end))))
+ (user-error "No further redo information for region"))
+
+ ;; get next node (but DON'T advance current node in tree yet, in case
+ ;; redoing fails)
+ (setq current (undo-tree-current buffer-undo-tree)
+ current (nth (undo-tree-node-branch current)
+ (undo-tree-node-next current)))
+ ;; remove any GC'd elements from node's redo list
+ (cl-decf (undo-tree-size buffer-undo-tree)
+ (undo-list-byte-size (undo-tree-node-redo current)))
+ (setf (undo-tree-node-redo current)
+ (undo-list-clean-GCd-elts (undo-tree-node-redo current)))
+ (cl-incf (undo-tree-size buffer-undo-tree)
+ (undo-list-byte-size (undo-tree-node-redo current)))
+ ;; redo one record from undo tree
+ (when redo-in-region
+ (setq pos (set-marker (make-marker) (point)))
+ (set-marker-insertion-type pos t))
+ (primitive-undo 1 (undo-tree-copy-list (undo-tree-node-redo current)))
+ (undo-boundary)
+ ;; advance current node in tree
+ (setf (undo-tree-current buffer-undo-tree) current)
+
+ ;; if preserving old undo record, discard new undo entries that
+ ;; `primitive-undo' has added to `buffer-undo-list', and remove any GC'd
+ ;; elements from node's redo list
+ (if preserve-undo
+ (progn
+ (undo-list-pop-changeset buffer-undo-list)
+ (cl-decf (undo-tree-size buffer-undo-tree)
+ (undo-list-byte-size (undo-tree-node-undo current)))
+ (setf (undo-tree-node-undo current)
+ (undo-list-clean-GCd-elts (undo-tree-node-undo current)))
+ (cl-incf (undo-tree-size buffer-undo-tree)
+ (undo-list-byte-size (undo-tree-node-undo current))))
+ ;; otherwise, record undo entries that `primitive-undo' has added to
+ ;; `buffer-undo-list' in current node's undo record, replacing
+ ;; existing entry if one already exists
+ (cl-decf (undo-tree-size buffer-undo-tree)
+ (undo-list-byte-size (undo-tree-node-undo current)))
+ (setf (undo-tree-node-undo current)
+ (undo-list-pop-changeset buffer-undo-list 'discard-pos))
+ (cl-incf (undo-tree-size buffer-undo-tree)
+ (undo-list-byte-size (undo-tree-node-undo current))))
+
+ ;; update timestamp
+ (unless preserve-timestamps
+ (setf (undo-tree-node-timestamp current) (current-time)))
+
+ ;; if redoing-in-region, record current node, region and direction so we
+ ;; can tell if redo-in-region is repeated, and re-activate mark if in
+ ;; `transient-mark-mode'
+ (if (not redo-in-region)
+ (undo-tree-node-clear-region-data current)
+ (goto-char pos)
+ (setf (undo-tree-node-redo-beginning current) (region-beginning)
+ (undo-tree-node-redo-end current) (region-end))
+ (set-marker pos nil)))
+
+ ;; redo deactivates the mark unless redoing-in-region
+ (setq deactivate-mark (not redo-in-region))))
+
+
+
+(defun undo-tree-switch-branch (branch)
+ "Switch to a different BRANCH of the undo tree.
+This will affect which branch to descend when *redoing* changes
+using `undo-tree-redo'."
+ (interactive (list (or (and prefix-arg (prefix-numeric-value prefix-arg))
+ (and (not (eq buffer-undo-list t))
+ (undo-list-transfer-to-tree)
+ (let ((b (undo-tree-node-branch
+ (undo-tree-current
+ buffer-undo-tree))))
+ (cond
+ ;; switch to other branch if only 2
+ ((= (undo-tree-num-branches) 2) (- 1 b))
+ ;; prompt if more than 2
+ ((> (undo-tree-num-branches) 2)
+ (read-number
+ (format "Branch (0-%d, on %d): "
+ (1- (undo-tree-num-branches)) b)))
+ ))))))
+ (unless undo-tree-mode
+ (user-error "Undo-tree mode not enabled in buffer"))
+ ;; throw error if undo is disabled in buffer
+ (when (eq buffer-undo-list t)
+ (user-error "No undo information in this buffer"))
+ ;; sanity check branch number
+ (when (<= (undo-tree-num-branches) 1)
+ (user-error "Not at undo branch point"))
+ (when (or (< branch 0) (> branch (1- (undo-tree-num-branches))))
+ (user-error "Invalid branch number"))
+ ;; transfer entries accumulated in `buffer-undo-list' to `buffer-undo-tree'
+ (undo-list-transfer-to-tree)
+ ;; switch branch
+ (setf (undo-tree-node-branch (undo-tree-current buffer-undo-tree))
+ branch)
+ (message "Switched to branch %d" branch))
+
+
+(defun undo-tree-set (node &optional preserve-timestamps)
+ ;; Set buffer to state corresponding to NODE. Returns intersection point
+ ;; between path back from current node and path back from selected NODE.
+ ;; Non-nil PRESERVE-TIMESTAMPS disables updating of timestamps in visited
+ ;; undo-tree nodes. (This should *only* be used when temporarily visiting
+ ;; another undo state and immediately returning to the original state
+ ;; afterwards. Otherwise, it could cause history-discarding errors.)
+ (let ((path (make-hash-table :test 'eq))
+ (n node))
+ (puthash (undo-tree-root buffer-undo-tree) t path)
+ ;; build list of nodes leading back from selected node to root, updating
+ ;; branches as we go to point down to selected node
+ (while (progn
+ (puthash n t path)
+ (when (undo-tree-node-previous n)
+ (setf (undo-tree-node-branch (undo-tree-node-previous n))
+ (undo-tree-position
+ n (undo-tree-node-next (undo-tree-node-previous n))))
+ (setq n (undo-tree-node-previous n)))))
+ ;; work backwards from current node until we intersect path back from
+ ;; selected node
+ (setq n (undo-tree-current buffer-undo-tree))
+ (while (not (gethash n path))
+ (setq n (undo-tree-node-previous n)))
+ ;; ascend tree until intersection node
+ (while (not (eq (undo-tree-current buffer-undo-tree) n))
+ (undo-tree-undo-1 nil nil preserve-timestamps))
+ ;; descend tree until selected node
+ (while (not (eq (undo-tree-current buffer-undo-tree) node))
+ (undo-tree-redo-1 nil nil preserve-timestamps))
+ n)) ; return intersection node
+
+
+
+(defun undo-tree-save-state-to-register (register)
+ "Store current undo-tree state to REGISTER.
+The saved state can be restored using
+`undo-tree-restore-state-from-register'.
+Argument is a character, naming the register."
+ (interactive "cUndo-tree state to register: ")
+ (unless undo-tree-mode
+ (user-error "Undo-tree mode not enabled in buffer"))
+ ;; throw error if undo is disabled in buffer
+ (when (eq buffer-undo-list t)
+ (user-error "No undo information in this buffer"))
+ ;; transfer entries accumulated in `buffer-undo-list' to `buffer-undo-tree'
+ (undo-list-transfer-to-tree)
+ ;; save current node to REGISTER
+ (set-register
+ register (registerv-make
+ (undo-tree-make-register-data
+ (current-buffer) (undo-tree-current buffer-undo-tree))
+ :print-func 'undo-tree-register-data-print-func))
+ ;; record REGISTER in current node, for visualizer
+ (setf (undo-tree-node-register (undo-tree-current buffer-undo-tree))
+ register))
+
+
+
+(defun undo-tree-restore-state-from-register (register)
+ "Restore undo-tree state from REGISTER.
+The state must be saved using `undo-tree-save-state-to-register'.
+Argument is a character, naming the register."
+ (interactive "*cRestore undo-tree state from register: ")
+ (unless undo-tree-mode
+ (user-error "Undo-tree mode not enabled in buffer"))
+ ;; throw error if undo is disabled in buffer, or if register doesn't contain
+ ;; an undo-tree node
+ (let ((data (registerv-data (get-register register))))
+ (cond
+ ((eq buffer-undo-list t)
+ (user-error "No undo information in this buffer"))
+ ((not (undo-tree-register-data-p data))
+ (user-error "Register doesn't contain undo-tree state"))
+ ((not (eq (current-buffer) (undo-tree-register-data-buffer data)))
+ (user-error "Register contains undo-tree state for a different buffer")))
+ ;; transfer entries accumulated in `buffer-undo-list' to `buffer-undo-tree'
+ (undo-list-transfer-to-tree)
+ ;; restore buffer state corresponding to saved node
+ (undo-tree-set (undo-tree-register-data-node data))))
+
+
+
+
+;;; =====================================================================
+;;; Undo-tree menu bar
+
+(defvar undo-tree-old-undo-menu-item nil)
+
+(defun undo-tree-update-menu-bar ()
+ "Update `undo-tree-mode' Edit menu items."
+ (if undo-tree-mode
+ (progn
+ ;; save old undo menu item, and install undo/redo menu items
+ (setq undo-tree-old-undo-menu-item
+ (cdr (assq 'undo (lookup-key global-map [menu-bar edit]))))
+ (define-key (lookup-key global-map [menu-bar edit])
+ [undo] '(menu-item "Undo" undo-tree-undo
+ :enable (and undo-tree-mode
+ (not buffer-read-only)
+ (not (eq t buffer-undo-list))
+ (not (eq nil buffer-undo-tree))
+ (undo-tree-node-previous
+ (undo-tree-current buffer-undo-tree)))
+ :help "Undo last operation"))
+ (define-key-after (lookup-key global-map [menu-bar edit])
+ [redo] '(menu-item "Redo" undo-tree-redo
+ :enable (and undo-tree-mode
+ (not buffer-read-only)
+ (not (eq t buffer-undo-list))
+ (not (eq nil buffer-undo-tree))
+ (undo-tree-node-next
+ (undo-tree-current buffer-undo-tree)))
+ :help "Redo last operation")
+ 'undo))
+ ;; uninstall undo/redo menu items
+ (define-key (lookup-key global-map [menu-bar edit])
+ [undo] undo-tree-old-undo-menu-item)
+ (define-key (lookup-key global-map [menu-bar edit])
+ [redo] nil)))
+
+(add-hook 'menu-bar-update-hook 'undo-tree-update-menu-bar)
+
+
+
+
+;;; =====================================================================
+;;; Persistent storage commands
+
+(defun undo-tree-make-history-save-file-name (file)
+ "Create the undo history file name for FILE.
+Normally this is the file's name with \".\" prepended and
+\".~undo-tree~\" appended.
+
+A match for FILE is sought in `undo-tree-history-directory-alist'
+\(see the documentation of that variable for details\). If the
+directory for the backup doesn't exist, it is created."
+ (let* ((backup-directory-alist undo-tree-history-directory-alist)
+ (name (make-backup-file-name-1 file)))
+ (concat (file-name-directory name) "." (file-name-nondirectory name)
+ ".~undo-tree~")))
+
+
+(defun undo-tree-save-history (&optional filename overwrite)
+ "Store undo-tree history to file.
+
+If optional argument FILENAME is omitted, default save file is
+\"..~undo-tree\" if buffer is visiting a file.
+Otherwise, prompt for one.
+
+If OVERWRITE is non-nil, any existing file will be overwritten
+without asking for confirmation."
+ (interactive)
+ (unless undo-tree-mode
+ (user-error "Undo-tree mode not enabled in buffer"))
+ (when (eq buffer-undo-list t)
+ (user-error "No undo information in this buffer"))
+ (undo-list-transfer-to-tree)
+ (when (and buffer-undo-tree (not (eq buffer-undo-tree t)))
+ (undo-tree-kill-visualizer)
+ ;; should be cleared already by killing the visualizer, but writes
+ ;; unreasable data if not for some reason, so just in case...
+ (undo-tree-clear-visualizer-data buffer-undo-tree)
+ (let ((buff (current-buffer))
+ tree)
+ ;; get filename
+ (unless filename
+ (setq filename
+ (if buffer-file-name
+ (undo-tree-make-history-save-file-name buffer-file-name)
+ (expand-file-name (read-file-name "File to save in: ") nil))))
+ (when (or (not (file-exists-p filename))
+ overwrite
+ (yes-or-no-p (format "Overwrite \"%s\"? " filename)))
+ ;; transform undo-tree into non-circular structure, and make tmp copy
+ (setq tree (undo-tree-copy buffer-undo-tree))
+ (undo-tree-decircle tree)
+ ;; discard undo-tree object pool before saving
+ (setf (undo-tree-object-pool tree) nil)
+ ;; run pre-save transformer functions
+ (when undo-tree-pre-save-element-functions
+ (undo-tree-mapc
+ (lambda (node)
+ (let ((changeset (undo-tree-node-undo node)))
+ (run-hook-wrapped
+ 'undo-tree-pre-save-element-functions
+ (lambda (fun)
+ (setq changeset (delq nil (mapcar fun changeset)))))
+ (setf (undo-tree-node-undo node) changeset))
+ (let ((changeset (undo-tree-node-redo node)))
+ (run-hook-wrapped
+ 'undo-tree-pre-save-element-functions
+ (lambda (fun)
+ (setq changeset (delq nil (mapcar fun changeset)))))
+ (setf (undo-tree-node-redo node) changeset)))
+ (undo-tree-root tree)))
+ ;; print undo-tree to file
+ ;; NOTE: We use `with-temp-buffer' instead of `with-temp-file' to
+ ;; allow `auto-compression-mode' to take effect, in case user
+ ;; has overridden or advised the default
+ ;; `undo-tree-make-history-save-file-name' to add a compressed
+ ;; file extension.
+ (with-auto-compression-mode
+ (with-temp-buffer
+ (prin1 (sha1 buff) (current-buffer))
+ (terpri (current-buffer))
+ (let ((print-circle t)) (prin1 tree (current-buffer)))
+ (write-region nil nil filename)))))))
+
+
+
+(defun undo-tree-load-history (&optional filename noerror)
+ "Load undo-tree history from file, for the current buffer.
+
+If optional argument FILENAME is null, default load file is
+\"..~undo-tree\" if buffer is visiting a file.
+Otherwise, prompt for one.
+
+If optional argument NOERROR is non-nil, return nil instead of
+signaling an error if file is not found.
+
+Note this will overwrite any existing undo history."
+ (interactive)
+ (unless undo-tree-mode
+ (user-error "Undo-tree mode not enabled in buffer"))
+ ;; get filename
+ (unless filename
+ (setq filename
+ (if buffer-file-name
+ (undo-tree-make-history-save-file-name buffer-file-name)
+ (expand-file-name (read-file-name "File to load from: ") nil))))
+
+ ;; attempt to read undo-tree from FILENAME
+ (catch 'load-error
+ (unless (file-exists-p filename)
+ (if noerror
+ (throw 'load-error nil)
+ (error "File \"%s\" does not exist; could not load undo-tree history"
+ filename)))
+ (let (buff hash tree)
+ (setq buff (current-buffer))
+ (with-auto-compression-mode
+ (with-temp-buffer
+ (insert-file-contents filename)
+ (goto-char (point-min))
+ (condition-case nil
+ (setq hash (read (current-buffer)))
+ (error
+ (kill-buffer nil)
+ (funcall (if noerror #'message #'user-error)
+ "Error reading undo-tree history from \"%s\"" filename)
+ (throw 'load-error nil)))
+ (unless (string= (sha1 buff) hash)
+ (kill-buffer nil)
+ (funcall (if noerror 'message 'user-error)
+ "Buffer has been modified; could not load undo-tree history")
+ (throw 'load-error nil))
+ (condition-case nil
+ (setq tree (read (current-buffer)))
+ (error
+ (kill-buffer nil)
+ (funcall (if noerror #'message #'error)
+ "Error reading undo-tree history from \"%s\"" filename)
+ (throw 'load-error nil)))
+ (kill-buffer nil)))
+ ;; run post-load transformer functions
+ (when undo-tree-post-load-element-functions
+ (undo-tree-mapc
+ (lambda (node)
+ (let ((changeset (undo-tree-node-undo node)))
+ (run-hook-wrapped
+ 'undo-tree-post-load-element-functions
+ (lambda (fun)
+ (setq changeset (delq nil (mapcar fun changeset)))))
+ (setf (undo-tree-node-undo node) changeset))
+ (let ((changeset (undo-tree-node-redo node)))
+ (run-hook-wrapped
+ 'undo-tree-post-load-element-functions
+ (lambda (fun)
+ (setq changeset (delq nil (mapcar fun changeset)))))
+ (setf (undo-tree-node-redo node) changeset)))
+ (undo-tree-root tree))) ;; initialise empty undo-tree object pool
+ (setf (undo-tree-object-pool tree)
+ (make-hash-table :test 'eq :weakness 'value))
+ ;; restore circular undo-tree data structure
+ (undo-tree-recircle tree)
+ ;; create undo-tree object pool
+ (setf (undo-tree-object-pool tree)
+ (make-hash-table :test 'eq :weakness 'value))
+ (setq buffer-undo-tree tree
+ buffer-undo-list '(nil undo-tree-canary)))))
+
+
+
+;; Versions of save/load functions for use in hooks
+(defun undo-tree-save-history-from-hook ()
+ (when (and undo-tree-mode undo-tree-auto-save-history
+ (not (eq buffer-undo-list t))
+ buffer-file-name
+ (file-writable-p
+ (undo-tree-make-history-save-file-name buffer-file-name)))
+ (undo-tree-save-history nil 'overwrite) nil))
+
+(define-obsolete-function-alias
+ 'undo-tree-save-history-hook 'undo-tree-save-history-from-hook
+ "`undo-tree-save-history-hook' is obsolete since undo-tree
+ version 0.6.6. Use `undo-tree-save-history-from-hook' instead.")
+
+
+(defun undo-tree-load-history-from-hook ()
+ (when (and undo-tree-mode undo-tree-auto-save-history
+ (not (eq buffer-undo-list t))
+ (not revert-buffer-in-progress-p))
+ (undo-tree-load-history nil 'noerror)))
+
+(define-obsolete-function-alias
+ 'undo-tree-load-history-hook 'undo-tree-load-history-from-hook
+ "`undo-tree-load-history-hook' is obsolete since undo-tree
+ version 0.6.6. Use `undo-tree-load-history-from-hook' instead.")
+
+
+;; install history-auto-save hooks
+(add-hook 'write-file-functions #'undo-tree-save-history-from-hook)
+(add-hook 'kill-buffer-hook #'undo-tree-save-history-from-hook)
+(add-hook 'find-file-hook #'undo-tree-load-history-from-hook)
+
+
+
+
+;;; =====================================================================
+;;; Visualizer drawing functions
+
+(defun undo-tree-visualize ()
+ "Visualize the current buffer's undo tree."
+ (interactive "*")
+ (unless undo-tree-mode
+ (user-error "Undo-tree mode not enabled in buffer"))
+ (deactivate-mark)
+ ;; throw error if undo is disabled in buffer
+ (when (eq buffer-undo-list t)
+ (user-error "No undo information in this buffer"))
+ ;; transfer entries accumulated in `buffer-undo-list' to `buffer-undo-tree'
+ (undo-list-transfer-to-tree)
+ ;; add hook to kill visualizer buffer if original buffer is changed
+ (add-hook 'before-change-functions 'undo-tree-kill-visualizer nil t)
+ ;; prepare *undo-tree* buffer, then draw tree in it
+ (let ((undo-tree buffer-undo-tree)
+ (buff (current-buffer))
+ (display-buffer-mark-dedicated 'soft))
+ (switch-to-buffer-other-window
+ (get-buffer-create undo-tree-visualizer-buffer-name))
+ (setq undo-tree-visualizer-parent-buffer buff)
+ (setq undo-tree-visualizer-parent-mtime
+ (and (buffer-file-name buff)
+ (nth 5 (file-attributes (buffer-file-name buff)))))
+ (setq undo-tree-visualizer-initial-node (undo-tree-current undo-tree))
+ (setq undo-tree-visualizer-spacing
+ (undo-tree-visualizer-calculate-spacing))
+ (make-local-variable 'undo-tree-visualizer-timestamps)
+ (make-local-variable 'undo-tree-visualizer-diff)
+ (setq buffer-undo-tree undo-tree)
+ (undo-tree-visualizer-mode)
+ ;; FIXME; don't know why `undo-tree-visualizer-mode' clears this
+ (setq buffer-undo-tree undo-tree)
+ (set (make-local-variable 'undo-tree-visualizer-lazy-drawing)
+ (or (eq undo-tree-visualizer-lazy-drawing t)
+ (and (numberp undo-tree-visualizer-lazy-drawing)
+ (>= (undo-tree-count undo-tree)
+ undo-tree-visualizer-lazy-drawing))))
+ (when undo-tree-visualizer-diff (undo-tree-visualizer-show-diff))
+ (let ((inhibit-read-only t)) (undo-tree-draw-tree undo-tree))))
+
+
+(defun undo-tree-kill-visualizer (&rest _dummy)
+ ;; Kill visualizer. Added to `before-change-functions' hook of original
+ ;; buffer when visualizer is invoked.
+ (unless (or undo-tree-inhibit-kill-visualizer
+ (null (get-buffer undo-tree-visualizer-buffer-name)))
+ (with-current-buffer undo-tree-visualizer-buffer-name
+ (undo-tree-visualizer-quit))))
+
+
+
+(defun undo-tree-draw-tree (undo-tree)
+ ;; Draw undo-tree in current buffer starting from NODE (or root if nil).
+ (let ((inhibit-read-only t)
+ (node (if undo-tree-visualizer-lazy-drawing
+ (undo-tree-current undo-tree)
+ (undo-tree-root undo-tree))))
+ (erase-buffer)
+ (setq undo-tree-visualizer-needs-extending-down nil
+ undo-tree-visualizer-needs-extending-up nil)
+ (undo-tree-clear-visualizer-data undo-tree)
+ (undo-tree-compute-widths node)
+ ;; lazy drawing starts vertically centred and displaced horizontally to
+ ;; the left (window-width/4), since trees will typically grow right
+ (if undo-tree-visualizer-lazy-drawing
+ (progn
+ (undo-tree-move-down (/ (window-height) 2))
+ (undo-tree-move-forward (max 2 (/ (window-width) 4)))) ; left margin
+ ;; non-lazy drawing starts in centre at top of buffer
+ (undo-tree-move-down 1) ; top margin
+ (undo-tree-move-forward
+ (max (/ (window-width) 2)
+ (+ (undo-tree-node-char-lwidth node)
+ ;; add space for left part of left-most time-stamp
+ (if undo-tree-visualizer-timestamps
+ (/ (- undo-tree-visualizer-spacing 4) 2)
+ 0)
+ 2)))) ; left margin
+ ;; link starting node to its representation in visualizer
+ (setf (undo-tree-node-marker node) (make-marker))
+ (set-marker-insertion-type (undo-tree-node-marker node) nil)
+ (move-marker (undo-tree-node-marker node) (point))
+ ;; draw undo-tree
+ (let ((undo-tree-insert-face 'undo-tree-visualizer-default-face)
+ node-list)
+ (if (not undo-tree-visualizer-lazy-drawing)
+ (undo-tree-extend-down node t)
+ (undo-tree-extend-down node)
+ (undo-tree-extend-up node)
+ (setq node-list undo-tree-visualizer-needs-extending-down
+ undo-tree-visualizer-needs-extending-down nil)
+ (while node-list (undo-tree-extend-down (pop node-list)))))
+ ;; highlight active branch
+ (let ((undo-tree-insert-face 'undo-tree-visualizer-active-branch-face))
+ (undo-tree-highlight-active-branch
+ (or undo-tree-visualizer-needs-extending-up
+ (undo-tree-root undo-tree))))
+ ;; highlight current node
+ (undo-tree-draw-node (undo-tree-current undo-tree) 'current)))
+
+
+(defun undo-tree-extend-down (node &optional bottom)
+ ;; Extend tree downwards starting from NODE and point. If BOTTOM is t,
+ ;; extend all the way down to the leaves. If BOTTOM is a node, extend down
+ ;; as far as that node. If BOTTOM is an integer, extend down as far as that
+ ;; line. Otherwise, only extend visible portion of tree. NODE is assumed to
+ ;; already have a node marker. Returns non-nil if anything was actually
+ ;; extended.
+ (let ((extended nil)
+ (cur-stack (list node))
+ next-stack)
+ ;; don't bother extending if BOTTOM specifies an already-drawn node
+ (unless (and (undo-tree-node-p bottom) (undo-tree-node-marker bottom))
+ ;; draw nodes layer by layer
+ (while (or cur-stack
+ (prog1 (setq cur-stack next-stack)
+ (setq next-stack nil)))
+ (setq node (pop cur-stack))
+ ;; if node is within range being drawn...
+ (if (or (eq bottom t)
+ (and (undo-tree-node-p bottom)
+ (not (eq (undo-tree-node-previous node) bottom)))
+ (and (integerp bottom)
+ (>= bottom (line-number-at-pos
+ (undo-tree-node-marker node))))
+ (and (null bottom)
+ (pos-visible-in-window-p (undo-tree-node-marker node)
+ nil t)))
+ ;; ...draw one layer of node's subtree (if not already drawn)
+ (progn
+ (unless (and (undo-tree-node-next node)
+ (undo-tree-node-marker
+ (nth (undo-tree-node-branch node)
+ (undo-tree-node-next node))))
+ (goto-char (undo-tree-node-marker node))
+ (undo-tree-draw-subtree node)
+ (setq extended t))
+ (setq next-stack
+ (append (undo-tree-node-next node) next-stack)))
+ ;; ...otherwise, postpone drawing until later
+ (push node undo-tree-visualizer-needs-extending-down))))
+ extended))
+
+
+(defun undo-tree-extend-up (node &optional top)
+ ;; Extend tree upwards starting from NODE. If TOP is t, extend all the way
+ ;; to root. If TOP is a node, extend up as far as that node. If TOP is an
+ ;; integer, extend up as far as that line. Otherwise, only extend visible
+ ;; portion of tree. NODE is assumed to already have a node marker. Returns
+ ;; non-nil if anything was actually extended.
+ (let ((extended nil) parent)
+ ;; don't bother extending if TOP specifies an already-drawn node
+ (unless (and (undo-tree-node-p top) (undo-tree-node-marker top))
+ (while node
+ (setq parent (undo-tree-node-previous node))
+ ;; if we haven't reached root...
+ (if parent
+ ;; ...and node is within range being drawn...
+ (if (or (eq top t)
+ (and (undo-tree-node-p top) (not (eq node top)))
+ (and (integerp top)
+ (< top (line-number-at-pos
+ (undo-tree-node-marker node))))
+ (and (null top)
+ ;; NOTE: we check point in case window-start is outdated
+ (< (min (line-number-at-pos (point))
+ (line-number-at-pos (window-start)))
+ (line-number-at-pos
+ (undo-tree-node-marker node)))))
+ ;; ...and it hasn't already been drawn
+ (when (not (undo-tree-node-marker parent))
+ ;; link parent node to its representation in visualizer
+ (undo-tree-compute-widths parent)
+ (undo-tree-move-to-parent node)
+ (setf (undo-tree-node-marker parent) (make-marker))
+ (set-marker-insertion-type
+ (undo-tree-node-marker parent) nil)
+ (move-marker (undo-tree-node-marker parent) (point))
+ ;; draw subtree beneath parent
+ (setq undo-tree-visualizer-needs-extending-down
+ (nconc (delq node (undo-tree-draw-subtree parent))
+ undo-tree-visualizer-needs-extending-down))
+ (setq extended t))
+ ;; ...otherwise, postpone drawing for later and exit
+ (setq undo-tree-visualizer-needs-extending-up (when parent node)
+ parent nil))
+
+ ;; if we've reached root, stop extending and add top margin
+ (setq undo-tree-visualizer-needs-extending-up nil)
+ (goto-char (undo-tree-node-marker node))
+ (undo-tree-move-up 1) ; top margin
+ (delete-region (point-min) (line-beginning-position)))
+ ;; next iteration
+ (setq node parent)))
+ extended))
+
+
+(defun undo-tree-expand-down (from &optional to)
+ ;; Expand tree downwards. FROM is the node to start expanding from. Stop
+ ;; expanding at TO if specified. Otherwise, just expand visible portion of
+ ;; tree and highlight active branch from FROM.
+ (when undo-tree-visualizer-needs-extending-down
+ (let ((inhibit-read-only t)
+ node-list extended)
+ ;; extend down as far as TO node
+ (when to
+ (setq extended (undo-tree-extend-down from to))
+ (goto-char (undo-tree-node-marker to))
+ (redisplay t)) ; force redisplay to scroll buffer if necessary
+ ;; extend visible portion of tree downwards
+ (setq node-list undo-tree-visualizer-needs-extending-down
+ undo-tree-visualizer-needs-extending-down nil)
+ (when node-list
+ (dolist (n node-list)
+ (when (undo-tree-extend-down n) (setq extended t)))
+ ;; highlight active branch in newly-extended-down portion, if any
+ (when extended
+ (let ((undo-tree-insert-face
+ 'undo-tree-visualizer-active-branch-face))
+ (undo-tree-highlight-active-branch from)))))))
+
+
+(defun undo-tree-expand-up (from &optional to)
+ ;; Expand tree upwards. FROM is the node to start expanding from, TO is the
+ ;; node to stop expanding at. If TO node isn't specified, just expand visible
+ ;; portion of tree and highlight active branch down to FROM.
+ (when undo-tree-visualizer-needs-extending-up
+ (let ((inhibit-read-only t)
+ extended node-list)
+ ;; extend up as far as TO node
+ (when to
+ (setq extended (undo-tree-extend-up from to))
+ (goto-char (undo-tree-node-marker to))
+ ;; simulate auto-scrolling if close to top of buffer
+ (when (<= (line-number-at-pos (point)) scroll-margin)
+ (undo-tree-move-up (if (= scroll-conservatively 0)
+ (/ (window-height) 2) 3))
+ (when (undo-tree-extend-up to) (setq extended t))
+ (goto-char (undo-tree-node-marker to))
+ (unless (= scroll-conservatively 0) (recenter scroll-margin))))
+ ;; extend visible portion of tree upwards
+ (and undo-tree-visualizer-needs-extending-up
+ (undo-tree-extend-up undo-tree-visualizer-needs-extending-up)
+ (setq extended t))
+ ;; extend visible portion of tree downwards
+ (setq node-list undo-tree-visualizer-needs-extending-down
+ undo-tree-visualizer-needs-extending-down nil)
+ (dolist (n node-list) (undo-tree-extend-down n))
+ ;; highlight active branch in newly-extended-up portion, if any
+ (when extended
+ (let ((undo-tree-insert-face
+ 'undo-tree-visualizer-active-branch-face))
+ (undo-tree-highlight-active-branch
+ (or undo-tree-visualizer-needs-extending-up
+ (undo-tree-root buffer-undo-tree))
+ from))))))
+
+
+
+(defun undo-tree-highlight-active-branch (node &optional end)
+ ;; Draw highlighted active branch below NODE in current buffer. Stop
+ ;; highlighting at END node if specified.
+ (let ((stack (list node)))
+ ;; draw active branch
+ (while stack
+ (setq node (pop stack))
+ (unless (or (eq node end)
+ (memq node undo-tree-visualizer-needs-extending-down))
+ (goto-char (undo-tree-node-marker node))
+ (setq node (undo-tree-draw-subtree node 'active)
+ stack (nconc stack node))))))
+
+
+(defun undo-tree-draw-node (node &optional current)
+ ;; Draw symbol representing NODE in visualizer. If CURRENT is non-nil, node
+ ;; is current node.
+ (goto-char (undo-tree-node-marker node))
+ (when undo-tree-visualizer-timestamps
+ (undo-tree-move-backward (/ undo-tree-visualizer-spacing 2)))
+
+ (let* ((undo-tree-insert-face (and undo-tree-insert-face
+ (or (and (consp undo-tree-insert-face)
+ undo-tree-insert-face)
+ (list undo-tree-insert-face))))
+ (register (undo-tree-node-register node))
+ (unmodified (if undo-tree-visualizer-parent-mtime
+ (undo-tree-node-unmodified-p
+ node undo-tree-visualizer-parent-mtime)
+ (undo-tree-node-unmodified-p node)))
+ node-string)
+ ;; check node's register (if any) still stores appropriate undo-tree state
+ (unless (and register
+ (undo-tree-register-data-p
+ (registerv-data (get-register register)))
+ (eq node (undo-tree-register-data-node
+ (registerv-data (get-register register)))))
+ (setq register nil))
+ ;; represent node by different symbols, depending on whether it's the
+ ;; current node, is saved in a register, or corresponds to an unmodified
+ ;; buffer
+ (setq node-string
+ (cond
+ (undo-tree-visualizer-timestamps
+ (undo-tree-timestamp-to-string
+ (undo-tree-node-timestamp node)
+ undo-tree-visualizer-relative-timestamps
+ current register))
+ (register (char-to-string register))
+ (unmodified "s")
+ (current "x")
+ (t "o"))
+ undo-tree-insert-face
+ (nconc
+ (cond
+ (current '(undo-tree-visualizer-current-face))
+ (unmodified '(undo-tree-visualizer-unmodified-face))
+ (register '(undo-tree-visualizer-register-face)))
+ undo-tree-insert-face))
+ ;; draw node and link it to its representation in visualizer
+ (undo-tree-insert node-string)
+ (undo-tree-move-backward (if undo-tree-visualizer-timestamps
+ (1+ (/ undo-tree-visualizer-spacing 2))
+ 1))
+ (move-marker (undo-tree-node-marker node) (point))
+ (put-text-property (point) (1+ (point)) 'undo-tree-node node)))
+
+
+(defun undo-tree-draw-subtree (node &optional active-branch)
+ ;; Draw subtree rooted at NODE. The subtree will start from point.
+ ;; If ACTIVE-BRANCH is non-nil, just draw active branch below NODE. Returns
+ ;; list of nodes below NODE.
+ (let ((num-children (length (undo-tree-node-next node)))
+ node-list pos trunk-pos n)
+ ;; draw node itself
+ (undo-tree-draw-node node)
+
+ (cond
+ ;; if we're at a leaf node, we're done
+ ((= num-children 0))
+
+ ;; if node has only one child, draw it (not strictly necessary to deal
+ ;; with this case separately, but as it's by far the most common case
+ ;; this makes the code clearer and more efficient)
+ ((= num-children 1)
+ (undo-tree-move-down 1)
+ (undo-tree-insert ?|)
+ (undo-tree-move-backward 1)
+ (undo-tree-move-down 1)
+ (undo-tree-insert ?|)
+ (undo-tree-move-backward 1)
+ (undo-tree-move-down 1)
+ (setq n (car (undo-tree-node-next node)))
+ ;; link next node to its representation in visualizer
+ (unless (markerp (undo-tree-node-marker n))
+ (setf (undo-tree-node-marker n) (make-marker))
+ (set-marker-insertion-type (undo-tree-node-marker n) nil))
+ (move-marker (undo-tree-node-marker n) (point))
+ ;; add next node to list of nodes to draw next
+ (push n node-list))
+
+ ;; if node has multiple children, draw branches
+ (t
+ (undo-tree-move-down 1)
+ (undo-tree-insert ?|)
+ (undo-tree-move-backward 1)
+ (move-marker (setq trunk-pos (make-marker)) (point))
+ ;; left subtrees
+ (undo-tree-move-backward
+ (- (undo-tree-node-char-lwidth node)
+ (undo-tree-node-char-lwidth
+ (car (undo-tree-node-next node)))))
+ (move-marker (setq pos (make-marker)) (point))
+ (setq n (cons nil (undo-tree-node-next node)))
+ (dotimes (_ (/ num-children 2))
+ (setq n (cdr n))
+ (when (or (null active-branch)
+ (eq (car n)
+ (nth (undo-tree-node-branch node)
+ (undo-tree-node-next node))))
+ (undo-tree-move-forward 2)
+ (undo-tree-insert ?_ (- trunk-pos pos 2))
+ (goto-char pos)
+ (undo-tree-move-forward 1)
+ (undo-tree-move-down 1)
+ (undo-tree-insert ?/)
+ (undo-tree-move-backward 2)
+ (undo-tree-move-down 1)
+ ;; link node to its representation in visualizer
+ (unless (markerp (undo-tree-node-marker (car n)))
+ (setf (undo-tree-node-marker (car n)) (make-marker))
+ (set-marker-insertion-type (undo-tree-node-marker (car n)) nil))
+ (move-marker (undo-tree-node-marker (car n)) (point))
+ ;; add node to list of nodes to draw next
+ (push (car n) node-list))
+ (goto-char pos)
+ (undo-tree-move-forward
+ (+ (undo-tree-node-char-rwidth (car n))
+ (undo-tree-node-char-lwidth (cadr n))
+ undo-tree-visualizer-spacing 1))
+ (move-marker pos (point)))
+ ;; middle subtree (only when number of children is odd)
+ (when (= (mod num-children 2) 1)
+ (setq n (cdr n))
+ (when (or (null active-branch)
+ (eq (car n)
+ (nth (undo-tree-node-branch node)
+ (undo-tree-node-next node))))
+ (undo-tree-move-down 1)
+ (undo-tree-insert ?|)
+ (undo-tree-move-backward 1)
+ (undo-tree-move-down 1)
+ ;; link node to its representation in visualizer
+ (unless (markerp (undo-tree-node-marker (car n)))
+ (setf (undo-tree-node-marker (car n)) (make-marker))
+ (set-marker-insertion-type (undo-tree-node-marker (car n)) nil))
+ (move-marker (undo-tree-node-marker (car n)) (point))
+ ;; add node to list of nodes to draw next
+ (push (car n) node-list))
+ (goto-char pos)
+ (undo-tree-move-forward
+ (+ (undo-tree-node-char-rwidth (car n))
+ (if (cadr n) (undo-tree-node-char-lwidth (cadr n)) 0)
+ undo-tree-visualizer-spacing 1))
+ (move-marker pos (point)))
+ ;; right subtrees
+ (move-marker trunk-pos (1+ trunk-pos))
+ (dotimes (_ (/ num-children 2))
+ (setq n (cdr n))
+ (when (or (null active-branch)
+ (eq (car n)
+ (nth (undo-tree-node-branch node)
+ (undo-tree-node-next node))))
+ (goto-char trunk-pos)
+ (undo-tree-insert ?_ (- pos trunk-pos 1))
+ (goto-char pos)
+ (undo-tree-move-backward 1)
+ (undo-tree-move-down 1)
+ (undo-tree-insert ?\\)
+ (undo-tree-move-down 1)
+ ;; link node to its representation in visualizer
+ (unless (markerp (undo-tree-node-marker (car n)))
+ (setf (undo-tree-node-marker (car n)) (make-marker))
+ (set-marker-insertion-type (undo-tree-node-marker (car n)) nil))
+ (move-marker (undo-tree-node-marker (car n)) (point))
+ ;; add node to list of nodes to draw next
+ (push (car n) node-list))
+ (when (cdr n)
+ (goto-char pos)
+ (undo-tree-move-forward
+ (+ (undo-tree-node-char-rwidth (car n))
+ (if (cadr n) (undo-tree-node-char-lwidth (cadr n)) 0)
+ undo-tree-visualizer-spacing 1))
+ (move-marker pos (point))))
+ ))
+ ;; return list of nodes to draw next
+ (nreverse node-list)))
+
+
+(defun undo-tree-node-char-lwidth (node)
+ ;; Return left-width of NODE measured in characters.
+ (if (= (length (undo-tree-node-next node)) 0) 0
+ (- (* (+ undo-tree-visualizer-spacing 1) (undo-tree-node-lwidth node))
+ (if (= (undo-tree-node-cwidth node) 0)
+ (1+ (/ undo-tree-visualizer-spacing 2)) 0))))
+
+
+(defun undo-tree-node-char-rwidth (node)
+ ;; Return right-width of NODE measured in characters.
+ (if (= (length (undo-tree-node-next node)) 0) 0
+ (- (* (+ undo-tree-visualizer-spacing 1) (undo-tree-node-rwidth node))
+ (if (= (undo-tree-node-cwidth node) 0)
+ (1+ (/ undo-tree-visualizer-spacing 2)) 0))))
+
+
+(defun undo-tree-insert (str &optional arg)
+ ;; Insert character or string STR ARG times, overwriting, and using
+ ;; `undo-tree-insert-face'.
+ (unless arg (setq arg 1))
+ (when (characterp str)
+ (setq str (make-string arg str))
+ (setq arg 1))
+ (dotimes (_ arg) (insert str))
+ (setq arg (* arg (length str)))
+ (undo-tree-move-forward arg)
+ ;; make sure mark isn't active, otherwise `backward-delete-char' might
+ ;; delete region instead of single char if transient-mark-mode is enabled
+ (setq mark-active nil)
+ (backward-delete-char arg)
+ (when undo-tree-insert-face
+ (put-text-property (- (point) arg) (point) 'face undo-tree-insert-face)))
+
+
+(defun undo-tree-move-down (&optional arg)
+ ;; Move down, extending buffer if necessary.
+ (let ((row (line-number-at-pos))
+ (col (current-column))
+ line)
+ (unless arg (setq arg 1))
+ (forward-line arg)
+ (setq line (line-number-at-pos))
+ ;; if buffer doesn't have enough lines, add some
+ (when (/= line (+ row arg))
+ (cond
+ ((< arg 0)
+ (insert (make-string (- line row arg) ?\n))
+ (forward-line (+ arg (- row line))))
+ (t (insert (make-string (- arg (- line row)) ?\n)))))
+ (undo-tree-move-forward col)))
+
+
+(defun undo-tree-move-up (&optional arg)
+ ;; Move up, extending buffer if necessary.
+ (unless arg (setq arg 1))
+ (undo-tree-move-down (- arg)))
+
+
+(defun undo-tree-move-forward (&optional arg)
+ ;; Move forward, extending buffer if necessary.
+ (unless arg (setq arg 1))
+ (let (n)
+ (cond
+ ((>= arg 0)
+ (setq n (- (line-end-position) (point)))
+ (if (> n arg)
+ (forward-char arg)
+ (end-of-line)
+ (insert (make-string (- arg n) ? ))))
+ ((< arg 0)
+ (setq arg (- arg))
+ (setq n (- (point) (line-beginning-position)))
+ (when (< (- n 2) arg) ; -2 to create left-margin
+ ;; no space left - shift entire buffer contents right!
+ (let ((pos (move-marker (make-marker) (point))))
+ (set-marker-insertion-type pos t)
+ (goto-char (point-min))
+ (while (not (eobp))
+ (insert-before-markers (make-string (- arg -2 n) ? ))
+ (forward-line 1))
+ (goto-char pos)))
+ (backward-char arg)))))
+
+
+(defun undo-tree-move-backward (&optional arg)
+ ;; Move backward, extending buffer if necessary.
+ (unless arg (setq arg 1))
+ (undo-tree-move-forward (- arg)))
+
+
+(defun undo-tree-move-to-parent (node)
+ ;; Move to position of parent of NODE, extending buffer if necessary.
+ (let* ((parent (undo-tree-node-previous node))
+ (n (undo-tree-node-next parent))
+ (l (length n)) p)
+ (goto-char (undo-tree-node-marker node))
+ (unless (= l 1)
+ ;; move horizontally
+ (setq p (undo-tree-position node n))
+ (cond
+ ;; node in centre subtree: no horizontal movement
+ ((and (= (mod l 2) 1) (= p (/ l 2))))
+ ;; node in left subtree: move right
+ ((< p (/ l 2))
+ (setq n (nthcdr p n))
+ (undo-tree-move-forward
+ (+ (undo-tree-node-char-rwidth (car n))
+ (/ undo-tree-visualizer-spacing 2) 1))
+ (dotimes (_ (- (/ l 2) p 1))
+ (setq n (cdr n))
+ (undo-tree-move-forward
+ (+ (undo-tree-node-char-lwidth (car n))
+ (undo-tree-node-char-rwidth (car n))
+ undo-tree-visualizer-spacing 1)))
+ (when (= (mod l 2) 1)
+ (setq n (cdr n))
+ (undo-tree-move-forward
+ (+ (undo-tree-node-char-lwidth (car n))
+ (/ undo-tree-visualizer-spacing 2) 1))))
+ (t ;; node in right subtree: move left
+ (setq n (nthcdr (/ l 2) n))
+ (when (= (mod l 2) 1)
+ (undo-tree-move-backward
+ (+ (undo-tree-node-char-rwidth (car n))
+ (/ undo-tree-visualizer-spacing 2) 1))
+ (setq n (cdr n)))
+ (dotimes (_ (- p (/ l 2) (mod l 2)))
+ (undo-tree-move-backward
+ (+ (undo-tree-node-char-lwidth (car n))
+ (undo-tree-node-char-rwidth (car n))
+ undo-tree-visualizer-spacing 1))
+ (setq n (cdr n)))
+ (undo-tree-move-backward
+ (+ (undo-tree-node-char-lwidth (car n))
+ (/ undo-tree-visualizer-spacing 2) 1)))))
+ ;; move vertically
+ (undo-tree-move-up 3)))
+
+
+(defun undo-tree-timestamp-to-string
+ (timestamp &optional relative current register)
+ ;; Convert TIMESTAMP to string (either absolute or RELATVE time), indicating
+ ;; if it's the CURRENT node and/or has an associated REGISTER.
+ (if relative
+ ;; relative time
+ (let ((time (floor (float-time
+ (time-subtract (current-time) timestamp))))
+ n)
+ (setq time
+ ;; years
+ (if (> (setq n (/ time 315360000)) 0)
+ (if (> n 999) "-ages" (format "-%dy" n))
+ (setq time (% time 315360000))
+ ;; days
+ (if (> (setq n (/ time 86400)) 0)
+ (format "-%dd" n)
+ (setq time (% time 86400))
+ ;; hours
+ (if (> (setq n (/ time 3600)) 0)
+ (format "-%dh" n)
+ (setq time (% time 3600))
+ ;; mins
+ (if (> (setq n (/ time 60)) 0)
+ (format "-%dm" n)
+ ;; secs
+ (format "-%ds" (% time 60)))))))
+ (setq time (concat
+ (if current "*" " ")
+ time
+ (if register (concat "[" (char-to-string register) "]")
+ " ")))
+ (setq n (length time))
+ (if (< n 9)
+ (concat (make-string (- 9 n) ? ) time)
+ time))
+ ;; absolute time
+ (concat (if current " *" " ")
+ (format-time-string "%H:%M:%S" timestamp)
+ (if register
+ (concat "[" (char-to-string register) "]")
+ " "))))
+
+
+
+
+;;; =====================================================================
+;;; Visualizer modes
+
+(define-derived-mode
+ undo-tree-visualizer-mode special-mode "undo-tree-visualizer"
+ "Major mode used in undo-tree visualizer.
+
+The undo-tree visualizer can only be invoked from a buffer in
+which `undo-tree-mode' is enabled. The visualizer displays the
+undo history tree graphically, and allows you to browse around
+the undo history, undoing or redoing the corresponding changes in
+the parent buffer.
+
+Within the undo-tree visualizer, the following keys are available:
+
+ \\{undo-tree-visualizer-mode-map}"
+ :syntax-table nil
+ :abbrev-table nil
+ (setq truncate-lines t)
+ (setq cursor-type nil)
+ (setq undo-tree-visualizer-selected-node nil))
+
+
+(define-minor-mode undo-tree-visualizer-selection-mode
+ "Toggle mode to select nodes in undo-tree visualizer."
+ :lighter "Select"
+ :keymap undo-tree-visualizer-selection-mode-map
+ :group undo-tree
+ (cond
+ ;; enable selection mode
+ (undo-tree-visualizer-selection-mode
+ (setq cursor-type 'box)
+ (setq undo-tree-visualizer-selected-node
+ (undo-tree-current buffer-undo-tree))
+ ;; erase diff (if any), as initially selected node is identical to current
+ (when undo-tree-visualizer-diff
+ (let ((buff (get-buffer undo-tree-diff-buffer-name))
+ (inhibit-read-only t))
+ (when buff (with-current-buffer buff (erase-buffer))))))
+ (t ;; disable selection mode
+ (setq cursor-type nil)
+ (setq undo-tree-visualizer-selected-node nil)
+ (goto-char (undo-tree-node-marker (undo-tree-current buffer-undo-tree)))
+ (when undo-tree-visualizer-diff (undo-tree-visualizer-update-diff)))
+ ))
+
+
+
+
+;;; =====================================================================
+;;; Visualizer commands
+
+(defun undo-tree-visualize-undo (&optional arg)
+ "Undo changes. A numeric ARG serves as a repeat count."
+ (interactive "p")
+ (unless (eq major-mode 'undo-tree-visualizer-mode)
+ (user-error "Undo-tree mode not enabled in buffer"))
+ (let ((old (undo-tree-current buffer-undo-tree))
+ current)
+ ;; undo in parent buffer
+ (switch-to-buffer-other-window undo-tree-visualizer-parent-buffer)
+ (deactivate-mark)
+ (unwind-protect
+ (let ((undo-tree-inhibit-kill-visualizer t)) (undo-tree-undo-1 arg))
+ (setq current (undo-tree-current buffer-undo-tree))
+ (switch-to-buffer-other-window undo-tree-visualizer-buffer-name)
+ ;; unhighlight old current node
+ (let ((undo-tree-insert-face 'undo-tree-visualizer-active-branch-face)
+ (inhibit-read-only t))
+ (undo-tree-draw-node old))
+ ;; when using lazy drawing, extend tree upwards as required
+ (when undo-tree-visualizer-lazy-drawing
+ (undo-tree-expand-up old current))
+ ;; highlight new current node
+ (let ((inhibit-read-only t)) (undo-tree-draw-node current 'current))
+ ;; update diff display, if any
+ (when undo-tree-visualizer-diff (undo-tree-visualizer-update-diff)))))
+
+
+(defun undo-tree-visualize-redo (&optional arg)
+ "Redo changes. A numeric ARG serves as a repeat count."
+ (interactive "p")
+ (unless (eq major-mode 'undo-tree-visualizer-mode)
+ (user-error "Undo-tree mode not enabled in buffer"))
+ (let ((old (undo-tree-current buffer-undo-tree))
+ current)
+ ;; redo in parent buffer
+ (switch-to-buffer-other-window undo-tree-visualizer-parent-buffer)
+ (deactivate-mark)
+ (unwind-protect
+ (let ((undo-tree-inhibit-kill-visualizer t)) (undo-tree-redo-1 arg))
+ (setq current (undo-tree-current buffer-undo-tree))
+ (switch-to-buffer-other-window undo-tree-visualizer-buffer-name)
+ ;; unhighlight old current node
+ (let ((undo-tree-insert-face 'undo-tree-visualizer-active-branch-face)
+ (inhibit-read-only t))
+ (undo-tree-draw-node old))
+ ;; when using lazy drawing, extend tree downwards as required
+ (when undo-tree-visualizer-lazy-drawing
+ (undo-tree-expand-down old current))
+ ;; highlight new current node
+ (let ((inhibit-read-only t)) (undo-tree-draw-node current 'current))
+ ;; update diff display, if any
+ (when undo-tree-visualizer-diff (undo-tree-visualizer-update-diff)))))
+
+
+(defun undo-tree-visualize-switch-branch-right (arg)
+ "Switch to next branch of the undo tree.
+This will affect which branch to descend when *redoing* changes
+using `undo-tree-redo' or `undo-tree-visualizer-redo'."
+ (interactive "p")
+ (unless (eq major-mode 'undo-tree-visualizer-mode)
+ (user-error "Undo-tree mode not enabled in buffer"))
+ ;; un-highlight old active branch below current node
+ (goto-char (undo-tree-node-marker (undo-tree-current buffer-undo-tree)))
+ (let ((undo-tree-insert-face 'undo-tree-visualizer-default-face)
+ (inhibit-read-only t))
+ (undo-tree-highlight-active-branch (undo-tree-current buffer-undo-tree)))
+ ;; increment branch
+ (let ((branch (undo-tree-node-branch (undo-tree-current buffer-undo-tree))))
+ (setf (undo-tree-node-branch (undo-tree-current buffer-undo-tree))
+ (cond
+ ((>= (+ branch arg) (undo-tree-num-branches))
+ (1- (undo-tree-num-branches)))
+ ((<= (+ branch arg) 0) 0)
+ (t (+ branch arg))))
+ (let ((inhibit-read-only t))
+ ;; highlight new active branch below current node
+ (goto-char (undo-tree-node-marker (undo-tree-current buffer-undo-tree)))
+ (let ((undo-tree-insert-face 'undo-tree-visualizer-active-branch-face))
+ (undo-tree-highlight-active-branch (undo-tree-current buffer-undo-tree)))
+ ;; re-highlight current node
+ (undo-tree-draw-node (undo-tree-current buffer-undo-tree) 'current))))
+
+
+(defun undo-tree-visualize-switch-branch-left (arg)
+ "Switch to previous branch of the undo tree.
+This will affect which branch to descend when *redoing* changes
+using `undo-tree-redo' or `undo-tree-visualizer-redo'."
+ (interactive "p")
+ (undo-tree-visualize-switch-branch-right (- arg)))
+
+
+(defun undo-tree-visualizer-quit ()
+ "Quit the undo-tree visualizer."
+ (interactive)
+ (unless (eq major-mode 'undo-tree-visualizer-mode)
+ (user-error "Undo-tree mode not enabled in buffer"))
+ (undo-tree-clear-visualizer-data buffer-undo-tree)
+ ;; remove kill visualizer hook from parent buffer
+ (unwind-protect
+ (with-current-buffer undo-tree-visualizer-parent-buffer
+ (remove-hook 'before-change-functions 'undo-tree-kill-visualizer t))
+ ;; kill diff buffer, if any
+ (when undo-tree-visualizer-diff (undo-tree-visualizer-hide-diff))
+ (let ((parent undo-tree-visualizer-parent-buffer)
+ window)
+ ;; kill visualizer buffer
+ (kill-buffer nil)
+ ;; switch back to parent buffer
+ (unwind-protect
+ (if (setq window (get-buffer-window parent))
+ (select-window window)
+ (switch-to-buffer parent))))))
+
+
+(defun undo-tree-visualizer-abort ()
+ "Quit the undo-tree visualizer and return buffer to original state."
+ (interactive)
+ (unless (eq major-mode 'undo-tree-visualizer-mode)
+ (user-error "Undo-tree mode not enabled in buffer"))
+ (let ((node undo-tree-visualizer-initial-node))
+ (undo-tree-visualizer-quit)
+ (undo-tree-set node)))
+
+
+(defun undo-tree-visualizer-set (&optional pos)
+ "Set buffer to state corresponding to undo tree node
+at POS, or point if POS is nil."
+ (interactive)
+ (unless (eq major-mode 'undo-tree-visualizer-mode)
+ (user-error "Undo-tree mode not enabled in buffer"))
+ (unless pos (setq pos (point)))
+ (let ((node (get-text-property pos 'undo-tree-node)))
+ (when node
+ ;; set parent buffer to state corresponding to node at POS
+ (switch-to-buffer-other-window undo-tree-visualizer-parent-buffer)
+ (let ((undo-tree-inhibit-kill-visualizer t)) (undo-tree-set node))
+ (switch-to-buffer-other-window undo-tree-visualizer-buffer-name)
+ ;; re-draw undo tree
+ (let ((inhibit-read-only t)) (undo-tree-draw-tree buffer-undo-tree))
+ (when undo-tree-visualizer-diff (undo-tree-visualizer-update-diff)))))
+
+
+(defun undo-tree-visualizer-mouse-set (pos)
+ "Set buffer to state corresponding to undo tree node
+at mouse event POS."
+ (interactive "@e")
+ (unless (eq major-mode 'undo-tree-visualizer-mode)
+ (user-error "Undo-tree mode not enabled in buffer"))
+ (undo-tree-visualizer-set (event-start (nth 1 pos))))
+
+
+(defun undo-tree-visualize-undo-to-x (&optional x)
+ "Undo to last branch point, register, or saved state.
+If X is the symbol `branch', undo to last branch point. If X is
+the symbol `register', undo to last register. If X is the symbol
+`saved', undo to last saved state. If X is null, undo to first of
+these that's encountered.
+
+Interactively, a single \\[universal-argument] specifies
+`branch', a double \\[universal-argument] \\[universal-argument]
+specifies `saved', and a negative prefix argument specifies
+`register'."
+ (interactive "P")
+ (unless (eq major-mode 'undo-tree-visualizer-mode)
+ (user-error "Undo-tree mode not enabled in buffer"))
+ (when (and (called-interactively-p 'any) x)
+ (setq x (prefix-numeric-value x)
+ x (cond
+ ((< x 0) 'register)
+ ((<= x 4) 'branch)
+ (t 'saved))))
+ (let ((current (if undo-tree-visualizer-selection-mode
+ undo-tree-visualizer-selected-node
+ (undo-tree-current buffer-undo-tree)))
+ (diff undo-tree-visualizer-diff)
+ r)
+ (undo-tree-visualizer-hide-diff)
+ (while (and (undo-tree-node-previous current)
+ (or (if undo-tree-visualizer-selection-mode
+ (progn
+ (undo-tree-visualizer-select-previous)
+ (setq current undo-tree-visualizer-selected-node))
+ (undo-tree-visualize-undo)
+ (setq current (undo-tree-current buffer-undo-tree)))
+ t)
+ ;; branch point
+ (not (or (and (or (null x) (eq x 'branch))
+ (> (undo-tree-num-branches) 1))
+ ;; register
+ (and (or (null x) (eq x 'register))
+ (setq r (undo-tree-node-register current))
+ (undo-tree-register-data-p
+ (setq r (registerv-data (get-register r))))
+ (eq current (undo-tree-register-data-node r)))
+ ;; saved state
+ (and (or (null x) (eq x 'saved))
+ (undo-tree-node-unmodified-p current))
+ ))))
+ ;; update diff display, if any
+ (when diff
+ (undo-tree-visualizer-show-diff
+ (when undo-tree-visualizer-selection-mode
+ undo-tree-visualizer-selected-node)))))
+
+
+(defun undo-tree-visualize-redo-to-x (&optional x)
+ "Redo to last branch point, register, or saved state.
+If X is the symbol `branch', redo to last branch point. If X is
+the symbol `register', redo to last register. If X is the sumbol
+`saved', redo to last saved state. If X is null, redo to first of
+these that's encountered.
+
+Interactively, a single \\[universal-argument] specifies
+`branch', a double \\[universal-argument] \\[universal-argument]
+specifies `saved', and a negative prefix argument specifies
+`register'."
+ (interactive "P")
+ (unless (eq major-mode 'undo-tree-visualizer-mode)
+ (user-error "Undo-tree mode not enabled in buffer"))
+ (when (and (called-interactively-p 'any) x)
+ (setq x (prefix-numeric-value x)
+ x (cond
+ ((< x 0) 'register)
+ ((<= x 4) 'branch)
+ (t 'saved))))
+ (let ((current (if undo-tree-visualizer-selection-mode
+ undo-tree-visualizer-selected-node
+ (undo-tree-current buffer-undo-tree)))
+ (diff undo-tree-visualizer-diff)
+ r)
+ (undo-tree-visualizer-hide-diff)
+ (while (and (undo-tree-node-next current)
+ (or (if undo-tree-visualizer-selection-mode
+ (progn
+ (undo-tree-visualizer-select-next)
+ (setq current undo-tree-visualizer-selected-node))
+ (undo-tree-visualize-redo)
+ (setq current (undo-tree-current buffer-undo-tree)))
+ t)
+ ;; branch point
+ (not (or (and (or (null x) (eq x 'branch))
+ (> (undo-tree-num-branches) 1))
+ ;; register
+ (and (or (null x) (eq x 'register))
+ (setq r (undo-tree-node-register current))
+ (undo-tree-register-data-p
+ (setq r (registerv-data (get-register r))))
+ (eq current (undo-tree-register-data-node r)))
+ ;; saved state
+ (and (or (null x) (eq x 'saved))
+ (undo-tree-node-unmodified-p current))
+ ))))
+ ;; update diff display, if any
+ (when diff
+ (undo-tree-visualizer-show-diff
+ (when undo-tree-visualizer-selection-mode
+ undo-tree-visualizer-selected-node)))))
+
+
+(defun undo-tree-visualizer-toggle-timestamps ()
+ "Toggle display of time-stamps."
+ (interactive)
+ (unless (eq major-mode 'undo-tree-visualizer-mode)
+ (user-error "Undo-tree mode not enabled in buffer"))
+ (setq undo-tree-visualizer-timestamps (not undo-tree-visualizer-timestamps))
+ (setq undo-tree-visualizer-spacing (undo-tree-visualizer-calculate-spacing))
+ ;; redraw tree
+ (let ((inhibit-read-only t)) (undo-tree-draw-tree buffer-undo-tree)))
+
+
+(defun undo-tree-visualizer-scroll-left (&optional arg)
+ (interactive "p")
+ (unless (eq major-mode 'undo-tree-visualizer-mode)
+ (user-error "Undo-tree mode not enabled in buffer"))
+ (scroll-left (or arg 1) t))
+
+
+(defun undo-tree-visualizer-scroll-right (&optional arg)
+ (interactive "p")
+ (unless (eq major-mode 'undo-tree-visualizer-mode)
+ (user-error "Undo-tree mode not enabled in buffer"))
+ (scroll-right (or arg 1) t))
+
+
+(defun undo-tree-visualizer-scroll-up (&optional arg)
+ (interactive "P")
+ (unless (eq major-mode 'undo-tree-visualizer-mode)
+ (user-error "Undo-tree mode not enabled in buffer"))
+ (if (or (and (numberp arg) (< arg 0)) (eq arg '-))
+ (undo-tree-visualizer-scroll-down arg)
+ ;; scroll up and expand newly-visible portion of tree
+ (unwind-protect
+ (scroll-up-command arg)
+ (undo-tree-expand-down
+ (nth (undo-tree-node-branch (undo-tree-current buffer-undo-tree))
+ (undo-tree-node-next (undo-tree-current buffer-undo-tree)))))
+ ;; signal error if at eob
+ (when (and (not undo-tree-visualizer-needs-extending-down) (eobp))
+ (scroll-up))))
+
+
+(defun undo-tree-visualizer-scroll-down (&optional arg)
+ (interactive "P")
+ (unless (eq major-mode 'undo-tree-visualizer-mode)
+ (user-error "Undo-tree mode not enabled in buffer"))
+ (if (or (and (numberp arg) (< arg 0)) (eq arg '-))
+ (undo-tree-visualizer-scroll-up arg)
+ ;; ensure there's enough room at top of buffer to scroll
+ (let ((scroll-lines
+ (or arg (- (window-height) next-screen-context-lines)))
+ (window-line (1- (line-number-at-pos (window-start)))))
+ (when (and undo-tree-visualizer-needs-extending-up
+ (< window-line scroll-lines))
+ (let ((inhibit-read-only t))
+ (goto-char (point-min))
+ (undo-tree-move-up (- scroll-lines window-line)))))
+ ;; scroll down and expand newly-visible portion of tree
+ (unwind-protect
+ (scroll-down-command arg)
+ (undo-tree-expand-up
+ (undo-tree-node-previous (undo-tree-current buffer-undo-tree))))
+ ;; signal error if at bob
+ (when (and (not undo-tree-visualizer-needs-extending-down) (bobp))
+ (scroll-down))))
+
+
+
+
+;;; =====================================================================
+;;; Visualizer selection mode commands
+
+(defun undo-tree-visualizer-select-previous (&optional arg)
+ "Move to previous node."
+ (interactive "p")
+ (unless (eq major-mode 'undo-tree-visualizer-mode)
+ (user-error "Undo-tree mode not enabled in buffer"))
+ (let ((node undo-tree-visualizer-selected-node))
+ (catch 'top
+ (dotimes (_ (or arg 1))
+ (unless (undo-tree-node-previous node) (throw 'top t))
+ (setq node (undo-tree-node-previous node))))
+ ;; when using lazy drawing, extend tree upwards as required
+ (when undo-tree-visualizer-lazy-drawing
+ (undo-tree-expand-up undo-tree-visualizer-selected-node node))
+ ;; update diff display, if any
+ (when (and undo-tree-visualizer-diff
+ (not (eq node undo-tree-visualizer-selected-node)))
+ (undo-tree-visualizer-update-diff node))
+ ;; move to selected node
+ (goto-char (undo-tree-node-marker node))
+ (setq undo-tree-visualizer-selected-node node)))
+
+
+(defun undo-tree-visualizer-select-next (&optional arg)
+ "Move to next node."
+ (interactive "p")
+ (unless (eq major-mode 'undo-tree-visualizer-mode)
+ (user-error "Undo-tree mode not enabled in buffer"))
+ (let ((node undo-tree-visualizer-selected-node))
+ (catch 'bottom
+ (dotimes (_ (or arg 1))
+ (unless (nth (undo-tree-node-branch node) (undo-tree-node-next node))
+ (throw 'bottom t))
+ (setq node
+ (nth (undo-tree-node-branch node) (undo-tree-node-next node)))))
+ ;; when using lazy drawing, extend tree downwards as required
+ (when undo-tree-visualizer-lazy-drawing
+ (undo-tree-expand-down undo-tree-visualizer-selected-node node))
+ ;; update diff display, if any
+ (when (and undo-tree-visualizer-diff
+ (not (eq node undo-tree-visualizer-selected-node)))
+ (undo-tree-visualizer-update-diff node))
+ ;; move to selected node
+ (goto-char (undo-tree-node-marker node))
+ (setq undo-tree-visualizer-selected-node node)))
+
+
+(defun undo-tree-visualizer-select-right (&optional arg)
+ "Move right to a sibling node."
+ (interactive "p")
+ (unless (eq major-mode 'undo-tree-visualizer-mode)
+ (user-error "Undo-tree mode not enabled in buffer"))
+ (let ((node undo-tree-visualizer-selected-node)
+ end)
+ (goto-char (undo-tree-node-marker undo-tree-visualizer-selected-node))
+ (setq end (line-end-position))
+ (catch 'end
+ (dotimes (_ arg)
+ (while (or (null node) (eq node undo-tree-visualizer-selected-node))
+ (forward-char)
+ (setq node (get-text-property (point) 'undo-tree-node))
+ (when (= (point) end) (throw 'end t)))))
+ (goto-char (undo-tree-node-marker
+ (or node undo-tree-visualizer-selected-node)))
+ (when (and undo-tree-visualizer-diff node
+ (not (eq node undo-tree-visualizer-selected-node)))
+ (undo-tree-visualizer-update-diff node))
+ (when node (setq undo-tree-visualizer-selected-node node))))
+
+
+(defun undo-tree-visualizer-select-left (&optional arg)
+ "Move left to a sibling node."
+ (interactive "p")
+ (unless (eq major-mode 'undo-tree-visualizer-mode)
+ (user-error "Undo-tree mode not enabled in buffer"))
+ (let ((node (get-text-property (point) 'undo-tree-node))
+ beg)
+ (goto-char (undo-tree-node-marker undo-tree-visualizer-selected-node))
+ (setq beg (line-beginning-position))
+ (catch 'beg
+ (dotimes (_ arg)
+ (while (or (null node) (eq node undo-tree-visualizer-selected-node))
+ (backward-char)
+ (setq node (get-text-property (point) 'undo-tree-node))
+ (when (= (point) beg) (throw 'beg t)))))
+ (goto-char (undo-tree-node-marker
+ (or node undo-tree-visualizer-selected-node)))
+ (when (and undo-tree-visualizer-diff node
+ (not (eq node undo-tree-visualizer-selected-node)))
+ (undo-tree-visualizer-update-diff node))
+ (when node (setq undo-tree-visualizer-selected-node node))))
+
+
+(defun undo-tree-visualizer-select (pos)
+ (let ((node (get-text-property pos 'undo-tree-node)))
+ (when node
+ ;; select node at POS
+ (goto-char (undo-tree-node-marker node))
+ ;; when using lazy drawing, extend tree up and down as required
+ (when undo-tree-visualizer-lazy-drawing
+ (undo-tree-expand-up undo-tree-visualizer-selected-node node)
+ (undo-tree-expand-down undo-tree-visualizer-selected-node node))
+ ;; update diff display, if any
+ (when (and undo-tree-visualizer-diff
+ (not (eq node undo-tree-visualizer-selected-node)))
+ (undo-tree-visualizer-update-diff node))
+ ;; update selected node
+ (setq undo-tree-visualizer-selected-node node)
+ )))
+
+
+(defun undo-tree-visualizer-mouse-select (pos)
+ "Select undo tree node at mouse event POS."
+ (interactive "@e")
+ (unless (eq major-mode 'undo-tree-visualizer-mode)
+ (user-error "Undo-tree mode not enabled in buffer"))
+ (undo-tree-visualizer-select (event-start (nth 1 pos))))
+
+
+
+
+;;; =====================================================================
+;;; Visualizer diff display
+
+(defun undo-tree-visualizer-toggle-diff ()
+ "Toggle diff display in undo-tree visualizer."
+ (interactive)
+ (unless (eq major-mode 'undo-tree-visualizer-mode)
+ (user-error "Undo-tree mode not enabled in buffer"))
+ (if undo-tree-visualizer-diff
+ (undo-tree-visualizer-hide-diff)
+ (undo-tree-visualizer-show-diff)))
+
+
+(defun undo-tree-visualizer-selection-toggle-diff ()
+ "Toggle diff display in undo-tree visualizer selection mode."
+ (interactive)
+ (unless (eq major-mode 'undo-tree-visualizer-mode)
+ (user-error "Undo-tree mode not enabled in buffer"))
+ (if undo-tree-visualizer-diff
+ (undo-tree-visualizer-hide-diff)
+ (let ((node (get-text-property (point) 'undo-tree-node)))
+ (when node (undo-tree-visualizer-show-diff node)))))
+
+
+(defun undo-tree-visualizer-show-diff (&optional node)
+ ;; show visualizer diff display
+ (setq undo-tree-visualizer-diff t)
+ (let ((buff (with-current-buffer undo-tree-visualizer-parent-buffer
+ (undo-tree-diff node)))
+ (display-buffer-mark-dedicated 'soft)
+ win)
+ (setq win (split-window))
+ (set-window-buffer win buff)
+ (shrink-window-if-larger-than-buffer win)))
+
+
+(defun undo-tree-visualizer-hide-diff ()
+ ;; hide visualizer diff display
+ (setq undo-tree-visualizer-diff nil)
+ (let ((win (get-buffer-window undo-tree-diff-buffer-name)))
+ (when win (with-selected-window win (kill-buffer-and-window)))))
+
+
+(defun undo-tree-diff (&optional node)
+ ;; Create diff between NODE and current state (or previous state and current
+ ;; state, if NODE is null). Returns buffer containing diff.
+ (let (tmpfile buff)
+ ;; generate diff
+ (let ((undo-tree-inhibit-kill-visualizer t)
+ (current (undo-tree-current buffer-undo-tree)))
+ (undo-tree-set (or node (undo-tree-node-previous current) current)
+ 'preserve-timestamps)
+ (setq tmpfile (diff-file-local-copy (current-buffer)))
+ (undo-tree-set current 'preserve-timestamps))
+ (setq buff (diff-no-select
+ tmpfile (current-buffer) nil 'noasync
+ (get-buffer-create undo-tree-diff-buffer-name)))
+ ;; delete process messages and useless headers from diff buffer
+ (let ((inhibit-read-only t))
+ (with-current-buffer buff
+ (goto-char (point-min))
+ (delete-region (point) (1+ (line-end-position 3)))
+ (goto-char (point-max))
+ (forward-line -2)
+ (delete-region (point) (point-max))
+ (setq cursor-type nil)
+ (setq buffer-read-only t)))
+ buff))
+
+
+(defun undo-tree-visualizer-update-diff (&optional node)
+ ;; update visualizer diff display to show diff between current state and
+ ;; NODE (or previous state, if NODE is null)
+ (with-current-buffer undo-tree-visualizer-parent-buffer
+ (undo-tree-diff node))
+ (let ((win (get-buffer-window undo-tree-diff-buffer-name)))
+ (when win
+ (balance-windows)
+ (shrink-window-if-larger-than-buffer win))))
+
+
+
+(provide 'undo-tree)
+
+;;; undo-tree.el ends here
diff --git a/site-lisp/extensions-submodule/ace-window b/site-lisp/extensions-submodule/ace-window
new file mode 160000
index 0000000..77115af
--- /dev/null
+++ b/site-lisp/extensions-submodule/ace-window
@@ -0,0 +1 @@
+Subproject commit 77115afc1b0b9f633084cf7479c767988106c196
diff --git a/site-lisp/extensions-submodule/avy b/site-lisp/extensions-submodule/avy
new file mode 160000
index 0000000..be61211
--- /dev/null
+++ b/site-lisp/extensions-submodule/avy
@@ -0,0 +1 @@
+Subproject commit be612110cb116a38b8603df367942e2bb3d9bdbe
diff --git a/site-lisp/extensions-submodule/citre b/site-lisp/extensions-submodule/citre
new file mode 160000
index 0000000..c617ace
--- /dev/null
+++ b/site-lisp/extensions-submodule/citre
@@ -0,0 +1 @@
+Subproject commit c617acef3dc2a88aaffa42a515ce7dbaba98228a
diff --git a/site-lisp/extensions-submodule/company-mode b/site-lisp/extensions-submodule/company-mode
new file mode 160000
index 0000000..9c12b02
--- /dev/null
+++ b/site-lisp/extensions-submodule/company-mode
@@ -0,0 +1 @@
+Subproject commit 9c12b02620ed8a7ae5369fc90217f1c730e48fa6
diff --git a/site-lisp/extensions-submodule/dash.el b/site-lisp/extensions-submodule/dash.el
new file mode 160000
index 0000000..6db80c7
--- /dev/null
+++ b/site-lisp/extensions-submodule/dash.el
@@ -0,0 +1 @@
+Subproject commit 6db80c711ce947f6c6fa11e5c2257fff2c79d139
diff --git a/site-lisp/extensions-submodule/emacs-which-key b/site-lisp/extensions-submodule/emacs-which-key
new file mode 160000
index 0000000..4d20bc8
--- /dev/null
+++ b/site-lisp/extensions-submodule/emacs-which-key
@@ -0,0 +1 @@
+Subproject commit 4d20bc852545a2e602f59084a630f888542052b1
diff --git a/site-lisp/extensions-submodule/go-mode.el b/site-lisp/extensions-submodule/go-mode.el
new file mode 160000
index 0000000..8dce1e3
--- /dev/null
+++ b/site-lisp/extensions-submodule/go-mode.el
@@ -0,0 +1 @@
+Subproject commit 8dce1e3ba1cdc34a856ad53c8421413cfe33660e
diff --git a/site-lisp/extensions-submodule/jsonian b/site-lisp/extensions-submodule/jsonian
new file mode 160000
index 0000000..22bd5e2
--- /dev/null
+++ b/site-lisp/extensions-submodule/jsonian
@@ -0,0 +1 @@
+Subproject commit 22bd5e20a653595b901ccfdc8780a0038755984d
diff --git a/site-lisp/extensions-submodule/lua-mode b/site-lisp/extensions-submodule/lua-mode
new file mode 160000
index 0000000..d074e41
--- /dev/null
+++ b/site-lisp/extensions-submodule/lua-mode
@@ -0,0 +1 @@
+Subproject commit d074e4134b1beae9ed4c9b512af741ca0d852ba3
diff --git a/site-lisp/extensions-submodule/markdown-mode b/site-lisp/extensions-submodule/markdown-mode
new file mode 160000
index 0000000..141f9a0
--- /dev/null
+++ b/site-lisp/extensions-submodule/markdown-mode
@@ -0,0 +1 @@
+Subproject commit 141f9a05d121f60fe5e411c0ad114e3d3216c9ad
diff --git a/site-lisp/extensions-submodule/modus-themes b/site-lisp/extensions-submodule/modus-themes
new file mode 160000
index 0000000..642cc5f
--- /dev/null
+++ b/site-lisp/extensions-submodule/modus-themes
@@ -0,0 +1 @@
+Subproject commit 642cc5f8358fd1f2911792a4bbed160d24e9b01b
diff --git a/site-lisp/extensions-submodule/multiple-cursors.el b/site-lisp/extensions-submodule/multiple-cursors.el
new file mode 160000
index 0000000..234806c
--- /dev/null
+++ b/site-lisp/extensions-submodule/multiple-cursors.el
@@ -0,0 +1 @@
+Subproject commit 234806c832994cadedb42596fe235e91bbd59e8c
diff --git a/site-lisp/extensions-submodule/swiper b/site-lisp/extensions-submodule/swiper
new file mode 160000
index 0000000..8c30f4c
--- /dev/null
+++ b/site-lisp/extensions-submodule/swiper
@@ -0,0 +1 @@
+Subproject commit 8c30f4cab5948aa8d942a3b2bbf5fb6a94d9441d
diff --git a/site-lisp/extensions-submodule/web-mode b/site-lisp/extensions-submodule/web-mode
new file mode 160000
index 0000000..df57cd0
--- /dev/null
+++ b/site-lisp/extensions-submodule/web-mode
@@ -0,0 +1 @@
+Subproject commit df57cd0beea9c6bdc64259bd11bde0c076a64cc9
diff --git a/site-lisp/extensions-submodule/yasnippet b/site-lisp/extensions-submodule/yasnippet
new file mode 160000
index 0000000..52a1c50
--- /dev/null
+++ b/site-lisp/extensions-submodule/yasnippet
@@ -0,0 +1 @@
+Subproject commit 52a1c5031912243c791c55e0fe345d04f219b507
diff --git a/site-lisp/init-config/init-ace-window.el b/site-lisp/init-config/init-ace-window.el
new file mode 100644
index 0000000..e596200
--- /dev/null
+++ b/site-lisp/init-config/init-ace-window.el
@@ -0,0 +1,10 @@
+;;; Require
+(require 'ace-window)
+
+;;; Code:
+;; 0-9 by default
+(setq aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l))
+
+(provide 'init-ace-window)
+
+;;; init-ace-window.el ends here
diff --git a/site-lisp/init-config/init-auto-save.el b/site-lisp/init-config/init-auto-save.el
new file mode 100644
index 0000000..15b7607
--- /dev/null
+++ b/site-lisp/init-config/init-auto-save.el
@@ -0,0 +1,14 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+(require 'auto-save)
+
+;;; Code:
+;; auto save after n second(s)
+;; (setq auto-save-idle 1)
+(setq auto-save-delete-trailing-whitespace t)
+(setq auto-save-silent t)
+(auto-save-enable)
+
+(provide 'init-auto-save)
+
+;;; init-auto-save.el ends here
diff --git a/site-lisp/init-config/init-avy.el b/site-lisp/init-config/init-avy.el
new file mode 100644
index 0000000..26e37ff
--- /dev/null
+++ b/site-lisp/init-config/init-avy.el
@@ -0,0 +1,19 @@
+;;; Require
+(require 'avy)
+
+;;; Code:
+;; Home row only (the default).
+(setq avy-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l))
+
+;; Any lower-case letter a-z.
+; (setq avy-keys (number-sequence ?a ?z))
+
+;; Any lower-case letter or number. Numbers are specified in the keyboard
+;; number-row order, so that the candidate following '9' will be '0'.
+; (setq avy-keys (nconc (number-sequence ?a ?z)
+; (number-sequence ?1 ?9)
+; '(?0)))
+
+(provide 'init-avy)
+
+;;; init-avy.el ends here
diff --git a/site-lisp/init-config/init-citre.el b/site-lisp/init-config/init-citre.el
new file mode 100644
index 0000000..e318c6e
--- /dev/null
+++ b/site-lisp/init-config/init-citre.el
@@ -0,0 +1,10 @@
+;;; Require
+(require 'citre)
+(require 'citre-config)
+
+;;; Code:
+;; (remove-hook 'find-file-hook #'citre-auto-enable-citre-mode)
+
+(provide 'init-citre)
+
+;;; init-citre.el ends here
diff --git a/site-lisp/init-config/init-coding-system.el b/site-lisp/init-config/init-coding-system.el
new file mode 100644
index 0000000..0f74e67
--- /dev/null
+++ b/site-lisp/init-config/init-coding-system.el
@@ -0,0 +1,34 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+
+;;; Code:
+(set-language-environment 'UTF-8)
+
+(set-default-coding-systems 'utf-8)
+(set-buffer-file-coding-system 'utf-8)
+(set-file-name-coding-system 'utf-8)
+(set-keyboard-coding-system 'utf-8)
+(set-next-selection-coding-system 'utf-8)
+(set-terminal-coding-system 'utf-8)
+
+(when *win64*
+ (set-next-selection-coding-system 'utf-16-le)
+ (set-selection-coding-system 'utf-16-le)
+ (set-clipboard-coding-system 'utf-16-le))
+
+(when (or *linux* *unix* *is-a-mac*)
+ (set-clipboard-coding-system 'utf-8)
+ (set-selection-coding-system 'utf-8))
+
+;; the final one will be selected first
+(prefer-coding-system 'cp950)
+(prefer-coding-system 'gb2312)
+(prefer-coding-system 'cp936)
+(prefer-coding-system 'gb18030)
+(prefer-coding-system 'utf-16)
+(prefer-coding-system 'utf-8-dos)
+(prefer-coding-system 'utf-8-unix)
+
+(provide 'init-coding-system)
+
+;;; init-coding-system ends here
diff --git a/site-lisp/init-config/init-company-mode.el b/site-lisp/init-config/init-company-mode.el
new file mode 100644
index 0000000..d138fce
--- /dev/null
+++ b/site-lisp/init-config/init-company-mode.el
@@ -0,0 +1,13 @@
+;;; Require
+(require 'company)
+(require 'company-ctags)
+
+;;; Code:
+(global-company-mode)
+
+(with-eval-after-load 'company
+ (company-ctags-auto-setup))
+
+(provide 'init-company-mode)
+
+;;; init-company-mode.el ends here
diff --git a/site-lisp/init-config/init-dired.el b/site-lisp/init-config/init-dired.el
new file mode 100644
index 0000000..9640ed9
--- /dev/null
+++ b/site-lisp/init-config/init-dired.el
@@ -0,0 +1,23 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+(require 'dired-display-buffer)
+(require 'dired-narrow)
+(require 'dired-subtree)
+
+;;; Code:
+(setq dired-listing-switches "-alh1v --group-directories-first")
+
+;; dired-subtree
+(setq dired-subtree-line-prefix " ")
+(setq dired-subtree-cycle-depth 3) ;; default `3'
+(setq dired-subtree-use-backgrounds nil) ;; default `t'
+;; (set-face-attribute 'dired-subtree-depth-1-face nil :background "#ced9db")
+;; (set-face-attribute 'dired-subtree-depth-2-face nil :background "#bbc9cc")
+;; (set-face-attribute 'dired-subtree-depth-3-face nil :background "#a7babe")
+;; (set-face-attribute 'dired-subtree-depth-4-face nil :background "#94aaaf")
+;; (set-face-attribute 'dired-subtree-depth-5-face nil :background "#809ba2")
+;; (set-face-attribute 'dired-subtree-depth-6-face nil :background "#6c8b93")
+
+(provide 'init-dired)
+
+;;; init-dired.el ends here
diff --git a/site-lisp/init-config/init-generic.el b/site-lisp/init-config/init-generic.el
new file mode 100644
index 0000000..aaa47f8
--- /dev/null
+++ b/site-lisp/init-config/init-generic.el
@@ -0,0 +1,149 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+
+;;; Code:
+(setq package-archives
+ '(("gnu" . "http://elpa.emacs-china.org/gnu/")
+ ("melpa" . "http://elpa.emacs-china.org/melpa/")))
+
+;; disable some bars
+(if window-system
+ (progn
+ (tool-bar-mode -1)
+ (menu-bar-mode -1)
+ (scroll-bar-mode -1))
+ (menu-bar-mode -1))
+
+;; map Win key and Hyper key to Super and Hyper for emacs on Windows.
+(when *win64*
+ (setq w32-pass-lwindow-to-system nil)
+ (setq w32-lwindow-modifier 'super) ; Left Windows key
+ (setq w32-pass-rwindow-to-system nil)
+ (setq w32-rwindow-modifier 'super) ; Right Windows key
+ (w32-register-hot-key [s-])
+ ;; (setq w32-pass-apps-to-system nil)
+ (setq w32-apps-modifier 'hyper) ; Menu/App key
+ (w32-register-hot-key [H-]))
+
+;; Restore emacs session.
+;(setq initial-buffer-choice t)
+;(run-with-timer 1 nil #'(lambda () (bury-buffer)))
+
+;; turn on word-wrap in all buffers
+(global-visual-line-mode t)
+(setq word-wrap-by-category t) ;; enhanced CJK word wrap since Emacs28
+
+;; line number and column number
+(global-display-line-numbers-mode t) ;; show line numbers in every mode
+(column-number-mode t) ;; show column number
+; Line numbers are not displayed when large files are used.
+(setq line-number-display-limit large-file-warning-threshold)
+(setq line-number-display-limit-width 1000)
+
+;; parentheses config
+(show-paren-mode t) ;; show bracket pairing
+(electric-pair-mode t) ;; automatic completion of parentheses
+
+;; highlight current line
+(global-hl-line-mode t)
+
+;; auto reload file content
+(global-auto-revert-mode t)
+
+;; delete selected text when input on it
+(delete-selection-mode t)
+
+;; record recently opened files
+(recentf-mode 1)
+(setq recentf-max-menu-items 25)
+(setq recentf-max-saved-items 25)
+
+;; treat OneWord or one_word as one word
+(global-subword-mode t)
+
+;; disable cursor blink
+(blink-cursor-mode -1)
+
+;; decompress a file when open it
+(auto-compression-mode t)
+
+;; disable the ring bell
+(setq ring-bell-function 'ignore)
+
+;; set `text-mode' as default major-mode
+(setq default-major-mode 'text-mode)
+
+;; use Posix format for time string
+(setq system-time-locale "C")
+
+;; replace yes/no with y/n
+;; but since emacs 28 user can use (use-short-answers t) to do it
+(fset 'yes-or-no-p 'y-or-n-p)
+
+;; do not show welcome page
+(setq inhibit-startup-screen t)
+
+;; use single space as sentence end, default is two
+(setq sentence-end-double-space nil)
+
+;; make key prompt faster
+(setq echo-keystrokes 0.1)
+
+;; increase IO performance
+(setq process-adaptive-read-buffering nil)
+(setq read-process-output-max (* 1024 1024))
+
+;; scrolling config
+(setq scroll-margin 3
+ scroll-conservatively 101
+ auto-window-vscroll nil)
+
+;; clean scratch buffer content
+; a non-clean scratch buffer will disturb session restore
+(setq initial-scratch-message "")
+
+;; exit emacs without confirmation to kill running processes
+(setq confirm-kill-processes nil)
+
+;; resize frame in pixel
+(setq frame-resize-pixelwise t)
+
+;; show a big square when cursor is on 'tab'
+(setq x-stretch-cursor t)
+
+;; delete duplicate minibuffer history
+(setq history-delete-duplicates t)
+
+;; allow scroll in isearch
+(setq isearch-allow-scroll t)
+
+;; don't ask me when close emacs with running process
+(defadvice save-buffers-kill-emacs (around no-query-kill-emacs activate)
+ "Prevent annoying \"Active processes exist\" query when you quit Emacs."
+ (cl-flet ((process-list ())) ad-do-it))
+
+;; don't ask when kill process buffer
+(setq kill-buffer-query-functions
+ (remq 'process-kill-buffer-query-function
+ kill-buffer-query-functions))
+
+;; (setq byte-compile-warnings
+;; (quote (
+;; ;; 显示的警告
+;; free-vars ;不在当前范围的引用变量
+;; unresolved ;不知道的函数
+;; callargs ;函数调用的参数和定义的不匹配
+;; obsolete ;荒废的变量和函数
+;; noruntime ;函数没有定义在运行时期
+;; interactive-only ;正常不被调用的命令
+;; make-local ;调用 `make-variable-buffer-local' 可能会不正确的
+;; mapcar ;`mapcar' 调用
+;; ;;
+;; ;; 抑制的警告
+;; (not redefine) ;重新定义的函数 (比如参数数量改变)
+;; (not cl-functions) ;`CL' 包中的运行时调用的函数
+;; )))
+
+(provide 'init-generic)
+
+;;; init-generic.el ends here
diff --git a/site-lisp/init-config/init-highlight-parentheses.el b/site-lisp/init-config/init-highlight-parentheses.el
new file mode 100644
index 0000000..97216b1
--- /dev/null
+++ b/site-lisp/init-config/init-highlight-parentheses.el
@@ -0,0 +1,11 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+(require 'highlight-parentheses)
+
+;;; Code:
+(setq hl-paren-colors '("DarkOrange" "DeepSkyBlue" "DarkRed"))
+(add-hook 'find-file-hook 'highlight-parentheses-mode t)
+
+(provide 'init-highlight-parentheses)
+
+;;; init-highlight-parentheses.el ends here
diff --git a/site-lisp/init-config/init-indent.el b/site-lisp/init-config/init-indent.el
new file mode 100644
index 0000000..4db9fae
--- /dev/null
+++ b/site-lisp/init-config/init-indent.el
@@ -0,0 +1,59 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+
+;;; Code:
+(setq-default indent-tabs-mode nil)
+(setq-default tab-width 4)
+
+(defun adjust-languages-indent (n)
+ (setq-local tab-width n)
+
+ (when (or (derived-mode-p 'c-mode))
+ (setq-local c-basic-offset n))
+
+ (when (or (derived-mode-p 'js-mode)
+ (derived-mode-p 'js2-mode)
+ (derived-mode-p 'web-mode))
+ (setq-local javascript-indent-level n)
+ (setq-local js-indent-level n)
+ (setq-local js2-basic-offset n)
+
+ (setq-local css-indent-offset n)
+
+ (setq-local web-mode-attr-indent-offset n)
+ (setq-local web-mode-attr-value-indent-offset n)
+ (setq-local web-mode-code-indent-offset n)
+ (setq-local web-mode-css-indent-offset n)
+ (setq-local web-mode-markup-indent-offset n)
+ (setq-local web-mode-sql-indent-offset n)
+ ))
+
+(dolist (hook (list 'c-mode-hook
+ 'c++-mode-hook
+ 'java-mode-hook
+ 'haskell-mode-hook
+ 'asm-mode-hook
+ 'sh-mode-hook
+ 'haskell-cabal-mode-hook
+ 'ruby-mode-hook
+ 'qml-mode-hook
+ 'scss-mode-hook
+ 'python-mode-hook
+ ))
+ (add-hook hook #'(lambda ()
+ (adjust-languages-indent 4)
+ )))
+
+(dolist (hook (list 'lua-mode-hook
+ 'org-mode-hook
+ 'js-mode-hook
+ 'web-mode-hook
+ 'yaml-mode-hook
+ ))
+ (add-hook hook #'(lambda ()
+ (adjust-languages-indent 2)
+ )))
+
+(provide 'init-indent)
+
+;;; init-indent.el ends here
diff --git a/site-lisp/init-config/init-mode.el b/site-lisp/init-config/init-mode.el
new file mode 100644
index 0000000..ca9cc81
--- /dev/null
+++ b/site-lisp/init-config/init-mode.el
@@ -0,0 +1,92 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+(require 'jsonian)
+(require 'lua-mode)
+
+;;; Code:
+;; bind ext to a specific mode
+(defun add-to-alist (alist-var elt-cons &optional no-replace)
+ "Add to the value of ALIST-VAR an element ELT-CONS if it isn't there yet.
+If an element with the same car as the car of ELT-CONS is already present,
+replace it with ELT-CONS unless NO-REPLACE is non-nil; if a matching
+element is not already present, add ELT-CONS to the front of the alist.
+The test for presence of the car of ELT-CONS is done with `equal'."
+ (let ((existing-element (assoc (car elt-cons) (symbol-value alist-var))))
+ (if existing-element
+ (or no-replace
+ (rplacd existing-element (cdr elt-cons)))
+ (set alist-var (cons elt-cons (symbol-value alist-var)))))
+ (symbol-value alist-var))
+
+(dolist (elt-cons '(
+ ("\\.org\\'" . org-mode)
+ ("\\.stumpwmrc\\'" . lisp-mode)
+ ("\\.jl\\'" . lisp-mode)
+ ("\\.asdf\\'" . lisp-mode)
+
+ ("\\.markdown" . markdown-mode)
+ ("\\.md" . markdown-mode)
+
+ ("\\.inc\\'" . asm-mode)
+
+ ("\\.py\\'" . python-mode)
+ ("SConstruct". python-mode)
+
+ ("\\.lua\\'" . lua-mode)
+
+ ("\\.json\\'" . jsonian-mode)
+
+ ("\\.go\\'" . go-mode)
+
+ ("\\.css\\'" . css-mode)
+ ("\\.wxss\\'" . css-mode)
+
+ ("\\.pdf\\'" . pdf-view-mode)
+
+ ("\\.ts\\'" . typescript-mode)
+ ("\\.tsx\\'" . typescript-mode)
+
+ ("\\.js.erb\\'" . web-mode)
+ ("\\.js\\'" . web-mode)
+ ("\\.wxs\\'" . web-mode)
+
+ ("\\.vue" . web-mode)
+ ("\\.wxml" . web-mode)
+ ("\\.blade\\.php\\'" . web-mode)
+ ("\\.phtml\\'" . web-mode)
+ ("\\.tpl\\.php\\'" . web-mode)
+ ("\\.jsp\\'" . web-mode)
+ ("\\.mustache\\'" . web-mode)
+ ("\\.djhtml\\'" . web-mode)
+ ("\\.html?\\'" . web-mode)
+ ("\\.jsx\\'" . web-mode)
+
+ ;; ("\\.rs$" . rust-mode)
+ ("CMakeLists\\.txt\\'" . cmake-mode)
+ ("\\.cmake\\'" . cmake-mode)
+ ))
+ (add-to-alist 'auto-mode-alist elt-cons))
+
+;;; Mode load.
+(autoload 'cmake-mode "cmake-mode")
+
+(autoload 'css-mode "css-mode")
+
+(autoload 'go-mode "go-mode")
+
+(autoload 'jsonian-mode "jsonian-mode")
+
+(autoload 'lua-mode "lua-mode")
+(setq lua-indent-level 2)
+
+(autoload 'markdown-mode "markdown-mode")
+
+(autoload 'python-mode "init-python")
+
+(autoload 'web-mode "web-mode")
+
+;; (autoload 'rust-mode "rust-mode")
+
+(provide 'init-mode)
+
+;;; init-mode.el ends here
diff --git a/site-lisp/init-config/init-neotree.el b/site-lisp/init-config/init-neotree.el
new file mode 100644
index 0000000..97cf1c7
--- /dev/null
+++ b/site-lisp/init-config/init-neotree.el
@@ -0,0 +1,12 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+(require 'neotree)
+
+;;; Code:
+(setq neo-theme 'ascii)
+(setq neo-show-hidden-files t)
+(setq neo-window-width 40)
+
+(provide 'init-neotree)
+
+;;; init-neotree.el ends here
diff --git a/site-lisp/init-config/init-org-todo.el b/site-lisp/init-config/init-org-todo.el
new file mode 100644
index 0000000..c33fb24
--- /dev/null
+++ b/site-lisp/init-config/init-org-todo.el
@@ -0,0 +1,37 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+(require 'org)
+
+;;; Code:
+(setq org-agenda-files '("~/Documents/ld_org_todo"))
+
+(setq org-capture-templates
+ '(
+ ("t" "todo" entry
+ (file+datetree "~/Documents/ld_org_todo/todo.org")
+ "* TODO [#C] %?\n\n%i"
+ :empty-lines 1 :tree-type month)
+ ("w" "waiting" entry
+ (file+datetree "~/Documents/ld_org_todo/waiting.org")
+ "* TODO [#C] %?"
+ :empty-lines 1 :tree-type month)
+ ))
+
+(setq org-refile-targets
+ '(
+ ("~/Documents/ld_org_todo/todo.org" :maxlevel . 4)
+ ("~/Documents/ld_org_todo/waiting.org" :maxlevel . 4)
+ (nil :maxlevel . 4)
+ ))
+
+(setq org-use-fast-todo-selection t)
+(setq org-todo-keywords '((sequence "TODO(t)" "DOING(i)"
+ "|" "DONE(d)" "ABORTED(a)")))
+(setq org-todo-keyword-faces '(("TODO" . "red")
+ ("DOING" . "blue")
+ ("DONE" . "forest green")
+ ("ABORTED" . "gray")))
+
+(provide 'init-org-todo)
+
+;;; init-org-todo.el ends here.
diff --git a/site-lisp/init-config/init-org.el b/site-lisp/init-config/init-org.el
new file mode 100644
index 0000000..59d188c
--- /dev/null
+++ b/site-lisp/init-config/init-org.el
@@ -0,0 +1,233 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+(require 'org)
+(require 'ox-publish)
+
+;;; Code:
+(defun ld-org-update-lastupdate-property ()
+ "If '#+LASTUPDATE' is in org file, update it to the current date/time."
+ (when (eq major-mode 'org-mode)
+ (save-excursion
+ (widen)
+ (goto-char (point-min))
+ (when (re-search-forward "^#\\+LASTUPDATE:" (point-max) t)
+ (progn
+ (setq lastupdate-point (point))
+ (if (not (equal lastupdate-point (line-end-position)))
+ (delete-region lastupdate-point (line-end-position)))
+ (insert (format-time-string " %Y/%m/%d %H:%M")))))))
+
+(add-hook 'before-save-hook #'ld-org-update-lastupdate-property)
+
+(setq org-startup-indented nil) ;; enable org-indent-mode at start, default nil.
+
+(setq org-goto-auto-isearch nil)
+(setq org-support-shift-select t)
+
+(setq org-use-sub-superscripts '{})
+(setq org-export-with-sub-superscripts '{})
+
+;; If it is not nil, strings below will be interpreted in exporting as
+;; Org HTML LaTeX UTF-8
+;; -----+----------+--------+-------
+;; \- \-
+;; -- – -- –
+;; --- — --- —
+;; ... … \ldots …
+(setq org-export-with-special-strings nil)
+
+;; (set-face-attribute 'org-level-1 nil
+;; :background "#fdf0ff"
+;; :foreground "#000000"
+;; :overline "#bcbcbc"
+;; :bold t
+;; :height 1.3)
+;; (set-face-attribute 'org-level-2 nil
+;; :foreground "#8f0075"
+;; :overline "#bcbcbc"
+;; :bold t
+;; :height 1.1)
+;; (set-face-attribute 'org-level-3 nil
+;; :foreground "#093060"
+;; :weight 'semi-bold)
+;; (set-face-attribute 'org-level-4 nil
+;; :foreground "#184034"
+;; :weight 'semi-bold)
+;; (set-face-attribute 'org-level-5 nil
+;; :foreground "#61284f"
+;; :weight 'semi-bold)
+;; (set-face-attribute 'org-level-6 nil
+;; :foreground "#3f3000"
+;; :weight 'semi-bold)
+;; (set-face-attribute 'org-level-7 nil
+;; :foreground "#5f0000"
+;; :weight 'semi-bold)
+;; (set-face-attribute 'org-level-8 nil
+;; :foreground "#541f4f"
+;; :weight 'semi-bold)
+
+;; (set-face-attribute 'org-block-begin-line nil
+;; :background "#f0f0f0"
+;; :foreground "#505050"
+;; :extend t)
+;; (set-face-attribute 'org-block nil
+;; :background "#f8f8f8"
+;; :foreground "#000000"
+;; :extend t
+;; )
+;; (set-face-attribute 'org-block-end-line nil
+;; :background "#f0f0f0"
+;; :foreground "#505050"
+;; :extend t)
+
+;; (set-face-attribute 'org-code nil
+;; :background "#f0f0f0"
+;; :foreground "#005a5f"
+;; :extend t)
+
+;; (set-face-attribute 'org-verbatim nil
+;; :background "#f0f0f0"
+;; :foreground "#8f0075"
+;; :extend t)
+
+(defun ld-org-custom-html-src-block (src-block _contents info)
+ "Transcode a SRC-BLOCK element from Org to HTML.
+CONTENTS holds the contents of the item. INFO is a plist holding
+contextual information.
+MAKE FINAL HTML TO BE COMPATIBLE WITH highlight.js"
+ (if (org-export-read-attribute :attr_html src-block :textarea)
+ (org-html--textarea-block src-block)
+ (let ((lang (org-element-property :language src-block))
+ (caption (org-export-get-caption src-block))
+ (code (org-html-format-code src-block info))
+ (label (let ((lbl (and (org-element-property :name src-block)
+ (org-export-get-reference src-block info))))
+ (if lbl (format " id=\"%s\"" lbl) ""))))
+ (if (not lang) (format "\n%s
" label code)
+ (format
+ "\n%s%s\n
"
+ (if (not caption) ""
+ (format ""
+ (org-export-data caption info)))
+ (format "\n%s
"
+ lang label lang code))))))
+
+(advice-add 'org-html-src-block :override 'ld-org-custom-html-src-block)
+
+(defun ld-org-custom-html-format-list-item (contents type checkbox info
+ &optional term-counter-id
+ headline)
+ "Format a list item into HTML."
+ (let ((class (if checkbox
+ (format " class=\"%s\""
+ (symbol-name checkbox)) ""))
+ (checkbox (concat (org-html-checkbox checkbox info)
+ (and checkbox " ")))
+ (br (org-html-close-tag "br" nil info))
+ (extra-newline (if (and (org-string-nw-p contents) headline) "\n" "")))
+ (concat
+ (pcase type
+ (`ordered
+ (let* ((counter term-counter-id)
+ (extra (if counter (format " value=\"%s\"" counter) "")))
+ (concat
+ (format "" class extra)
+ (when headline (concat headline br)))))
+ (`unordered
+ (let* ((id term-counter-id)
+ (extra (if id (format " id=\"%s\"" id) "")))
+ (concat
+ (format "" class extra)
+ (when headline (concat headline br)))))
+ (`descriptive
+ (let* ((term term-counter-id))
+ (setq term (or term "(no term)"))
+ ;; Check-boxes in descriptive lists are associated to tag.
+ (concat (format "%s"
+ class (concat checkbox term))
+ ""))))
+ (unless (eq type 'descriptive) checkbox)
+ extra-newline
+ (and (org-string-nw-p contents) (org-trim contents))
+ extra-newline
+ (pcase type
+ (`ordered "
")
+ (`unordered "")
+ (`descriptive "
")))))
+
+(advice-add 'org-html-format-list-item :override 'ld-org-custom-html-format-list-item)
+
+(defun ld-org-export-publish()
+ "Publish org and then generate sitemap.xml file."
+ (interactive)
+ ;; import project settings
+ (require 'ld-org-publish-project-desc)
+ ;; publish site
+ (org-publish-all)
+ ;; create sitemap for search engine
+ (let (
+ ;; FULL PATH to doc root. MUST end with a slash.
+ (ld-site-pub-path-article-root "~/Public/ld_org_article_publish/article/")
+ ;; file name of sitemap file, relative to webroot.
+ ;; file name format: .xml
+ (ld-site-sitemap-file-name "sitemap.xml")
+ ;; site domain name
+ (ld-site-domain-name "lishouzhong.com")
+ ;; gzip it or not. t for true, nil for false.
+ (ld-site-sitemap-gzip-it-p nil))
+
+ (print (concat "begin: " (format-time-string "%Y-%m-%dT%T")))
+
+ ;; rename file to backup ~ if already exist
+ (let (f1 f2)
+ (setq f1 (concat ld-site-pub-path-article-root ld-site-sitemap-file-name))
+ (setq f2 (concat f1 ".gz"))
+ (when (file-exists-p f1)
+ (rename-file f1 (concat f1 "~") t))
+ (when (file-exists-p f2)
+ (rename-file f2 (concat f2 "~") t)))
+
+ ;; create sitemap buffer
+ (let (article-file-path sitemap-buffer)
+ (setq article-file-path
+ (concat ld-site-pub-path-article-root ld-site-sitemap-file-name))
+ (setq sitemap-buffer (find-file article-file-path))
+ (erase-buffer)
+ (set-buffer-file-coding-system 'unix)
+ (insert "
+")
+
+ (require 'find-lisp)
+
+ (let ((process-sitemap-content
+ (lambda (article-file-path dest-buffer)
+ (when (not (string-match "/zzz" article-file-path)) ; dir/file starting with zzz are not public
+ (with-temp-buffer
+ (insert-file-contents article-file-path nil nil nil t)
+ (goto-char 1)
+ (when (not (search-forward "")
+ (insert (concat
+ "http://"
+ ld-site-domain-name
+ "/"
+ (substring article-file-path
+ (length (expand-file-name ld-site-pub-path-article-root)))))
+ (insert "\n"))))))))
+ (mapc
+ (lambda (x) (funcall process-sitemap-content x sitemap-buffer))
+ (find-lisp-find-files ld-site-pub-path-article-root "\\.html$")))
+
+ (insert "")
+
+ (save-buffer)
+
+ (when ld-site-sitemap-gzip-it-p
+ (shell-command (concat "gzip " article-file-path))))
+
+ (print (concat "finished: " (format-time-string "%Y-%m-%dT%T")))))
+
+(provide 'init-org)
+
+;;; init-org.el ends here
diff --git a/site-lisp/init-config/init-proxy.el b/site-lisp/init-config/init-proxy.el
new file mode 100644
index 0000000..fa191b8
--- /dev/null
+++ b/site-lisp/init-config/init-proxy.el
@@ -0,0 +1,45 @@
+;; -*- coding: utf-8; -*-
+;;; Require
+
+;;; Code:
+(defun proxy-socks-show ()
+ "Show SOCKS proxy."
+ (interactive)
+ (when (fboundp 'cadddr)
+ (if (bound-and-true-p socks-noproxy)
+ (message "Current SOCKS%d proxy is %s:%d"
+ (cadddr socks-server) (cadr socks-server) (caddr socks-server))
+ (message "No SOCKS proxy"))))
+
+(defun proxy-socks-enable ()
+ "Enable SOCKS proxy."
+ (interactive)
+ (require 'socks)
+ (setq url-gateway-method 'socks
+ socks-noproxy '("localhost")
+ socks-server '("Default server" "127.0.0.1" 10800 5))
+ (setenv "all_proxy" "socks5://127.0.0.1:10800")
+ (proxy-socks-show))
+
+(defun proxy-socks-disable ()
+ "Disable SOCKS proxy."
+ (interactive)
+ (require 'socks)
+ (setq url-gateway-method 'native
+ socks-noproxy nil)
+ (setenv "all_proxy" "")
+ (proxy-socks-show))
+
+(defun proxy-socks-toggle ()
+ "Toggle SOCKS proxy."
+ (interactive)
+ (require 'socks)
+ (if (bound-and-true-p socks-noproxy)
+ (proxy-socks-disable)
+ (proxy-socks-enable)))
+
+;; (proxy-socks-enable)
+
+(provide 'init-proxy)
+
+;;; init-proxy.el ends here
diff --git a/site-lisp/init-config/init-session.el b/site-lisp/init-config/init-session.el
new file mode 100644
index 0000000..baf3fea
--- /dev/null
+++ b/site-lisp/init-config/init-session.el
@@ -0,0 +1,51 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+(require 'auto-save)
+
+;;; Code:
+
+(setq desktop-load-locked-desktop t) ; don't popup dialog ask user, load anyway
+(setq desktop-restore-frames nil) ; don't restore any frame
+
+(defun kill-unused-buffers ()
+ (interactive)
+ (ignore-errors
+ (save-excursion
+ (dolist (buf (buffer-list))
+ (set-buffer buf)
+ (when (and (string-prefix-p "*" (buffer-name)) (string-suffix-p "*" (buffer-name)))
+ (kill-buffer buf))
+ ))))
+
+(defun emacs-session-restore ()
+ "Restore emacs session."
+ (interactive)
+ (ignore-errors
+ ;; Kill other windows.
+ (delete-other-windows)
+ ;; Kill unused buffers.
+ (kill-unused-buffers)
+ ;; Restore session.
+ (desktop-read "~/.emacs.d/")
+ ))
+
+(defun emacs-session-save (&optional arg)
+ "Save emacs session."
+ (interactive "p")
+ (ignore-errors
+ (if (equal arg 4)
+ ;; Kill all buffers if with prefix argument.
+ (mapc 'kill-buffer (buffer-list))
+ ;; Kill unused buffers.
+ (kill-unused-buffers)
+ ;; Save all buffers before exit.
+ (auto-save-buffers))
+ ;; Save session.
+ (make-directory "~/.emacs.d/" t)
+ (desktop-save "~/.emacs.d/")
+ ;; Exit emacs.
+ (kill-emacs)))
+
+(provide 'init-session)
+
+;;; init-session.el ends here
diff --git a/site-lisp/init-config/init-shortcut.el b/site-lisp/init-config/init-shortcut.el
new file mode 100644
index 0000000..81c0bea
--- /dev/null
+++ b/site-lisp/init-config/init-shortcut.el
@@ -0,0 +1,256 @@
+;; -*- coding: utf-8; -*-
+
+;;; There are 3 sections:
+;;; - unset keys
+;;; - extensions
+;;; - extensions-local
+;;; - shortcut on built-in function
+
+;;; ------------ unset keys
+
+; originally
+; 'C-i' is TAB
+; 'C-r' is isearch-backward
+(lazy-load-unset-keys
+ '("C-z" "C-\\" "C-'" "C-i" "C-r"))
+
+
+
+;;; ------------ extensions
+
+;; ------ ace-window
+(lazy-load-global-keys
+ '(
+ ("M-o" . ace-window)
+ )
+ "init-ace-window")
+
+;; ------ avy
+(lazy-load-global-keys
+ '(
+ ("M-g c" . avy-goto-char)
+ ("M-g w" . avy-goto-word-1)
+ ("M-g s" . avy-goto-word-0)
+ ("M-g l l" . avy-goto-line)
+ ("M-g j" . avy-next)
+ ("M-g k" . avy-prev)
+ )
+ "init-avy")
+
+;; ------ citre
+(lazy-load-global-keys
+ '(
+ ("C-x c c" . citre-mode)
+ ("C-x c j" . citre-jump)
+ ("C-x c J" . citre-jump-back)
+ ("C-x c p" . citre-ace-peek)
+ ("C-x c u" . citre-update-this-tags-file)
+ )
+ "init-citre")
+
+;; ------ theme
+(lazy-load-global-keys
+ '(
+ ("" . ld-modus-themes-toggle)
+ )
+ "init-theme")
+
+;; ------ multiple cursors
+(lazy-load-global-keys
+ '(
+ ("C-M-." . mc/mark-next-like-this)
+ ("C-M-," . mc/unmark-next-like-this)
+ ("M-<" . mc/mark-previous-like-this)
+ ("M->" . mc/unmark-previous-like-this)
+ ("C-M-<" . mc/edit-beginnings-of-lines)
+ ("M-" . mc/add-cursor-on-click)
+ )
+ "multiple-cursors")
+
+;; ------ swiper
+(lazy-load-set-keys
+ '(
+ ("C-s" . swiper-isearch)
+ ("C-c s" . counsel-rg)
+ ))
+
+
+
+;;; ------------ extensions-local
+
+(lazy-load-set-keys
+ '(
+ ;; dired-display-buffer
+ ("o" . dired-display-buffer)
+ ;; dired-narrow
+ ("/" . dired-narrow)
+ ;; dired-subtree
+ ("" . dired-subtree-cycle)
+ ("SPC" . dired-subtree-toggle)
+ ("C-p" . dired-subtree-previous-sibling)
+ ("C-n" . dired-subtree-next-sibling)
+ ("r" . dired-subtree-revert)
+ )
+ dired-mode-map)
+
+(lazy-load-global-keys
+ '(
+ ("C-c e e" . toggle-echo-keys)
+ ("C-c e c" . echo-keys-clean)
+ )
+ "echo-keys") ;show every pressed keys
+
+(lazy-load-global-keys
+ '(
+ ("C-c x e" . ld-eval-elisp-to-next-line)
+ )
+ "evals") ;execute code
+
+(lazy-load-global-keys
+ '(
+ ("C-<" . ld-un-indent)
+ ("C->" . ld-indent)
+ )
+ "force-indent") ;control 4 spaces indent manually
+
+(lazy-load-global-keys
+ '(
+ ("C-c \\" . goto-last-change)
+ )
+ "goto-last-change")
+
+(lazy-load-global-keys
+ '(
+ ("M-g l p" . goto-line-preview)
+ )
+ "goto-line-preview")
+
+(lazy-load-global-keys
+ '(
+ ("C-c m h a" . highlight-indentation-mode)
+ ("C-c m h c" . highlight-indentation-current-column-mode)
+ )
+ "highlight-indentation")
+
+(lazy-load-global-keys
+ '(
+ ("C-r i" . ld-indent-buffer)
+ ("C-r r" . ld-rename-file-and-buffer)
+ ("C-r d" . ld-delete-file-and-buffer)
+ ("C-r e" . ld-revert-buffer-no-confirm)
+ ("C-i r" . ld-find-file-in-root) ; open file with root by sudo
+ )
+ "ld-buffer-operations")
+
+(lazy-load-global-keys
+ '(
+ ("C-;" . ld-cursor-position-1-store) ;store cursor position
+ ("C-'" . ld-cursor-position-1-jump) ;jump to cursor position
+ ("C-c ," . ld-cursor-position-stack-push) ;push cursor position to stack
+ ("C-c ." . ld-cursor-position-stack-pop) ;pop corsor position from stack
+ )
+ "ld-goto-cursor-stack")
+
+(lazy-load-global-keys
+ '(
+ ("M-N" . ld-delete-one-block-backward)
+ ("M-M" . ld-delete-one-block-forward)
+ )
+ "ld-delete-block") ;delete a block (eg. a word) forward and backward
+
+(lazy-load-global-keys
+ '(
+ ("C-i r" . ld-find-file-in-root) ; open file with root by sudo
+ )
+ "ld-file-operations")
+
+(lazy-load-global-keys
+ '(
+ ("M-g l p" . ld-goto-percent-line)
+ ("M-g t p" . ld-goto-percent-text)
+ ("M-g t c" . ld-goto-column)
+ )
+ "ld-goto-simple")
+
+(lazy-load-global-keys
+ '(
+ ("M-p" . ld-move-text-up)
+ ("M-n" . ld-move-text-down)
+ ("C-c l d" . ld-duplicate-current-line-or-region)
+ ("C-c l D" . ld-duplicate-and-comment-current-line-or-region)
+ ("C-c l k" . ld-delete-current-line)
+ ("C-c m l" . ld-mark-line)
+ )
+ "ld-text-operations")
+
+(lazy-load-global-keys
+ '(
+ ("C-c w t" . ld-toggle-one-window)
+ )
+ "ld-toggle-one-window") ;maxmize current window and size back
+
+(lazy-load-global-keys
+ '(("C-x j" . neotree-toggle))
+ "init-neotree")
+
+(lazy-load-global-keys
+ '(
+ ("M-j" . watch-next-window-up-line) ; 'up' to see previous content
+ ("M-k" . watch-next-window-down-line) ; 'down' to see further content
+ ("M-J" . watch-next-window-up)
+ ("M-K" . watch-next-window-down)
+ )
+ "scroll-next-window")
+
+(lazy-load-global-keys
+ '(
+ ("" . emacs-session-save)
+ )
+ "init-session")
+
+
+
+;;; ------------ shortcut on built-in function
+
+;; ------ org related
+(lazy-load-set-keys
+ '(
+ ("C-c o c" . org-capture)
+ ("C-c o a" . org-agenda)
+ ("C-c o l r" . org-list-repair)
+ ))
+
+;; ------ move cursors in current buffer
+(lazy-load-set-keys
+ '(
+ ("M-g b k" . beginning-of-buffer)
+ ("M-g b j" . end-of-buffer)
+ ("M-g h j" . forward-paragraph)
+ ("M-g h k" . backward-paragraph)
+ ("M-g l y" . backward-up-list) ;向左跳出 LIST
+ ("M-g l o" . up-list) ;向右跳出 LIST
+ ("M-g l u" . backward-down-list) ;向左跳进 LIST
+ ("M-g l i" . down-list) ;向右跳进 LIST
+ ("M-g f a" . beginning-of-defun) ;函数开头
+ ("M-g f e" . end-of-defun) ;函数末尾
+ ))
+
+;; ------ other
+(lazy-load-set-keys
+ '(
+ ("C-z r" . global-hl-line-mode)
+ ("C-z l" . display-line-numbers-mode)
+ ("M--" . text-scale-decrease)
+ ("M-=" . text-scale-increase)
+ ("M-," . bury-buffer)
+ ("M-." . unbury-buffer)
+ ("C-c m m" . set-mark-command) ; replace C-Space for Chinese input method
+ ("M-;" . comment-dwim)
+ ("C-c r" . recentf-open-files)
+ ))
+
+
+
+(provide 'init-shortcut)
+
+;;; init-shortcut.el ends here
diff --git a/site-lisp/init-config/init-swiper.el b/site-lisp/init-config/init-swiper.el
new file mode 100644
index 0000000..8a064ea
--- /dev/null
+++ b/site-lisp/init-config/init-swiper.el
@@ -0,0 +1,34 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+(require 'ivy)
+(require 'counsel)
+(require 'swiper)
+
+;;; Code:
+(setq ivy-use-virtual-buffers t)
+(setq ivy-initial-inputs-alist nil)
+(setq ivy-count-format "(%d/%d) ")
+
+;; Map commands to their minimum required input length.
+;; That is the number of characters prompted for before fetching
+;; candidates. The special key t is used as a fallback.
+(setq ivy-more-chars-alist '((t . 2)))
+
+(ivy-mode 1)
+
+(setq counsel-rg-base-command
+ `("rg"
+ "--max-columns" "0"
+ "--with-filename"
+ "--no-heading"
+ "--line-number"
+ "--color" "never"
+ "%s"
+ ,@(and (memq system-type '(ms-dos windows-nt))
+ (list "--path-separator" "/" "."))))
+
+(counsel-mode 1)
+
+(provide 'init-swiper)
+
+;; init-swiper.el ends here
diff --git a/site-lisp/init-config/init-theme.el b/site-lisp/init-config/init-theme.el
new file mode 100644
index 0000000..59d9c8a
--- /dev/null
+++ b/site-lisp/init-config/init-theme.el
@@ -0,0 +1,82 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+(require 'modus-themes)
+
+;;; Code:
+;; font
+(when (and window-system *win64*)
+ (let ((default-font (font-spec :name "Sarasa Fixed SC"))
+ (cn-font (font-spec :name "Sarasa Fixed SC")))
+ (set-face-attribute 'default nil :font default-font :height 116)
+ (dolist (charset '(kana han symbol cjk-misc bopomofo))
+ (set-fontset-font t charset cn-font)))
+ (set-face-font 'fixed-pitch "Sarasa Fixed SC"))
+
+;; theme
+;; In all of the following, WEIGHT is a symbol such as `semibold',
+;; `light', `bold', or anything mentioned in `modus-themes-weights'.
+(setq modus-themes-italic-constructs t
+ modus-themes-bold-constructs nil
+ modus-themes-mixed-fonts t
+ modus-themes-variable-pitch-ui nil
+ modus-themes-custom-auto-reload t
+ modus-themes-disable-other-themes t
+
+ ;; Options for `modus-themes-prompts' are either nil (the
+ ;; default), or a list of properties that may include any of those
+ ;; symbols: `italic', `WEIGHT'
+ modus-themes-prompts '(italic bold)
+
+ ;; The `modus-themes-completions' is an alist that reads two
+ ;; keys: `matches', `selection'. Each accepts a nil value (or
+ ;; empty list) or a list of properties that can include any of
+ ;; the following (for WEIGHT read further below):
+ ;;
+ ;; `matches' :: `underline', `italic', `WEIGHT'
+ ;; `selection' :: `underline', `italic', `WEIGHT'
+ modus-themes-completions
+ '((matches . (extrabold))
+ (selection . (semibold italic text-also)))
+
+ modus-themes-org-blocks 'gray-background ; {nil,'gray-background,'tinted-background}
+
+ ;; The `modus-themes-headings' is an alist: read the manual's
+ ;; node about it or its doc string. Basically, it supports
+ ;; per-level configurations for the optional use of
+ ;; `variable-pitch' typography, a height value as a multiple of
+ ;; the base font size (e.g. 1.5), and a `WEIGHT'.
+ modus-themes-headings
+ '((1 . (variable-pitch 1.5))
+ (2 . (1.3))
+ (agenda-date . (1.3))
+ (agenda-structure . (variable-pitch light 1.8))
+ (t . (1.1))))
+
+(defun ld-modus-operandi ()
+ "Light theme."
+ (interactive)
+ ;; load theme firstly and then do user customization
+ ;; otherwise modus-themes will override the face user had assigned
+ (load-theme 'modus-operandi :no-confirm))
+
+(defun ld-modus-vivendi-tinted ()
+ "Dark theme."
+ (interactive)
+ ;; load theme firstly and then do user customization
+ ;; otherwise modus-themes will override the face user had assigned
+ (load-theme 'modus-vivendi-tinted :no-confirm))
+
+(defun ld-modus-themes-toggle ()
+ (interactive)
+ (if (eq (car custom-enabled-themes) 'modus-operandi)
+ (ld-modus-vivendi-tinted)
+ (ld-modus-operandi)))
+
+;; active light theme
+(if window-system
+ (ld-modus-operandi)
+ (ld-modus-vivendi-tinted))
+
+(provide 'init-theme)
+
+;;; init-theme.el ends here
diff --git a/site-lisp/init-config/init-time.el b/site-lisp/init-config/init-time.el
new file mode 100644
index 0000000..099240f
--- /dev/null
+++ b/site-lisp/init-config/init-time.el
@@ -0,0 +1,14 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+
+;;; Code:
+
+(setq display-time-day-and-date t)
+(setq display-time-format "%Y/%m/%d %H:%M")
+(setq display-time-24hr-format t)
+(display-time-mode 1)
+(display-time)
+
+(provide 'init-time)
+
+;;; init-time.el ends here
diff --git a/site-lisp/init-config/init-undo-tree.el b/site-lisp/init-config/init-undo-tree.el
new file mode 100644
index 0000000..af5020f
--- /dev/null
+++ b/site-lisp/init-config/init-undo-tree.el
@@ -0,0 +1,19 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+(require 'undo-tree)
+
+;;; Code:
+(global-undo-tree-mode)
+
+;; ;; --- undo-tree
+;; (lazy-load-local-keys
+;; '(
+;; ("C-/" . undo-tree-undo)
+;; ("C-?" . undo-tree-redo)
+;; )
+;; undo-tree-map
+;; "undo-tree")
+
+(provide 'init-undo-tree)
+
+;;; init-undo-tree.el ends here
diff --git a/site-lisp/init-config/init-which-key.el b/site-lisp/init-config/init-which-key.el
new file mode 100644
index 0000000..213ce9b
--- /dev/null
+++ b/site-lisp/init-config/init-which-key.el
@@ -0,0 +1,17 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+(require 'which-key)
+
+;;; Code:
+(setq which-key-show-early-on-C-h t)
+(setq which-key-idle-delay 10000)
+(setq which-key-idle-secondary-delay 0.05)
+(setq which-key-max-description-length nil) ;; show entire key name
+
+(which-key-mode)
+
+(which-key-setup-side-window-bottom)
+
+(provide 'init-which-key)
+
+;;; init-which-key.el ends here
diff --git a/site-lisp/init-config/init-yasnippet.el b/site-lisp/init-config/init-yasnippet.el
new file mode 100644
index 0000000..f126f43
--- /dev/null
+++ b/site-lisp/init-config/init-yasnippet.el
@@ -0,0 +1,17 @@
+;; -*- coding: utf-8; -*-
+;;; Require:
+(require 'yasnippet)
+
+;;; Code:
+(setq yas-snippet-dirs (list (concat ld-emacs-root-dir "/snippets")))
+(yas-global-mode 1)
+
+;; Disable yasnippet mode on some mode.
+(dolist (hooks (list
+ 'term-mode-hook
+ ))
+ (add-hook hooks #'(lambda () (yas-minor-mode -1))))
+
+(provide 'init-yasnippet)
+
+;;; init-yasnippet.el ends here
diff --git a/site-lisp/init-config/init.el b/site-lisp/init-config/init.el
new file mode 100644
index 0000000..8d03481
--- /dev/null
+++ b/site-lisp/init-config/init.el
@@ -0,0 +1,46 @@
+;;; -*- coding: utf-8-unix; lexical-binding: t; -*-
+
+(let (
+ ;; Make startup faster by reducing the frequency of garbage collection.
+ ;; default (* 800 1000) bytes
+ (gc-cons-threshold most-positive-fixnum)
+ ;; default 0.1
+ (gc-cons-percentage 0.6))
+
+ ;; keep frame size
+ (setq frame-inhibit-implied-resize t)
+
+ ;; from local extensions
+ ;; firstly loaded part
+ (require 'init-theme)
+ (require 'lazy-load)
+ (require 'init-generic)
+ (require 'ld-tools)
+
+ (require 'init-auto-save)
+ (require 'init-coding-system)
+ (require 'init-company-mode)
+ (require 'init-dired)
+ (require 'init-highlight-parentheses)
+ (require 'init-indent)
+ (require 'init-mode)
+ (require 'init-org-todo)
+ (require 'init-org)
+ (require 'init-proxy)
+ (require 'init-swiper)
+ (require 'init-time)
+ (require 'init-undo-tree)
+ (require 'init-which-key)
+ (require 'init-yasnippet)
+
+ ;; restore session
+ (require 'init-session)
+ (emacs-session-restore)
+
+ ;; finally load other plugins dynamically
+ (require 'init-shortcut)
+)
+
+(provide 'init)
+
+;;; init.el ends here
diff --git a/site-lisp/snippets/lua-mode/function b/site-lisp/snippets/lua-mode/function
new file mode 100644
index 0000000..c02ca67
--- /dev/null
+++ b/site-lisp/snippets/lua-mode/function
@@ -0,0 +1,6 @@
+# -*- mode: snippet -*-
+# name: function
+# key: fun
+# --
+function ${1: fun_name}()
+end
\ No newline at end of file
diff --git a/site-lisp/snippets/org-mode/bloghead b/site-lisp/snippets/org-mode/bloghead
new file mode 100644
index 0000000..bf44fcb
--- /dev/null
+++ b/site-lisp/snippets/org-mode/bloghead
@@ -0,0 +1,8 @@
+# -*- mode: snippet -*-
+# name: bloghead
+# key: bh
+# --
+
+#+TITLE:
+#+DATE:
+#+LASTUPDATE:
\ No newline at end of file
diff --git a/site-lisp/snippets/org-mode/notehead b/site-lisp/snippets/org-mode/notehead
new file mode 100644
index 0000000..fd7f678
--- /dev/null
+++ b/site-lisp/snippets/org-mode/notehead
@@ -0,0 +1,7 @@
+# -*- mode: snippet -*-
+# name: notehead
+# key: nh
+# --
+
+#+TITLE:
+#+LASTUPDATE:
\ No newline at end of file
diff --git a/site-lisp/snippets/sh-mode/bang b/site-lisp/snippets/sh-mode/bang
new file mode 100644
index 0000000..5e11f0e
--- /dev/null
+++ b/site-lisp/snippets/sh-mode/bang
@@ -0,0 +1,6 @@
+# -*- mode: snippet -*-
+# name: bang
+# key: !
+# --
+#!/usr/bin/env bash
+$0
\ No newline at end of file
diff --git a/site-lisp/snippets/snippet-mode/cont b/site-lisp/snippets/snippet-mode/cont
new file mode 100644
index 0000000..3783d54
--- /dev/null
+++ b/site-lisp/snippets/snippet-mode/cont
@@ -0,0 +1,5 @@
+# -*- mode: snippet -*-
+# name: cont
+# key: cont
+# --
+# contributor: `user-full-name`
\ No newline at end of file
diff --git a/site-lisp/snippets/snippet-mode/elisp b/site-lisp/snippets/snippet-mode/elisp
new file mode 100644
index 0000000..768e94d
--- /dev/null
+++ b/site-lisp/snippets/snippet-mode/elisp
@@ -0,0 +1,5 @@
+# -*- mode: snippet -*-
+# name: elisp
+# key: `
+# --
+\`$0\`
\ No newline at end of file
diff --git a/site-lisp/snippets/snippet-mode/field b/site-lisp/snippets/snippet-mode/field
new file mode 100644
index 0000000..12ff0e0
--- /dev/null
+++ b/site-lisp/snippets/snippet-mode/field
@@ -0,0 +1,6 @@
+# name : ${ ... } field
+# contributor : joaotavora
+# key : $f
+# key: field
+# --
+\${${1:${2:n}:}$3${4:\$(${5:lisp-fn})}\}$0
\ No newline at end of file
diff --git a/site-lisp/snippets/snippet-mode/group b/site-lisp/snippets/snippet-mode/group
new file mode 100644
index 0000000..3ae8fd2
--- /dev/null
+++ b/site-lisp/snippets/snippet-mode/group
@@ -0,0 +1,5 @@
+# -*- mode: snippet -*-
+# name: group
+# key: group
+# --
+# group : ${1:group}
\ No newline at end of file
diff --git a/site-lisp/snippets/snippet-mode/mirror b/site-lisp/snippets/snippet-mode/mirror
new file mode 100644
index 0000000..2a45042
--- /dev/null
+++ b/site-lisp/snippets/snippet-mode/mirror
@@ -0,0 +1,6 @@
+# name : ${n:$(...)} mirror
+# key : $m
+# contributor : joaotavora
+# key: mirror
+# --
+\${${2:n}:${4:\$(${5:reflection-fn})}\}$0
\ No newline at end of file
diff --git a/site-lisp/snippets/snippet-mode/vars b/site-lisp/snippets/snippet-mode/vars
new file mode 100644
index 0000000..ec4e4b6
--- /dev/null
+++ b/site-lisp/snippets/snippet-mode/vars
@@ -0,0 +1,13 @@
+# -*- mode: snippet -*-
+# name : Snippet header
+# contributor : joaotavora
+# key: vars
+# --
+# name : $1${2:
+# key : ${3:trigger-key}}${4:
+# keybinding : ${5:keybinding}}${6:
+# expand-env : (${7:})}
+# contributor : $6
+# key: vars
+# --
+$0
\ No newline at end of file
diff --git a/site-start.el b/site-start.el
new file mode 100644
index 0000000..f6f2646
--- /dev/null
+++ b/site-start.el
@@ -0,0 +1,51 @@
+(require 'cl-lib)
+
+(defun add-subdirs-to-load-path (search-dir)
+ (interactive)
+ (let* ((dir (file-name-as-directory search-dir)))
+ (dolist (subdir
+ ;; 过滤出不必要的目录,提升Emacs启动速度
+ (cl-remove-if
+ #'(lambda (subdir)
+ (or
+ ;; 不是目录的文件都移除
+ (not (file-directory-p (concat dir subdir)))
+ ;; 父目录、 语言相关和版本控制目录都移除
+ (member subdir '("." ".."
+ "dist" "node_modules" "__pycache__"
+ "RCS" "CVS" "rcs" "cvs" ".git" ".github"))))
+ (directory-files dir)))
+ (let ((subdir-path (concat dir (file-name-as-directory subdir))))
+ ;; 目录下有 .el .so .dll 文件的路径才添加到 `load-path' 中,提升Emacs启动速度
+ (when (cl-some #'(lambda (subdir-file)
+ (and (file-regular-p (concat subdir-path subdir-file))
+ ;; .so .dll 文件指非Elisp语言编写的Emacs动态库
+ (member (file-name-extension subdir-file) '("el" "so" "dll"))))
+ (directory-files subdir-path))
+
+ ;; 注意:add-to-list 函数的第三个参数必须为 t ,表示加到列表末尾
+ ;; 这样Emacs会从父目录到子目录的顺序搜索Elisp插件,顺序反过来会导致Emacs无法正常启动
+ (add-to-list 'load-path subdir-path t))
+
+ ;; 继续递归搜索子目录
+ (add-subdirs-to-load-path subdir-path)))))
+
+;; get emacs version and operating system type
+(defvar *emacs27* (>= emacs-major-version 27))
+(defvar *is-a-mac* (eq system-type 'darwin))
+(defvar *win64* (eq system-type 'windows-nt))
+(defvar *cygwin* (eq system-type 'cygwin))
+(defvar *linux* (or (eq system-type 'gnu/linux) (eq system-type 'linux)))
+(defvar *unix* (or *linux*
+ (eq system-type 'usg-unix-v)
+ (eq system-type 'berkeley-unix)))
+
+(defvar ld-emacs-root-dir (file-truename "~/ld-emacs/site-lisp"))
+
+(if *win64*
+ (add-subdirs-to-load-path ld-emacs-root-dir))
+
+(if *linux*
+ (add-subdirs-to-load-path "/usr/share/emacs/ld"))
+
+(require 'init)