(when (eq system-type 'darwin)
(setq default-frame-alist '((fullscreen . fullboth))))
-(defvar esy/init-step-times nil)
-
-(defmacro esy/init-step (name _doc &rest body)
- (declare (indent defun)
- (doc-string 2))
- (let ((start-time-symbol (gensym)))
- `(let ((,start-time-symbol (current-time)))
- ,@body
- (push (cons ',name
- (time-subtract (current-time) ,start-time-symbol))
- esy/init-step-times))))
-
-(esy/init-step gc
- "Temporarily increase GC threshold to expedite Emacs startup."
- (let ((normal-gc-cons-threshold (* 20 1024 1024))
- (init-gc-cons-threshold (* 1024 1024 1024))
- (normal-mode-line-format mode-line-format))
- (setq gc-cons-threshold init-gc-cons-threshold
- mode-line-format nil)
- (add-hook 'after-init-hook
- (lambda ()
- (setq gc-cons-threshold normal-gc-cons-threshold
- mode-line-format normal-mode-line-format)))))
-
-(esy/init-step vars
- "Set some variables."
- (setq
- ;; my name
- user-full-name "Eshel Yaron"
- ;; my email address
- user-mail-address "me@eshelyaron.com"
- ;; direct Custom definitions to some file and forget about it
- custom-file (expand-file-name "custom.el" user-emacs-directory)
- ;; silence native compilation warnings
- native-comp-async-report-warnings-errors 'silent
- ;; only display the warning buffer if something's really broken
- warning-minimum-level :error
- ;; don't use stale .elc files
- load-prefer-newer t
- ;; maximize Emacs on startup
- ;; default-frame-alist '((fullscreen . fullboth))
- ;; disable popup dialogs
- use-dialog-box nil
- ;; disable splash screen
- inhibit-startup-screen t
- ;; make the scratch message more concise
- initial-scratch-message ";; Go.\n"
- ;; don't ring the bell
- ring-bell-function #'ignore
- ;; make C-x b obey display-buffer-alist
- switch-to-buffer-obey-display-actions t
- ;; disable new mail mode line indication
- display-time-mail-function #'ignore
- ;; enable recursive minibuffers
- enable-recursive-minibuffers t
- bug-reference-url-format "https://debbugs.gnu.org/%s"
- ;; save bookmarks immediately
- bookmark-save-flag 1
- ;; show matches count in isearch prompt
- isearch-lazy-count t
- ;; kill Eat terminal buffers when their process exits
- eat-kill-buffer-on-exit t
- ;; don't spawn new frames in Ediff
- ediff-window-setup-function #'ediff-setup-windows-plain
- duplicate-line-final-position -1
- tramp-kubernetes-namespace "argo"
- ;; make some space in the Packages menu
- package-archive-column-width 12
- package-version-column-width 28
- ;; configure package archives
- package-archives '(("melpa" . "http://melpa.org/packages/")
- ("gnu" . "https://elpa.gnu.org/devel/")
- ("nongnu" . "https://elpa.nongnu.org/nongnu-devel/"))
- ;; select some packages to install
- package-selected-packages '(
- avy
- bbdb
- corfu
- debbugs
- devdocs
- diff-hl
- eat
- eglot
- ef-themes
- elfeed
- embark-consult
- emms
- gnu-elpa-keyring-update
- gnuplot
- graphql-mode
- graphviz-dot-mode
- htmlize
- ialign
- keycast
- kubernetes
- lin
- magit
- markdown-mode
- mastodon
- ob-prolog
- orderless
- org
- org-transclusion
- osm
- package-lint
- paredit
- pdf-tools
- rainbow-delimiters
- rainbow-mode
- rg
- smtpmail-multi
- sqlformat
- sweeprolog
- terraform-mode
- vterm
- whitespace-cleanup-mode
- )
- ;; configure Org capture templates
- org-capture-templates '(("t" "Todo [inbox]" entry
- (file+headline "~/org/inbox.org" "Tasks")
- "** TODO [#B] %^{Task} %^g
+;;; Temporarily increase GC threshold to expedite Emacs startup
+(let ((normal-gc-cons-threshold (* 20 1024 1024))
+ (init-gc-cons-threshold (* 1024 1024 1024))
+ (normal-mode-line-format mode-line-format))
+ (setq gc-cons-threshold init-gc-cons-threshold
+ mode-line-format nil)
+ (add-hook 'after-init-hook
+ (lambda ()
+ (setq gc-cons-threshold normal-gc-cons-threshold
+ mode-line-format normal-mode-line-format))))
+
+;;; Set some variables
+(setq
+ ;; my name
+ user-full-name "Eshel Yaron"
+ ;; my email address
+ user-mail-address "me@eshelyaron.com"
+ ;; direct Custom definitions to some file and forget about it
+ custom-file (expand-file-name "custom.el" user-emacs-directory)
+ ;; silence native compilation warnings
+ native-comp-async-report-warnings-errors 'silent
+ ;; only display the warning buffer if something's really broken
+ warning-minimum-level :error
+ ;; don't use stale .elc files
+ load-prefer-newer t
+ ;; maximize Emacs on startup
+ ;; default-frame-alist '((fullscreen . fullboth))
+ ;; disable popup dialogs
+ use-dialog-box nil
+ ;; disable splash screen
+ inhibit-startup-screen t
+ ;; make the scratch message more concise
+ initial-scratch-message ";; Go.\n"
+ ;; don't ring the bell
+ ring-bell-function #'ignore
+ ;; make C-x b obey display-buffer-alist
+ switch-to-buffer-obey-display-actions t
+ ;; disable new mail mode line indication
+ display-time-mail-function #'ignore
+ ;; enable recursive minibuffers
+ enable-recursive-minibuffers t
+ bug-reference-url-format "https://debbugs.gnu.org/%s"
+ ;; save bookmarks immediately
+ bookmark-save-flag 1
+ ;; show matches count in isearch prompt
+ isearch-lazy-count t
+ ;; kill Eat terminal buffers when their process exits
+ eat-kill-buffer-on-exit t
+ ;; don't spawn new frames in Ediff
+ ediff-window-setup-function #'ediff-setup-windows-plain
+ duplicate-line-final-position -1
+ tramp-kubernetes-namespace "argo"
+ ;; make some space in the Packages menu
+ package-archive-column-width 12
+ package-version-column-width 28
+ ;; configure package archives
+ package-archives '(("melpa" . "http://melpa.org/packages/")
+ ("gnu" . "https://elpa.gnu.org/devel/")
+ ("nongnu" . "https://elpa.nongnu.org/nongnu-devel/"))
+ ;; select some packages to install
+ package-selected-packages '(
+ avy
+ bbdb
+ corfu
+ debbugs
+ devdocs
+ diff-hl
+ eat
+ eglot
+ ef-themes
+ elfeed
+ embark-consult
+ emms
+ gnu-elpa-keyring-update
+ gnuplot
+ graphql-mode
+ graphviz-dot-mode
+ htmlize
+ ialign
+ keycast
+ kubernetes
+ lin
+ magit
+ markdown-mode
+ mastodon
+ ob-prolog
+ orderless
+ org
+ org-transclusion
+ osm
+ package-lint
+ paredit
+ pdf-tools
+ rainbow-delimiters
+ rainbow-mode
+ rg
+ smtpmail-multi
+ sqlformat
+ sweeprolog
+ terraform-mode
+ vterm
+ whitespace-cleanup-mode
+ )
+ ;; configure Org capture templates
+ org-capture-templates '(("t" "Todo [inbox]" entry
+ (file+headline "~/org/inbox.org" "Tasks")
+ "** TODO [#B] %^{Task} %^g
:PROPERTIES:
:CreatedAt: %u
:CapturedAt: %a
:CapturedAs: Inbox Task
:END:"
- :prepend t
- :empty-lines 1
- :immediate-finish t)
- ("w" "Work [inbox]" entry
- (file+headline "~/org/inbox.org" "Tasks")
- "** TODO [#B] %^{Task} :work:
+ :prepend t
+ :empty-lines 1
+ :immediate-finish t)
+ ("w" "Work [inbox]" entry
+ (file+headline "~/org/inbox.org" "Tasks")
+ "** TODO [#B] %^{Task} :work:
:PROPERTIES:
:CreatedAt: %u
:CapturedAt: %a
:CapturedAs: Work Task
:END:"
- :prepend t
- :empty-lines 1
- :immediate-finish t)
- ("c" "New Calendar Event" entry
- (file+headline "~/org/inbox.org" "Calendar")
- "** %^{Title} %^g
+ :prepend t
+ :empty-lines 1
+ :immediate-finish t)
+ ("c" "New Calendar Event" entry
+ (file+headline "~/org/inbox.org" "Calendar")
+ "** %^{Title} %^g
:PROPERTIES:
:CreatedAt: %u
:CapturedAt: %a
:END:
%(format-time-string \"<%Y-%m-%d %H:%M\" (org-read-date t t))-%(format-time-string \"%H:%M>\" (org-read-date t t))
%i"
- :prepend t
- :empty-lines 1
- :immediate-finish t))
- ;; point Org to file agenda file
- org-agenda-files '("~/org/inbox.org")
- org-default-notes-file "~/org/inbox.org"
- ;; weeks start on Sunday
- org-agenda-start-on-weekday 0
- ;; use a nice unicode ellipsis
- org-ellipsis "…"
- ;; open everything withing Emacs
- org-file-apps '((t . emacs))
- ;; enable Org speed commands
- org-use-speed-commands t
- ;; task states
- org-todo-keywords '((sequence
- "TODO(t)"
- "BLOCKED(b@/!)"
- "INPROGRESS(i!)"
- "|"
- "DONE(d!)"
- "CANCELED(c@)"))
- ;; set a task to INPROGRESS when I clock into it
- org-clock-in-switch-to-state "INPROGRESS"
- ;; log the finish time of tasks in Org
- org-log-done 'time
- org-log-into-drawer t
- ;; minimal prompt for task states
- org-use-fast-todo-selection 'expert
- ;; set up a group of mutual exclusive tags
- org-tag-alist '((:startgroup)
- ("adi" . ?a)
- ("holland" . ?h)
- ("work" . ?w)
- ("studies" . ?s)
- ("esols" . ?e)
- ("personal" . ?p)
- (:endgroup))
- ;; minimal prompt for tags
- org-fast-tag-selection-single-key 'expert
- ;; allow evaluating some languages with Org Babel
- org-babel-load-languages '((emacs-lisp . t)
- (shell . t)
- (sql . t)
- (prolog . t))
- ;; do not ask for confirmation when evaluating Org source blocks
- org-confirm-babel-evaluate nil
- ;; extra modules to load along with Org
- org-modules '(ol-bbdb
- ol-bibtex
- ol-docview
- ol-gnus
- ol-info
- ol-irc
- ol-mhe
- ol-eww
- ob-sql
- org-tempo)
- ;; refile Org entries to my agenda file(s)
- org-refile-targets '((org-agenda-files . (:maxlevel . 5))
- (nil . (:maxlevel . 3)))
- ;; use full outline path when prompting for refile target
- org-refile-use-outline-path t
- ;; archive for Org entries
- org-archive-location "~/org/journal.org::datetree/* Finished Tasks :ARCHIVE:"
- ;; associate Org Babel languages with major modes
- org-src-lang-modes '(("SWI-Prolog" . sweeprolog)
- ("Dockerfile" . dockerfile-ts)
- ("bash" . sh)
- ("shell" . sh)
- ("toml" . toml-ts))
- ;; preview LaTeX fragments in Org buffers via SVG
- org-preview-latex-default-process 'dvisvgm
- ;; don't auto-save remote files
- remote-file-name-inhibit-auto-save t
- ;; don't ask me about it either
- tramp-allow-unsafe-temporary-files t
- ;; increase maximum number of recent files Emacs remembers
- recentf-max-saved-items 128
- ;; increase maximum kill ring size
- kill-ring-max 256
- ;; save text copied from another program to the kill ring
- save-interprogram-paste-before-kill 2048
- ;; have C-u followed by repeated C-SPC keep popping
- set-mark-command-repeat-pop t
- ;; enable italic text in code
- modus-themes-italic-constructs t
- ;; enable bold text in code
- modus-themes-bold-constructs t
- ;; use fixed-pitch text where appropriate
- modus-themes-mixed-fonts t
- ;; make prompts bold
- modus-themes-prompts '(bold)
- ;; give source blocks a distinct background shade
- modus-themes-org-blocks 'gray-background
- ;; use variable-pitch text here are there
- modus-themes-variable-pitch-ui t
- ;; fine-tune some theme settings
- modus-themes-common-palette-overrides '((border-mode-line-active unspecified)
- (border-mode-line-inactive unspecified)
- (bg-mode-line-active bg-blue-intense)
- (fg-mode-line-active fg-main)
- (fringe unspecified)
- (underline-link border)
- (underline-link-visited border)
- (underline-link-symbolic border)
- (fg-region unspecified))
- ef-themes-mixed-fonts t
- ef-themes-variable-pitch-ui t
- ef-themes-region nil
- osm-copyright nil
- ;; follow links to version-controlled files without confirming
- vc-follow-symlinks t
- ;; jump from .c to .h files with find-sibling-file
- find-sibling-rules '(("\\([^/]+\\)\\.c\\'" "\\1.h"))
- ;; configure multiple SMTP accounts
- smtpmail-multi-accounts '((esy . ("eshelshay.yaron@gmail.com"
- "smtp.gmail.com" 587
- "eshelshay.yaron@gmail.com"
- starttls nil nil nil))
- (swp . ("eshel@swi-prolog.org"
- "mail.swi-prolog.com" 587
- "eshel@swi-prolog.org"
- starttls nil nil nil))
- (me . ("me@eshelyaron.com"
- "mail.eshelyaron.com"
- 587
- "me@eshelyaron.com"
- starttls nil nil nil))
- ;; (uva . ("eshel.yaron@student.uva.nl"
- ;; "smtp.office365.com"
- ;; 587
- ;; "eshel.yaron@student.uva.nl"
- ;; starttls nil nil nil))
- )
- ;; associate email addresses with SMTP accounts
- smtpmail-multi-associations '(("eshelshay\.yaron@gmail\.com" esy)
- ("eshel@swi-prolog\.org" swp)
- ("me@eshelyaron\.com" me)
- ;; ("eshel\.yaron@student\.uva\.nl" uva)
+ :prepend t
+ :empty-lines 1
+ :immediate-finish t))
+ ;; point Org to file agenda file
+ org-agenda-files '("~/org/inbox.org")
+ org-default-notes-file "~/org/inbox.org"
+ ;; weeks start on Sunday
+ org-agenda-start-on-weekday 0
+ ;; use a nice unicode ellipsis
+ org-ellipsis "…"
+ ;; open everything withing Emacs
+ org-file-apps '((t . emacs))
+ ;; enable Org speed commands
+ org-use-speed-commands t
+ ;; task states
+ org-todo-keywords '((sequence
+ "TODO(t)"
+ "BLOCKED(b@/!)"
+ "INPROGRESS(i!)"
+ "|"
+ "DONE(d!)"
+ "CANCELED(c@)"))
+ ;; set a task to INPROGRESS when I clock into it
+ org-clock-in-switch-to-state "INPROGRESS"
+ ;; log the finish time of tasks in Org
+ org-log-done 'time
+ org-log-into-drawer t
+ ;; minimal prompt for task states
+ org-use-fast-todo-selection 'expert
+ ;; set up a group of mutual exclusive tags
+ org-tag-alist '((:startgroup)
+ ("adi" . ?a)
+ ("holland" . ?h)
+ ("work" . ?w)
+ ("studies" . ?s)
+ ("esols" . ?e)
+ ("personal" . ?p)
+ (:endgroup))
+ ;; minimal prompt for tags
+ org-fast-tag-selection-single-key 'expert
+ ;; allow evaluating some languages with Org Babel
+ org-babel-load-languages '((emacs-lisp . t)
+ (shell . t)
+ (sql . t)
+ (prolog . t))
+ ;; do not ask for confirmation when evaluating Org source blocks
+ org-confirm-babel-evaluate nil
+ ;; extra modules to load along with Org
+ org-modules '(ol-bbdb
+ ol-bibtex
+ ol-docview
+ ol-gnus
+ ol-info
+ ol-irc
+ ol-mhe
+ ol-eww
+ ob-sql
+ org-tempo)
+ ;; refile Org entries to my agenda file(s)
+ org-refile-targets '((org-agenda-files . (:maxlevel . 5))
+ (nil . (:maxlevel . 3)))
+ ;; use full outline path when prompting for refile target
+ org-refile-use-outline-path t
+ ;; archive for Org entries
+ org-archive-location "~/org/journal.org::datetree/* Finished Tasks :ARCHIVE:"
+ ;; associate Org Babel languages with major modes
+ org-src-lang-modes '(("SWI-Prolog" . sweeprolog)
+ ("Dockerfile" . dockerfile-ts)
+ ("bash" . sh)
+ ("shell" . sh)
+ ("toml" . toml-ts))
+ ;; preview LaTeX fragments in Org buffers via SVG
+ org-preview-latex-default-process 'dvisvgm
+ ;; don't auto-save remote files
+ remote-file-name-inhibit-auto-save t
+ ;; don't ask me about it either
+ tramp-allow-unsafe-temporary-files t
+ ;; increase maximum number of recent files Emacs remembers
+ recentf-max-saved-items 128
+ ;; increase maximum kill ring size
+ kill-ring-max 256
+ ;; save text copied from another program to the kill ring
+ save-interprogram-paste-before-kill 2048
+ ;; have C-u followed by repeated C-SPC keep popping
+ set-mark-command-repeat-pop t
+ ;; enable italic text in code
+ modus-themes-italic-constructs t
+ ;; enable bold text in code
+ modus-themes-bold-constructs t
+ ;; use fixed-pitch text where appropriate
+ modus-themes-mixed-fonts t
+ ;; make prompts bold
+ modus-themes-prompts '(bold)
+ ;; give source blocks a distinct background shade
+ modus-themes-org-blocks 'gray-background
+ ;; use variable-pitch text here are there
+ modus-themes-variable-pitch-ui t
+ ;; fine-tune some theme settings
+ modus-themes-common-palette-overrides '((border-mode-line-active unspecified)
+ (border-mode-line-inactive unspecified)
+ (bg-mode-line-active bg-blue-intense)
+ (fg-mode-line-active fg-main)
+ (fringe unspecified)
+ (underline-link border)
+ (underline-link-visited border)
+ (underline-link-symbolic border)
+ (fg-region unspecified))
+ ef-themes-mixed-fonts t
+ ef-themes-variable-pitch-ui t
+ ef-themes-region nil
+ osm-copyright nil
+ ;; follow links to version-controlled files without confirming
+ vc-follow-symlinks t
+ ;; jump from .c to .h files with find-sibling-file
+ find-sibling-rules '(("\\([^/]+\\)\\.c\\'" "\\1.h"))
+ ;; configure multiple SMTP accounts
+ smtpmail-multi-accounts '((esy . ("eshelshay.yaron@gmail.com"
+ "smtp.gmail.com" 587
+ "eshelshay.yaron@gmail.com"
+ starttls nil nil nil))
+ (swp . ("eshel@swi-prolog.org"
+ "mail.swi-prolog.com" 587
+ "eshel@swi-prolog.org"
+ starttls nil nil nil))
+ (me . ("me@eshelyaron.com"
+ "mail.eshelyaron.com"
+ 587
+ "me@eshelyaron.com"
+ starttls nil nil nil))
+ ;; (uva . ("eshel.yaron@student.uva.nl"
+ ;; "smtp.office365.com"
+ ;; 587
+ ;; "eshel.yaron@student.uva.nl"
+ ;; starttls nil nil nil))
+ )
+ ;; associate email addresses with SMTP accounts
+ smtpmail-multi-associations '(("eshelshay\.yaron@gmail\.com" esy)
+ ("eshel@swi-prolog\.org" swp)
+ ("me@eshelyaron\.com" me)
+ ;; ("eshel\.yaron@student\.uva\.nl" uva)
+ )
+ ;; set function for sending email
+ send-mail-function #'smtpmail-multi-send-it
+ message-send-mail-function #'smtpmail-multi-send-it
+ ;; set email user agent
+ mail-user-agent 'gnus-user-agent
+ read-mail-command 'gnus
+ ;; make Gnus not prompt as much
+ gnus-expert-user t
+ ;; make Gnus read the dribble file on startup without prompting
+ gnus-always-read-dribble-file t
+ ;; don't break pages in incoming email
+ gnus-break-pages nil
+ ;; don't show the Gnus startup screen
+ gnus-inhibit-startup-message t
+ ;; set Gnus backends
+ gnus-select-method '(nntp "news.gmane.io")
+ gnus-cite-parse-max-size nil
+ gnus-face-1 'bold
+ gnus-face-2 'gnus-cite-1
+ gnus-face-3 'shadow
+ gnus-summary-line-format "%1{%U%R%z%}%B%2{%~(form (replace-regexp-in-string \" via .*\" \"\" (gnus-summary-from-or-to-or-newsgroups gnus-tmp-header gnus-tmp-from)))@%} %3{(%&user-date;, %k)%}:%* %s\n"
+ gnus-simplify-subject-functions '(gnus-simplify-subject-re)
+ gnus-treat-display-smileys nil
+ gnus-secondary-select-methods '((nnimap "gmail"
+ (nnimap-address "imap.gmail.com")
+ (nnimap-server-port "imaps")
+ (nnimap-stream ssl))
+ (nnimap "me"
+ (nnimap-address "mail.eshelyaron.com")
+ (nnimap-server-port "imaps")
+ (nnimap-stream ssl))
+ (nnimap "swi"
+ (nnimap-address "mail.swi-prolog.com")
+ (nnimap-server-port "imaps")
+ (nnimap-stream ssl))
+ ;; (nnimap "uva"
+ ;; (nnimap-address "outlook.office365.com")
+ ;; (nnimap-server-port "imaps")
+ ;; (nnimap-stream ssl))
)
- ;; set function for sending email
- send-mail-function #'smtpmail-multi-send-it
- message-send-mail-function #'smtpmail-multi-send-it
- ;; set email user agent
- mail-user-agent 'gnus-user-agent
- read-mail-command 'gnus
- ;; make Gnus not prompt as much
- gnus-expert-user t
- ;; make Gnus read the dribble file on startup without prompting
- gnus-always-read-dribble-file t
- ;; don't break pages in incoming email
- gnus-break-pages nil
- ;; don't show the Gnus startup screen
- gnus-inhibit-startup-message t
- ;; set Gnus backends
- gnus-select-method '(nntp "news.gmane.io")
- gnus-cite-parse-max-size nil
- gnus-face-1 'bold
- gnus-face-2 'gnus-cite-1
- gnus-face-3 'shadow
- gnus-summary-line-format "%1{%U%R%z%}%B%2{%~(form (replace-regexp-in-string \" via .*\" \"\" (gnus-summary-from-or-to-or-newsgroups gnus-tmp-header gnus-tmp-from)))@%} %3{(%&user-date;, %k)%}:%* %s\n"
- gnus-simplify-subject-functions '(gnus-simplify-subject-re)
- gnus-treat-display-smileys nil
- gnus-secondary-select-methods '((nnimap "gmail"
- (nnimap-address "imap.gmail.com")
- (nnimap-server-port "imaps")
- (nnimap-stream ssl))
- (nnimap "me"
- (nnimap-address "mail.eshelyaron.com")
- (nnimap-server-port "imaps")
- (nnimap-stream ssl))
- (nnimap "swi"
- (nnimap-address "mail.swi-prolog.com")
- (nnimap-server-port "imaps")
- (nnimap-stream ssl))
- ;; (nnimap "uva"
- ;; (nnimap-address "outlook.office365.com")
- ;; (nnimap-server-port "imaps")
- ;; (nnimap-stream ssl))
- )
- ;; simpler message
- gnus-no-groups-message "No new articles"
- ;; don't close other windows when opening Gnus
- gnus-use-full-window nil
- ;; make Gnus handle this content types
- gnus-article-treat-types '("text/plain"
- "text/x-verbatim"
- "text/x-patch"
- "text/html"
- "text/calendar")
- ;; capture calendar events from Gnus to my Org inbox file
- gnus-icalendar-org-capture-file "~/org/inbox.org"
- ;; put calendar events under the Calendar heading
- gnus-icalendar-org-capture-headline '("Calendar")
- ;; use ISO format for calendar dates
- calendar-date-style 'iso
- ;; maintain legibility of rendered text in HTML mails
- shr-color-visible-luminance-min 75
- ;; my Mastodon settings
- mastodon-instance-url "https://emacs.ch"
- mastodon-active-user "eshel"
- ;; direct Magit to my Git checkouts directory
- magit-repository-directories '(("~/checkouts/" . 1))
- ;; have Dired operations target another visible Dired buffer
- dired-dwim-target t
- vterm-max-scrollback 2048
- vterm-kill-buffer-on-exit nil
- vterm-use-vterm-prompt-detection-method t
- ;; use MPV with EMMS
- emms-player-list '(emms-player-mpv)
- ;; free-style numbering plan
- bbdb-phone-style nil
- ;; allow cycling through mail address completion candidate
- bbdb-complete-mail-allow-cycling t
- ;; don't pop up BBDB records after completing mail addresses
- bbdb-completion-display-record nil
- eww-auto-rename-buffer 'title
- browse-url-browser-function #'eww-browse-url
- browse-url-generic-program "open"
- global-auto-revert-non-file-buffers t
- auto-revert-verbose nil
- query-about-changed-file t
- ;; show flymake diagnostics as overlays at eol
- ;; flymake-show-diagnostics-at-end-of-line t
- kill-do-not-save-duplicates t
- show-trailing-whitespace t
- read-extended-command-predicate #'command-completion-default-include-p
- completions-format 'one-column
- completion-auto-select nil
- completion-styles '(orderless partial-completion basic)
- completion-show-help nil
- ;; completions-header-format nil
- ;; completion-auto-help 'visible
- completions-max-height 16
- completion-auto-wrap t
- corfu-cycle t
- corfu-indexed-start 1
- shell-kill-buffer-on-exit t
- ;; allow disabling confirming before compilation via local variables
- safe-local-variable-values '((compilation-read-command . nil))
- ;; TeX-view-program-selection '((output-pdf "PDF Tools"))
- ;; TeX-source-correlate-start-server t
- xref-show-definitions-function #'consult-xref
- xref-show-xrefs-function #'consult-xref
- xref-search-program 'ripgrep
- ;; include CWD in shell command prompts
- shell-command-prompt-show-cwd t
- sqlformat-command 'pgformatter
- sql-input-ring-file-name (expand-file-name ".sqli-history" user-emacs-directory)
- ;; use relative line numbers
- display-line-numbers-type 'relative
- ;; persist Git commit message history
- savehist-additional-variables '(log-edit-comment-ring)
- ;; IRC stuff
- rcirc-default-nick "esy"
- rcirc-server-alist '(("irc.libera.chat"
- :channels ("#emacs")
- :port 6697
- :encryption tls))
- rcirc-log-flag t
- ;; use my custom project-prompting function
- project-prompter #'esy/read-project-by-name
- ;; have common bindings initially hidden in the output of C-h b
- describe-bindings-outline-rules `((match-regexp
- .
- ,(regexp-opt
- '("Key translations"
- "Global Bindings:"
- "Function key map translations"
- "pixel-scroll-precision-mode"
- "context-menu-mode")))))
- (setq-default indent-tabs-mode nil))
-
-(esy/init-step theme
- "Load and enable a custom theme."
- ;; (require-theme 'modus-themes)
- ;; (load-theme 'modus-vivendi)
- (require 'ef-themes)
-
- (defvar esy/ef-themes-ring (make-ring 16))
-
- (defun esy/ef-themes-load-previous ()
- (interactive)
- (when (< 0 (ring-length esy/ef-themes-ring))
- (let ((theme (ring-remove esy/ef-themes-ring 0)))
- (message "Loaded `%s'" theme)
- (ef-themes--load-theme theme))))
-
- (defun esy/ef-themes-load-random ()
- (interactive)
- (let* ((theme (ef-themes--current-theme))
- (themes (seq-filter
- (lambda (other-theme)
- (not (equal other-theme theme)))
- '(
- ef-autumn
- ef-bio
- ef-cherie
- ef-cyprus
- ef-dark
- ef-day
- ef-duo-dark
- ef-duo-light
- ef-elea-dark
- ef-elea-light
- ef-frost
- ef-kassio
- ef-light
- ef-maris-dark
- ef-maris-light
- ef-night
- ef-spring
- ef-summer
- ef-symbiosis
- ef-trio-dark
- ef-trio-light
- ef-winter
- )))
- (n (random (length themes)))
- (pick (nth n themes))
- (loaded (if (null pick) (car themes) pick)))
- (ring-insert esy/ef-themes-ring theme)
- (ef-themes--load-theme loaded)
- (message "Loaded `%s'" loaded)))
-
- (esy/ef-themes-load-random))
-
-(esy/init-step fonts
- "Set up the Iosevka font family."
- (when (eq system-type 'darwin)
- (set-face-attribute 'default nil :height 130))
- (set-face-attribute 'default nil :family "Iosevka")
- (set-face-attribute 'fixed-pitch nil :family "Iosevka")
- (set-face-attribute 'variable-pitch nil :family "Iosevka Etoile"))
-
-(esy/init-step load-path
- "Add custom code directory to `load-path'."
- (add-to-list 'load-path (expand-file-name "lisp/" user-emacs-directory))
- (autoload 'some-button "some-button" nil t)
- (autoload 'esy-capture "esy-capture" nil t)
- (autoload 'pdf-view-mode "pdf-view" nil t))
-
-(esy/init-step commands
- "Define custom commands."
- (defvar-keymap transpose-lines-repeat-map
- :doc "Repeat map for \\[transpose-lines]"
- "C-t" #'transpose-lines)
-
- (put 'transpose-lines 'repeat-map 'transpose-lines-repeat-map)
-
- ;; utility commands
- (defun esy/kill-dwim ()
- "When region is active, kill region, otherwise kill last word."
- (interactive)
- (if (region-active-p)
- (let ((beg (region-beginning))
- (end (region-end)))
- (kill-region beg end))
- (let ((end (point)))
- (backward-word)
- (kill-region (point) end))))
-
- (defvar duplicate-line-final-position)
-
- (defun duplicate-line-stay (arg)
- (interactive "p")
- (let ((duplicate-line-final-position 0))
- (duplicate-line arg)))
-
- (defun esy/ttyper ()
- (interactive)
- (eat "ttyper" t))
-
- (defun esy/hut-builds ()
- (interactive)
- (let ((eat-kill-buffer-on-exit nil))
- (eat "hut builds show -f" t)))
-
- (defun esy/pulse-line (&optional _)
- "Pulse current line."
- (interactive)
- (pulse-momentary-highlight-one-line))
-
- (defun esy/seconds-to-date-string (seconds)
- "Decode and display the date corresponding to SECONDS."
- (interactive (list (if (use-region-p)
- (string-to-number (buffer-substring (region-beginning)
- (region-end)))
- (read-number "Unix timestamp: " (round (float-time))))))
- (message (format-time-string "%FT%T%z" (seconds-to-time seconds))))
-
- (defun esy/find-init-file (init)
- "Find the Emacs INIT file."
- (interactive (list user-init-file))
- (find-file init))
-
- (with-eval-after-load 'shell
- (keymap-set shell-mode-map "SPC" #'comint-magic-space))
-
- (defvar-local esy/log-buffer-filename nil
- "File in which the current buffer is logged.")
-
- (defun esy/log-buffer ()
- "Save the current buffer under the ~/logs/ directory."
- (interactive)
- (let ((filename (or esy/log-buffer-filename
- (setq esy/log-buffer-filename
- (expand-file-name
- (concat mode-name
- "_"
- (format-time-string
- "%Y%m%d%H%M%S"
- (current-time))
- ".log")
- "~/logs")))))
- (save-restriction
- (widen)
- (write-region (point-min)
- (point-max)
- filename))))
-
- (with-eval-after-load 'comint
- (keymap-set comint-mode-map "C-c C-q" #'esy/log-buffer))
-
- (defun esy/recent-log-summary ()
- "Display a summary of my website's most recent access log."
- (interactive)
- (async-shell-command (string-join (list "ssh"
- "root@direct.eshelyaron.com"
- (shell-quote-argument
- (string-join '("grep -E '\"GET.+\" 2' /var/log/apache2/access.log"
- "tr -d '\"'"
- "tr -d \"'\""
- "cut -f 7,11,12 -d ' '"
- "sort"
- "uniq -c"
- "sort -n")
- " | ")))
- " ")
- "*Recent Log Summary*"))
-
- (defun esy/access-log-summary ()
- "Display a summary of my website's access log."
- (interactive)
- (let ((default-directory "/ssh:root@direct.eshelyaron.com:/var/log/apache2/"))
- (async-shell-command (string-join '("zcat access.log.*.gz"
- "cat - access.log access.log.1"
- "grep -E '\"GET.+\" 2'"
- "tr -d '\"'"
- "tr -d \"'\""
- "cut -f 7,11,12 -d ' '"
- "sort"
- "uniq -c"
- "sort -n")
- " | ")
- "*Access Log Summary*")))
-
- (defun esy/clone (remote)
- (interactive (list (let ((default (thing-at-point 'url)))
- (read-string (format-prompt "Clone" default)
- nil nil default))))
- (let ((dir (expand-file-name
- (file-name-base (car (url-path-and-query
- (url-generic-parse-url remote))))
- "~/checkouts")))
- (vc-clone remote 'Git dir)
- (find-file dir)))
-
- (defun esy/json-path-to-position (pos)
- "Return the JSON path from the document's root to the element at POS.
+ ;; simpler message
+ gnus-no-groups-message "No new articles"
+ ;; don't close other windows when opening Gnus
+ gnus-use-full-window nil
+ ;; make Gnus handle this content types
+ gnus-article-treat-types '("text/plain"
+ "text/x-verbatim"
+ "text/x-patch"
+ "text/html"
+ "text/calendar")
+ ;; capture calendar events from Gnus to my Org inbox file
+ gnus-icalendar-org-capture-file "~/org/inbox.org"
+ ;; put calendar events under the Calendar heading
+ gnus-icalendar-org-capture-headline '("Calendar")
+ ;; use ISO format for calendar dates
+ calendar-date-style 'iso
+ ;; maintain legibility of rendered text in HTML mails
+ shr-color-visible-luminance-min 75
+ ;; my Mastodon settings
+ mastodon-instance-url "https://emacs.ch"
+ mastodon-active-user "eshel"
+ ;; direct Magit to my Git checkouts directory
+ magit-repository-directories '(("~/checkouts/" . 1))
+ ;; have Dired operations target another visible Dired buffer
+ dired-dwim-target t
+ vterm-max-scrollback 2048
+ vterm-kill-buffer-on-exit nil
+ vterm-use-vterm-prompt-detection-method t
+ ;; use MPV with EMMS
+ emms-player-list '(emms-player-mpv)
+ ;; free-style numbering plan
+ bbdb-phone-style nil
+ ;; allow cycling through mail address completion candidate
+ bbdb-complete-mail-allow-cycling t
+ ;; don't pop up BBDB records after completing mail addresses
+ bbdb-completion-display-record nil
+ eww-auto-rename-buffer 'title
+ browse-url-browser-function #'eww-browse-url
+ browse-url-generic-program "open"
+ global-auto-revert-non-file-buffers t
+ auto-revert-verbose nil
+ query-about-changed-file t
+ ;; show flymake diagnostics as overlays at eol
+ ;; flymake-show-diagnostics-at-end-of-line t
+ kill-do-not-save-duplicates t
+ show-trailing-whitespace t
+ read-extended-command-predicate #'command-completion-default-include-p
+ completions-format 'one-column
+ completion-auto-select nil
+ completion-styles '(orderless partial-completion basic)
+ completion-show-help nil
+ ;; completions-header-format nil
+ ;; completion-auto-help 'visible
+ completions-max-height 16
+ completion-auto-wrap t
+ corfu-cycle t
+ corfu-indexed-start 1
+ shell-kill-buffer-on-exit t
+ ;; allow disabling confirming before compilation via local variables
+ safe-local-variable-values '((compilation-read-command . nil))
+ ;; TeX-view-program-selection '((output-pdf "PDF Tools"))
+ ;; TeX-source-correlate-start-server t
+ xref-show-definitions-function #'consult-xref
+ xref-show-xrefs-function #'consult-xref
+ xref-search-program 'ripgrep
+ ;; include CWD in shell command prompts
+ shell-command-prompt-show-cwd t
+ sqlformat-command 'pgformatter
+ sql-input-ring-file-name (expand-file-name ".sqli-history" user-emacs-directory)
+ ;; use relative line numbers
+ display-line-numbers-type 'relative
+ ;; persist Git commit message history
+ savehist-additional-variables '(log-edit-comment-ring)
+ ;; IRC stuff
+ rcirc-default-nick "esy"
+ rcirc-server-alist '(("irc.libera.chat"
+ :channels ("#emacs")
+ :port 6697
+ :encryption tls))
+ rcirc-log-flag t
+ ;; use my custom project-prompting function
+ project-prompter #'esy/read-project-by-name
+ ;; have common bindings initially hidden in the output of C-h b
+ describe-bindings-outline-rules `((match-regexp
+ .
+ ,(regexp-opt
+ '("Key translations"
+ "Global Bindings:"
+ "Function key map translations"
+ "pixel-scroll-precision-mode"
+ "context-menu-mode")))))
+(setq-default indent-tabs-mode nil)
+
+;;; Load and enable a custom theme
+;; (require-theme 'modus-themes)
+;; (load-theme 'modus-vivendi)
+(require 'ef-themes)
+
+(defvar esy/ef-themes-ring (make-ring 16))
+
+(defun esy/ef-themes-load-previous ()
+ (interactive)
+ (when (< 0 (ring-length esy/ef-themes-ring))
+ (let ((theme (ring-remove esy/ef-themes-ring 0)))
+ (message "Loaded `%s'" theme)
+ (ef-themes--load-theme theme))))
+
+(defun esy/ef-themes-load-random ()
+ (interactive)
+ (let* ((theme (ef-themes--current-theme))
+ (themes (seq-filter
+ (lambda (other-theme)
+ (not (equal other-theme theme)))
+ '(
+ ef-autumn
+ ef-bio
+ ef-cherie
+ ef-cyprus
+ ef-dark
+ ef-day
+ ef-duo-dark
+ ef-duo-light
+ ef-elea-dark
+ ef-elea-light
+ ef-frost
+ ef-kassio
+ ef-light
+ ef-maris-dark
+ ef-maris-light
+ ef-night
+ ef-spring
+ ef-summer
+ ef-symbiosis
+ ef-trio-dark
+ ef-trio-light
+ ef-winter
+ )))
+ (n (random (length themes)))
+ (pick (nth n themes))
+ (loaded (if (null pick) (car themes) pick)))
+ (ring-insert esy/ef-themes-ring theme)
+ (ef-themes--load-theme loaded)
+ (message "Loaded `%s'" loaded)))
+
+(esy/ef-themes-load-random)
+
+;;; Set up the Iosevka font family
+(when (eq system-type 'darwin)
+ (set-face-attribute 'default nil :height 130))
+(set-face-attribute 'default nil :family "Iosevka")
+(set-face-attribute 'fixed-pitch nil :family "Iosevka")
+(set-face-attribute 'variable-pitch nil :family "Iosevka Etoile")
+
+;;; Add custom code directory to `load-path'
+(add-to-list 'load-path (expand-file-name "lisp/" user-emacs-directory))
+(autoload 'some-button "some-button" nil t)
+(autoload 'esy-capture "esy-capture" nil t)
+(autoload 'pdf-view-mode "pdf-view" nil t)
+
+;;; Define custom commands
+(defvar-keymap transpose-lines-repeat-map
+ :doc "Repeat map for \\[transpose-lines]"
+ "C-t" #'transpose-lines)
+
+(put 'transpose-lines 'repeat-map 'transpose-lines-repeat-map)
+
+;; utility commands
+(defun esy/kill-dwim ()
+ "When region is active, kill region, otherwise kill last word."
+ (interactive)
+ (if (region-active-p)
+ (let ((beg (region-beginning))
+ (end (region-end)))
+ (kill-region beg end))
+ (let ((end (point)))
+ (backward-word)
+ (kill-region (point) end))))
+
+(defvar duplicate-line-final-position)
+
+(defun duplicate-line-stay (arg)
+ (interactive "p")
+ (let ((duplicate-line-final-position 0))
+ (duplicate-line arg)))
+
+(defun esy/ttyper ()
+ (interactive)
+ (eat "ttyper" t))
+
+(defun esy/hut-builds ()
+ (interactive)
+ (let ((eat-kill-buffer-on-exit nil))
+ (eat "hut builds show -f" t)))
+
+(defun esy/pulse-line (&optional _)
+ "Pulse current line."
+ (interactive)
+ (pulse-momentary-highlight-one-line))
+
+(defun esy/seconds-to-date-string (seconds)
+ "Decode and display the date corresponding to SECONDS."
+ (interactive (list (if (use-region-p)
+ (string-to-number (buffer-substring (region-beginning)
+ (region-end)))
+ (read-number "Unix timestamp: " (round (float-time))))))
+ (message (format-time-string "%FT%T%z" (seconds-to-time seconds))))
+
+(defun esy/find-init-file (init)
+ "Find the Emacs INIT file."
+ (interactive (list user-init-file))
+ (find-file init))
+
+(with-eval-after-load 'shell
+ (keymap-set shell-mode-map "SPC" #'comint-magic-space))
+
+(defvar-local esy/log-buffer-filename nil
+ "File in which the current buffer is logged.")
+
+(defun esy/log-buffer ()
+ "Save the current buffer under the ~/logs/ directory."
+ (interactive)
+ (let ((filename (or esy/log-buffer-filename
+ (setq esy/log-buffer-filename
+ (expand-file-name
+ (concat mode-name
+ "_"
+ (format-time-string
+ "%Y%m%d%H%M%S"
+ (current-time))
+ ".log")
+ "~/logs")))))
+ (save-restriction
+ (widen)
+ (write-region (point-min)
+ (point-max)
+ filename))))
+
+(with-eval-after-load 'comint
+ (keymap-set comint-mode-map "C-c C-q" #'esy/log-buffer))
+
+(defun esy/recent-log-summary ()
+ "Display a summary of my website's most recent access log."
+ (interactive)
+ (async-shell-command (string-join (list "ssh"
+ "root@direct.eshelyaron.com"
+ (shell-quote-argument
+ (string-join '("grep -E '\"GET.+\" 2' /var/log/apache2/access.log"
+ "tr -d '\"'"
+ "tr -d \"'\""
+ "cut -f 7,11,12 -d ' '"
+ "sort"
+ "uniq -c"
+ "sort -n")
+ " | ")))
+ " ")
+ "*Recent Log Summary*"))
+
+(defun esy/access-log-summary ()
+ "Display a summary of my website's access log."
+ (interactive)
+ (let ((default-directory "/ssh:root@direct.eshelyaron.com:/var/log/apache2/"))
+ (async-shell-command (string-join '("zcat access.log.*.gz"
+ "cat - access.log access.log.1"
+ "grep -E '\"GET.+\" 2'"
+ "tr -d '\"'"
+ "tr -d \"'\""
+ "cut -f 7,11,12 -d ' '"
+ "sort"
+ "uniq -c"
+ "sort -n")
+ " | ")
+ "*Access Log Summary*")))
+
+(defun esy/clone (remote)
+ (interactive (list (let ((default (thing-at-point 'url)))
+ (read-string (format-prompt "Clone" default)
+ nil nil default))))
+ (let ((dir (expand-file-name
+ (file-name-base (car (url-path-and-query
+ (url-generic-parse-url remote))))
+ "~/checkouts")))
+ (vc-clone remote 'Git dir)
+ (find-file dir)))
+
+(defun esy/json-path-to-position (pos)
+ "Return the JSON path from the document's root to the element at POS.
The path is represented as a list of strings and integers,
corresponding to the object keys and array indices that lead from
the root to the element at POS."
- (named-let loop ((node (treesit-node-at pos)) (acc nil))
- (if-let ((parent (treesit-parent-until
- node
- (lambda (n)
- (member (treesit-node-type n)
- '("pair" "array"))))))
- (loop parent
- (cons
- (pcase (treesit-node-type parent)
- ("pair"
- (treesit-node-text
- (treesit-node-child (treesit-node-child parent 0) 1) t))
- ("array"
- (named-let check ((i 1))
- (if (< pos (treesit-node-end (treesit-node-child parent i)))
- (/ (1- i) 2)
- (check (+ i 2))))))
- acc))
- acc)))
-
- (defun esy/json-path-at-point (point &optional kill)
- "Display the JSON path at POINT. When KILL is non-nil, kill it too.
+ (named-let loop ((node (treesit-node-at pos)) (acc nil))
+ (if-let ((parent (treesit-parent-until
+ node
+ (lambda (n)
+ (member (treesit-node-type n)
+ '("pair" "array"))))))
+ (loop parent
+ (cons
+ (pcase (treesit-node-type parent)
+ ("pair"
+ (treesit-node-text
+ (treesit-node-child (treesit-node-child parent 0) 1) t))
+ ("array"
+ (named-let check ((i 1))
+ (if (< pos (treesit-node-end (treesit-node-child parent i)))
+ (/ (1- i) 2)
+ (check (+ i 2))))))
+ acc))
+ acc)))
+
+(defun esy/json-path-at-point (point &optional kill)
+ "Display the JSON path at POINT. When KILL is non-nil, kill it too.
Interactively, POINT is point and KILL is the prefix argument."
- (interactive "d\nP" json-ts-mode)
- (let ((path (mapconcat (lambda (o) (format "%s" o))
- (esy/json-path-to-position point)
- ".")))
- (if kill
- (progn (kill-new path) (message "Copied: %s" path))
- (message path))
- path))
-
- (defun esy/transcribe ()
- (interactive)
- (message "Recording...")
- (let ((process
- (start-process "ffmpeg" nil "ffmpeg"
- "-f" "avfoundation"
- "-i" "1:0"
- "-ar" "16000"
- "-y"
- "/tmp/foo.wav")))
- (set-transient-map
- (make-sparse-keymap) nil
- (lambda ()
- (message "Stopping recording")
- (interrupt-process process)
- (accept-process-output process 1 nil t)
- (message "Transcribing...")
- (message
- (string-trim-right
- (string-trim-left
- (with-temp-buffer
- (call-process "whisper"
- nil '(t nil) nil
- "-m"
- "/Users/eshelyaron/checkouts/whisper.cpp/models/ggml-base.en.bin"
- "--no-timestamps" "-f" "/tmp/foo.wav")
- (goto-char (point-min))
- (while (search-forward "[BLANK_AUDIO]" nil t)
- (replace-match "" nil t))
- (buffer-string))))))
- "Recording... Press any key to stop")))
-
- (defun esy/record (timeout)
- (interactive "p")
- (message "Recording...")
- (call-process "ffmpeg"
- nil nil nil
- "-f" "avfoundation"
- "-i" "1:0" "-t"
- (number-to-string (max timeout 2))
- "-ar" "16000" "-y" "/tmp/foo.wav")
- (openai-chat (string-trim-right
- (string-trim-left
- (with-temp-buffer
- (call-process "whisper"
- nil '(t nil) nil
- "-m"
- "/Users/eshelyaron/checkouts/whisper.cpp/models/ggml-base.en.bin"
- "--no-timestamps" "-f" "/tmp/foo.wav")
- (goto-char (point-min))
- (while (search-forward "[BLANK_AUDIO]" nil t)
- (replace-match "" nil t))
- (buffer-string))))))
-
- (defun esy/dedicate-window (window flag)
- (interactive (list (get-buffer-window) (not current-prefix-arg)))
- (message "Window is %s dedicated to buffer %s."
- (if flag (if (window-dedicated-p) "already" "now") "no longer")
- (buffer-name))
- (set-window-dedicated-p window flag))
-
- (defun esy/bump (tag-prefix)
- (interactive (list "v"))
- (require 'lisp-mnt)
- (require 'magit-apply)
- (let ((date (format-time-string "%F" (current-time)))
- (current-version (save-excursion (lm-header "package-version"))))
- (unless current-version
- (user-error "No Package-Version Elisp header found"))
- (let ((next-version (mapconcat #'number-to-string
- (pcase (version-to-list
- current-version)
- (`(,major ,minor ,patch)
- (list major (1+ minor) patch)))
- ".")))
- (with-current-buffer (find-file "NEWS.org")
- (goto-char (point-min))
- (re-search-forward (concat "^\\(" org-outline-regexp "\\)") nil t)
- (beginning-of-line)
- (if (looking-at (rx "* Version "
- (group-n 1
- (+ digit) "."
- (+ digit) "."
- (+ digit))
- " in development"))
- (progn
- (setq next-version (match-string-no-properties 1))
- (end-of-line)
- (delete-char -14)
- (insert "on " date)
- (forward-char 2))
- (insert "* Version " next-version " on " date "\n\n")))
- (lm-header "package-version")
- (delete-region (point) (pos-eol))
- (insert next-version)
- (vc-print-root-log)
- (find-file-other-window "NEWS.org")
- (named-let loop ()
- (recursive-edit)
- (unless (y-or-n-p "OK to stage, commit and tag the new version?")
- (loop)))
- (unless (= (call-process "git" nil nil nil
- "add" (buffer-file-name) "NEWS.org")
- 0)
- (error "Git add failed"))
- (unless (= (call-process "git" nil nil nil
- "commit" "-m"
- (concat "Announce recent changes "
- "in NEWS.org "
- "and bump version to "
- next-version))
- 0)
- (error "Git commit failed"))
- (unless (= (call-process "git" nil nil nil
- "tag" "-s" "-m"
- (concat "Release version "
- next-version)
- (concat tag-prefix next-version))
- 0)
- (error "Git tag failed"))))))
-
-(esy/init-step auto-exec-permissions
- "Ensure scripts ran with `executable-interpret' are executable."
- (with-eval-after-load 'executable
- (define-advice executable-interpret (:before (&rest _) ensure-executable)
- (unless (file-exists-p buffer-file-name)
- (basic-save-buffer))
- (executable-make-buffer-file-executable-if-script-p))))
-
-(esy/init-step pulse-on-window-selection-change
- "Pulse the line around point after switching windows."
-
- (defun esy/pulse-on-window-selection-change (&rest _)
- (pulse-momentary-highlight-one-line))
-
- (add-hook 'window-selection-change-functions
- #'esy/pulse-on-window-selection-change))
-
-(esy/init-step startup-message
- "Override the default startup message."
- (define-advice startup-echo-area-message (:override () report-init-time)
- (format "%s started in %s. Hack away."
- (propertize "Emacs" 'face 'success)
- (propertize (emacs-init-time) 'face 'error ))))
-
-(esy/init-step packages
- "Ensure external packages are installed."
- (package-install-selected-packages))
-
-(esy/init-step prog-mode-hook
- "Extend standard programming mode hooks."
- (dolist (mode '(bug-reference-prog-mode
- display-fill-column-indicator-mode
- display-line-numbers-mode
- flymake-mode
- flyspell-prog-mode
- rainbow-delimiters-mode))
- (add-hook 'prog-mode-hook mode))
- (add-hook 'lisp-data-mode-hook #'paredit-mode))
-
-(esy/init-step other-hooks
- "Extend other standard hooks."
- (add-hook 'text-mode-hook #'flyspell-mode))
-
-(esy/init-step bindings
- "Bind some keys."
- (keymap-global-set "C-c c" #'org-capture)
- (keymap-global-set "C-c l" #'org-store-link)
- (keymap-global-set "C-c a" #'org-agenda)
- (keymap-global-set "C-c v" #'vterm-other-window)
- (keymap-global-set "C-c S" #'scratch-buffer)
- (keymap-global-set "C-c m" #'esy/emms-map)
- (keymap-global-set "C-c !" #'consult-flymake)
- (keymap-global-set "C-c C" #'esy-publish-create-post)
- (keymap-global-set "C-c O" #'openai-chat)
- (keymap-global-set "C-c E" #'elfeed)
- (keymap-global-set "C-c T" #'esy/ttyper)
- (keymap-global-set "C-c R" #'esy/record)
- (keymap-global-set "C-c G" #'gnus)
- (keymap-global-set "C-c M" #'mastodon)
- (keymap-global-set "C-c F" #'esy/find-init-file)
- (keymap-global-set "C-c P" #'list-packages)
- (keymap-global-set "C-c SPC" #'consult-mark)
- (keymap-global-set "C-c 1" #'delete-other-windows)
- (keymap-global-set "C-c 2" #'split-window-below)
- (keymap-global-set "C-c 3" #'split-window-right)
- (keymap-global-set "<remap> <kill-region>" #'esy/kill-dwim)
- (keymap-global-set "<remap> <goto-line>" #'consult-goto-line)
- (keymap-global-set "<remap> <suspend-frame>" #'zap-up-to-char)
- (keymap-global-set "<remap> <imenu>" #'consult-imenu)
- (keymap-global-set "s-n" #'duplicate-line)
- (keymap-global-set "s-p" #'duplicate-line-stay)
- (keymap-global-set "s-u" #'universal-argument)
- (keymap-global-set "s--" #'negative-argument)
- (keymap-global-set "M-#" #'dictionary-search)
- (keymap-global-set "M-o" #'previous-buffer)
- (keymap-global-set "M-O" #'next-buffer)
- (keymap-global-set "C-," #'backward-delete-char)
- (keymap-global-set "C-." #'embark-act)
- (keymap-global-set "C-;" #'avy-goto-char-timer)
- (keymap-global-set "C-s-f" #'toggle-frame-fullscreen)
- (keymap-global-set "C-s-l" #'esy/pulse-line)
- (keymap-global-set "\"" #'insert-pair)
- (keymap-global-set "<f5>" #'esy/ef-themes-load-random)
- (keymap-global-set "s-]" #'esy/ef-themes-load-random)
- (keymap-global-set "s-[" #'esy/ef-themes-load-previous)
-
- (keymap-set ctl-x-map "b" #'consult-buffer)
- (keymap-set ctl-x-4-map "b" #'consult-buffer-other-window)
- (keymap-set ctl-x-5-map "b" #'consult-buffer-other-frame)
- (keymap-set ctl-x-map "r b" #'consult-bookmark)
- (keymap-set ctl-x-map "C-b" #'ibuffer)
-
- (keymap-set search-map "r" #'rg)
- (keymap-set search-map "l" #'rg-literal)
- (keymap-set search-map "b" #'some-button)
-
- (keymap-set window-prefix-map "p" #'windmove-swap-states-up)
- (keymap-set window-prefix-map "n" #'windmove-swap-states-down)
- (keymap-set window-prefix-map "a" #'windmove-swap-states-left)
- (keymap-set window-prefix-map "e" #'windmove-swap-states-right)
- (keymap-set window-prefix-map "d" #'esy/dedicate-window)
-
- (dolist (command '(windmove-swap-states-up
- windmove-swap-states-down
- windmove-swap-states-left
- windmove-swap-states-right))
- (put command 'repeat-map 'window-prefix-map))
-
- ;; digit arguments with the super modifier
- (dotimes (i 10)
- (keymap-global-set (concat "s-" (number-to-string i))
- #'digit-argument))
-
- ;; unbind some keys
- (dolist (key '("s-a" "s-d" "s-e" "s-f" "s-g" "s-h" "s-j" "s-k" "s-l"
- "s-m" "s-o" "s-q" "s-s" "s-t" "s-w" "s-x" "s-y" "s-z"))
- (keymap-global-unset key))
-
- ;; enable some commands
- (dolist (command '(set-goal-column
- narrow-to-region
- narrow-to-page
- downcase-region
- upcase-region))
- (put command 'disabled nil))
-
- ;; disable some commands
- (put 'suspend-frame 'disabled t))
-
-(esy/init-step project
- "Configure project management commands."
- (with-eval-after-load 'project
- (define-advice project--find-in-directory (:override (dir) no-remote-projects)
- (unless (file-remote-p dir)
- (run-hook-with-args-until-success 'project-find-functions dir)))
- (add-to-list 'project-switch-commands '(project-compile "Compile"))
- (add-to-list 'project-switch-commands '(rg-project "rg"))
- (add-to-list 'project-switch-commands '(magit-project-status "Magit"))
- (add-to-list 'project-switch-commands '(project-shell "Shell"))
- (when (boundp 'project-prefix-map)
- (define-key project-prefix-map "R" #'rg-project)
- (define-key project-prefix-map "m" #'magit-project-status))
-
- (defvar esy/project-name-history nil)
-
- (defvar esy/projects-directory "~/checkouts/")
-
- (defun esy/read-project-by-name ()
- "Read a project name and return its root directory.
+ (interactive "d\nP" json-ts-mode)
+ (let ((path (mapconcat (lambda (o) (format "%s" o))
+ (esy/json-path-to-position point)
+ ".")))
+ (if kill
+ (progn (kill-new path) (message "Copied: %s" path))
+ (message path))
+ path))
+
+(defun esy/transcribe ()
+ (interactive)
+ (message "Recording...")
+ (let ((process
+ (start-process "ffmpeg" nil "ffmpeg"
+ "-f" "avfoundation"
+ "-i" "1:0"
+ "-ar" "16000"
+ "-y"
+ "/tmp/foo.wav")))
+ (set-transient-map
+ (make-sparse-keymap) nil
+ (lambda ()
+ (message "Stopping recording")
+ (interrupt-process process)
+ (accept-process-output process 1 nil t)
+ (message "Transcribing...")
+ (message
+ (string-trim-right
+ (string-trim-left
+ (with-temp-buffer
+ (call-process "whisper"
+ nil '(t nil) nil
+ "-m"
+ "/Users/eshelyaron/checkouts/whisper.cpp/models/ggml-base.en.bin"
+ "--no-timestamps" "-f" "/tmp/foo.wav")
+ (goto-char (point-min))
+ (while (search-forward "[BLANK_AUDIO]" nil t)
+ (replace-match "" nil t))
+ (buffer-string))))))
+ "Recording... Press any key to stop")))
+
+(defun esy/record (timeout)
+ (interactive "p")
+ (message "Recording...")
+ (call-process "ffmpeg"
+ nil nil nil
+ "-f" "avfoundation"
+ "-i" "1:0" "-t"
+ (number-to-string (max timeout 2))
+ "-ar" "16000" "-y" "/tmp/foo.wav")
+ (openai-chat (string-trim-right
+ (string-trim-left
+ (with-temp-buffer
+ (call-process "whisper"
+ nil '(t nil) nil
+ "-m"
+ "/Users/eshelyaron/checkouts/whisper.cpp/models/ggml-base.en.bin"
+ "--no-timestamps" "-f" "/tmp/foo.wav")
+ (goto-char (point-min))
+ (while (search-forward "[BLANK_AUDIO]" nil t)
+ (replace-match "" nil t))
+ (buffer-string))))))
+
+(defun esy/dedicate-window (window flag)
+ (interactive (list (get-buffer-window) (not current-prefix-arg)))
+ (message "Window is %s dedicated to buffer %s."
+ (if flag (if (window-dedicated-p) "already" "now") "no longer")
+ (buffer-name))
+ (set-window-dedicated-p window flag))
+
+(defun esy/bump (tag-prefix)
+ (interactive (list "v"))
+ (require 'lisp-mnt)
+ (require 'magit-apply)
+ (let ((date (format-time-string "%F" (current-time)))
+ (current-version (save-excursion (lm-header "package-version"))))
+ (unless current-version
+ (user-error "No Package-Version Elisp header found"))
+ (let ((next-version (mapconcat #'number-to-string
+ (pcase (version-to-list
+ current-version)
+ (`(,major ,minor ,patch)
+ (list major (1+ minor) patch)))
+ ".")))
+ (with-current-buffer (find-file "NEWS.org")
+ (goto-char (point-min))
+ (re-search-forward (concat "^\\(" org-outline-regexp "\\)") nil t)
+ (beginning-of-line)
+ (if (looking-at (rx "* Version "
+ (group-n 1
+ (+ digit) "."
+ (+ digit) "."
+ (+ digit))
+ " in development"))
+ (progn
+ (setq next-version (match-string-no-properties 1))
+ (end-of-line)
+ (delete-char -14)
+ (insert "on " date)
+ (forward-char 2))
+ (insert "* Version " next-version " on " date "\n\n")))
+ (lm-header "package-version")
+ (delete-region (point) (pos-eol))
+ (insert next-version)
+ (vc-print-root-log)
+ (find-file-other-window "NEWS.org")
+ (named-let loop ()
+ (recursive-edit)
+ (unless (y-or-n-p "OK to stage, commit and tag the new version?")
+ (loop)))
+ (unless (= (call-process "git" nil nil nil
+ "add" (buffer-file-name) "NEWS.org")
+ 0)
+ (error "Git add failed"))
+ (unless (= (call-process "git" nil nil nil
+ "commit" "-m"
+ (concat "Announce recent changes "
+ "in NEWS.org "
+ "and bump version to "
+ next-version))
+ 0)
+ (error "Git commit failed"))
+ (unless (= (call-process "git" nil nil nil
+ "tag" "-s" "-m"
+ (concat "Release version "
+ next-version)
+ (concat tag-prefix next-version))
+ 0)
+ (error "Git tag failed")))))
+
+;;; Ensure scripts ran with `executable-interpret' are executable
+(with-eval-after-load 'executable
+ (define-advice executable-interpret (:before (&rest _) ensure-executable)
+ (unless (file-exists-p buffer-file-name)
+ (basic-save-buffer))
+ (executable-make-buffer-file-executable-if-script-p)))
+
+;;; Pulse the line around point after switching windows
+
+(defun esy/pulse-on-window-selection-change (&rest _)
+ (pulse-momentary-highlight-one-line))
+
+(add-hook 'window-selection-change-functions
+ #'esy/pulse-on-window-selection-change)
+
+;;; Override the default startup message
+(define-advice startup-echo-area-message (:override () report-init-time)
+ (format "%s started in %s. Hack away."
+ (propertize "Emacs" 'face 'success)
+ (propertize (emacs-init-time) 'face 'error )))
+
+;;; Ensure external packages are installed
+(package-install-selected-packages)
+
+;;; Extend standard programming mode hooks
+(dolist (mode '(bug-reference-prog-mode
+ display-fill-column-indicator-mode
+ display-line-numbers-mode
+ flymake-mode
+ flyspell-prog-mode
+ rainbow-delimiters-mode))
+ (add-hook 'prog-mode-hook mode))
+(add-hook 'lisp-data-mode-hook #'paredit-mode)
+
+;;; Extend other standard hooks
+(add-hook 'text-mode-hook #'flyspell-mode)
+
+;;; Bind some keys
+(keymap-global-set "C-c c" #'org-capture)
+(keymap-global-set "C-c l" #'org-store-link)
+(keymap-global-set "C-c a" #'org-agenda)
+(keymap-global-set "C-c v" #'vterm-other-window)
+(keymap-global-set "C-c S" #'scratch-buffer)
+(keymap-global-set "C-c m" #'esy/emms-map)
+(keymap-global-set "C-c !" #'consult-flymake)
+(keymap-global-set "C-c C" #'esy-publish-create-post)
+(keymap-global-set "C-c O" #'openai-chat)
+(keymap-global-set "C-c E" #'elfeed)
+(keymap-global-set "C-c T" #'esy/ttyper)
+(keymap-global-set "C-c R" #'esy/record)
+(keymap-global-set "C-c G" #'gnus)
+(keymap-global-set "C-c M" #'mastodon)
+(keymap-global-set "C-c F" #'esy/find-init-file)
+(keymap-global-set "C-c P" #'list-packages)
+(keymap-global-set "C-c SPC" #'consult-mark)
+(keymap-global-set "C-c 1" #'delete-other-windows)
+(keymap-global-set "C-c 2" #'split-window-below)
+(keymap-global-set "C-c 3" #'split-window-right)
+(keymap-global-set "<remap> <kill-region>" #'esy/kill-dwim)
+(keymap-global-set "<remap> <goto-line>" #'consult-goto-line)
+(keymap-global-set "<remap> <suspend-frame>" #'zap-up-to-char)
+(keymap-global-set "<remap> <imenu>" #'consult-imenu)
+(keymap-global-set "s-n" #'duplicate-line)
+(keymap-global-set "s-p" #'duplicate-line-stay)
+(keymap-global-set "s-u" #'universal-argument)
+(keymap-global-set "s--" #'negative-argument)
+(keymap-global-set "M-#" #'dictionary-search)
+(keymap-global-set "M-o" #'previous-buffer)
+(keymap-global-set "M-O" #'next-buffer)
+(keymap-global-set "C-," #'backward-delete-char)
+(keymap-global-set "C-." #'embark-act)
+(keymap-global-set "C-;" #'avy-goto-char-timer)
+(keymap-global-set "C-s-f" #'toggle-frame-fullscreen)
+(keymap-global-set "C-s-l" #'esy/pulse-line)
+(keymap-global-set "\"" #'insert-pair)
+(keymap-global-set "<f5>" #'esy/ef-themes-load-random)
+(keymap-global-set "s-]" #'esy/ef-themes-load-random)
+(keymap-global-set "s-[" #'esy/ef-themes-load-previous)
+
+(keymap-set ctl-x-map "b" #'consult-buffer)
+(keymap-set ctl-x-4-map "b" #'consult-buffer-other-window)
+(keymap-set ctl-x-5-map "b" #'consult-buffer-other-frame)
+(keymap-set ctl-x-map "r b" #'consult-bookmark)
+(keymap-set ctl-x-map "C-b" #'ibuffer)
+
+(keymap-set search-map "r" #'rg)
+(keymap-set search-map "l" #'rg-literal)
+(keymap-set search-map "b" #'some-button)
+
+(keymap-set window-prefix-map "p" #'windmove-swap-states-up)
+(keymap-set window-prefix-map "n" #'windmove-swap-states-down)
+(keymap-set window-prefix-map "a" #'windmove-swap-states-left)
+(keymap-set window-prefix-map "e" #'windmove-swap-states-right)
+(keymap-set window-prefix-map "d" #'esy/dedicate-window)
+
+(dolist (command '(windmove-swap-states-up
+ windmove-swap-states-down
+ windmove-swap-states-left
+ windmove-swap-states-right))
+ (put command 'repeat-map 'window-prefix-map))
+
+;; digit arguments with the super modifier
+(dotimes (i 10)
+ (keymap-global-set (concat "s-" (number-to-string i))
+ #'digit-argument))
+
+;; unbind some keys
+(dolist (key '("s-a" "s-d" "s-e" "s-f" "s-g" "s-h" "s-j" "s-k" "s-l"
+ "s-m" "s-o" "s-q" "s-s" "s-t" "s-w" "s-x" "s-y" "s-z"))
+ (keymap-global-unset key))
+
+;; enable some commands
+(dolist (command '(set-goal-column
+ narrow-to-region
+ narrow-to-page
+ downcase-region
+ upcase-region))
+ (put command 'disabled nil))
+
+;; disable some commands
+(put 'suspend-frame 'disabled t)
+
+;;; Configure project management commands
+(with-eval-after-load 'project
+ (define-advice project--find-in-directory (:override (dir) no-remote-projects)
+ (unless (file-remote-p dir)
+ (run-hook-with-args-until-success 'project-find-functions dir)))
+ (add-to-list 'project-switch-commands '(project-compile "Compile"))
+ (add-to-list 'project-switch-commands '(rg-project "rg"))
+ (add-to-list 'project-switch-commands '(magit-project-status "Magit"))
+ (add-to-list 'project-switch-commands '(project-shell "Shell"))
+ (when (boundp 'project-prefix-map)
+ (define-key project-prefix-map "R" #'rg-project)
+ (define-key project-prefix-map "m" #'magit-project-status))
+
+ (defvar esy/project-name-history nil)
+
+ (defvar esy/projects-directory "~/checkouts/")
+
+ (defun esy/read-project-by-name ()
+ "Read a project name and return its root directory.
If no known project matches the selected name, prompt for a
sub-directory of `esy/projects-directory' using the selected name
as the initial input for completion, and return that directory."
- (let* ((name-dir-alist
- (delete
- nil
- (mapcar (lambda (dir)
- (when-let ((proj (project-current nil dir)))
- (cons (project-name proj) dir)))
- (project-known-project-roots))))
- (current (project-current))
- (default (and current (project-name current)))
- (name (completing-read (format-prompt "Project" default)
- name-dir-alist
- nil nil nil
- 'esy/project-name-history
- default)))
- (or (alist-get name name-dir-alist nil nil #'string=)
- (let* ((dir (read-directory-name "Project root directory: "
- esy/projects-directory
- nil t name))
- (project (project-current nil dir)))
- (when project (project-remember-project project))
- dir))))))
-
-(esy/init-step sql
- "Configure SQL connections."
- (with-eval-after-load 'sql
- (defun esy/update-sql-connection-alist ()
- (interactive)
- (auth-source-forget-all-cached)
- (setq sql-connection-alist (delete nil
- (mapcar (lambda (source)
- (pcase (split-string (plist-get source :host)
- (rx "^"))
- (`(,con ,db ,host)
- (list (intern con)
- (list 'sql-product ''postgres)
- (list 'sql-user (plist-get source :user))
- (list 'sql-port (string-to-number (plist-get source :port)))
- (list 'sql-password (funcall (plist-get source :secret)))
- (list 'sql-server host)
- (list 'sql-database db)))))
- (auth-source-search :port 5432 :max 10)))))
- (esy/update-sql-connection-alist)
- (add-hook 'sql-interactive-mode-hook #'toggle-truncate-lines)
- (add-hook 'sql-interactive-mode-hook #'abbrev-mode)
- (define-key sql-mode-map (kbd "C-c C-f") #'sqlformat)
- (define-advice sql-comint-postgres (:around (fun &rest args) use-sql-password)
- (let ((process-environment
- (nconc
- (list (format "PGPASSWORD=%s" sql-password))
- process-environment)))
- (apply fun args)))))
-
-(esy/init-step org
- "Configure Org mode."
- (with-eval-after-load 'org
- (keymap-unset org-mode-map "C-," t)
- (esy-publish-setup))
-
- (with-eval-after-load 'org-agenda
- (add-to-list 'org-agenda-custom-commands
- '("w" "Work TODOs" tags-todo "+work"))
- (add-to-list 'org-agenda-custom-commands
- '("h" "Holland TODOs" tags-todo "+holland"))))
-
-(esy/init-step email
- "Configure email via `gnus'."
- (with-eval-after-load 'gnus
- (require 'gnus-icalendar)
- (add-hook 'gnus-group-mode-hook #'gnus-topic-mode)
- (gnus-icalendar-setup)
- (gnus-icalendar-org-setup)
- (bbdb-initialize 'gnus 'mail 'message)))
-
-(esy/init-step dired
- "Configure `dired'."
- (with-eval-after-load 'dired
- (put 'dired-find-alternate-file 'disabled nil)))
-
-(esy/init-step tramp
- "Configure remote access via `tramp'."
- (with-eval-after-load 'tramp
- (tramp-set-completion-function "ssh" '((tramp-parse-netrc "~/.authinfo.gpg")))
- (add-to-list 'backup-directory-alist (cons tramp-file-name-regexp nil))
- (define-advice completion-file-name-table (:filter-args (args) no-remote-file-name-completion)
- (let ((string (car args))
- (pred (cadr args))
- (action (caddr args)))
- (if (and (file-remote-p string) (eq pred #'file-exists-p))
- (list string nil action)
- (list string pred action))))))
-
-(esy/init-step proced
- "Configure `proced'."
- (with-eval-after-load 'proced
- (add-hook 'proced-mode-hook (lambda ()
- (setq proced-auto-update-flag t)))))
-
-(esy/init-step time
- "Configure `world-clock'."
- (with-eval-after-load 'time
- (add-to-list 'zoneinfo-style-world-list '("Europe/Amsterdam" "Amsterdam"))))
-
-(esy/init-step flyspell
- "Configure spelling errors correction via `flyspell'."
- (with-eval-after-load 'flyspell
- (keymap-unset flyspell-mode-map "C-," t)
- (keymap-unset flyspell-mode-map "C-." t)
- (keymap-unset flyspell-mode-map "C-;" t)
- (keymap-unset flyspell-mode-map "C-M-i" t)))
-
-(esy/init-step minibuffer-completion
- "Configure minibuffer completions."
-
- (defvar esy/completing-read-commands nil)
-
- (add-to-list 'savehist-additional-variables
- 'esy/completing-read-commands)
-
- (define-advice completing-read (:before (&rest _) record-command)
- (cl-incf (alist-get this-command esy/completing-read-commands 0)))
-
- (add-to-list 'display-buffer-alist
- '("\\*Completions\\*"
- (display-buffer-reuse-window display-buffer-at-bottom)
- (window-parameters (mode-line-format . none))))
-
- (add-hook 'completion-list-mode-hook
- (lambda ()
- (setq-local cursor-in-non-selected-windows nil)))
-
- (dolist (key-binding '(("C-p" . minibuffer-previous-completion)
- ("C-n" . minibuffer-next-completion)
- ("M-j" . minibuffer-force-complete-and-exit)))
- (keymap-set minibuffer-local-completion-map
- (car key-binding)
- (cdr key-binding)))
-
- (with-eval-after-load 'consult
- (with-eval-after-load 'embark
- (require 'embark-consult))))
-
-(esy/init-step capf
- "Set up some global `completion-at-point-functions'."
- (defun esy/dabbrev-capf ()
- "Workaround for issue with `dabbrev-capf'."
- (require 'dabbrev)
- (dabbrev--reset-global-variables)
- (setq dabbrev-case-fold-search nil)
- (pcase (let ((inhibit-message t))
- (ignore-errors (dabbrev-capf)))
- (`(,beg ,end ,table . ,_)
- (list beg end table :exclusive 'no))))
-
- (add-to-list 'completion-at-point-functions #'esy/dabbrev-capf)
-
- (defun esy/file-capf ()
- "File completion at point function."
- (let ((bs (bounds-of-thing-at-point 'filename)))
- (when bs
- (let* ((start (car bs))
- (end (cdr bs)))
- `(,start ,end completion--file-name-table . (:exclusive no))))))
-
- (add-to-list 'completion-at-point-functions #'esy/file-capf))
-
-(esy/init-step lin
- "Configure highlighting of the current line via `lin'."
- (with-eval-after-load 'lin
- (add-to-list 'lin-mode-hooks 'gnus-summary-mode-hook)
- (add-to-list 'lin-mode-hooks 'gnus-group-mode-hook)
- (add-to-list 'lin-mode-hooks 'gnus-server-mode-hook)))
-
-(esy/init-step corfu
- "Configure `completion-in-region' UI with `corfu'."
- (let ((original-completion-in-region-function completion-in-region-function))
- (defun esy/setup-corfu (&rest args)
- (setq completion-in-region-function original-completion-in-region-function)
- (global-corfu-mode)
- (apply #'completion-in-region args)))
- (setq completion-in-region-function #'esy/setup-corfu)
- (with-eval-after-load 'corfu
- (defun esy/margin-formatter (metadata)
- "Format METADATA for `corfu-margin-formatters'."
- (pcase (cdr (assoc 'category metadata))
- ('dabbrev (lambda (_) "… "))))
- (add-to-list 'corfu-margin-formatters #'esy/margin-formatter)
- (corfu-indexed-mode)))
-
-(esy/init-step minor-modes
- "Enable some global minor modes."
- (dolist (mode '(
- column-number-mode
- context-menu-mode
- display-time-mode
- display-battery-mode
- global-auto-revert-mode
- global-diff-hl-mode
- global-whitespace-cleanup-mode
- lin-global-mode
- minibuffer-depth-indicate-mode
- mouse-avoidance-mode
- pixel-scroll-precision-mode
- recentf-mode
- repeat-mode
- save-place-mode
- savehist-mode
- show-paren-mode
- transient-mark-mode
- ))
- (funcall mode)))
-
-(esy/init-step emms
- "Set up EMMS."
- (dolist (command '(emms-start
- emms-stop
- emms-next
- emms-previous
- emms-pause
- emms-seek
- emms-seek-to
- emms-seek-forward
- emms-seek-backward
- emms-show))
- (autoload command "emms"))
-
- (defvar-keymap esy/emms-map
- :doc "My keymap for EMMS commands."
- :prefix 'esy/emms-map
- "N" #'emms-next
- "P" #'emms-previous
- "S" #'emms-stop
- "b" #'emms-seek-backward
- "f" #'emms-seek-fofrward
- "k" #'emms-seek
- "m" #'emms-show
- "p" #'emms-pause
- "s" #'emms-start
- "t" #'emms-seek-to)
-
- (dolist (command '(emms-start
- emms-stop
- emms-next
- emms-previous
- emms-pause
- emms-seek
- emms-seek-to
- emms-seek-forward
- emms-seek-backward
- emms-show))
- (put command 'repeat-map 'esy/emms-map))
-
- (with-eval-after-load 'emms
- (emms-minimalistic)))
-
-(esy/init-step pdf
- "Configure PDF handling."
- (with-eval-after-load 'pdf-view
- (require 'pdf-tools))
- (with-eval-after-load 'pdf-tools
- (pdf-tools-install :no-query)
- (add-hook 'pdf-view-mode-hook #'pdf-view-midnight-minor-mode))
- (add-to-list 'revert-without-query "\\.pdf\\'"))
-
-;; (esy/init-step tex
-;; "Configure TeX via `auctex'."
-;; (with-eval-after-load 'tex
-;; (add-hook 'TeX-after-compilation-finished-functions
-;; #'TeX-revert-document-buffer)))
-
-(esy/init-step vterm
- "Configure terminal emulation via `vterm'."
- (with-eval-after-load 'vterm
- (when (eq system-type 'darwin)
- (setq vterm-shell "/bin/zsh"))
- (add-to-list 'vterm-tramp-shells '("kubernetes" "/bin/bash"))))
-
-(esy/init-step paredit
- "Remove some over-intrusive keybindings in `paredit'."
- (with-eval-after-load 'paredit
- (keymap-unset paredit-mode-map "M-s" t)
- (keymap-unset paredit-mode-map "M-?" t)))
-
-(esy/init-step sweep
- "Configure Prolog integration via `sweeprolog'."
- (setq-default prolog-system 'swi)
- (add-to-list 'major-mode-remap-alist '(prolog-mode . sweeprolog-mode))
- (with-eval-after-load 'sweeprolog
- (setq sweeprolog-top-level-persistent-history
- (locate-user-emacs-file ".sweep_history"))
- (keymap-global-set "C-c p" sweeprolog-prefix-map)
- (add-hook 'sweeprolog-mode-hook #'sweeprolog-electric-layout-mode)
- (add-hook 'sweeprolog-top-level-mode-hook #'compilation-shell-minor-mode)))
-
-(esy/init-step rg
- "Configure recursive grepping via `rg'."
- (with-eval-after-load 'rg
- (add-to-list 'rg-custom-type-aliases '("Prolog" . "*.pl *.plt *.pro *.prolog"))))
-
-(esy/init-step terraform
- "Configure custom faces in `terraform-mode'."
- (with-eval-after-load 'terraform-mode
- (setq terraform--resource-name-face font-lock-function-name-face
- terraform--resource-type-face font-lock-type-face)))
-
-(esy/init-step auto-mode
- "Associate major modes with files based on their extensions."
- (dolist (cell '(("Dockerfile" . dockerfile-ts-mode)
- ("\\.ya?ml\\'" . yaml-ts-mode)
- ("\\.toml\\'" . toml-ts-mode)
- ("\\.json\\'" . json-ts-mode)
- ("\\.tfstate\\'" . json-ts-mode)
- ("\\.ts\\'" . typescript-ts-mode)
- ("\\.rb\\'" . ruby-ts-mode)
- ("\\.rs\\'" . rust-ts-mode)
- ("\\.go\\'" . go-ts-mode)
- ("\\.pdf\\'" . pdf-view-mode)
- ("\\.plt?\\'" . prolog-mode)))
- (push cell auto-mode-alist)))
-
-(esy/init-step kill-hook
- "Compile this file (if changed) when Emacs is killed."
- (defun esy/compile-config ()
+ (let* ((name-dir-alist
+ (delete
+ nil
+ (mapcar (lambda (dir)
+ (when-let ((proj (project-current nil dir)))
+ (cons (project-name proj) dir)))
+ (project-known-project-roots))))
+ (current (project-current))
+ (default (and current (project-name current)))
+ (name (completing-read (format-prompt "Project" default)
+ name-dir-alist
+ nil nil nil
+ 'esy/project-name-history
+ default)))
+ (or (alist-get name name-dir-alist nil nil #'string=)
+ (let* ((dir (read-directory-name "Project root directory: "
+ esy/projects-directory
+ nil t name))
+ (project (project-current nil dir)))
+ (when project (project-remember-project project))
+ dir)))))
+
+;;; Configure SQL connections
+(with-eval-after-load 'sql
+ (defun esy/update-sql-connection-alist ()
(interactive)
- (when (file-newer-than-file-p user-init-file
- (byte-compile-dest-file user-init-file))
- (byte-compile-file user-init-file))
- (when (file-newer-than-file-p early-init-file
- (byte-compile-dest-file early-init-file))
- (byte-compile-file early-init-file)))
-
- (add-hook 'kill-emacs-hook #'esy/compile-config 10))
-
-(esy/init-step help
- "Configure Help."
- (with-eval-after-load 'help-fns
- (with-eval-after-load 'shortdoc
- (add-hook 'help-fns-describe-function-functions
- #'shortdoc-help-fns-examples-function))))
-
-(esy/init-step dict
- "Configure dictionary."
- (with-eval-after-load 'dictionary
- (setopt dictionary-search-interface 'help
- dictionary-default-dictionary "gcide"
- dictionary-default-strategy "prefix"
- dictionary-server "dict.org")))
-
-(esy/init-step elfeed
- "Configure news feeds."
- (with-eval-after-load 'elfeed
- (keymap-set elfeed-show-mode-map "S-SPC" #'scroll-down-command)
-
- (defvar esy/feeds-file (locate-user-emacs-file "feeds.eld"))
-
- (defun esy/feeds ()
+ (auth-source-forget-all-cached)
+ (setq sql-connection-alist (delete nil
+ (mapcar (lambda (source)
+ (pcase (split-string (plist-get source :host)
+ (rx "^"))
+ (`(,con ,db ,host)
+ (list (intern con)
+ (list 'sql-product ''postgres)
+ (list 'sql-user (plist-get source :user))
+ (list 'sql-port (string-to-number (plist-get source :port)))
+ (list 'sql-password (funcall (plist-get source :secret)))
+ (list 'sql-server host)
+ (list 'sql-database db)))))
+ (auth-source-search :port 5432 :max 10)))))
+ (esy/update-sql-connection-alist)
+ (add-hook 'sql-interactive-mode-hook #'toggle-truncate-lines)
+ (add-hook 'sql-interactive-mode-hook #'abbrev-mode)
+ (define-key sql-mode-map (kbd "C-c C-f") #'sqlformat)
+ (define-advice sql-comint-postgres (:around (fun &rest args) use-sql-password)
+ (let ((process-environment
+ (nconc
+ (list (format "PGPASSWORD=%s" sql-password))
+ process-environment)))
+ (apply fun args))))
+
+;;; Configure Org mode
+(with-eval-after-load 'org
+ (keymap-unset org-mode-map "C-," t)
+ (esy-publish-setup))
+
+(with-eval-after-load 'org-agenda
+ (add-to-list 'org-agenda-custom-commands
+ '("w" "Work TODOs" tags-todo "+work"))
+ (add-to-list 'org-agenda-custom-commands
+ '("h" "Holland TODOs" tags-todo "+holland")))
+
+;;; Configure email via `gnus'
+(with-eval-after-load 'gnus
+ (require 'gnus-icalendar)
+ (add-hook 'gnus-group-mode-hook #'gnus-topic-mode)
+ (gnus-icalendar-setup)
+ (gnus-icalendar-org-setup)
+ (bbdb-initialize 'gnus 'mail 'message))
+
+;;; Configure `dired'
+(with-eval-after-load 'dired
+ (put 'dired-find-alternate-file 'disabled nil))
+
+;;; Configure remote access via `tramp'
+(with-eval-after-load 'tramp
+ (tramp-set-completion-function "ssh" '((tramp-parse-netrc "~/.authinfo.gpg")))
+ (add-to-list 'backup-directory-alist (cons tramp-file-name-regexp nil))
+ (define-advice completion-file-name-table (:filter-args (args) no-remote-file-name-completion)
+ (let ((string (car args))
+ (pred (cadr args))
+ (action (caddr args)))
+ (if (and (file-remote-p string) (eq pred #'file-exists-p))
+ (list string nil action)
+ (list string pred action)))))
+
+;;; Configure `proced'
+(with-eval-after-load 'proced
+ (add-hook 'proced-mode-hook (lambda ()
+ (setq proced-auto-update-flag t))))
+
+;;; Configure `world-clock'
+(with-eval-after-load 'time
+ (add-to-list 'zoneinfo-style-world-list '("Europe/Amsterdam" "Amsterdam")))
+
+;;; Configure spelling errors correction via `flyspell'
+(with-eval-after-load 'flyspell
+ (keymap-unset flyspell-mode-map "C-," t)
+ (keymap-unset flyspell-mode-map "C-." t)
+ (keymap-unset flyspell-mode-map "C-;" t)
+ (keymap-unset flyspell-mode-map "C-M-i" t))
+
+;;; Configure minibuffer completions
+
+(defvar esy/completing-read-commands nil)
+
+(add-to-list 'savehist-additional-variables
+ 'esy/completing-read-commands)
+
+(define-advice completing-read (:before (&rest _) record-command)
+ (cl-incf (alist-get this-command esy/completing-read-commands 0)))
+
+(add-to-list 'display-buffer-alist
+ '("\\*Completions\\*"
+ (display-buffer-reuse-window display-buffer-at-bottom)
+ (window-parameters (mode-line-format . none))))
+
+(add-hook 'completion-list-mode-hook
+ (lambda ()
+ (setq-local cursor-in-non-selected-windows nil)))
+
+(dolist (key-binding '(("C-p" . minibuffer-previous-completion)
+ ("C-n" . minibuffer-next-completion)
+ ("M-j" . minibuffer-force-complete-and-exit)))
+ (keymap-set minibuffer-local-completion-map
+ (car key-binding)
+ (cdr key-binding)))
+
+(with-eval-after-load 'consult
+ (with-eval-after-load 'embark
+ (require 'embark-consult)))
+
+;;; Set up some global `completion-at-point-functions'
+(defun esy/dabbrev-capf ()
+ "Workaround for issue with `dabbrev-capf'."
+ (require 'dabbrev)
+ (dabbrev--reset-global-variables)
+ (setq dabbrev-case-fold-search nil)
+ (pcase (let ((inhibit-message t))
+ (ignore-errors (dabbrev-capf)))
+ (`(,beg ,end ,table . ,_)
+ (list beg end table :exclusive 'no))))
+
+(add-to-list 'completion-at-point-functions #'esy/dabbrev-capf)
+
+(defun esy/file-capf ()
+ "File completion at point function."
+ (let ((bs (bounds-of-thing-at-point 'filename)))
+ (when bs
+ (let* ((start (car bs))
+ (end (cdr bs)))
+ `(,start ,end completion--file-name-table . (:exclusive no))))))
+
+(add-to-list 'completion-at-point-functions #'esy/file-capf)
+
+;;; Configure highlighting of the current line via `lin'
+(with-eval-after-load 'lin
+ (add-to-list 'lin-mode-hooks 'gnus-summary-mode-hook)
+ (add-to-list 'lin-mode-hooks 'gnus-group-mode-hook)
+ (add-to-list 'lin-mode-hooks 'gnus-server-mode-hook))
+
+;;; Configure `completion-in-region' UI with `corfu'
+(let ((original-completion-in-region-function completion-in-region-function))
+ (defun esy/setup-corfu (&rest args)
+ (setq completion-in-region-function original-completion-in-region-function)
+ (global-corfu-mode)
+ (apply #'completion-in-region args)))
+(setq completion-in-region-function #'esy/setup-corfu)
+(with-eval-after-load 'corfu
+ (defun esy/margin-formatter (metadata)
+ "Format METADATA for `corfu-margin-formatters'."
+ (pcase (cdr (assoc 'category metadata))
+ ('dabbrev (lambda (_) "… "))))
+ (add-to-list 'corfu-margin-formatters #'esy/margin-formatter)
+ (corfu-indexed-mode))
+
+;;; Enable some global minor modes
+(dolist (mode '(
+ column-number-mode
+ context-menu-mode
+ display-time-mode
+ display-battery-mode
+ global-auto-revert-mode
+ global-diff-hl-mode
+ global-whitespace-cleanup-mode
+ lin-global-mode
+ minibuffer-depth-indicate-mode
+ mouse-avoidance-mode
+ pixel-scroll-precision-mode
+ recentf-mode
+ repeat-mode
+ save-place-mode
+ savehist-mode
+ show-paren-mode
+ transient-mark-mode
+ ))
+ (funcall mode))
+
+;;; Set up EMMS
+(dolist (command '(emms-start
+ emms-stop
+ emms-next
+ emms-previous
+ emms-pause
+ emms-seek
+ emms-seek-to
+ emms-seek-forward
+ emms-seek-backward
+ emms-show))
+ (autoload command "emms"))
+
+(defvar-keymap esy/emms-map
+ :doc "My keymap for EMMS commands."
+ :prefix 'esy/emms-map
+ "N" #'emms-next
+ "P" #'emms-previous
+ "S" #'emms-stop
+ "b" #'emms-seek-backward
+ "f" #'emms-seek-fofrward
+ "k" #'emms-seek
+ "m" #'emms-show
+ "p" #'emms-pause
+ "s" #'emms-start
+ "t" #'emms-seek-to)
+
+(dolist (command '(emms-start
+ emms-stop
+ emms-next
+ emms-previous
+ emms-pause
+ emms-seek
+ emms-seek-to
+ emms-seek-forward
+ emms-seek-backward
+ emms-show))
+ (put command 'repeat-map 'esy/emms-map))
+
+(with-eval-after-load 'emms
+ (emms-minimalistic))
+
+;;; Configure PDF handling
+(with-eval-after-load 'pdf-view
+ (require 'pdf-tools))
+(with-eval-after-load 'pdf-tools
+ (pdf-tools-install :no-query)
+ (add-hook 'pdf-view-mode-hook #'pdf-view-midnight-minor-mode))
+(add-to-list 'revert-without-query "\\.pdf\\'")
+
+;;; Configure terminal emulation via `vterm'
+(with-eval-after-load 'vterm
+ (when (eq system-type 'darwin)
+ (setq vterm-shell "/bin/zsh"))
+ (add-to-list 'vterm-tramp-shells '("kubernetes" "/bin/bash")))
+
+;;; Remove some over-intrusive keybindings in `paredit'
+(with-eval-after-load 'paredit
+ (keymap-unset paredit-mode-map "M-s" t)
+ (keymap-unset paredit-mode-map "M-?" t))
+
+;;; Configure Prolog integration via `sweeprolog'
+(setq-default prolog-system 'swi)
+(add-to-list 'major-mode-remap-alist '(prolog-mode . sweeprolog-mode))
+(with-eval-after-load 'sweeprolog
+ (setq sweeprolog-top-level-persistent-history
+ (locate-user-emacs-file ".sweep_history"))
+ (keymap-global-set "C-c p" sweeprolog-prefix-map)
+ (add-hook 'sweeprolog-mode-hook #'sweeprolog-electric-layout-mode)
+ (add-hook 'sweeprolog-top-level-mode-hook #'compilation-shell-minor-mode))
+
+;;; Configure recursive grepping via `rg'
+(with-eval-after-load 'rg
+ (add-to-list 'rg-custom-type-aliases '("Prolog" . "*.pl *.plt *.pro *.prolog")))
+
+;;; Configure custom faces in `terraform-mode'
+(with-eval-after-load 'terraform-mode
+ (setq terraform--resource-name-face font-lock-function-name-face
+ terraform--resource-type-face font-lock-type-face))
+
+;;; Associate major modes with files based on their extensions
+(dolist (cell '(("Dockerfile" . dockerfile-ts-mode)
+ ("\\.ya?ml\\'" . yaml-ts-mode)
+ ("\\.toml\\'" . toml-ts-mode)
+ ("\\.json\\'" . json-ts-mode)
+ ("\\.tfstate\\'" . json-ts-mode)
+ ("\\.ts\\'" . typescript-ts-mode)
+ ("\\.rb\\'" . ruby-ts-mode)
+ ("\\.rs\\'" . rust-ts-mode)
+ ("\\.go\\'" . go-ts-mode)
+ ("\\.pdf\\'" . pdf-view-mode)
+ ("\\.plt?\\'" . prolog-mode)))
+ (push cell auto-mode-alist))
+
+;;; Configure Help
+(with-eval-after-load 'help-fns
+ (with-eval-after-load 'shortdoc
+ (add-hook 'help-fns-describe-function-functions
+ #'shortdoc-help-fns-examples-function)))
+
+;;; Configure dictionary
+(with-eval-after-load 'dictionary
+ (setopt dictionary-search-interface 'help
+ dictionary-default-dictionary "gcide"
+ dictionary-default-strategy "prefix"
+ dictionary-server "dict.org"))
+
+;;; Configure news feeds
+(with-eval-after-load 'elfeed
+ (keymap-set elfeed-show-mode-map "S-SPC" #'scroll-down-command)
+
+ (defvar esy/feeds-file (locate-user-emacs-file "feeds.eld"))
+
+ (defun esy/feeds ()
+ (with-temp-buffer
+ (insert-file-contents-literally esy/feeds-file)
+ (goto-char (point-min))
+ (read (current-buffer))))
+
+ (defun esy/read-feed-keywords ()
+ (mapcar #'intern
+ (completing-read-multiple
+ "Keywords: "
+ (let ((keywords nil))
+ (dolist
+ (feed-keywords
+ (mapcar
+ #'cdr
+ (esy/feeds)))
+ (mapc (lambda (keyword)
+ (add-to-list 'keywords keyword))
+ feed-keywords))
+ (mapcar #'symbol-name keywords)))))
+
+ (defun esy/add-feed (url keywords)
+ (interactive (let ((default (thing-at-point-url-at-point)))
+ (list (read-string (format-prompt "Feed URL"
+ default)
+ nil nil default)
+ (esy/read-feed-keywords))))
+ (let ((feeds (cons (cons url keywords) (esy/feeds))))
(with-temp-buffer
- (insert-file-contents-literally esy/feeds-file)
- (goto-char (point-min))
- (read (current-buffer))))
-
- (defun esy/read-feed-keywords ()
- (mapcar #'intern
- (completing-read-multiple
- "Keywords: "
- (let ((keywords nil))
- (dolist
- (feed-keywords
- (mapcar
- #'cdr
- (esy/feeds)))
- (mapc (lambda (keyword)
- (add-to-list 'keywords keyword))
- feed-keywords))
- (mapcar #'symbol-name keywords)))))
-
- (defun esy/add-feed (url keywords)
- (interactive (let ((default (thing-at-point-url-at-point)))
- (list (read-string (format-prompt "Feed URL"
- default)
- nil nil default)
- (esy/read-feed-keywords))))
- (let ((feeds (cons (cons url keywords) (esy/feeds))))
- (with-temp-buffer
- (pp (cons (cons url keywords) (esy/feeds))
- (current-buffer))
- (write-region (point-min)
- (point-max)
- esy/feeds-file))
- (setq elfeed-feeds feeds)))
-
- (defun esy/update-feeds ()
- (interactive)
- (setq elfeed-feeds (esy/feeds)))
-
- (with-eval-after-load 'eww
- (defun esy/eww-add-feed (url keywords)
- (interactive (list (eww-read-alternate-url)
- (esy/read-feed-keywords))
- eww-mode)
- (esy/add-feed url keywords)))
-
- (setq
- ;; don't use bold face for unread Elfeed entries
- elfeed-search-face-alist '((unread (default)))
- ;; read feeds list from a separate file
- elfeed-feeds (esy/feeds))))
-
-(esy/init-step currency
- "Track currency exchange rates."
-
- (defvar esy/eur-to-ils-rates nil)
-
- (add-to-list 'savehist-additional-variables
- 'esy/eur-to-ils-rates)
-
- (defun esy/update-eur-to-ils-rate ()
- (require 'dom)
- (let* ((time (float-time))
- (from "EUR")
- (to "ILS")
- (amount 1)
- (url (url-parse-make-urlobj "https"
- nil
- nil
- "www.x-rates.com"
- nil
- (concat "/calculator/?"
- (url-build-query-string `(("from" ,from)
- ("to" ,to)
- ("amount" ,amount)))))))
- (url-retrieve url
- (lambda (_)
- (goto-char (point-min))
- (search-forward "\n\n")
- (push (cons time
- (string-to-number (caddar (dom-by-class (libxml-parse-html-region (point))
- "ccOutputRslt"))))
- esy/eur-to-ils-rates))
- nil t)))
-
- (run-at-time t 300 #'esy/update-eur-to-ils-rate)
-
- (defun esy/plot-eur-to-ils-rates (height width)
- (require 'svg)
- (let* ((svg (svg-create width height
- :stroke "green"))
- (time (float-time))
- (frame (* 60 60 24))
- (data (named-let loop ((rates esy/eur-to-ils-rates)
- (acc nil)
- (min most-positive-fixnum)
- (max most-negative-fixnum))
- (if rates
- (let ((x (* (/ (+ frame (- (caar rates) time))
- frame)
- width)))
- (if (< 0 x)
- (let ((val (cdar rates)))
- (loop (cdr rates)
- (cons (cons x (cdar rates)) acc)
- (min val min)
- (max val max)))
- (list acc min max)))
- (list acc min max))))
- (points (nth 0 data))
- (min (nth 1 data))
- (max (nth 2 data))
- (normalized-points (mapcar (lambda (xy)
- (cons (car xy)
- (- height (* height
- (/ (- (cdr xy) min)
- (- max min))))))
- points)))
- (svg-polyline svg normalized-points :fill-color "none")
- (svg-image svg)))
-
- (defun esy/plot-eur-rates ()
+ (pp (cons (cons url keywords) (esy/feeds))
+ (current-buffer))
+ (write-region (point-min)
+ (point-max)
+ esy/feeds-file))
+ (setq elfeed-feeds feeds)))
+
+ (defun esy/update-feeds ()
(interactive)
- (with-current-buffer-window "EUR to ILS" nil nil
- (insert-image (esy/plot-eur-to-ils-rates 300 500))
- (insert "\n")))
+ (setq elfeed-feeds (esy/feeds)))
- (push '(:eval (concat "1€=" (number-to-string (cdar esy/eur-to-ils-rates)) "₪ "))
- global-mode-string))
+ (with-eval-after-load 'eww
+ (defun esy/eww-add-feed (url keywords)
+ (interactive (list (eww-read-alternate-url)
+ (esy/read-feed-keywords))
+ eww-mode)
+ (esy/add-feed url keywords)))
-(dolist (step (sort esy/init-step-times (lambda (l r) (time-less-p (cdr r) (cdr l)))))
- (message "%f %s"
- (float-time (cdr step))
- (car step)))
+ (setq
+ ;; don't use bold face for unread Elfeed entries
+ elfeed-search-face-alist '((unread (default)))
+ ;; read feeds list from a separate file
+ elfeed-feeds (esy/feeds)))
+
+;;; Track currency exchange rates
+
+(defvar esy/eur-to-ils-rates nil)
+
+(add-to-list 'savehist-additional-variables
+ 'esy/eur-to-ils-rates)
+
+(defun esy/update-eur-to-ils-rate ()
+ (require 'dom)
+ (let* ((time (float-time))
+ (from "EUR")
+ (to "ILS")
+ (amount 1)
+ (url (url-parse-make-urlobj "https"
+ nil
+ nil
+ "www.x-rates.com"
+ nil
+ (concat "/calculator/?"
+ (url-build-query-string `(("from" ,from)
+ ("to" ,to)
+ ("amount" ,amount)))))))
+ (url-retrieve url
+ (lambda (_)
+ (goto-char (point-min))
+ (search-forward "\n\n")
+ (push (cons time
+ (string-to-number (caddar (dom-by-class (libxml-parse-html-region (point))
+ "ccOutputRslt"))))
+ esy/eur-to-ils-rates))
+ nil t)))
+
+(run-at-time t 300 #'esy/update-eur-to-ils-rate)
+
+(defun esy/plot-eur-to-ils-rates (height width)
+ (require 'svg)
+ (let* ((svg (svg-create width height
+ :stroke "green"))
+ (time (float-time))
+ (frame (* 60 60 24))
+ (data (named-let loop ((rates esy/eur-to-ils-rates)
+ (acc nil)
+ (min most-positive-fixnum)
+ (max most-negative-fixnum))
+ (if rates
+ (let ((x (* (/ (+ frame (- (caar rates) time))
+ frame)
+ width)))
+ (if (< 0 x)
+ (let ((val (cdar rates)))
+ (loop (cdr rates)
+ (cons (cons x (cdar rates)) acc)
+ (min val min)
+ (max val max)))
+ (list acc min max)))
+ (list acc min max))))
+ (points (nth 0 data))
+ (min (nth 1 data))
+ (max (nth 2 data))
+ (normalized-points (mapcar (lambda (xy)
+ (cons (car xy)
+ (- height (* height
+ (/ (- (cdr xy) min)
+ (- max min))))))
+ points)))
+ (svg-polyline svg normalized-points :fill-color "none")
+ (svg-image svg)))
+
+(defun esy/plot-eur-rates ()
+ (interactive)
+ (with-current-buffer-window "EUR to ILS" nil nil
+ (insert-image (esy/plot-eur-to-ils-rates 300 500))
+ (insert "\n")))
+
+(push '(:eval (concat "1€=" (number-to-string (cdar esy/eur-to-ils-rates)) "₪ "))
+ global-mode-string)
(provide 'init)
;;; init.el ends here