Skip to content

Instantly share code, notes, and snippets.

@orazdow
Last active May 1, 2024 00:00
Show Gist options
  • Save orazdow/98d85929a70d58729e1ef0a97a754b14 to your computer and use it in GitHub Desktop.
Save orazdow/98d85929a70d58729e1ef0a97a754b14 to your computer and use it in GitHub Desktop.
init.el
;; windows emacs home setup
;; Place this file in C:\Users\Username\AppData\Roaming
;; put gpg4win GnuPG\bin in PATH before msys2
;; in threat protection settings add exclusions for emacs, .emacs.d folders
(setenv "HOME" "C:/Users/ollie")
(setq user-emacs-directory "C:/Users/ollie/.emacs.d/")
(setq user-init-file "C:/Users/ollie/.emacs.d/init.el")
(setq default-directory "C:/Users/ollie")
(setq package-gnupghome-dir "~/.emacs.d/elpa/gnupg")
; if msys2 usr/bin in path:
(setq-default find-program "C:/msys64/usr/bin/find.exe")
(deftheme benthic-zone "the Benthic Zone..")
(let (
(black "#00041a")
(black2 "#080019")
(blue "#94bfff")
(blue2 "#6dddfd")
(blue3 "#4f5987")
(blue4 "#858db7")
(blue5 "#656fa4")
(cyan "#39ffba")
(cyan2 "#01f7f7")
(orange "#ffc22a")
(orange2 "#ffad4a")
(pink "#f1b3f1")
(purple "#e193f2")
(purple2 "#9470ff")
(red "#ff446a")
(red2 "#ff3f85")
(white "#f8f8f0")
(white2 "#effaff")
(yellow "#ffee7a")
(yellow2 "#ffdf3a")
(grey "dddddd")
(grey2 "#aaaaaa")
(grey3 "#999999")
(grey4 "#dbdce2")
(darkgrey "#2b2d4f"))
(custom-theme-set-faces 'benthic-zone
`(default ((t (:background ,black2 :foreground ,white2 ))))
`(region ((t (:background ,darkgrey))))
`(hl-line ((t ( :extend t :background "#0f0f1e"))))
`(cursor ((t (:foreground "#a7F3F3"))))
`(secondary-selection ((t (:background ,black ))))
`(fringe ((t (:background ,black ))))
`(mode-line-inactive ((t (:background ,black :foreground ,grey4 ))))
`(mode-line ((t (:background "#000000" :foreground "#c7c8ce" ))))
`(vertical-border ((t (:foreground "#000000" ))))
`(highlight ((t (:background unspecified :foreground unspecified))))
`(window-divider ((t (:background ,black :foreground ,black ))))
`(font-lock-function-name-face ((t (:foreground ,red :fontStyle :bold nil :italic nil :underline nil ))))
`(font-lock-comment-face ((t (:foreground ,grey3 ))))
`(font-lock-string-face ((t (:foreground ,blue2 ))))
`(font-lock-keyword-face ((t (:foreground ,red ))))
`(font-lock-type-face ((t (:foreground ,cyan :fontStyle :underline nil ))))
`(font-lock-variable-name-face ((t (:foreground ,cyan :fontStyle :bold nil :italic nil :underline nil ))))
`(font-lock-keyword-face ((t (:foreground ,purple ))))
`(font-lock-number-face ((t (:foreground ,purple ))))
`(highlight-numbers-number ((t (:foreground ,purple ))))
`(font-lock-operator-face ((t (:foreground ,red ))))
`(highlight-operators-face ((t (:foreground ,red ))))
`(font-lock-builtin-face ((t (:foreground ,pink :fontStyle :bold nil :italic nil :underline nil ))))
;; `(font-lock-property-name-face ((t (:foreground ,red ))))
`(font-lock-property-use-face ((t (:foreground ,white2 ))))
`(font-lock-function-call-face ((t (:foreground ,cyan ))))
`(font-lock-constant-face ((t (:foreground ,purple ))))
`(line-number ((t (:foreground "#707070" ))))
`(line-number-current-line ((t (:foreground ,white2 ))))
`(show-paren-match ((t (:background unspecified :foreground ,cyan ))))
`(show-paren-mismatch ((t (:background unspecified :foreground ,red))))
;; `(js2-object-property ((t (:foreground ,cyan2 ))))
`(js2-function-param ((t (:foreground ,orange2 ))))
`(js2-function-call ((t ( :foreground ,cyan2 ))))
`(vertical-border ((((class color) (min-colors 89)) (:foreground ,black))))
`(border ((t (:background unspecified :foreground unspecified))))
`(iedit-occurrence ((t (:foreground "white" :background "LightSeaGreen"))))
;; '(js2-warning ((((class color) (min-colors 89)) (:underline "#f99157"))))
;; '(js2-error ((((class color) (min-colors 89)) (:foreground nil :underline "#f2777a"))))
;; '(js2-external-variable ((((class color) (min-colors 89)) (:foreground "#cc99cc"))))
;; '(js2-instance-member ((((class color) (min-colors 89)) (:foreground "#6699cc"))))
;; '(js2-object-property-access ((((class color) (min-colors 89)) (:foreground "#f99157"))))
;; '(js2-private-member ((((class color) (min-colors 89)) (:foreground "#cc99cc"))))
))
;; (window-divider-mode 0)
;; add numbers, operators highlighting
(add-hook 'js-mode-hook #'(lambda ()
(font-lock-add-keywords nil
'(("\\b\\([0-9]+\\([eE][+-]?[0-9]*\\)?\\)\\b" 1 'font-lock-number-face)
("\\([!%&*+<-?|~^-]\\)" 0 'font-lock-operator-face)
("\\([^/]\\)\\(/\\)\\([^/]\\)" 2 'font-lock-operator-face)
)
)))
;;;###autoload
(when load-file-name
(add-to-list 'custom-theme-load-path
(file-name-as-directory (file-name-directory load-file-name))))
;;;###autoload
(defun benthic-zone-theme()
(interactive)
(load-theme 'benthic-zone t))
(provide-theme 'benthic-zone)
(set-face-attribute 'default nil :background "#000000" :foreground "#eeeeee")
(tool-bar-mode -1)
(menu-bar-mode -1)
(scroll-bar-mode -1)
(setq inhibit-startup-message t)
(setq gc-cons-threshold most-positive-fixnum gc-cons-percentage 0.6)
(defvar _alist file-name-handler-alist)
(setq file-name-handler-alist nil)
(add-hook 'emacs-startup-hook (lambda () (setq file-name-handler-alist _alist)))
(require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
; -------------- themes --------------
(defun theme-init () (interactive)
(load "~/.emacs.d/theme/benthic-zone-theme.el")
(load-theme 'benthic-zone)
;; (when (display-graphic-p) (set-face-attribute 'default nil :background "#080019"))
;; (set-face-attribute 'hl-line nil :extend t :background "gray6")
;; (set-face-attribute 'neo-dir-link-face nil :foreground "light sky blue")
;; (set-face-attribute 'neo-expand-btn-face nil :foreground "SteelBlue1")
;; (set-face-attribute 'neo-file-link-face nil :foreground "alice blue")
;; (set-face-attribute 'neo-root-dir-face nil :background 'unspecified :foreground "azure" :box nil)
;; (set-face-attribute 'mini-echo-major-mode nil :weight 'normal)
;; (set-face-attribute 'success nil :foreground "DeepSkyBlue")
(set-face-attribute 'success nil :foreground "turquoise" :weight 'normal)
(set-face-attribute 'warning nil :foreground "orchid4")
(set-face-attribute 'dashboard-text-banner nil :height 0.7 :width 'condensed :family "courier" :foreground "DodgerBlue")
(set-cursor-color "#a7F3F3")
)
(add-hook 'after-init-hook 'theme-init)
; -------------- fonts -----------------
(set-frame-font "Roboto Mono 10")
;; (set-frame-font "Droid Sans Mono 11")
;; (set-frame-font "Liberation Mono 12")
;; (set-frame-font "Meslo LG M 11")
;; (set-frame-font "Menlo 11")
; -------------- settings --------------
(setq make-backup-files nil
create-lockfiles nil
auto-save-default nil
ring-bell-function 'ignore
recentf-auto-cleanup 480
use-short-answers t
help-window-select t
warning-minimum-level :error
mouse-wheel-scroll-amount '(9 ((shift) . 40))
mouse-wheel-progressive-speed nil
mouse-wheel-tilt-scroll t
void-text-area-pointer nil
bookmark-set-fringe-mark nil
dired-kill-when-opening-new-dired-buffer t
frame-title-format "%f"
custom-safe-themes t)
(setq-default truncate-lines t
display-line-numbers-type t
display-line-numbers-width 3
font-lock-maximum-decoration 3
indent-tabs-mode nil
cursor-type 'bar
scroll-bar-width 20
c-basic-offset 4
tab-width 4)
(recentf-mode t)
(savehist-mode 1)
(winner-mode t)
;; (ivy-mode 1)
(show-paren-mode t)
(global-eldoc-mode -1)
(electric-pair-mode t)
(fringe-mode '(4 . 4))
(transient-mark-mode t)
(global-hl-line-mode 1)
;; (global-font-lock-mode t)
(delete-selection-mode t)
(pixel-scroll-precision-mode 1)
(defalias 'yes-or-no-p 'y-or-n-p)
(add-hook 'prog-mode-hook 'display-line-numbers-mode)
(define-key messages-buffer-mode-map (kbd "<escape>") 'quit-window)
(define-key help-mode-map (kbd "<escape>") 'quit-window)
(define-key minibuffer-local-map (kbd "<escape>") 'keyboard-escape-quit)
(defun display-startup-echo-area-message ()(message "~~--==--~~"))
; -------------- keys --------------
; C-x, C-c, C-v
(cua-mode t)
(setq-default cua-copy-keep-mark t)
; C-x C-f, C-x f find file
(global-set-key (kbd "C-x f") 'counsel-find-file)
(global-set-key (kbd "C-x C-f") 'counsel-find-file)
; C-x k, C-x C-k, C-K kill buffer
(global-set-key (kbd "C-x C-k") 'kill-buffer)
(global-set-key (kbd "C-S-k") 'kill-this-buffer)
; M-p toggle neotree
(global-set-key (kbd "M-p") 'neotree-toggle)
; C-P toggle menu bar
(global-set-key (kbd "C-S-p") 'toggle-menu-bar-mode-from-frame)
; M-z, counsel M-x
(global-set-key (kbd "M-x") 'counsel-M-x)
; C-B eval buffer
(global-set-key (kbd "C-S-b") 'eval-buffer)
; backtab
(global-set-key (kbd "<backtab>") 'indent-rigidly-left-to-tab-stop)
; zoom
(global-set-key (kbd "C-+") 'text-scale-increase)
(global-set-key (kbd "C-_") 'text-scale-decrease)
; C-x C-a open recent files
(global-set-key (kbd "C-x C-a") 'recentf-open-files)
; C-= expand region
(global-set-key (kbd "C-=") 'er/expand-region)
; C-; iedit mode
(global-set-key (kbd "C-;") 'iedit-mode)
; C-x C-b ivy-switch-buffer
(global-set-key (kbd "C-x C-b") 'ivy-switch-buffer)
; c-x r counsel recentf
(global-set-key (kbd "C-x r") 'counsel-recentf)
; C-c C-a mark all
(define-key global-map (kbd "C-c") (make-sparse-keymap))
(global-set-key (kbd "C-c C-a") 'mark-whole-buffer)
; mouse side buttons
(global-set-key (kbd "<mouse-4>") 'previous-buffer)
(global-set-key (kbd "<mouse-5>") 'next-buffer)
(global-set-key (kbd "C-<mouse-4>") 'pixel-scroll-interpolate-down)
(global-set-key (kbd "C-<mouse-5>") 'pixel-scroll-interpolate-up)
(global-set-key (kbd "M-<mouse-4>") 'delete-other-windows)
(global-set-key (kbd "M-<mouse-5>") 'split-window-right)
; split window, refocus
(global-set-key (kbd "C-x 2") (lambda () (interactive)(split-window-vertically) (other-window 1)))
(global-set-key (kbd "C-x 3") (lambda () (interactive)(split-window-horizontally) (other-window 1)))
; window focus
(global-set-key (kbd "M-S-<right>") 'windmove-right)
(global-set-key (kbd "M-S-<left>") 'windmove-left)
(global-set-key (kbd "M-S-<up>") 'windmove-up)
(global-set-key (kbd "M-S-<down>") 'windmove-down)
; C-/ comment
(global-set-key (kbd "C-/") 'toggle-comment)
; C-l select line
(global-set-key (kbd "C-l") 'select-current-line)
(global-set-key (kbd "C-S-l") 'recenter-top-bottom)
(global-set-key (kbd "M-n") 'narrow-or-widen-dwim)
(global-set-key (kbd "M-S-n") 'narrow-or-widen-paragraph)
(global-set-key (kbd "C-:") 'narrow-or-widen-paragraph-iedit)
(global-set-key (kbd "C-x C-c") 'fast-kill-emacs)
(global-set-key (kbd "C-c l r") 'list-colors-display)
(global-set-key (kbd "M-i") 'backward-sentence)
(global-set-key (kbd "M-o") 'forward-sentence)
(global-set-key (kbd "M-s") 'mc/mark-next-like-this)
; jump to...
; async proces...
; M-h mark parag
; M-up/dn move-text
; M-n narrow/widen
; C-x C-w save as
; C-c C-a mark all
; C-x C-c fast quit
; C-/ comment
; C-l select line
; C-c l r display colors
; C-x left/right switch buffer
; C-x 0 delete window
; C-x 1 delete other windows
; C-x 2 split window below
; C-x 3 split window right
; M-S-<dir> focus window
; M-s mc mark next
; C-S isearch selected
(defun isearch-selected ()
(interactive) (deactivate-mark)
(call-interactively 'isearch-forward-symbol-at-point))
(global-set-key (kbd "C-S-s") 'isearch-selected)
; keep search mark on esc
(defun isearch-mark-exit ()
(interactive)
(isearch-done)
(push-mark isearch-other-end 'no-message 'activate))
(define-key isearch-mode-map (kbd "<escape>") 'isearch-mark-exit)
; C-z, C-y undo/redo
(use-package undo-fu
:ensure t
:config
(global-set-key (kbd "C-z") 'undo-fu-only-undo)
(global-set-key (kbd "C-y") 'undo-fu-only-redo))
; C-l select line
(defun select-current-line ()
(interactive)
(forward-line 0)
(set-mark-command nil)
(forward-line 1))
; counsel / ivy, prescient
(use-package counsel
:ensure t
:after (ivy)
:config
(setq ivy-height 8)
;; (setq ivy-use-virtual-buffers t)
(setq ivy-initial-inputs-alist nil)
(define-key ivy-minibuffer-map (kbd "TAB") 'ivy-partial)
(define-key ivy-minibuffer-map (kbd "<return>") 'ivy-alt-done)
(define-key ivy-minibuffer-map (kbd "<escape>") 'minibuffer-keyboard-quit)
:bind (("C-x C-f" . counsel-find-file)))
(use-package ivy-prescient :ensure t
:after counsel
:config
(setq prescient-sort-length-enable nil)
;; (setq ivy-prescient-retain-classic-highlighting t)
(setq prescient-frequency-decay 0.5)
(setq ivy-prescient-enable-filtering nil)
(ivy-prescient-mode 1)
(prescient-persist-mode 1))
; auto-complete
(use-package company
:ensure t
:config
(setq company-backends '((company-dabbrev company-dabbrev-code)))
;; (setq company-backends '((company-dabbrev-code company-capf)))
(setq company-idle-delay 0.1)
(setq company-dabbrev-minimum-length 2)
(add-hook 'after-init-hook 'global-company-mode)
(eval-after-load 'company '(progn
; https://emacs.stackexchange.com/a/76825
(set-face-attribute 'company-tooltip-selection nil
:background 'unspecified
:foreground "#B58900"
:underline nil
:weight 'normal))))
; basic tab mode
(use-package stupid-indent-mode
:ensure t
:defer t
:hook ((emacs-lisp-mode js-mode) . stupid-indent-mode))
;; ; js2 mode
;; (use-package js2-mode
;; :ensure t
;; :config
;; (setq js2-strict-missing-semi-warning nil)
;; ;; (add-to-list 'auto-mode-alist '("\\.js\\'" . js2-mode))
;; )
; mini-echo
(use-package mini-echo :ensure t
:config
(mini-echo-define-segment "buffer-pos" "" :fetch
(let ((pos (mini-echo-segment--extract "%l" 'force)))
(mini-echo-segment--print (format "%s/%d" pos
(count-lines (point-min) (point-max))) 'mini-echo-buffer-position)))
(mini-echo-define-segment "dir" "" :fetch
(let ((dir (directory-file-name (file-name-directory default-directory))))
(mini-echo-segment--print (concat (file-name-nondirectory dir) "/") 'mini-echo-project)))
(setq mini-echo-default-segments
'(:long ("buffer-pos" "major-mode" "buffer-name" "dir")
:short ( "major-mode" "buffer-name-short")))
(setq mini-echo-position-format "%l|%p")
(setq mini-echo-buffer-status-style 'color)
(set-face-attribute 'mini-echo-major-mode nil :weight 'normal)
(set-face-attribute 'mini-echo-violet nil :foreground "Royalblue1")
(set-face-attribute 'mini-echo-project nil :foreground "SteelBlue1")
(mini-echo-mode))
;; (use-package simple-modeline :ensure t
;; :hook (after-init . simple-modeline-mode))
; dashboard
(use-package dashboard
:ensure t
:config
(setq dashboard-projects-backend 'project-el)
(setq dashboard-center-content t)
(setq dashboard-banner-logo-title nil)
(setq dashboard-startup-banner "~/.emacs.d/wob.txt") ; 'logo
(setq dashboard-set-footer nil)
(setq dashboard-set-init-info t)
(setq dashboard-init-info "--== wobmacs ==--")
(setq dashboard-projects-switch-function 'project-switch-project)
(setq dashboard-items '((recents . 5)(projects . 5)(bookmarks . 5)))
(dashboard-setup-startup-hook))
; neotree
(use-package neotree
:ensure t
:init
(setq neo-smart-open t)
(setq neo-theme 'nerd)
(add-hook 'neo-after-create-hook
(lambda (&rest _)
(set-window-scroll-bars (get-buffer-window "*NeoTree*") nil nil)))
(require 'neotree))
; neotree return focus
(defun around-neotree-toggle (orig-fun &rest args)
(let ((last-window (selected-window)))
(prog1 (apply orig-fun args)
(when (and last-window (neo-global--window-exists-p))
(select-window last-window)))))
(advice-add 'neotree-toggle :around #'around-neotree-toggle)
; neotree update when open
;; (defun neotree-open-update (&rest _)
;; (when (and (buffer-file-name) (neo-global--window-exists-p))
;; (neo-buffer--change-root (file-name-directory (buffer-file-name)))))
;; (advice-add 'find-file :after 'neotree-open-update)
; iedit
(use-package iedit :ensure t)
; multiple-cursors
(use-package multiple-cursors :ensure t)
; expand region
(use-package expand-region :ensure t)
; move-text
(use-package move-text :ensure t :config
(move-text-default-bindings))
; burly bookmark
(use-package burly :ensure t :config
(setq burly-bookmark-prefix ""))
; windresize
;; (use-package windresize :ensure t :config
;; (define-key windresize-map (kbd "<return>") 'windresize-exit))
;; (use-package highlight-numbers :ensure t
;; :hook ((js-mode) . highlight-numbers-mode))
; project-x
;; (load "~/.emacs.d/project-x.el")
;; (project-x-mode +1)
;; (setq project-x-local-identifier '("package.json" ".project"))
;; ; horizontal-split shell
;; (defun shellp ()
;; (interactive)
;; (let ((w (selected-window)))
;; (split-window-below)
;; (select-window (next-window))
;; (shell)
;; (switch-to-buffer "*shell*")))
;; ; kill shellp
;; (defun shell-mode-kill ()
;; (when (derived-mode-p 'shell-mode)
;; (unless (= (length (window-list)) 1)
;; (delete-window)) t))
;; (add-hook 'kill-buffer-hook 'shell-mode-kill)
; open explorer
(defun explorer()
(interactive)
(shell-command "explorer ."))
; fast quit
(defun fast-kill-emacs ()
"save some buffers, then exit unconditionally"
(interactive)
(save-some-buffers nil t)
(kill-emacs))
; minibuffer mouse unfocus
(defun stop-using-minibuffer ()
(when (and (not (minibufferp))
(>= (recursion-depth) 1) (active-minibuffer-window))
(abort-recursive-edit)))
(add-hook 'mouse-leave-buffer-hook 'stop-using-minibuffer)
; sublime-style comment toggle
(defun toggle-comment ()
(interactive)
(let ((beg (if (region-active-p) (region-beginning) (line-beginning-position)))
(end (if (region-active-p) (region-end) (line-end-position))))
(comment-or-uncomment-region beg end)))
; keep mark on cua copy
(defun copy-keep-mark (orig-fun &rest args)
(prog1 (apply orig-fun args) (message "copied")
(run-with-idle-timer 0.7 nil (lambda () (message "")))
(setq deactivate-mark nil)))
(when (bound-and-true-p cua-copy-keep-mark)
(advice-add 'cua-copy-region :around 'copy-keep-mark))
; right click copy selection
(defadvice mouse-save-then-kill (around mouse2-copy-region activate)
(when (region-active-p)
(copy-region-as-kill (region-beginning) (region-end))) ad-do-it)
; pgup/dn less than default
(defvar fk/default-scroll-lines 40)
(defun fk/scroll-up (orig-func &optional arg)
(apply orig-func (list (or arg fk/default-scroll-lines))))
(defun fk/scroll-down (orig-func &optional arg)
(apply orig-func (list (or arg fk/default-scroll-lines))))
(advice-add 'scroll-up :around 'fk/scroll-up)
(advice-add 'scroll-down :around 'fk/scroll-down)
; endlessparentheses.com/emacs-narrow-or-widen-dwim.html
(defun narrow-or-widen-dwim (p)
(interactive "P")
(cond ((and (buffer-narrowed-p) (not p)) (widen))
((region-active-p)
(narrow-to-region (region-beginning) (region-end)))
(t (narrow-to-defun))))
(defun narrow-or-widen-paragraph ()
(interactive)
(if (buffer-narrowed-p)
(widen)
(mark-paragraph)
(narrow-to-region (region-beginning) (region-end))))
(defun narrow-or-widen-paragraph-iedit ()
(interactive)
(let ((original-point (point)))
(if (buffer-narrowed-p)
(progn
(widen)
(iedit-mode -1))
(mark-paragraph)
(narrow-to-region (region-beginning) (region-end))
(deactivate-mark)
(goto-char original-point)
(iedit-mode))))
(custom-set-variables
;; custom-set-variables was added by Custom.
;; If you edit it by hand, you could mess it up, so be careful.
;; Your init file should contain only one such instance.
;; If there is more than one, they won't work right.
'(package-selected-packages
'(simple-modeline multiple-cursors windresize burly move-text expand-region iedit neotree dashboard undo-fu stupid-indent-mode mini-echo counsel company)))
;;; project-x.el --- Extra convenience features for project.el -*- lexical-binding: t -*-
;; Copyright (C) 2021 Karthik Chikmagalur
;; Author: Karthik Chikmagalur <karthik.chikmagalur@gmail.com>
;; URL: https://github.com/karthink/project-x
;; Version: 0.1.5
;; Package-Requires: ((emacs "27.1"))
;; This file is NOT part of GNU Emacs.
;; This file is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; For a full copy of the GNU General Public License
;; see <http://www.gnu.org/licenses/>.
;;
;;; Commentary:
;;
;; project-x provides some convenience features for project.el:
;; - Recognize any directory with a `.project' file as a project.
;; - Save and restore project files and window configurations across sessions
;;
;; COMMANDS:
;;
;; project-x-window-state-save : Save the window configuration of currently open project buffers
;; project-x-window-state-load : Load a previously saved project window configuration
;;
;; CUSTOMIZATION:
;;
;; `project-x-window-list-file': File to store project window configurations
;; `project-x-local-identifier': String matched against file names to decide if a
;; directory is a project
;; `project-x-save-interval': Interval in seconds between autosaves of the
;; current project.
;;
;; by Karthik Chikmagalur
;; <karthik.chikmagalur@gmail.com>
;;; Code:
(require 'project)
(eval-when-compile (require 'subr-x))
(eval-when-compile (require 'seq))
(defvar project-prefix-map)
(defvar project-switch-commands)
(declare-function project-prompt-project-dir "project")
(declare-function project--buffer-list "project")
(declare-function project-buffers "project")
(defgroup project-x nil
"Convenience features for the Project library."
:group 'project)
;; Persistent project sessions
;; -------------------------------------
(defcustom project-x-window-list-file
(locate-user-emacs-file "project-window-list")
"File in which to save project window configurations by default."
:type 'file
:group 'project-x)
(defcustom project-x-save-interval nil
"Saves the current project state with this interval.
When set to nil auto-save is disabled."
:type '(choice (const :tag "Disabled" nil)
integer)
:group 'project-x)
(defvar project-x-window-alist nil
"Alist of window configurations associated with known projects.")
(defvar project-x-save-timer nil
"Timer for auto-saving project state.")
(defun project-x--window-state-write (&optional file)
"Write project window states to `project-x-window-list-file'.
If FILE is specified, write to it instead."
(when project-x-window-alist
(require 'pp)
(unless file (make-directory (file-name-directory project-x-window-list-file) t))
(with-temp-file (or file project-x-window-list-file)
(insert ";;; -*- lisp-data -*-\n")
(let ((print-level nil) (print-length nil))
(pp project-x-window-alist (current-buffer))))
(message (format "Wrote project window state to %s" project-x-window-list-file))))
(defun project-x--window-state-read (&optional file)
"Read project window states from `project-x-window-list-file'.
If FILE is specified, read from it instead."
(and (or file
(file-exists-p project-x-window-list-file))
(with-temp-buffer
(insert-file-contents (or file project-x-window-list-file))
(condition-case nil
(if-let ((win-state-alist (read (current-buffer))))
(setq project-x-window-alist win-state-alist)
(message (format "Could not read %s" project-x-window-list-file)))
(error (message (format "Could not read %s" project-x-window-list-file)))))))
(defun project-x-window-state-save (&optional arg)
"Save current window state of project.
With optional prefix argument ARG, query for project."
(interactive "P")
(when-let* ((dir (cond (arg (project-prompt-project-dir))
((project-current)
(project-root (project-current)))))
(default-directory dir))
(unless project-x-window-alist (project-x--window-state-read))
(let ((file-list))
;; Collect file-list of all the open project buffers
(dolist (buf
(funcall (if (fboundp 'project--buffers-list)
#'project--buffers-list
#'project-buffers)
(project-current))
file-list)
(if-let ((file-name (or (buffer-file-name buf)
(with-current-buffer buf
(and (derived-mode-p 'dired-mode)
dired-directory)))))
(push file-name file-list)))
(setf (alist-get dir project-x-window-alist nil nil 'equal)
(list (cons 'files file-list)
(cons 'windows (window-state-get nil t)))))
(message (format "Saved project state for %s" dir))))
(defun project-x-window-state-load (dir)
"Load the saved window state for project with directory DIR.
If DIR is unspecified query the user for a project instead."
(interactive (list (project-prompt-project-dir)))
(unless project-x-window-alist (project-x--window-state-read))
(if-let* ((project-x-window-alist)
(project-state (alist-get dir project-x-window-alist
nil nil 'equal)))
(let ((file-list (alist-get 'files project-state))
(window-config (alist-get 'windows project-state)))
(dolist (file-name file-list nil)
(find-file file-name))
(window-state-put window-config nil 'safe)
(message (format "Restored project state for %s" dir)))
(message (format "No saved window state for project %s" dir))))
(defun project-x-windows ()
"Restore the last saved window state of the chosen project."
(interactive)
(project-x-window-state-load (project-root (project-current))))
;; Recognize directories as projects by defining a new project backend `local'
;; -------------------------------------
(defcustom project-x-local-identifier ".project"
"Filename(s) that identifies a directory as a project.
You can specify a single filename or a list of names."
:type '(choice (string :tag "Single file")
(repeat (string :tag "Filename")))
:group 'project-x)
(cl-defmethod project-root ((project (head local)))
"Return root directory of current PROJECT."
(cdr project))
(defun project-x-try-local (dir)
"Determine if DIR is a non-VC project.
DIR must include a .project file to be considered a project."
(if-let ((root (if (listp project-x-local-identifier)
(seq-some (lambda (n)
(locate-dominating-file dir n))
project-x-local-identifier)
(locate-dominating-file dir project-x-local-identifier))))
(cons 'local root)))
;;;###autoload
(define-minor-mode project-x-mode
"Minor mode to enable extra convenience features for project.el.
When enabled, save and load project window states.
Recognize any directory that contains (or whose parent
contains) a special file as a project."
:global t
:version "0.10"
:lighter ""
:group 'project-x
(if project-x-mode
;;Turning the mode ON
(progn
(add-hook 'project-find-functions 'project-x-try-local 90)
(add-hook 'kill-emacs-hook 'project-x--window-state-write)
(project-x--window-state-read)
(define-key project-prefix-map (kbd "w") 'project-x-window-state-save)
(define-key project-prefix-map (kbd "j") 'project-x-window-state-load)
(if (listp project-switch-commands)
(add-to-list 'project-switch-commands
'(?j "Restore windows" project-x-windows) t)
(message "`project-switch-commands` is not a list, not adding 'restore windows' command"))
(when project-x-save-interval
(setq project-x-save-timer
(run-with-timer 0 (max project-x-save-interval 5)
#'project-x-window-state-save))))
(remove-hook 'project-find-functions 'project-x-try-local 90)
(remove-hook 'kill-emacs-hook 'project-x--window-state-write)
(define-key project-prefix-map (kbd "w") nil)
(define-key project-prefix-map (kbd "j") nil)
(when (listp project-switch-commands)
(delete '(?j "Restore windows" project-x-windows) project-switch-commands))
(when (timerp project-x-save-timer)
(cancel-timer project-x-save-timer))))
(provide 'project-x)
;;; project-x.el ends here
%@@@%%%#*#*++=++*#####*++++++*##*+++++******+++++++++++++++++++++++++++**+++**************
***###*****+++***+++++++++++++++++++++++++++++++++++++++++++++++++++++*###********##***##*
+*++*+*##%%%###*##+++++++++++++++++++++++++++++++++++=++=+++**++++*+++*######*++++*######%
+++#####%%######%*+++++++++==+===++=+++=+===++++==++++++++#%#**+++***++**++*##+++++++****#
+++*+++*++++***#%**++++=+=========+#**+===++++*++++**#########*+*#**###*+#####*+++*+++****
+++++++++=+++++++=++*+++==========#%#+*##**######*=++++*#%##%%#+++*+*##########**++++++**+
==========--==+===========+*###*******++**#%%%%##**#******#####********######**+++++++++*+
=-======---------=--==++***######*#+=++=+**###***#####****#####+*#***#####*+==+*++++++==++
-====-------------===*#***###*#####*******+***++++*****#**#####*#***+**##*=-=========+=+++
-----::::::--:-=*#*===*##****+**++++==+*##*+**=+**+*+*%%#***+****+*+==++*#--==========++++
:-:::::::::::=**+*****+***#***#+=-+****#*#**#*-===++***##***#*+-----=-====---=-==========+
::::-::::-::+###****+***##*++****+=+*##**#***+====+=====+*####*-::::::-:---==+------======
::::::::::-+%%%###***+####*++++*#**+++++*****+*+++++===+++*+*++*::-::::------==---==--=-==
::::::::::-::=#%######****#+*******++*+++**+***++==--:=+*##******::::::::::::----:---:-==-
:::::::.:.:.:::*+=+*##*#***+++#***==+====-.:::-:=-+++=+*+*%%%%###+:...::::::::--:::-------
:::::...:.:..:::::..:::.==--=:::.:::....................::----=--=:.:::--:.::--:-:::::::--
::.:.....:..:.............................................:...:.....:.::::::..:::::::-:::=
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment