Compare commits
1 commit
main
...
no-straigh
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8f2be92ff |
42 changed files with 1252 additions and 2980 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -9,6 +9,4 @@
|
||||||
/elisp/gourmet.el
|
/elisp/gourmet.el
|
||||||
/elisp/wd-imaas.el
|
/elisp/wd-imaas.el
|
||||||
/ha-theme-results.org
|
/ha-theme-results.org
|
||||||
/support/dashboard
|
/support/dashboard
|
||||||
/.emacs.desktop
|
|
||||||
/.*-notes.org
|
|
||||||
|
|
@ -44,7 +44,7 @@ Best success comes from using the [[https://github.com/d12frosted/homebrew-emacs
|
||||||
I find that I need to … at least, on my work computer, install two different versions of Emacs that I use to distinguish one for “work” and the other for other activities, like IRC and [[file:ha-feed-reader.org][elfeed]]. To that end, I run the following command to install Emacs:
|
I find that I need to … at least, on my work computer, install two different versions of Emacs that I use to distinguish one for “work” and the other for other activities, like IRC and [[file:ha-feed-reader.org][elfeed]]. To that end, I run the following command to install Emacs:
|
||||||
#+begin_src sh
|
#+begin_src sh
|
||||||
brew reinstall $(brew deps emacs-plus@30)
|
brew reinstall $(brew deps emacs-plus@30)
|
||||||
brew reinstall emacs-plus@30 --with-mailutils
|
brew install emacs-plus@30 --with-native-comp --with-mailutils --with-imagemagick --with-savchenkovaleriy-big-sur-icon --with-no-frame-refocus --debug
|
||||||
#+end_src
|
#+end_src
|
||||||
And if it fails, choose =shell= and type:
|
And if it fails, choose =shell= and type:
|
||||||
#+begin_src sh
|
#+begin_src sh
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,6 @@ A literate programming file for bootstraping my Emacs Configuration.
|
||||||
#+begin_src emacs-lisp :exports none
|
#+begin_src emacs-lisp :exports none
|
||||||
;;; bootstrap.el --- file for bootstraping my Emacs Configuration
|
;;; bootstrap.el --- file for bootstraping my Emacs Configuration
|
||||||
;;
|
;;
|
||||||
;; ██████████
|
|
||||||
;; ░░███░░░░░█
|
|
||||||
;; ░███ █ ░ █████████████ ██████ ██████ █████
|
|
||||||
;; ░██████ ░░███░░███░░███ ░░░░░███ ███░░███ ███░░
|
|
||||||
;; ░███░░█ ░███ ░███ ░███ ███████ ░███ ░░░ ░░█████
|
|
||||||
;; ░███ ░ █ ░███ ░███ ░███ ███░░███ ░███ ███ ░░░░███
|
|
||||||
;; ██████████ █████░███ █████░░████████░░██████ ██████
|
|
||||||
;; ░░░░░░░░░░ ░░░░░ ░░░ ░░░░░ ░░░░░░░░ ░░░░░░ ░░░░░░
|
|
||||||
|
|
||||||
;; © 2021-2023 Howard X. Abrams
|
;; © 2021-2023 Howard X. Abrams
|
||||||
;; Licensed under a Creative Commons Attribution 4.0 International License.
|
;; Licensed under a Creative Commons Attribution 4.0 International License.
|
||||||
;; See http://creativecommons.org/licenses/by/4.0/
|
;; See http://creativecommons.org/licenses/by/4.0/
|
||||||
|
|
@ -36,8 +27,6 @@ A literate programming file for bootstraping my Emacs Configuration.
|
||||||
* Introduction
|
* Introduction
|
||||||
This file contains all the variable definitions and library loading for the other files in my project.
|
This file contains all the variable definitions and library loading for the other files in my project.
|
||||||
|
|
||||||
I'm installing everything using the [[https://github.com/raxod502/straight.el#getting-started][straight.el]] for package installation and management. This is initialization code configured in [[file:initialize][initialize]], and calls to =use-package= now accepts a =:straight= parameter that allows me to retrieve special versions of some packages.
|
|
||||||
|
|
||||||
See the details in [[https://dev.to/jkreeftmeijer/emacs-package-management-with-straight-el-and-use-package-3oc8][this essay]].
|
See the details in [[https://dev.to/jkreeftmeijer/emacs-package-management-with-straight-el-and-use-package-3oc8][this essay]].
|
||||||
|
|
||||||
* Initial Settings
|
* Initial Settings
|
||||||
|
|
@ -107,7 +96,7 @@ Getting tired off all the packages that I load spewing a bunch of warnings that
|
||||||
The following packages come with Emacs, but seems like they still need loading:
|
The following packages come with Emacs, but seems like they still need loading:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package cl-lib
|
(use-package cl-lib
|
||||||
:straight (:type built-in)
|
|
||||||
:init (defun first (elt) (car elt))
|
:init (defun first (elt) (car elt))
|
||||||
:commands (first))
|
:commands (first))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
"A WAV or AU file used at the completion of a function.")
|
"A WAV or AU file used at the completion of a function.")
|
||||||
|
|
||||||
;; My replacement in case we can't play internal sounds:
|
;; My replacement in case we can't play internal sounds:
|
||||||
(defun beep-beep (&rest _)
|
(defun beep-beep ()
|
||||||
"Play a default notification sound file.
|
"Play a default notification sound file.
|
||||||
Customize the variable, `beep-alert-sound-file' to adjust the sound."
|
Customize the variable, `beep-alert-sound-file' to adjust the sound."
|
||||||
(if (fboundp 'play-sound-internal)
|
(if (fboundp 'play-sound-internal)
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ start some music to indicate we are working, and set a timer.
|
||||||
Call `ha-focus-break' when finished."
|
Call `ha-focus-break' when finished."
|
||||||
(interactive)
|
(interactive)
|
||||||
(ha-focus-countdown-timer 25 'ha-focus-break)
|
(ha-focus-countdown-timer 25 'ha-focus-break)
|
||||||
(ha-focus--command "tell application \"VLC\" to play")
|
(ha-focus--command "tell application \"Spotify\" to play")
|
||||||
(if (eq major-mode 'org-mode)
|
(if (eq major-mode 'org-mode)
|
||||||
(org-clock-in)
|
(org-clock-in)
|
||||||
(org-clock-in-last))
|
(org-clock-in-last))
|
||||||
|
|
@ -47,7 +47,7 @@ This also starts another break timer, that calls
|
||||||
`ha-focus-break-over' when finished."
|
`ha-focus-break-over' when finished."
|
||||||
(interactive)
|
(interactive)
|
||||||
(run-with-idle-timer 30 nil 'ha-focus-capture)
|
(run-with-idle-timer 30 nil 'ha-focus-capture)
|
||||||
(ha-focus--command "tell application \"VLC\" to stop")
|
(ha-focus--command "tell application \"Spotify\" to pause")
|
||||||
(ha-focus-countdown-timer 5 'ha-focus-break-over)
|
(ha-focus-countdown-timer 5 'ha-focus-break-over)
|
||||||
(message "🍅 Time to take a break."))
|
(message "🍅 Time to take a break."))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,223 +26,13 @@ A literate programming file configuring critical applications.
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Can we call the following /applications/? I guess.
|
Can we call the following /applications/? I guess.
|
||||||
* Agentic Interface
|
|
||||||
Ethic issues aside, I’m [[https://technobabble.bearblog.dev/fine-ill-try-ai/][trying AI]] … primarily because my company requires my participation. I appreciate the approaches from my fellow Emacsians, for while VSCode may be a fine editor, it can’t compete with my creation here.
|
|
||||||
** Agent Shell
|
|
||||||
Installing Xenodium’s [[https://github.com/xenodium/agent-shell][agent-shell]], requires his [[https://github.com/xenodium/acp.el][ACP package]] for accessing the installed [[https://agentclientprotocol.com/][ACP libraries]], and his [[https://github.com/xenodium/shell-maker][shell-maker]] package.
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(use-package acp
|
|
||||||
:straight (:type git :host github :repo "xenodium/acp.el"))
|
|
||||||
|
|
||||||
(use-package shell-maker
|
|
||||||
:straight (:type git :host github :repo "xenodium/shell-maker"))
|
|
||||||
|
|
||||||
(use-package agent-shell
|
|
||||||
:straight (:type git :host github :repo "xenodium/agent-shell")
|
|
||||||
:after acp
|
|
||||||
|
|
||||||
:custom
|
|
||||||
(agent-shell-display-action '(display-buffer-in-previous-window))
|
|
||||||
|
|
||||||
:config
|
|
||||||
(ha-leader "a i" '("agent chat" . agent-shell))
|
|
||||||
|
|
||||||
;; Evil state-specific RET behavior: insert mode = newline, normal mode = send
|
|
||||||
(evil-define-key 'insert agent-shell-mode-map (kbd "RET") #'newline)
|
|
||||||
(evil-define-key 'insert agent-shell-mode-map (kbd "C-RET") #'agent-shell-submit)
|
|
||||||
(evil-define-key 'normal agent-shell-mode-map (kbd "RET") #'comint-send-input)
|
|
||||||
|
|
||||||
;; Configure *agent-shell-diff* buffers to start in Emacs state
|
|
||||||
(add-hook 'diff-mode-hook
|
|
||||||
(lambda ()
|
|
||||||
(when (string-match-p "\\*agent-shell-diff\\*" (buffer-name))
|
|
||||||
(evil-emacs-state)))))
|
|
||||||
#+END_SRC
|
|
||||||
*** Notifications
|
|
||||||
When my /artificial intern/ completes a task, I have long since nipped out to the kitchen, put the kettle on ... buttering scones... and getting crumbs and bits of food out of those round brown straw mats that the teapot goes on.
|
|
||||||
|
|
||||||
I would like the /intern/ to notify me when it needs attention, so I noticed the [[https://github.com/zackattackz/agent-shell-notifications/][agent-shell-notifications]] project can connect the =agent-shell= project with the [[https://github.com/konrad1977/knockknock/][knock-knock]] project (which is similar to my [[https://howardism.org/Technical/Emacs/beep-for-emacs.html][beep project]]).
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(use-package knockknock
|
|
||||||
:straight (knockknock :type git :host github :repo "konrad1977/knockknock")
|
|
||||||
:init
|
|
||||||
(setq knockknock-border-color "brown")
|
|
||||||
(setq knockknock-border-width 2)
|
|
||||||
(setq knockknock-default-duration 4)
|
|
||||||
:config
|
|
||||||
(advice-add 'knockknock-notify :after #'beep-beep))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Testing it out:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp :tangle no
|
|
||||||
(knockknock-notify :title "Attention"
|
|
||||||
:message "Claude needs attention"
|
|
||||||
:icon "nf-md-robot_confused")
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
The configuration for [[https://github.com/zackattackz/agent-shell-notifications/][agent-shell-notifications]] and hook it to the =agent-shell-notifications-provider=:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(use-package agent-shell-notifications
|
|
||||||
:straight (agent-shell-notifications
|
|
||||||
:type git
|
|
||||||
:host github
|
|
||||||
:repo "zackattackz/agent-shell-notifications")
|
|
||||||
:hook
|
|
||||||
;; Enable notifications in each agent-shell buffer
|
|
||||||
(agent-shell-mode . agent-shell-notifications-mode)
|
|
||||||
|
|
||||||
:config
|
|
||||||
;; Notification display timeout in seconds (0 = never expire (the default), -1 = backend default)
|
|
||||||
;; (setq agent-shell-notifications-timeout 5)
|
|
||||||
|
|
||||||
;; Seconds to wait before notifying when the shell is already visible (default: 10)
|
|
||||||
;; (setq agent-shell-notifications-idle-timeout 30)
|
|
||||||
|
|
||||||
;; Use the knockknock backend instead of the default libnotify
|
|
||||||
(setq agent-shell-notifications-provider 'agent-shell-notifications-knockknock)
|
|
||||||
|
|
||||||
;; While that code is _supposed_ to do this, I seem to need to do this manually:
|
|
||||||
(require 'agent-shell-notifications-knockknock))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
I might switch to [[https://github.com/cxa/agent-shell-macext#start-of-content][agent-shell-macext]] if I can’t get KnockKnock to work well on my Linux system.
|
|
||||||
*** Agent Skills
|
|
||||||
The following pulls in [[https://github.com/xenodium/emacs-skills][emacs-skills]], a Claude Agent skills for Emacs:
|
|
||||||
|
|
||||||
#+BEGIN_SRC sh
|
|
||||||
claude plugin marketplace add xenodium/emacs-skills
|
|
||||||
claude plugin install emacs-skills@xenodium-emacs-skills
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Often update with:
|
|
||||||
|
|
||||||
#+BEGIN_SRC sh
|
|
||||||
claude plugin marketplace update xenodium-emacs-skills
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
The most helpful is =/open= that you run afterwards to pull the changes into buffers for complete review. Use the =/highlight= variation to see the changes highlighted.
|
|
||||||
*** Agent Sidebar
|
|
||||||
Using the [[https://github.com/cmacrae/agent-shell-sidebar][agent-shell-sidebar]] project, we can easily open/close the Agent buffer window:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(use-package agent-shell-sidebar
|
|
||||||
:after agent-shell
|
|
||||||
:straight (:host github :repo "cmacrae/agent-shell-sidebar")
|
|
||||||
:bind (("s-i" . agent-shell-sidebar-toggle-focus)
|
|
||||||
("s-I" . agent-shell-sidebar-toggle)))
|
|
||||||
#+END_SRC
|
|
||||||
*** Claude Code
|
|
||||||
To begin install the dependencies:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp :tangle no
|
|
||||||
(use-package agent-shell
|
|
||||||
:ensure-system-package
|
|
||||||
((claude . "brew install claude-code")
|
|
||||||
(claude-agent-acp . "npm install -g @agentclientprotocol/claude-agent-acp")))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
And point Emacs to it:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(use-package agent-shell
|
|
||||||
:config
|
|
||||||
(setq agent-shell-preferred-agent-config (agent-shell-anthropic-make-claude-code-config)
|
|
||||||
agent-shell-anthropic-claude-acp-command
|
|
||||||
`,(file-expand-wildcards "/opt/homebrew/bin/claude-agent-acp")))
|
|
||||||
;; /opt/homebrew/Cellar/node/26.0.0/bin/claude-agent-acp
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
*** Cursor
|
|
||||||
Cursor, through ACP constantly drops its token on the floor. This annoyance makes me want to use something else.
|
|
||||||
|
|
||||||
The Cursor interface requires installing the ACP libraries, and a suggestion to install the [[https://github.com/blowmage/cursor-agent-acp-npm][cursor-agent-acp]] project:
|
|
||||||
|
|
||||||
#+BEGIN_SRC sh
|
|
||||||
npm install -g @blowmage/cursor-agent-acp
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Appears we need to install the [[https://github.com/zalab-inc/cursor_agent][cursor-agent]] CLI as well. Install it with this command:
|
|
||||||
|
|
||||||
#+BEGIN_SRC sh
|
|
||||||
curl https://cursor.com/install -fsSL | bash
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Or can we do both of these through Emacs:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp :tangle no
|
|
||||||
(use-package agent-shell
|
|
||||||
:ensure-system-package
|
|
||||||
((cursor-agent . "brew install cursor-cli")
|
|
||||||
(claude-agent-acp . "npm install -g @blowmage/cursor-agent-acp")))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Change the *default browser* to Workday’s favorite, Chrome, and login:
|
|
||||||
|
|
||||||
#+BEGIN_SRC sh
|
|
||||||
cursor-agent login
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Now the Emacs configuration:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp :tangle no
|
|
||||||
(use-package agent-shell
|
|
||||||
:custom
|
|
||||||
(agent-shell-cursor-command `,(file-expand-wildcards "/opt/homebrew/Cellar/node/*/bin/cursor-agent-acp"))
|
|
||||||
|
|
||||||
:config
|
|
||||||
(setq agent-shell-preferred-agent-config (agent-shell-cursor-make-agent-config)))
|
|
||||||
#+END_SRC
|
|
||||||
*** Gemini
|
|
||||||
First install the [[https://github.com/google-gemini/gemini-cli][gemini-cli]]:
|
|
||||||
#+BEGIN_SRC sh
|
|
||||||
brew install gemini-cli
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
And let’s make that the default now:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp :tangle no
|
|
||||||
(use-package agent-shell
|
|
||||||
:config
|
|
||||||
(setq agent-shell-preferred-agent-config (agent-shell-google-make-gemini-config)))
|
|
||||||
#+END_SRC
|
|
||||||
** AI Code Interface
|
|
||||||
While the =agent-shell= offers a /vibe-codey/ interface to Chatbots, the [[https://github.com/tninja/ai-code-interface.el][ai-code-interface]] offers a more /programmatic/ interface.
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(use-package ai-code
|
|
||||||
:straight (:host github :repo "tninja/ai-code-interface.el")
|
|
||||||
:config
|
|
||||||
;; use codex as backend, other options are 'claude-code, 'gemini,
|
|
||||||
;; 'github-copilot-cli, 'opencode, 'grok, 'cursor, 'kiro,
|
|
||||||
;; 'codebuddy, 'aider, 'eca, 'agent-shell, 'claude-code-ide,
|
|
||||||
;; 'claude-code-el
|
|
||||||
(ai-code-set-backend 'gemini)
|
|
||||||
|
|
||||||
;; Enable global keybinding for the main menu
|
|
||||||
(global-set-key (kbd "C-c a") #'ai-code-menu)
|
|
||||||
|
|
||||||
;; Optional: Enable @ file completion in comments and AI sessions
|
|
||||||
(ai-code-prompt-filepath-completion-mode 1)
|
|
||||||
|
|
||||||
;; Optional: Ask AI to run test after code changes, for a tighter build-test loop
|
|
||||||
;; (setq ai-code-auto-test-type 'ask-me)
|
|
||||||
|
|
||||||
;; Optional: In AI session buffers, SPC in Evil normal state triggers the prompt-enter UI
|
|
||||||
;; (with-eval-after-load 'evil (ai-code-backends-infra-evil-setup))
|
|
||||||
|
|
||||||
;; Optional: Set up Magit integration for AI commands in Magit popups
|
|
||||||
(with-eval-after-load 'magit
|
|
||||||
(ai-code-magit-setup-transients)))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
* Git and Magit
|
* Git and Magit
|
||||||
Can not live without [[https://magit.vc/][Magit]], a Git porcelain for Emacs. I stole the bulk of this work from Doom Emacs.
|
Can not live without [[https://magit.vc/][Magit]], a Git porcelain for Emacs. I stole the bulk of this work from Doom Emacs.
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
|
(use-package magit)
|
||||||
(use-package magit
|
(use-package magit
|
||||||
|
:load-path "~/.emacs.d/repos/magit"
|
||||||
;; See https://github.com/magit/magit/wiki/Emacsclient for why we need to set:
|
;; See https://github.com/magit/magit/wiki/Emacsclient for why we need to set:
|
||||||
:custom (with-editor-emacsclient-executable "emacsclient")
|
:custom (with-editor-emacsclient-executable "emacsclient")
|
||||||
|
|
||||||
|
|
@ -300,7 +90,7 @@ Can not live without [[https://magit.vc/][Magit]], a Git porcelain for Emacs. I
|
||||||
** VC Diff Highlight
|
** VC Diff Highlight
|
||||||
The [[https://github.com/dgutov/diff-hl][diff-hl project]], while more active, has more features than the [[https://github.com/syohex/emacs-git-gutter-fringe][git-gutter-fringe]] project.
|
The [[https://github.com/dgutov/diff-hl][diff-hl project]], while more active, has more features than the [[https://github.com/syohex/emacs-git-gutter-fringe][git-gutter-fringe]] project.
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp :tangle no
|
||||||
(use-package diff-hl
|
(use-package diff-hl
|
||||||
:custom
|
:custom
|
||||||
(diff-hl-side 'right)
|
(diff-hl-side 'right)
|
||||||
|
|
@ -313,7 +103,7 @@ The [[https://github.com/dgutov/diff-hl][diff-hl project]], while more active, h
|
||||||
|
|
||||||
Turning on the mode, as well as binding some new /leader/ keys:
|
Turning on the mode, as well as binding some new /leader/ keys:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp :tangle no
|
||||||
(use-package diff-hl
|
(use-package diff-hl
|
||||||
:config
|
:config
|
||||||
(global-diff-hl-mode)
|
(global-diff-hl-mode)
|
||||||
|
|
@ -334,7 +124,7 @@ Turning on the mode, as well as binding some new /leader/ keys:
|
||||||
|
|
||||||
This project (and others) can use repeat mode, but
|
This project (and others) can use repeat mode, but
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp :tangle no
|
||||||
(repeat-mode)
|
(repeat-mode)
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
|
@ -427,7 +217,7 @@ The crucial parts of this helper function are that we "wash" the result using =a
|
||||||
The functions below depend on [[help:magit-thing-at-point][magit-thing-at-point]], and this depends on the [[https://sr.ht/~pkal/compat/][compat]] library, so let’s grab that stuff:
|
The functions below depend on [[help:magit-thing-at-point][magit-thing-at-point]], and this depends on the [[https://sr.ht/~pkal/compat/][compat]] library, so let’s grab that stuff:
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp :tangle no
|
||||||
(use-package compat
|
(use-package compat
|
||||||
:straight (:host github :repo "emacs-straight/compat"))
|
;; :vc (:url "https://github.com/emacs-straight/compat"))
|
||||||
|
|
||||||
(use-package magit-section
|
(use-package magit-section
|
||||||
:commands magit-thing-at-point)
|
:commands magit-thing-at-point)
|
||||||
|
|
@ -523,7 +313,7 @@ Using the [[https://github.com/emacsmirror/gist][gist package]] to write code sn
|
||||||
The gist project depends on the [[https://github.com/sigma/gh.el][gh library]]. There seems to be a problem with it.
|
The gist project depends on the [[https://github.com/sigma/gh.el][gh library]]. There seems to be a problem with it.
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp :tangle no
|
||||||
(use-package gh
|
(use-package gh
|
||||||
:straight (:host github :repo "sigma/gh.el"))
|
;; :vc (:url "https://github.com/sigma/gh.el"))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** Forge
|
** Forge
|
||||||
|
|
@ -555,51 +345,15 @@ Let's extend Magit with [[https://github.com/magit/forge][Magit Forge]] for work
|
||||||
Every /so often/, pop over to the following URLs and generate a new token where the *Note* is =forge=, and then copy that into the [[file:~/.authinfo.gpg][~/.authinfo.gpg]]:
|
Every /so often/, pop over to the following URLs and generate a new token where the *Note* is =forge=, and then copy that into the [[file:~/.authinfo.gpg][~/.authinfo.gpg]]:
|
||||||
- [[https://gitlab.com/-/user_settings/personal_access_tokens][Gitlab]]
|
- [[https://gitlab.com/-/user_settings/personal_access_tokens][Gitlab]]
|
||||||
- [[https://github.com/settings/tokens][Github]]
|
- [[https://github.com/settings/tokens][Github]]
|
||||||
|
and make sure this works:
|
||||||
|
|
||||||
Make sure this works:
|
#+begin_src emacs-lisp :tangle no :results replace
|
||||||
|
|
||||||
#+begin_src emacs-lisp :tangle no :results replace
|
|
||||||
(ghub-request "GET" "/user" nil
|
(ghub-request "GET" "/user" nil
|
||||||
:forge 'github
|
:forge 'github
|
||||||
:host "api.github.com"
|
:host "api.github.com"
|
||||||
:username "howardabrams"
|
:username "howardabrams"
|
||||||
:auth 'forge)
|
:auth 'forge)
|
||||||
|
#+end_src
|
||||||
#+end_src
|
|
||||||
|
|
||||||
** Magit Github
|
|
||||||
Jonathan Chu’s [[https://github.com/jonathanchu/magit-gh][magit-gh]] project is /simpler/ than [[#Forge][Forge]] (see [[https://jonathanchu.is/posts/introducing-magit-gh/][this essay]] for details).
|
|
||||||
|
|
||||||
First, install and configure the [[https://github.com/cli/cli/blob/trunk/docs/install_macos.md#homebrew][Github CLI]] program.
|
|
||||||
#+BEGIN_SRC sh
|
|
||||||
brew install gh
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Create a =GITHUB_TOKEN= under =/settings/tokens=.
|
|
||||||
The required scopes are =repo=, =read:org=, =admin:public_key=.
|
|
||||||
Also, these don’t last long, so return and regenerate routinely.
|
|
||||||
|
|
||||||
Next, [[https://cli.github.com/manual/gh_auth_login][configure it]] with:
|
|
||||||
#+BEGIN_SRC sh
|
|
||||||
gh auth login --hostname ${GH_HOST:-github.com}
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
And pass in the =GITHUB_TOKEN= environment variable.
|
|
||||||
|
|
||||||
Verify that this works:
|
|
||||||
#+BEGIN_SRC sh
|
|
||||||
gh pr list
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
With the =gh= CLI working, we can install and use this project:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(use-package magit-gh
|
|
||||||
:after magit
|
|
||||||
:straight (:type git :host github :repo "jonathanchu/magit-gh"))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
|
|
||||||
** Pushing is Bad
|
** Pushing is Bad
|
||||||
Pushing directly to the upstream branch is /bad form/, as one should create a pull request, etc. To prevent an accidental push, we /double-check/ first:
|
Pushing directly to the upstream branch is /bad form/, as one should create a pull request, etc. To prevent an accidental push, we /double-check/ first:
|
||||||
|
|
||||||
|
|
@ -613,11 +367,10 @@ Pushing directly to the upstream branch is /bad form/, as one should create a pu
|
||||||
(magit-get "branch" branch "remote"))))
|
(magit-get "branch" branch "remote"))))
|
||||||
(user-error "Push to upstream aborted by user"))))
|
(user-error "Push to upstream aborted by user"))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** Github Search?
|
** Github Search?
|
||||||
Wanna see an example of how other’s use a particular function?
|
Wanna see an example of how other’s use a particular function?
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defun ha-github-code-search(&optional search)
|
(defun my-github-search(&optional search)
|
||||||
(interactive (list (read-string "Search: " (thing-at-point 'symbol))))
|
(interactive (list (read-string "Search: " (thing-at-point 'symbol))))
|
||||||
(let* ((language (cond ((eq major-mode 'python-mode) "Python")
|
(let* ((language (cond ((eq major-mode 'python-mode) "Python")
|
||||||
((eq major-mode 'emacs-lisp-mode) "Emacs Lisp")
|
((eq major-mode 'emacs-lisp-mode) "Emacs Lisp")
|
||||||
|
|
@ -658,6 +411,7 @@ Web pages look pretty good with EWW, but I'm having difficulty getting it to ren
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package eww
|
(use-package eww
|
||||||
|
:after major-mode-hydra
|
||||||
:init
|
:init
|
||||||
(setq browse-url-browser-function 'eww-browse-url
|
(setq browse-url-browser-function 'eww-browse-url
|
||||||
browse-url-secondary-browser-function 'browse-url-default-browser
|
browse-url-secondary-browser-function 'browse-url-default-browser
|
||||||
|
|
@ -756,7 +510,8 @@ Make my EWW browsers /look/ like an Org file with the [[https://github.com/cheny
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package shrface
|
(use-package shrface
|
||||||
:straight (:host github :repo "chenyanming/shrface")
|
;; :vc (:url "https://github.com/chenyanming/shrface")
|
||||||
|
:after major-mode-hydra
|
||||||
:config
|
:config
|
||||||
(shrface-basic)
|
(shrface-basic)
|
||||||
;; (shrface-trial)
|
;; (shrface-trial)
|
||||||
|
|
@ -891,7 +646,7 @@ Instead I use Emacs' built-in directory lister (which accepts the standard, =dir
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package ls-lisp
|
(use-package ls-lisp
|
||||||
:straight (:type built-in)
|
|
||||||
:config
|
:config
|
||||||
(setq ls-lisp-use-insert-directory-program nil
|
(setq ls-lisp-use-insert-directory-program nil
|
||||||
dired-listing-switches
|
dired-listing-switches
|
||||||
|
|
@ -902,7 +657,7 @@ And [[https://www.masteringemacs.org/article/dired-shell-commands-find-xargs-rep
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package dired-x
|
(use-package dired-x
|
||||||
:straight (:type built-in))
|
)
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
The advantage of =dired-x= is the ability to have [[https://www.emacswiki.org/emacs/DiredExtra#Dired_X][shell command guessing]] when selecting one or more files, and running a shell command on them with ~!~ or ~&~.
|
The advantage of =dired-x= is the ability to have [[https://www.emacswiki.org/emacs/DiredExtra#Dired_X][shell command guessing]] when selecting one or more files, and running a shell command on them with ~!~ or ~&~.
|
||||||
|
|
@ -916,7 +671,7 @@ The [[https://github.com/alexluigit/dirvish][dirvish]] project aims to make a pr
|
||||||
I’m beginning with dirvish to use the [[https://github.com/alexluigit/dirvish/blob/main/docs/CUSTOMIZING.org][sample configuration]] and change it:
|
I’m beginning with dirvish to use the [[https://github.com/alexluigit/dirvish/blob/main/docs/CUSTOMIZING.org][sample configuration]] and change it:
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp :tangle no
|
||||||
(use-package dirvish
|
(use-package dirvish
|
||||||
:straight (:host github :repo "alexluigit/dirvish")
|
;; :vc (:url "https://github.com/alexluigit/dirvish")
|
||||||
:init (dirvish-override-dired-mode)
|
:init (dirvish-override-dired-mode)
|
||||||
|
|
||||||
:custom
|
:custom
|
||||||
|
|
@ -1053,6 +808,28 @@ I do want to change a couple of bindings, as ~j~ to pull up a =completing-read=
|
||||||
(define-key dired-mode-map (kbd "n") 'evil-search-next)
|
(define-key dired-mode-map (kbd "n") 'evil-search-next)
|
||||||
(define-key dired-mode-map (kbd ",") 'major-mode-hydras/dired-mode/body)
|
(define-key dired-mode-map (kbd ",") 'major-mode-hydras/dired-mode/body)
|
||||||
#+end_src
|
#+end_src
|
||||||
|
* Annotations
|
||||||
|
Let's try [[https://github.com/bastibe/annotate.el][annotate-mode]], which allows you to drop "notes" and then move to them (yes, serious overlap with bookmarks, which we will return to).
|
||||||
|
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(use-package annotate
|
||||||
|
:config
|
||||||
|
(ha-leader
|
||||||
|
"t A" '("annotations" . annotate-mode)
|
||||||
|
|
||||||
|
"n" '(:ignore t :which-key "notes")
|
||||||
|
"n a" '("toggle mode" . annotate-mode)
|
||||||
|
"n n" '("annotate" . annotate-annotate)
|
||||||
|
"n d" '("delete" . annotate-delete)
|
||||||
|
"n s" '("summary" . annotate-show-annotation-summary)
|
||||||
|
"n j" '("next" . annotate-goto-next-annotation)
|
||||||
|
"n k" '("prev" . annotate-goto-previous-annotation)
|
||||||
|
|
||||||
|
;; If a shift binding isn't set, it defaults to non-shift version
|
||||||
|
;; Use SPC N N to jump to the next error:
|
||||||
|
"n N" '("next error" . flycheck-next-error)))
|
||||||
|
#+end_src
|
||||||
|
Keep the annotations simple, almost /tag-like/, and then the summary allows you to display them.
|
||||||
* Keepass
|
* Keepass
|
||||||
Use the [[https://github.com/ifosch/keepass-mode][keepass-mode]] to view a /read-only/ version of my Keepass file in Emacs:
|
Use the [[https://github.com/ifosch/keepass-mode][keepass-mode]] to view a /read-only/ version of my Keepass file in Emacs:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
|
|
@ -1100,6 +877,8 @@ The [[Evil Collection][evil-collection]] package adds the following keybindings:
|
||||||
I’d like write notes in org files that link to the PDFs (and maybe visa versa), using the [[https://github.com/weirdNox/org-noter][org-noter]] package:
|
I’d like write notes in org files that link to the PDFs (and maybe visa versa), using the [[https://github.com/weirdNox/org-noter][org-noter]] package:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package org-noter
|
(use-package org-noter
|
||||||
|
:load-path "/User/howard.abrams/.emacs.d/repos/org-noter"
|
||||||
|
:after major-mode-hydra
|
||||||
:config
|
:config
|
||||||
(major-mode-hydra-define org-noter-doc-mode-map nil
|
(major-mode-hydra-define org-noter-doc-mode-map nil
|
||||||
("Notes"
|
("Notes"
|
||||||
|
|
@ -1133,5 +912,4 @@ Let's provide a name so that the file can be required:
|
||||||
|
|
||||||
# Local Variables:
|
# Local Variables:
|
||||||
# eval: (add-hook 'after-save-hook #'org-babel-tangle t t)
|
# eval: (add-hook 'after-save-hook #'org-babel-tangle t t)
|
||||||
# jinx-local-words: "Emacsians VSCode"
|
|
||||||
# End:
|
# End:
|
||||||
|
|
|
||||||
171
ha-aux-apps.org
171
ha-aux-apps.org
|
|
@ -26,12 +26,12 @@ A literate programming file for helper apps in Emacs.
|
||||||
#+end_src
|
#+end_src
|
||||||
* Introduction
|
* Introduction
|
||||||
The following applications are not needed. I alternate between trying to /stay in Emacs/ taking advantage of the consistent interface, and using a stand-alone app on my Workday computer.
|
The following applications are not needed. I alternate between trying to /stay in Emacs/ taking advantage of the consistent interface, and using a stand-alone app on my Workday computer.
|
||||||
* Social Media Apps
|
* Federation
|
||||||
** Mastodon
|
** Mastodon
|
||||||
Glad to see the 2FA feature is working on the [[https://codeberg.org/martianh/mastodon.el][mastodon.el]] project, and even more glad to see the great birdland diaspora arrive in the land of the toots.
|
Glad to see the 2FA feature is working on the [[https://codeberg.org/martianh/mastodon.el][mastodon.el]] project, and even more glad to see the great birdland diaspora arrive in the land of the toots.
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package mastodon
|
(use-package mastodon
|
||||||
:straight (:host codeberg :repo "martianh/mastodon.el")
|
;; :vc (:url "https://codeberg.org/martianh/mastodon.el")
|
||||||
:init
|
:init
|
||||||
(setq mastodon-instance-url "https://pdx.social"
|
(setq mastodon-instance-url "https://pdx.social"
|
||||||
mastodon-active-user "howard"))
|
mastodon-active-user "howard"))
|
||||||
|
|
@ -41,7 +41,7 @@ I would like a dedicate perspective to Mastodon, and I would like a leader key s
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package mastodon
|
(use-package mastodon
|
||||||
:config
|
:config
|
||||||
(ha-leader "a m" `("mastodon" . ,(lambda () (interactive) (ha-tab-bar-new "mastodon" #'mastodon))))
|
(ha-leader "a m" `("mastodon" . ,(ha-app-perspective "mastodon" #'mastodon)))
|
||||||
|
|
||||||
(defun ha-mastodon-scroll-or-more ()
|
(defun ha-mastodon-scroll-or-more ()
|
||||||
"Scroll a window, and at the end, get more entries in timeline."
|
"Scroll a window, and at the end, get more entries in timeline."
|
||||||
|
|
@ -53,35 +53,35 @@ I would like a dedicate perspective to Mastodon, and I would like a leader key s
|
||||||
|
|
||||||
(use-package major-mode-hydra
|
(use-package major-mode-hydra
|
||||||
:config
|
:config
|
||||||
(major-mode-hydra-define mastodon-mode (:quit-key "q")
|
(major-mode-hydra-define mastodon-mode nil
|
||||||
("Timelines"
|
("Timelines"
|
||||||
(("u" mastodon-tl-update "update")
|
(("u" mastodon-tl--update "update")
|
||||||
("F" mastodon-tl-get-federated-timeline "Federated")
|
("F" mastodon-tl--get-federated-timeline "Federated")
|
||||||
("H" mastodon-tl-get-home-timeline "Home")
|
("H" mastodon-tl--get-home-timeline "Home")
|
||||||
("L" mastodon-tl-get-local-timeline "Local")
|
("L" mastodon-tl--get-local-timeline "Local")
|
||||||
("T" mastodon-tl-get-tag-timeline "Hashtag"))
|
("T" mastodon-tl--get-tag-timeline "Hashtag"))
|
||||||
"Specials"
|
"Specials"
|
||||||
(("M" mastodon-notifications--get-mentions "Mentions")
|
(("M" mastodon-notifications--get-mentions "Mentions")
|
||||||
("N" mastodon-notifications-get "Notifications")
|
("N" mastodon-notifications-get "Notifications")
|
||||||
("A" mastodon-tl-followed-tags-timeline "All Tags")
|
("A" mastodon-tl--followed-tags-timeline "All Tags")
|
||||||
("S" mastodon-profile-view-bookmarks "Saved bookmarks")
|
("S" mastodon-profile--view-bookmarks "Saved bookmarks")
|
||||||
("O" mastodon-profile-my-profile "My Profile"))
|
("O" mastodon-profile--my-profile "My Profile"))
|
||||||
"Post"
|
"Post"
|
||||||
(("c" mastodon-toot "Compose toot")
|
(("c" mastodon-toot "Compose toot")
|
||||||
("e" mastodon-toot-edit-toot-at-point "Edit toot")
|
("e" mastodon-toot--edit-toot-at-point "Edit toot")
|
||||||
("t" mastodon-tl-thread "Read thread")
|
("t" mastodon-tl--thread "Read thread")
|
||||||
("r" mastodon-toot-reply "Reply")
|
("r" mastodon-toot--reply "Reply")
|
||||||
("m" mastodon-tl-dm-user "Direct Msg")
|
("m" mastodon-tl--dm-user "Direct Msg")
|
||||||
("d" mastodon-toot-delete-toot "Delete"))
|
("d" mastodon-toot--delete-toot "Delete"))
|
||||||
"Toot"
|
"Toot"
|
||||||
(("f" mastodon-toot-toggle-favourite "Favorite")
|
(("f" mastodon-toot--toggle-favourite "Favorite")
|
||||||
("b" mastodon-toot-toggle-boost "Boost")
|
("b" mastodon-toot--toggle-boost "Boost")
|
||||||
("s" mastodon-toot-toggle-bookmark "Save")
|
("s" mastodon-toot--toggle-bookmark "Save")
|
||||||
("y" mastodon-toot-copy-toot-url "Copy URL")
|
("y" mastodon-toot--copy-toot-url "Copy URL")
|
||||||
("Y" mastodon-toot-copy-toot-text "Copy text"))
|
("Y" mastodon-toot--copy-toot-text "Copy text"))
|
||||||
"Navigation"
|
"Navigation"
|
||||||
(("n" mastodon-tl-next-tab-item "next" :color pink)
|
(("n" mastodon-tl--next-tab-item "next" :color pink)
|
||||||
("p" mastodon-tl-previous-tab-item "previous" :color pink)
|
("p" mastodon-tl--previous-tab-item "previous" :color pink)
|
||||||
("," ha-mastodon-scroll-or-more "...more" :color pink))))))
|
("," ha-mastodon-scroll-or-more "...more" :color pink))))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
|
|
@ -95,8 +95,7 @@ Let’s turn on non-fixed-width fonts to make everything easier to read:
|
||||||
Yet another encrypted chat/VoIP client-server, but unlike Signal and Telegram, [[matrix.org][Matrix]] is act ually open source. In other words, a project for nerds. We’ll be using Alphapapa’s latest [[https://github.com/alphapapa/ement.el][ement]] project.
|
Yet another encrypted chat/VoIP client-server, but unlike Signal and Telegram, [[matrix.org][Matrix]] is act ually open source. In other words, a project for nerds. We’ll be using Alphapapa’s latest [[https://github.com/alphapapa/ement.el][ement]] project.
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package ement
|
(use-package ement
|
||||||
:straight (:host github :repo "alphapapa/ement.el")
|
;; :vc (:url "https://github.com/alphapapa/ement.el")
|
||||||
:after major-mode-hydra
|
|
||||||
:config
|
:config
|
||||||
(major-mode-hydra-define ement-room-mode (:quit-key "q")
|
(major-mode-hydra-define ement-room-mode (:quit-key "q")
|
||||||
("Send"
|
("Send"
|
||||||
|
|
@ -118,6 +117,7 @@ Yet another encrypted chat/VoIP client-server, but unlike Signal and Telegram, [
|
||||||
(ement-connect :user-id username
|
(ement-connect :user-id username
|
||||||
:password password
|
:password password
|
||||||
:uri-prefix "https://matrix.org"))
|
:uri-prefix "https://matrix.org"))
|
||||||
|
|
||||||
(ha-leader
|
(ha-leader
|
||||||
"a x S" '("send" . ement-send-direct-message)
|
"a x S" '("send" . ement-send-direct-message)
|
||||||
"a x s" '("send" . ement-room-send-message)
|
"a x s" '("send" . ement-room-send-message)
|
||||||
|
|
@ -143,10 +143,23 @@ Perhaps we can make it more attractive:
|
||||||
ement-room-timestamp-format ""
|
ement-room-timestamp-format ""
|
||||||
ement-room-send-message-filter 'ement-room-send-org-filter))
|
ement-room-send-message-filter 'ement-room-send-org-filter))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
* Other
|
||||||
|
** Twitter
|
||||||
|
The venerable [[https://github.com/hayamiz/twittering-mode/tree/master][twittering-mode]] allows me to follow all the twits.
|
||||||
|
#+begin_src emacs-lisp :tangle no
|
||||||
|
(use-package twittering-mode
|
||||||
|
:init
|
||||||
|
(setq twittering-use-master-password t
|
||||||
|
epa-pinentry-mode 'loopback)
|
||||||
|
:config
|
||||||
|
(defalias 'epa--decode-coding-string 'decode-coding-string)
|
||||||
|
(ha-leader "a t" '("twitter" . twit)))
|
||||||
|
#+end_src
|
||||||
|
And we are no longer using this package.
|
||||||
** Telega
|
** Telega
|
||||||
I'm thinking the [[https://zevlg.github.io/telega.el/][Telega package]] would be better than Bitlbee for Telegram communication.
|
I'm thinking the [[https://zevlg.github.io/telega.el/][Telega package]] would be better than Bitlbee for Telegram communication.
|
||||||
|
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp
|
||||||
(use-package telega
|
(use-package telega
|
||||||
:config
|
:config
|
||||||
(setq telega-chat-show-avatars nil
|
(setq telega-chat-show-avatars nil
|
||||||
|
|
@ -162,21 +175,21 @@ I'm thinking the [[https://zevlg.github.io/telega.el/][Telega package]] would be
|
||||||
(when (fboundp 'evil-insert-state)
|
(when (fboundp 'evil-insert-state)
|
||||||
(add-hook 'telega-chat-mode-hook 'evil-insert-state))
|
(add-hook 'telega-chat-mode-hook 'evil-insert-state))
|
||||||
|
|
||||||
(ha-leader "a t" `("telega" . ,(lambda () (interactive) (ha-tab-bar-new "telega" #'telega)))))
|
(ha-leader "a t" `("telega" . ,(ha-app-perspective "telega" #'telega))))
|
||||||
#+end_src
|
#+end_src
|
||||||
For some reason, you need [[https://github.com/Fanael/rainbow-identifiers][rainbow-identifiers]] to work, oh, I guess the docs state this.
|
For some reason, you need [[https://github.com/Fanael/rainbow-identifiers][rainbow-identifiers]] to work, oh, I guess the docs state this.
|
||||||
|
|
||||||
In the Telega chat’s, let’s turn on non-fixed-width fonts:
|
In the Telega chat’s, let’s turn on non-fixed-width fonts:
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp
|
||||||
(use-package telega
|
(use-package telega
|
||||||
:hook (telega-chat-mode . mixed-pitch-mode))
|
:hook (telega-chat-mode . mixed-pitch-mode))
|
||||||
#+end_src
|
#+end_src
|
||||||
* RPG DM
|
* RPG DM
|
||||||
Been working on my [[https://gitlab.com/howardabrams/emacs-rpgdm][RPG DM project]] for getting Emacs helping as a /Dungeon Master's Assistant/. The idea is to be able to roll dice and whatnot. What I find most useful is the [[https://gitlab.com/howardabrams/emacs-rpgdm/-/blob/main/rpgdm-tables.el][random tables]].
|
Been working on my [[https://gitlab.com/howardabrams/emacs-rpgdm][RPG DM project]] for getting Emacs helping as a /Dungeon Master's Assistant/. The idea is to be able to roll dice and whatnot. What I find most useful is the [[https://gitlab.com/howardabrams/emacs-rpgdm/-/blob/main/rpgdm-tables.el][random tables]].
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp
|
||||||
(when (f-directory? "~/src/emacs-rpgdm")
|
(when (f-directory? "~/src/emacs-rpgdm")
|
||||||
(use-package rpgdm
|
(use-package rpgdm
|
||||||
:straight (:local-repo "~/src/emacs-rpgdm")
|
:load-path "~/src/emacs-rpgdm"
|
||||||
:commands (rpgdm-mode rpgdm-tables-load)
|
:commands (rpgdm-mode rpgdm-tables-load)
|
||||||
:init (setq rpgdm-base (expand-file-name "~/src/emacs-rpgdm"))
|
:init (setq rpgdm-base (expand-file-name "~/src/emacs-rpgdm"))
|
||||||
:config (ha-leader "t D" '("rpg dm" . rpgdm-mode))))
|
:config (ha-leader "t D" '("rpg dm" . rpgdm-mode))))
|
||||||
|
|
@ -186,7 +199,7 @@ Working on my new replacement of my DM code:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(when (f-directory? "~/src/emacs-rpgtk")
|
(when (f-directory? "~/src/emacs-rpgtk")
|
||||||
(use-package rpgtk
|
(use-package rpgtk
|
||||||
:straight (:local-repo "~/src/emacs-rpgtk")
|
:load-path "~/src/emacs-rpgtk"
|
||||||
:after hydra
|
:after hydra
|
||||||
;; :commands (rpgtk-mode rpgtk-tables-load rpgtk-dice rpgtk-message)
|
;; :commands (rpgtk-mode rpgtk-tables-load rpgtk-dice rpgtk-message)
|
||||||
:custom
|
:custom
|
||||||
|
|
@ -204,7 +217,7 @@ And my [[https://gitlab.com/howardabrams/emacs-ironsworn][new Ironsworn project]
|
||||||
(when (f-directory? "~/src/emacs-ironsworn")
|
(when (f-directory? "~/src/emacs-ironsworn")
|
||||||
(use-package rpgdm-ironsworn
|
(use-package rpgdm-ironsworn
|
||||||
:after rpgdm
|
:after rpgdm
|
||||||
:straight (:local-repo "~/src/emacs-ironsworn")
|
:load-path "~/src/emacs-ironsworn"
|
||||||
:init
|
:init
|
||||||
(setq rpgdm-ironsworn-project (expand-file-name "~/src/emacs-ironsworn")
|
(setq rpgdm-ironsworn-project (expand-file-name "~/src/emacs-ironsworn")
|
||||||
;; Ignore org links that call my RPG functions:
|
;; Ignore org links that call my RPG functions:
|
||||||
|
|
@ -214,89 +227,17 @@ And my [[https://gitlab.com/howardabrams/emacs-ironsworn][new Ironsworn project]
|
||||||
#+end_src
|
#+end_src
|
||||||
The project is interesting, and I should record a tutorial how to use it.
|
The project is interesting, and I should record a tutorial how to use it.
|
||||||
* Playing Music
|
* Playing Music
|
||||||
After building a NAS out of a Raspberry Pi to hold my /ripped/ CD collection, let’s control my playlists from Emacs.
|
Why not? Let’s see if the [[https://github.com/isamert/empv.el][empv]] project is sufficient. First install =mpv=, as in:
|
||||||
A more /rich/ experience than the [[https://github.com/isamert/empv.el][empv]] project is Álvaro’s [[https://github.com/xenodium/ready-player][ready-player]]. First install [[https://mpv.io/][mpv]]:
|
#+begin_src sh
|
||||||
|
|
||||||
#+BEGIN_SRC sh
|
|
||||||
brew install mpv
|
brew install mpv
|
||||||
#+END_SRC
|
#+end_src
|
||||||
|
What else?
|
||||||
And make sure we can play it:
|
#+begin_src emacs-lisp
|
||||||
|
(use-package empv
|
||||||
#+BEGIN_SRC sh
|
;; :vc (:url "https://github.com/isamert/empv.el")
|
||||||
mpv "/Volumes/music/Steely Dan/Aja/01 Black Cow.mp3"
|
:general (ha-leader
|
||||||
#+END_SRC
|
"a p" '(empv-map :wk "play music")))
|
||||||
|
#+end_src
|
||||||
And stream my favorite local Jazz radio station:
|
|
||||||
|
|
||||||
#+BEGIN_SRC sh
|
|
||||||
mpv --terminal --force-window=no --no-resume-playback \
|
|
||||||
'https://ais-sa3.cdnstream1.com/2442_128.aac/playlist.m3u8' &
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
And then the configuration:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(use-package ready-player
|
|
||||||
:straight (:type git :host github :repo "xenodium/ready-player")
|
|
||||||
:config
|
|
||||||
(when (eq system-type 'darwin)
|
|
||||||
(set-fontset-font t nil "SF Pro Display" nil 'append)
|
|
||||||
(ready-player-macos-use-sf-symbols))
|
|
||||||
|
|
||||||
(setq ready-player-my-media-collection-location "/Volumes/music")
|
|
||||||
|
|
||||||
(defun ha-ready-player-load-playlist ()
|
|
||||||
"Open a playlist in the correct location."
|
|
||||||
(interactive)
|
|
||||||
(let ((default-directory
|
|
||||||
(expand-file-name "Playlists/"
|
|
||||||
ready-player-my-media-collection-location)))
|
|
||||||
(setq ready-player-shuffle t)
|
|
||||||
(call-interactively 'ready-player-load-m3u-playlist)))
|
|
||||||
|
|
||||||
(transient-append-suffix 'ready-player-menu "?"
|
|
||||||
'("P" "Load Playlist" ha-ready-player-load-playlist))
|
|
||||||
|
|
||||||
(defun ha-ready-player-load-directory ()
|
|
||||||
"Open a directory in the correct location."
|
|
||||||
(interactive)
|
|
||||||
(let ((default-directory
|
|
||||||
(file-name-as-directory ready-player-my-media-collection-location)))
|
|
||||||
(setq ready-player-shuffle nil)
|
|
||||||
(call-interactively 'ready-player-load-directory)))
|
|
||||||
|
|
||||||
(transient-append-suffix 'ready-player-menu "d"
|
|
||||||
'("A" "Play Album" ha-ready-player-load-directory))
|
|
||||||
|
|
||||||
(ha-leader "a r" '("ready player" . ready-player-menu))
|
|
||||||
(ready-player-mode +1))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Since ready-player doesn’t support [[https://github.com/xenodium/ready-player/issues/36][adjusting the volume]], I can, at least, set the /default volume/ low enough that my speaker adjustment affects it:
|
|
||||||
|
|
||||||
#+BEGIN_SRC conf :tangle ~/.config/mpv/mpv.conf
|
|
||||||
volume=60
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Would love to have the /playing/ media in a particular tab-bar and in a particular location.
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp :tangle no
|
|
||||||
(defun ha-ready-player-switch ()
|
|
||||||
"Switch to the buffer window of the ready-player."
|
|
||||||
(interactive)
|
|
||||||
(let* ((buf-list
|
|
||||||
(seq-filter (lambda (buf)
|
|
||||||
(string-match (rx string-start "ready-player: "
|
|
||||||
(+ (not "*")) string-end)
|
|
||||||
(buffer-name buf)))
|
|
||||||
(buffer-list)))
|
|
||||||
(buffer (car buf-list))
|
|
||||||
(tab (tab-bar-get-buffer-tab (buffer-name buffer) t)))
|
|
||||||
(if tab
|
|
||||||
(tab-bar-switch-to-tab (alist-get 'name tab))
|
|
||||||
(tab-bar-switch-to-tab "main"))))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
* Technical Artifacts :noexport:
|
* Technical Artifacts :noexport:
|
||||||
Let's =provide= a name so we can =require= this file:
|
Let's =provide= a name so we can =require= this file:
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#+TITLE: Capturing Notes with Org
|
#+title: Capturing Notes with Org
|
||||||
#+AUTHOR: Howard X. Abrams
|
#+author: Howard X. Abrams
|
||||||
#+DATE: 2020-09-18
|
#+date: 2020-09-18
|
||||||
#+TAGS: emacs org
|
#+tags: emacs org
|
||||||
|
|
||||||
A literate programming file for configuring org for capturing notes.
|
A literate programming file for configuring org for capturing notes.
|
||||||
|
|
||||||
|
|
@ -233,7 +233,7 @@ And for even quicker work, we can have special scripts tied to special keybindin
|
||||||
The [[https://gitlab.com/aimebertrand/org-mac-link][org-mac-link]] project makes it easy to tell Emacs to retrieve information from other apps, e.g. the URL of the opened tab in Firefox.
|
The [[https://gitlab.com/aimebertrand/org-mac-link][org-mac-link]] project makes it easy to tell Emacs to retrieve information from other apps, e.g. the URL of the opened tab in Firefox.
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package org-mac-link
|
(use-package org-mac-link
|
||||||
:straight (:host gitlab :repo "aimebertrand/org-mac-link")
|
;; :vc (:url "https://gitlab.com/aimebertrand/org-mac-link")
|
||||||
:config
|
:config
|
||||||
(ha-leader "i" '("insert app info" . org-mac-link-get-link)))
|
(ha-leader "i" '("insert app info" . org-mac-link-get-link)))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
@ -244,7 +244,6 @@ I’m use [[https://github.com/deseven/icanhazshortcut][ICanHazShortcut]] to hav
|
||||||
#+begin_src sh :shebang "#!/bin/bash" :tangle ~/bin/emacs-capture-clock
|
#+begin_src sh :shebang "#!/bin/bash" :tangle ~/bin/emacs-capture-clock
|
||||||
/usr/bin/osascript ~/bin/emacs-capture-clock.scr
|
/usr/bin/osascript ~/bin/emacs-capture-clock.scr
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
But the following Applescript does the work:
|
But the following Applescript does the work:
|
||||||
#+begin_src applescript :sheband "#!/usr/bin/osascript" :tangle ~/bin/emacs-capture-clock.scr
|
#+begin_src applescript :sheband "#!/usr/bin/osascript" :tangle ~/bin/emacs-capture-clock.scr
|
||||||
tell application "System Events" to set theApp to name of first application process whose frontmost is true
|
tell application "System Events" to set theApp to name of first application process whose frontmost is true
|
||||||
|
|
@ -274,7 +273,6 @@ Now we have some goodies on the clipboard, and the script uses =emacsclient= to
|
||||||
(ignore-errors
|
(ignore-errors
|
||||||
(delete-frame)))
|
(delete-frame)))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Oh, and it this is from the Terminal program, let’s wrap it in a block:
|
Oh, and it this is from the Terminal program, let’s wrap it in a block:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defun ha-external-capture-code-to-org ()
|
(defun ha-external-capture-code-to-org ()
|
||||||
|
|
@ -290,10 +288,136 @@ Oh, and it this is from the Terminal program, let’s wrap it in a block:
|
||||||
(ignore-errors
|
(ignore-errors
|
||||||
(delete-frame)))
|
(delete-frame)))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
#+begin_src conf :tangle ~/.config/iCanHazShortcut/config.ini
|
||||||
|
[main]
|
||||||
|
config version = 2
|
||||||
|
shell = /bin/bash -l
|
||||||
|
populate_menu_with_actions = yes
|
||||||
|
show_hotkeys_in_menu = yes
|
||||||
|
check_for_updates = yes
|
||||||
|
start_on_login = yes
|
||||||
|
show_icon_in_statusbar = yes
|
||||||
|
set_workdir_with_cd = no
|
||||||
|
window_x = -988
|
||||||
|
window_y = 172
|
||||||
|
window_width = 600
|
||||||
|
window_height = 361
|
||||||
|
shortcut_column_enabled = yes
|
||||||
|
action_column_enabled = yes
|
||||||
|
command_column_enabled = yes
|
||||||
|
workdir_column_enabled = no
|
||||||
|
shortcut_column_width = 80
|
||||||
|
action_column_width = 160
|
||||||
|
command_column_width = 173
|
||||||
|
workdir_column_width = 100
|
||||||
|
|
||||||
|
[shortcut1]
|
||||||
|
shortcut = ⇧⌃⌥⌘E
|
||||||
|
action = Personal Emacs
|
||||||
|
command = open -a Emacs
|
||||||
|
workdir =
|
||||||
|
enabled = yes
|
||||||
|
|
||||||
|
[shortcut2]
|
||||||
|
shortcut = ⇧⌃⌥E
|
||||||
|
action = Work Emacs
|
||||||
|
command = FOR_WORK=yes open -a Emacs-Work
|
||||||
|
workdir =
|
||||||
|
enabled = yes
|
||||||
|
|
||||||
|
[shortcut3]
|
||||||
|
shortcut = ⇧⌃⌥⌘X
|
||||||
|
action = Emacs Capture
|
||||||
|
command = ~/bin/emacs-capture
|
||||||
|
workdir =
|
||||||
|
enabled = yes
|
||||||
|
|
||||||
|
[shortcut4]
|
||||||
|
shortcut = ⇧⌃⌥X
|
||||||
|
action = Emacs Capture Clipboard
|
||||||
|
command = ~/bin/emacs-capture-clock
|
||||||
|
workdir =
|
||||||
|
enabled = yes
|
||||||
|
|
||||||
|
[shortcut5]
|
||||||
|
shortcut = ⇧⌃⌥T
|
||||||
|
action = iTerm
|
||||||
|
command = open -a iTerm
|
||||||
|
workdir =
|
||||||
|
enabled = yes
|
||||||
|
|
||||||
|
[shortcut6]
|
||||||
|
shortcut = ⇧⌃⌥S
|
||||||
|
action = Slack
|
||||||
|
command = open -a Slack
|
||||||
|
workdir =
|
||||||
|
enabled = yes
|
||||||
|
|
||||||
|
[shortcut7]
|
||||||
|
shortcut = ⇧⌃⌥W
|
||||||
|
action = Spotify
|
||||||
|
command = open -a Spotify
|
||||||
|
workdir =
|
||||||
|
enabled = yes
|
||||||
|
|
||||||
|
[shortcut8]
|
||||||
|
shortcut = ⇧⌃⌥F
|
||||||
|
action = Firefox
|
||||||
|
command = open -a Firefox
|
||||||
|
workdir =
|
||||||
|
enabled = yes
|
||||||
|
|
||||||
|
[shortcut9]
|
||||||
|
shortcut = ⇧⌃⌥C
|
||||||
|
action = Chome
|
||||||
|
command = ~/bin/chrome.scr
|
||||||
|
workdir =
|
||||||
|
enabled = yes
|
||||||
|
|
||||||
|
[shortcut10]
|
||||||
|
shortcut = ⇧⌃⌥Q
|
||||||
|
action = Keepass
|
||||||
|
command = open -a KeepassXC
|
||||||
|
workdir =
|
||||||
|
enabled = yes
|
||||||
|
|
||||||
|
[shortcut11]
|
||||||
|
shortcut = ⇧⌃⌥Z
|
||||||
|
action = Zoom
|
||||||
|
command = open -a zoom.us
|
||||||
|
workdir =
|
||||||
|
enabled = yes
|
||||||
|
|
||||||
|
[shortcut12]
|
||||||
|
shortcut = ⌃F1
|
||||||
|
action = Mute Zoom
|
||||||
|
command = ~/bin/zoom-muter
|
||||||
|
workdir =
|
||||||
|
enabled = yes
|
||||||
|
|
||||||
|
[shortcut13]
|
||||||
|
shortcut = ⇧⌃⌥⌘M
|
||||||
|
action = Capture Meeting
|
||||||
|
command = ~/bin/emacs-capture-meeting
|
||||||
|
workdir =
|
||||||
|
enabled = yes
|
||||||
|
|
||||||
|
[shortcut14]
|
||||||
|
shortcut = ⇧⌃⌥⌘B
|
||||||
|
action = Outlook
|
||||||
|
command = open -a "Microsoft Outlook"
|
||||||
|
workdir =
|
||||||
|
enabled = yes
|
||||||
|
|
||||||
|
[shortcut15]
|
||||||
|
shortcut = ⇧⌃⌥⌘D
|
||||||
|
action = Discord
|
||||||
|
command = open -a Discord
|
||||||
|
workdir =
|
||||||
|
enabled = yes
|
||||||
|
#+end_src
|
||||||
Configure the *ICanHazShortcut* shortcuts to call these scripts, as in this screenshot:
|
Configure the *ICanHazShortcut* shortcuts to call these scripts, as in this screenshot:
|
||||||
[[file:screenshots/icanhazshortcuts.png]]
|
[[file:screenshots/icanhazshortcuts.png]]
|
||||||
|
|
||||||
And here is the configuration file for that:
|
And here is the configuration file for that:
|
||||||
#+begin_src conf :tangle ~/.config/iCanHazShortcut/config.ini :mkdirp yes
|
#+begin_src conf :tangle ~/.config/iCanHazShortcut/config.ini :mkdirp yes
|
||||||
[main]
|
[main]
|
||||||
|
|
|
||||||
|
|
@ -1,354 +0,0 @@
|
||||||
#+TITLE: Taking Notes with Org
|
|
||||||
#+AUTHOR: Howard X. Abrams
|
|
||||||
#+DATE: 2026-07-14
|
|
||||||
#+FILETAGS: emacs hamacs
|
|
||||||
#+LASTMOD: [2026-07-14 Tue]
|
|
||||||
|
|
||||||
A literate programming file for configuring Emacs to take notes.
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp :exports none
|
|
||||||
;;; ha-code-notes --- configuring Emacs to take notes. -*- lexical-binding: t; -*-
|
|
||||||
;;
|
|
||||||
;; © 2026 Howard X. Abrams
|
|
||||||
;; Licensed under a Creative Commons Attribution 4.0 International License.
|
|
||||||
;; See http://creativecommons.org/licenses/by/4.0/
|
|
||||||
;;
|
|
||||||
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams>
|
|
||||||
;; Maintainer: Howard X. Abrams
|
|
||||||
;; Created: July 14, 2026
|
|
||||||
;;
|
|
||||||
;; While obvious, GNU Emacs does not include this file or project.
|
|
||||||
;;
|
|
||||||
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
|
|
||||||
;; /Users/howard.abrams/src/hamacs/ha-code-notes.org
|
|
||||||
;; And tangle the file to recreate this one.
|
|
||||||
;;
|
|
||||||
;;; Code:
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
* Introduction
|
|
||||||
Seems silly to have a full set of instructions for /taking notes/ using Org-mode, because that is what it does, but this is a bit special.
|
|
||||||
|
|
||||||
When I [[file:ha-capturing-notes.org][capture notes]] with a link to some source code, the /destination/ is a *clocked in* task. Useful, but seems to require a dedicated task and some forethought. The following project pairs a “source code file” (which can be any file, actually) and a “notes file” with headers based on the function in the original file.
|
|
||||||
|
|
||||||
#+begin_src dot :file ha-code-notes-illustration.png :exports file :results file
|
|
||||||
digraph G {
|
|
||||||
rankdir=LR;
|
|
||||||
// Make to icons side-by-side
|
|
||||||
code_file [shape=note, label="", fixedsize=true, width=1, height=1.5];
|
|
||||||
note_file [shape=note, label="", fixedsize=true, width=1, height=1.5];
|
|
||||||
|
|
||||||
// Create a couple of label to live _below_ the icons:
|
|
||||||
code_label [shape=plaintext, label="foobar.py"];
|
|
||||||
note_label [shape=plaintext, label=".foobar-note.org"];
|
|
||||||
{ rank=same; code_file; code_label; }
|
|
||||||
{ rank=same; note_file; note_label; }
|
|
||||||
code_file -> code_label [style=invis];
|
|
||||||
note_file -> note_label [style=invis];
|
|
||||||
|
|
||||||
// Connect the code and note icons:
|
|
||||||
code_file -> note_file;
|
|
||||||
}
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
#+attr_org: :width 800px
|
|
||||||
[[file:ha-code-notes-illustration.png]]
|
|
||||||
|
|
||||||
This allows me to /wax poetic/ with a parallel, but separate org file.
|
|
||||||
* Helper Functions
|
|
||||||
While this project started simple, I’ve expanded the ideas, and this requires some custom helper functions. For instance, I want to add a buffer-level /property/ to an org-mode file, for instance:
|
|
||||||
|
|
||||||
#+begin_example
|
|
||||||
#+TITLE: The title of the file
|
|
||||||
#+PROPERTY: key1 value1
|
|
||||||
#+end_example
|
|
||||||
|
|
||||||
The tricky bit about this function is that if (in the above example) =key1= exists, we should /replace/ it, not add to it.
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(defun ha-org-set-buffer-property (key value)
|
|
||||||
"Set a buffer-level property at the top of an Org file.
|
|
||||||
If KEY already refers to a #+PROPERTY, replace it.
|
|
||||||
Otherwise, insert it at the end of the Org header lines."
|
|
||||||
(let ((property-line (format "#+PROPERTY: %s %s" key value))
|
|
||||||
(magic-re (rx (group line-start "#+PROPERTY:"
|
|
||||||
space (literal key) space
|
|
||||||
(zero-or-more any)
|
|
||||||
line-end))))
|
|
||||||
(defun process-line ()
|
|
||||||
"Helper function returns non-nil if more to process.
|
|
||||||
This works because `replace-match' and `insert' return nil,
|
|
||||||
while `forward-line' returns a non-nil value."
|
|
||||||
(cond
|
|
||||||
((looking-at magic-re) (replace-match property-line))
|
|
||||||
((looking-at "#") (forward-line))
|
|
||||||
(t (insert property-line ?\n))))
|
|
||||||
|
|
||||||
(save-excursion
|
|
||||||
(goto-char (point-min))
|
|
||||||
(while (process-line)))))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
And we need the ability to read it. The Org API doesn’t have a function to read it without parsing the entire structure, so we use the =org-collect-keywords= for the =PROPERTY= value, and then parse the results.
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(defun ha-org-get-buffer-property (key)
|
|
||||||
"Get the value of a buffer-level #+PROPERTY matching KEY."
|
|
||||||
(let* ((properties (thread-last "PROPERTY"
|
|
||||||
(list) ; Requires a list of keywords
|
|
||||||
(org-collect-keywords)
|
|
||||||
(car))) ; A list of lists? Get first entry
|
|
||||||
;; Each entry in properties is a string with the key and value:
|
|
||||||
(entry (seq-find (lambda (k) (s-starts-with? key k)) properties)))
|
|
||||||
(substring entry (1+ (s-index-of " " entry)))))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
They work like:
|
|
||||||
#+BEGIN_SRC emacs-lisp :tangle no
|
|
||||||
(ha-org-set-buffer-property "foor" "bar")
|
|
||||||
(ha-org-get-buffer-property "foo") ; ⟹ "bar"
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
IMenu has a /mode-agnostic/ approach to jumping to /sections/. This could work whether the file is an formatted in org-mode, markdown, or even source code. This function allows me to jump to a particular header in either Org, Markdown, or other files that define the =defun= interface differently.
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(defun imenu-goto (header)
|
|
||||||
"Jump to the first `imenu' entry whose name contains HEADER.
|
|
||||||
Like `consult-imenu', this matches HEADER as a case-insensitive
|
|
||||||
substring rather than requiring an exact name, and jumps straight
|
|
||||||
to the first hit instead of prompting.
|
|
||||||
|
|
||||||
Note that this won't work if IMenu is stale and requires refreshing."
|
|
||||||
;; Overshadowing imenu's lookup function seems overly sneaky!
|
|
||||||
(let ((imenu-name-lookup-function
|
|
||||||
(lambda (str name)
|
|
||||||
(let ((case-fold-search t))
|
|
||||||
(string-match-p (regexp-quote str) name)))))
|
|
||||||
;; Calling `imenu' programmatically is a pain!
|
|
||||||
(when-let ((item (imenu--in-alist header (imenu--make-index-alist))))
|
|
||||||
(imenu item))))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
* Code Notes
|
|
||||||
This function defines what the notes filename should look like, loads it in a side window, and adds a /back reference/ in the form of an org-mode =property=. Next it needs to either find the section that matches the function in the code we are writing about, or jumps to the bottom and creates it.
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(defun ha-code-notes ()
|
|
||||||
"Open an Org file based on the current opened file.
|
|
||||||
The pattern for choosing the name of the org file is:
|
|
||||||
|
|
||||||
foobar.py --> .foobar-notes.org
|
|
||||||
|
|
||||||
The Org header will be the name of the function in the original source
|
|
||||||
code file. This means you have one note section per function, which
|
|
||||||
should be fine in practice because functions are small and succinct,
|
|
||||||
right?"
|
|
||||||
(interactive)
|
|
||||||
(let* ((orig-file (buffer-file-name))
|
|
||||||
(line-num (line-number-at-pos))
|
|
||||||
(orig-parent (file-name-directory orig-file))
|
|
||||||
(orig-base (file-name-base orig-file))
|
|
||||||
|
|
||||||
;; Keep in mind the `orig-parent' has a final slash, so the
|
|
||||||
;; initial . here marks it as hidden:
|
|
||||||
(note-file (format "%s.%s-notes.org"
|
|
||||||
orig-parent orig-base))
|
|
||||||
(header (which-function)))
|
|
||||||
|
|
||||||
;; With the above local variables defined, we can open the file in
|
|
||||||
;; a window (pane) to the side:
|
|
||||||
(find-file-other-window note-file)
|
|
||||||
(ha-org-set-buffer-property "XREF" orig-file)
|
|
||||||
|
|
||||||
(goto-char (point-min)) ; jump to start of file
|
|
||||||
|
|
||||||
;; The `condition-case' is Elisp's way of a try..catch:
|
|
||||||
(condition-case nil
|
|
||||||
;; Find the first Org header that matches `header':
|
|
||||||
(re-search-forward (rx line-start
|
|
||||||
(one-or-more "*")
|
|
||||||
(one-or-more space)
|
|
||||||
(optional (or "=" "~"))
|
|
||||||
(literal header)
|
|
||||||
(optional (or "=" "~"))))
|
|
||||||
(error
|
|
||||||
;; We didn't find a header matching the function, so we jump to
|
|
||||||
;; the end of the file and create a new heading:
|
|
||||||
(goto-char (point-max))
|
|
||||||
(org-insert-heading)
|
|
||||||
(insert (format "%s" header))
|
|
||||||
(org-insert-property-drawer)
|
|
||||||
(org-set-property "XREF_LINE" (number-to-string line-num))
|
|
||||||
;; In case we want to change the section header name, we store
|
|
||||||
;; the name of the function that led us here as a property:
|
|
||||||
(org-set-property "XREF_NAME" header)
|
|
||||||
|
|
||||||
(when-let ((buf (find-buffer-visiting orig-file)))
|
|
||||||
(with-current-buffer buf
|
|
||||||
(ha-code--fringe-notes)))))
|
|
||||||
|
|
||||||
;; We are somewhere in the file, so go to the end of the block:
|
|
||||||
(org-end-of-subtree)
|
|
||||||
|
|
||||||
;; If the subtree ends mid-line, insert a newline:
|
|
||||||
(unless (eq (line-beginning-position) (point))
|
|
||||||
(end-of-line)
|
|
||||||
(insert "\n"))))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Use the builtin autoinsert feature to inject a basic template at the beginning of the notes file when we first create the notes file:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(use-package autoinsert
|
|
||||||
:config
|
|
||||||
(define-auto-insert
|
|
||||||
(cons (rx "/." (one-or-more (not "/")) "-notes.org" string-end) "Org Notes Template")
|
|
||||||
'("Short description: "
|
|
||||||
"#+TITLE: "
|
|
||||||
(s-titleized-words (s-replace-regexp (rx (any "-" "_")) " "
|
|
||||||
(file-name-base (buffer-file-name))))
|
|
||||||
\n
|
|
||||||
"#+DATE:" (format-time-string "%Y-%m-%d %a")
|
|
||||||
\n
|
|
||||||
"#+LASTMOD:" (format-time-string "[%Y-%m-%d %a]")
|
|
||||||
\n \n)))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
If we are /inside/ one of these note files, let’s have a quick way to return back to the original “code” file:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(defun ha-code-notes-return ()
|
|
||||||
"Return to the code referenced in the notes.
|
|
||||||
Essentially pretends we have a backlink without a database."
|
|
||||||
(interactive)
|
|
||||||
(let* ((filename (ha-org-get-buffer-property "XREF"))
|
|
||||||
(line-num (car (org-property-values "XREF_LINE")))
|
|
||||||
(function (or (car (org-property-values "XREF_NAME")) (which-function))))
|
|
||||||
|
|
||||||
;; If the property is set, load that file (which jumps to the
|
|
||||||
;; buffer if it is displayed), otherwise, we assume the previous
|
|
||||||
;; buffer contains it:
|
|
||||||
(if filename
|
|
||||||
(find-file-other-window filename)
|
|
||||||
(switch-to-prev-buffer))
|
|
||||||
(if line-num
|
|
||||||
(goto-line (string-to-number line-num)))
|
|
||||||
|
|
||||||
;; If the line number got out of sync so that the point is no
|
|
||||||
;; longer in the correct function, use `find-function' to
|
|
||||||
;; reposition the point:
|
|
||||||
(unless (equal (which-function) function)
|
|
||||||
(if (derived-mode-p 'prog-mode)
|
|
||||||
(xref-find-definitions function)
|
|
||||||
(imenu-goto function)))))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
And give us keybinding that will either go to the notes (if we are in some code) or return to the source code (if we are in our notes):
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(defun ha-code-notes-dwim ()
|
|
||||||
"Open the notes buffer, or return to the code."
|
|
||||||
(interactive)
|
|
||||||
(when (buffer-file-name)
|
|
||||||
(if (string-match (rx "/." ; A hidden file
|
|
||||||
(one-or-more (not "/"))
|
|
||||||
"-notes.org" string-end)
|
|
||||||
(buffer-file-name))
|
|
||||||
(ha-code-notes-return)
|
|
||||||
(ha-code-notes))))
|
|
||||||
|
|
||||||
(ha-leader "n c" '("code notes" . ha-code-notes-dwim))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
** Fringe Indicators for Notes
|
|
||||||
|
|
||||||
A code file with an associated notes file is easy to forget about. Let's mark, in the fringe, every line that has a note, so we notice it as we scroll past:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(define-fringe-bitmap 'ha-code-notes-bitmap
|
|
||||||
[#b00001100
|
|
||||||
#b00010110
|
|
||||||
#b00010111
|
|
||||||
#b00101110
|
|
||||||
#b00101110
|
|
||||||
#b01011100
|
|
||||||
#b01011100
|
|
||||||
#b10110000
|
|
||||||
#b10010000
|
|
||||||
#b11100000]
|
|
||||||
nil nil 'center)
|
|
||||||
|
|
||||||
(defface ha-code-notes-face
|
|
||||||
'((t :foreground "yellow"))
|
|
||||||
"Face for the fringe marker indicating a line has an associated note.")
|
|
||||||
|
|
||||||
(defvar-local ha-code-notes-fringe-overlays nil
|
|
||||||
"Overlays marking lines in this buffer that have notes in the paired notes file.")
|
|
||||||
|
|
||||||
(defun ha-code-notes--fringe ()
|
|
||||||
"Mark, in the fringe, every line in this buffer that has a note.
|
|
||||||
Notes live in the paired `.BASE-notes.org' file (see
|
|
||||||
`ha-code-notes') as headings whose XREF/XREF_LINE
|
|
||||||
properties point back to a line in this file."
|
|
||||||
(mapc #'delete-overlay ha-code-notes--fringe-overlays)
|
|
||||||
(setq ha-code--notes--fringe-overlays nil)
|
|
||||||
(let* ((orig-file (buffer-file-name))
|
|
||||||
(note-file (and orig-file
|
|
||||||
(format "%s.%s-notes.org"
|
|
||||||
(file-name-directory orig-file)
|
|
||||||
(file-name-base orig-file))))
|
|
||||||
lines)
|
|
||||||
(when (and note-file (file-exists-p note-file))
|
|
||||||
(with-temp-buffer
|
|
||||||
(insert-file-contents note-file)
|
|
||||||
(org-mode)
|
|
||||||
(org-map-entries
|
|
||||||
(lambda ()
|
|
||||||
(when (equal (ha-org-get-buffer-property "XREF") orig-file)
|
|
||||||
(push (string-to-number (org-entry-get nil "XREF_LINE")) lines)))))
|
|
||||||
(dolist (line lines)
|
|
||||||
(save-excursion
|
|
||||||
(goto-char (point-min))
|
|
||||||
(forward-line (1- line))
|
|
||||||
(let ((ov (make-overlay (point) (point))))
|
|
||||||
(overlay-put ov 'before-string
|
|
||||||
(propertize "x" 'display
|
|
||||||
'(left-fringe ha-code-notes-bitmap
|
|
||||||
ha-code-notes-face)))
|
|
||||||
(push ov ha-code-notes--fringe-overlays)))))))
|
|
||||||
|
|
||||||
(add-hook 'find-file-hook #'ha-code-notes--fringe)
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
New notes and edited notes should refresh the markers too, so we hook into saving a notes file, and refresh right after inserting a new XREF property drawer:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(defun ha-code-notes--fringe-notes-refresh-all ()
|
|
||||||
"Refresh fringe note markers in every buffer after saving a notes file."
|
|
||||||
(when (string-match (rx "-notes.org" string-end) (buffer-file-name))
|
|
||||||
(dolist (buf (buffer-list))
|
|
||||||
(with-current-buffer buf
|
|
||||||
(when buffer-file-name
|
|
||||||
(ha-code--fringe-notes))))))
|
|
||||||
|
|
||||||
(add-hook 'after-save-hook #'ha-code-notes--fringe-notes-refresh-all)
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
* Technical Artifacts :noexport:
|
|
||||||
|
|
||||||
Let's =provide= a name so we can =require= this file:
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp :exports none
|
|
||||||
(provide 'ha-code-notes)
|
|
||||||
;;; ha-code-notes.el ends here
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
#+DESCRIPTION: configuring Emacs to take notes.
|
|
||||||
|
|
||||||
#+PROPERTY: header-args:sh :tangle no
|
|
||||||
#+PROPERTY: header-args:emacs-lisp :tangle yes
|
|
||||||
#+PROPERTY: header-args :results none :eval no-export :comments no mkdirp yes
|
|
||||||
|
|
||||||
#+OPTIONS: num:nil toc:nil todo:nil tasks:nil tags:nil date:nil
|
|
||||||
#+OPTIONS: skip:nil author:nil email:nil creator:nil timestamp:nil
|
|
||||||
#+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
|
|
||||||
551
ha-config.org
551
ha-config.org
|
|
@ -146,7 +146,8 @@ After reading [[https://irreal.org/blog/?p=12139][Jon Sander’s essay]] as well
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package emacs-everywhere
|
(use-package emacs-everywhere
|
||||||
:straight (:host github :repo "tecosaur/emacs-everywhere"))
|
;; :vc (:url "https://github.com/tecosaur/emacs-everywhere")
|
||||||
|
)
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
This package /called outside of Emacs/, so I bound a keybinding to iCanHazShortcut:
|
This package /called outside of Emacs/, so I bound a keybinding to iCanHazShortcut:
|
||||||
|
|
@ -164,20 +165,16 @@ I’ve often called =imenu= to easily jump to a function definition in a file (o
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defun ha-imenu-setup ()
|
(defun ha-imenu-setup ()
|
||||||
"Set up the imenu customization. Use in hooks."
|
"Set up the imenu customization. Use in hooks."
|
||||||
(condition-case err
|
(ignore-errors
|
||||||
(imenu-add-menubar-index)
|
(imenu-add-menubar-index)
|
||||||
(setq-local imenu-auto-rescan t)
|
(setq-local imenu-auto-rescan t)
|
||||||
(when (derived-mode-p 'prog-mode)
|
(when (derived-mode-p 'prog-mode)
|
||||||
(setq-local imenu-sort-function 'imenu--sort-by-name))
|
(setq-local imenu-sort-function 'imenu--sort-by-name))))
|
||||||
|
|
||||||
(imenu-unavailable
|
|
||||||
(let ((inhibit-message t))
|
|
||||||
(message "Warning: %s" (error-message-string err))))))
|
|
||||||
|
|
||||||
(add-hook 'org-mode-hook 'ha-imenu-setup)
|
(add-hook 'org-mode-hook 'ha-imenu-setup)
|
||||||
(add-hook 'markdown-mode-hook 'ha-imenu-setup)
|
(add-hook 'markdown-mode-hook 'ha-imenu-setup)
|
||||||
(add-hook 'makefile-mode-hook 'ha-imenu-setup)
|
|
||||||
(add-hook 'prog-mode-hook 'ha-imenu-setup)
|
(add-hook 'prog-mode-hook 'ha-imenu-setup)
|
||||||
|
(add-hook 'makefile-mode-hook 'ha-imenu-setup)
|
||||||
#+end_src
|
#+end_src
|
||||||
** File Access
|
** File Access
|
||||||
*** Remote Files
|
*** Remote Files
|
||||||
|
|
@ -229,7 +226,6 @@ When I push changes to my files to Gerrit and other code review, I don’t want
|
||||||
The [[https://www.emacswiki.org/emacs/RecentFiles][recentf]] feature has been in Emacs for a long time, but it has a problem with Tramp, as we need to turn off the cleanup feature that attempts to =stat= all the files and remove them from the =recent= accessed list if they are readable. The requires recentf to open up a remote files which blocks Emacs at the most inopportune times… like when trying to reboot the machine.
|
The [[https://www.emacswiki.org/emacs/RecentFiles][recentf]] feature has been in Emacs for a long time, but it has a problem with Tramp, as we need to turn off the cleanup feature that attempts to =stat= all the files and remove them from the =recent= accessed list if they are readable. The requires recentf to open up a remote files which blocks Emacs at the most inopportune times… like when trying to reboot the machine.
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package recentf
|
(use-package recentf
|
||||||
:straight (:type built-in)
|
|
||||||
:config
|
:config
|
||||||
(setq recentf-auto-cleanup 'never) ;; disable before we start recentf!
|
(setq recentf-auto-cleanup 'never) ;; disable before we start recentf!
|
||||||
(recentf-mode 1))
|
(recentf-mode 1))
|
||||||
|
|
@ -340,10 +336,13 @@ The [[https://github.com/minad/vertico][vertico]] package puts the completing re
|
||||||
(use-package vertico
|
(use-package vertico
|
||||||
:config (vertico-mode))
|
:config (vertico-mode))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
My issue with Vertico is when calling =find-file=, the Return key opens =dired=, instead of inserting the directory at point. This package addresses this:
|
My issue with Vertico is when calling =find-file=, the Return key opens =dired=, instead of inserting the directory at point. This package addresses this:
|
||||||
#+begin_src emacs-lisp
|
|
||||||
|
#+begin_src emacs-lisp :tangle no
|
||||||
(use-package vertico-directory
|
(use-package vertico-directory
|
||||||
:straight (el-patch :files ("~/.emacs.d/straight/repos/vertico/extensions/vertico-directory.el"))
|
:load-path "~/.emacs.d/repos/vertigo/extensions"
|
||||||
|
;; :straight (:files ("~/.emacs.d/straight/repos/vertico/extensions/vertico-directory.el"))
|
||||||
;; More convenient directory navigation commands
|
;; More convenient directory navigation commands
|
||||||
:bind (:map vertico-map
|
:bind (:map vertico-map
|
||||||
("RET" . vertico-directory-enter)
|
("RET" . vertico-directory-enter)
|
||||||
|
|
@ -384,7 +383,7 @@ How does it compare? Once upon a time, I enjoyed typing ~plp~ for =package-list-
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package fussy
|
(use-package fussy
|
||||||
;; :straight (:host github :repo "jojojames/fussy")
|
;; ;; :vc (:url "https://github.com/jojojames/fussy")
|
||||||
:config
|
:config
|
||||||
(push 'fussy completion-styles)
|
(push 'fussy completion-styles)
|
||||||
(setq completion-category-defaults nil
|
(setq completion-category-defaults nil
|
||||||
|
|
@ -575,7 +574,7 @@ The [[https://github.com/minad/cape][Cape project]] deliver particular [[help:co
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp :tangle no
|
#+BEGIN_SRC emacs-lisp :tangle no
|
||||||
(use-package cape
|
(use-package cape
|
||||||
:straight (:host github :repo "minad/cape")
|
;; :vc (:url "https://github.com/minad/cape")
|
||||||
:init
|
:init
|
||||||
(setq completion-at-point-functions (list #'cape-emoji)))
|
(setq completion-at-point-functions (list #'cape-emoji)))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
@ -589,50 +588,20 @@ Each programming environment might need some particular love. For instance:
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
*** Company
|
|
||||||
The [[http://company-mode.github.io/][company project]] for completion back-ends work well with LSP.
|
|
||||||
|
|
||||||
Do I want it to display whenever there is a pause in the conversation (set =company-minimum-prefix-length= to a large number, or set =company-idle-delay=), or do I want it to only show when I push the =TAB= or other key (see =company-indent-or-complete-common=).
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(use-package company
|
|
||||||
:custom
|
|
||||||
(company-minimum-prefix-length 3) ; default
|
|
||||||
(company-idle-delay 0.5)
|
|
||||||
(company-tooltip-align-annotations t)
|
|
||||||
(company-tooltip-limit 9)
|
|
||||||
(company-tooltip-flip-when-above t)
|
|
||||||
(company-show-quick-access 'left)
|
|
||||||
|
|
||||||
:bind (("M-/" . company-complete)
|
|
||||||
:map company-mode-map
|
|
||||||
("M-/" . company-other-backend))
|
|
||||||
|
|
||||||
:config
|
|
||||||
(add-to-list 'company-backends 'company-yasnippet)
|
|
||||||
|
|
||||||
(set-face-attribute 'company-tooltip nil
|
|
||||||
:family "Cascadia Code NF"
|
|
||||||
:height 120)
|
|
||||||
|
|
||||||
:hook (after-init . global-company-mode))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Another idea, is I can trigger the company with a M-/ but then view /other backends/ by re-hitting that keybinding.
|
|
||||||
*** Corfu
|
*** Corfu
|
||||||
The default completion system either inserts the first option directly in the text (without cycling, so let’s hope it gets it right the first time), or presents choices in another buffer (who wants to hop to it to select an expansion).
|
The default completion system either inserts the first option directly in the text (without cycling, so let’s hope it gets it right the first time), or presents choices in another buffer (who wants to hop to it to select an expansion).
|
||||||
|
|
||||||
After using [[http://company-mode.github.io/][company]] for my completion back-end, I switched to [[https://github.com/minad/corfu][corfu]] as it works with the variable-spaced font of my org files (also see [[https://takeonrules.com/2022/01/17/switching-from-company-to-corfu-for-emacs-completion/][this essay]] for my initial motivation).
|
After using [[http://company-mode.github.io/][company]] for my completion back-end, I switched to [[https://github.com/minad/corfu][corfu]] as it works with the variable-spaced font of my org files (also see [[https://takeonrules.com/2022/01/17/switching-from-company-to-corfu-for-emacs-completion/][this essay]] for my initial motivation).
|
||||||
|
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp
|
||||||
(use-package corfu
|
(use-package corfu
|
||||||
|
;; :vc (:url "https://github.com/minad/corfu")
|
||||||
:custom
|
:custom
|
||||||
(corfu-cycle t)
|
(corfu-cycle t)
|
||||||
(corfu-separator ?\s)
|
(corfu-separator ?\s)
|
||||||
:init
|
:config
|
||||||
(global-corfu-mode))
|
(global-corfu-mode))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
*** Snippets
|
*** Snippets
|
||||||
Using [[https://github.com/joaotavora/yasnippet][yasnippet]] to expand templates into text:
|
Using [[https://github.com/joaotavora/yasnippet][yasnippet]] to expand templates into text:
|
||||||
|
|
||||||
|
|
@ -708,7 +677,7 @@ As I've mentioned [[http://www.howardism.org/Technical/Emacs/beep-for-emacs.html
|
||||||
'libnotify)))
|
'libnotify)))
|
||||||
|
|
||||||
(use-package beep
|
(use-package beep
|
||||||
:straight (:local-repo "~/src/hamacs/elisp")
|
:load-path "~/src/hamacs/elisp"
|
||||||
:hook (after-init . ha-random-startup-message)
|
:hook (after-init . ha-random-startup-message)
|
||||||
:commands (beep-when-finished beep-when-run-too-long)
|
:commands (beep-when-finished beep-when-run-too-long)
|
||||||
:config
|
:config
|
||||||
|
|
@ -725,7 +694,7 @@ As I've mentioned [[http://www.howardism.org/Technical/Emacs/beep-for-emacs.html
|
||||||
"Yippee"
|
"Yippee"
|
||||||
"What's next?"
|
"What's next?"
|
||||||
"Notification complete. Happy?"
|
"Notification complete. Happy?"
|
||||||
"Beware, lest the dragons fly"))))
|
"Beware lest the dragons fly"))))
|
||||||
(beep-when-finished msg)))
|
(beep-when-finished msg)))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
|
|
@ -809,37 +778,6 @@ If you hit the following keys /before/ you select a target, you get special acti
|
||||||
- ~z~ :: =zap-to-char= … kill from current point to the target
|
- ~z~ :: =zap-to-char= … kill from current point to the target
|
||||||
I’m not thinking of ideas of what would be useful, e.g. ~v~ to highlight from cursor to target, etc.
|
I’m not thinking of ideas of what would be useful, e.g. ~v~ to highlight from cursor to target, etc.
|
||||||
|
|
||||||
What is missing is copying the entire line:
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(use-package avy
|
|
||||||
:config
|
|
||||||
(defun avy-action-copy-whole-line (pt)
|
|
||||||
(save-excursion
|
|
||||||
(goto-char pt)
|
|
||||||
(cl-destructuring-bind (start . end)
|
|
||||||
(bounds-of-thing-at-point 'line)
|
|
||||||
(copy-region-as-kill start end)))
|
|
||||||
(select-window
|
|
||||||
(cdr
|
|
||||||
(ring-ref avy-ring 0)))
|
|
||||||
t)
|
|
||||||
|
|
||||||
(defun avy-action-copy-rest-line (pt)
|
|
||||||
(save-excursion
|
|
||||||
(goto-char pt)
|
|
||||||
(let ((start pt)
|
|
||||||
(end (line-end-position)))
|
|
||||||
(copy-region-as-kill start end)))
|
|
||||||
(select-window
|
|
||||||
(cdr
|
|
||||||
(ring-ref avy-ring 0)))
|
|
||||||
t)
|
|
||||||
|
|
||||||
(setf (alist-get ?w avy-dispatch-alist) 'avy-action-copy
|
|
||||||
(alist-get ?W avy-dispatch-alist) 'avy-action-copy-whole-line
|
|
||||||
(alist-get ?N avy-dispatch-alist) 'avy-action-copy-rest-line))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Want to know something amazing. In a Terminal, like =vterm= or =eshell=, I run ~s-g~ and pinpoint the UUID in the output of a long command. Then type ~y~ and then ~C-y~ to paste that ID without even moving the mouse.
|
Want to know something amazing. In a Terminal, like =vterm= or =eshell=, I run ~s-g~ and pinpoint the UUID in the output of a long command. Then type ~y~ and then ~C-y~ to paste that ID without even moving the mouse.
|
||||||
*** Link Hint, the Link Jumper
|
*** Link Hint, the Link Jumper
|
||||||
The [[info:emacs#Goto Address mode][Goto Address]] mode (see this [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Goto-Address-mode.html][online link]]) turns URLs into clickable links. Nice feature and built into Emacs, but it requires using the mouse or moving to the URL and hitting ~Return~ (if you like this idea, check out [[https://xenodium.com/actionable-urls-in-emacs-buffers/][Álvaro Ramírez's configuration]] for this).
|
The [[info:emacs#Goto Address mode][Goto Address]] mode (see this [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Goto-Address-mode.html][online link]]) turns URLs into clickable links. Nice feature and built into Emacs, but it requires using the mouse or moving to the URL and hitting ~Return~ (if you like this idea, check out [[https://xenodium.com/actionable-urls-in-emacs-buffers/][Álvaro Ramírez's configuration]] for this).
|
||||||
|
|
@ -957,6 +895,16 @@ I like the idea of dropping returnable bookmarks, but with /good defaults/ for t
|
||||||
(bookmark-set label)))
|
(bookmark-set label)))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
The built-in behavior doesn’t honor either /projects/ or /perspectives/, but I use [[https://codeberg.org/ideasman42/emacs-bookmark-in-project][bookmark-in-project]] package to make a =project=-specific bookmarks and use that to jump to only bookmarks in the current project.
|
||||||
|
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(use-package bookmark-in-project
|
||||||
|
:bind
|
||||||
|
(("C-x r m" . bookmark-in-project-toggle)
|
||||||
|
("C-x r M" . ha-bookmark-set)))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
** Minor Keybinding Annoys
|
** Minor Keybinding Annoys
|
||||||
I like ~C-a~ to go to the beginning of the line, but what about getting to the beginning of text on that line? In Evil, you have ~^~ for beginning of line, and ~0~ for first text. Why not have ~C-a~ toggle between them both:
|
I like ~C-a~ to go to the beginning of the line, but what about getting to the beginning of text on that line? In Evil, you have ~^~ for beginning of line, and ~0~ for first text. Why not have ~C-a~ toggle between them both:
|
||||||
|
|
||||||
|
|
@ -981,13 +929,15 @@ Since I wasn’t using all the features that [[https://github.com/bbatsov/projec
|
||||||
:config
|
:config
|
||||||
(ha-leader
|
(ha-leader
|
||||||
"p" '(:ignore t :which-key "projects")
|
"p" '(:ignore t :which-key "projects")
|
||||||
"p p" '("switch project" . ha-tab-bar-new-project)
|
"p W" '("initialize workspace" . ha-workspace-initialize)
|
||||||
|
"p n" '("new project space" . ha-project-persp)
|
||||||
|
|
||||||
"p !" '("run cmd in project root" . project-shell-command)
|
"p !" '("run cmd in project root" . project-shell-command)
|
||||||
"p &" '("run cmd async" . project-async-shell-command)
|
"p &" '("run cmd async" . project-async-shell-command)
|
||||||
"p a" '("add new project" . project-remember-projects-under)
|
"p a" '("add new project" . project-remember-projects-under)
|
||||||
"p d" '("dired" . project-dired)
|
"p d" '("dired" . project-dired)
|
||||||
"p k" '("kill project buffers" . project-kill-buffers)
|
"p k" '("kill project buffers" . project-kill-buffers)
|
||||||
|
"p p" '("switch project" . project-switch-project)
|
||||||
"p x" '("remove known project" . project-forget-project)
|
"p x" '("remove known project" . project-forget-project)
|
||||||
|
|
||||||
"p f" '("find file" . project-find-file)
|
"p f" '("find file" . project-find-file)
|
||||||
|
|
@ -1001,246 +951,204 @@ Since I wasn’t using all the features that [[https://github.com/bbatsov/projec
|
||||||
"p s" '("project shell" . project-shell)))
|
"p s" '("project shell" . project-shell)))
|
||||||
#+end_src
|
#+end_src
|
||||||
** Workspaces
|
** Workspaces
|
||||||
A /workspace/ (at least to me) requires a quick jump to a collection of buffer windows organized around a project or task. Later versions of Emacs use [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Tab-Bars.html][Tab Bars]] which group windows and buffers in a perspective. The code that follows is a Poor Person’s Workspace package. Also let’s dive into the end section of [[https://www.masteringemacs.org/article/demystifying-emacs-window-manager][Mickey Petersen's essay]] on the subject.
|
A /workspace/ (at least to me) requires a quick jump to a collection of buffer windows organized around a project or task. For this, I'm basing my work on the [[https://github.com/nex3/perspective-el][perspective.el]] project.
|
||||||
|
|
||||||
Couple notes:
|
I build a Hydra to dynamically list the current projects as well as select the project.
|
||||||
- Function, =tab-bar-switch-to-tab=, switches or /creates/ a tab. We will always use this.
|
To do this, we need a way to generate a string of the perspectives in alphabetical order:
|
||||||
- We can switch to a tab by number with =tab-bar-select-tab=
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(setq tab-bar-show 1 ; hide bar if <= 1 tabs open
|
|
||||||
tab-bar-close-button-show nil ; hide tab close / X button
|
|
||||||
tab-bar-new-tab-choice "*dashboard*" ; buffer to show in new tabs
|
|
||||||
tab-bar-tab-hints t ; show tab numbers
|
|
||||||
tab-bar-separator " "
|
|
||||||
tab-bar-button-relief 0
|
|
||||||
|
|
||||||
;; Jump to a tab by numbers (see the keybindings set later):
|
|
||||||
tab-bar-select-tab-modifiers '(super control))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
I’ve struggled to /programmatically/ create sane workspaces, so let’s just save them off:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(desktop-save-mode 1)
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
New workspace is a tab with a specific name that opens up a specific buffer or application. My motive for such a complicated function allows me to pre-create tabs with already running applications or files.
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defun ha-tab-bar-new (name &optional bff)
|
(defun ha--persp-label (num names)
|
||||||
"Create a new tab with a NAME.
|
"Return string of numbered elements.
|
||||||
With a non-nil BFF, call BFF as a function or switch
|
NUM is the starting number and NAMES is a list of strings."
|
||||||
to the BFF buffer or the files listed."
|
(when names
|
||||||
(interactive "sWorkspace Name: ")
|
|
||||||
(tab-bar-switch-to-tab name)
|
|
||||||
(when bff
|
|
||||||
(cond
|
|
||||||
((listp bff) (find-file (car bff))
|
|
||||||
(dolist (f (cdr bff))
|
|
||||||
(split-window-right)
|
|
||||||
(find-file f)))
|
|
||||||
((fboundp bff) (call-interactively bff))
|
|
||||||
((bufferp bff) (switch-to-buffer bff)))))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
Create a new tab associated with a project:
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
|
||||||
(defun ha-tab-bar-new-project (project-dir)
|
|
||||||
"Create a new tab/workspace based on a project.
|
|
||||||
The project is defined by the PROJECT-DIR directory."
|
|
||||||
(interactive (list (completing-read "Project: " (project-known-project-roots))))
|
|
||||||
(let ((name (project-name (project-current nil project-dir)))
|
|
||||||
(default-directory project-dir))
|
|
||||||
(ha-tab-bar-new name)
|
|
||||||
(project-switch-project project-dir)))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
If we close a tab that is a project, we want to close all the buffers associated with it. I wouldn’t do this if it wasn’t so easy to re-create them:
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
|
||||||
(defun ha-tab-bar-delete (tab-name)
|
|
||||||
"Delete a tab, TAB-NAME, and all buffers associated with it."
|
|
||||||
(interactive
|
|
||||||
(list (completing-read "Close tab by name: "
|
|
||||||
(mapcar (lambda (tab)
|
|
||||||
(alist-get 'name tab))
|
|
||||||
(funcall tab-bar-tabs-function)))))
|
|
||||||
(dolist (buf (ha-tab-bar-buffers tab-name))
|
|
||||||
(kill-buffer buf))
|
|
||||||
(tab-bar-close-tab-by-name tab-name))
|
|
||||||
|
|
||||||
(defun ha-tab-bar-buffers (tab-name)
|
|
||||||
"Return list of buffers associated with TAB-NAME."
|
|
||||||
(seq-filter (lambda (b)
|
|
||||||
(thread-last b
|
|
||||||
(tab-bar-get-buffer-tab)
|
|
||||||
(alist-get 'name)
|
|
||||||
(string-equal tab-name)))
|
|
||||||
(buffer-list)))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
And let’s make one tab to be /special/:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(defun ha-switch-to-special ()
|
|
||||||
"Change the perspective to the Sprint/Notes project."
|
|
||||||
(interactive)
|
|
||||||
(tab-bar-switch-to-tab "main"))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
And some shortcut keys from the =general= project:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(use-package emacs
|
|
||||||
:config
|
|
||||||
(general-nmap :prefix "SPC"
|
|
||||||
"<tab>" '(:ignore t :which-key "workspaces")
|
|
||||||
"<tab> <tab>" '("switch" . tab-switch)
|
|
||||||
"<tab> p" '("new project" . ha-tab-bar-new-project)
|
|
||||||
"<tab> n" '("new space" . ha-tab-bar-new)
|
|
||||||
"<tab> u" '("update names" . ha-tab-bar-update-names)
|
|
||||||
"<tab> d" '("delete space" . ha-tab-bar-delete)
|
|
||||||
"<tab> t" '("toggle tab-bar" . tab-bar-mode)
|
|
||||||
"<tab> w" '("special space" . ha-switch-to-special)
|
|
||||||
"<tab> `" '("recent" . tab-bar-switch-to-recent-tab))
|
|
||||||
|
|
||||||
(global-set-key (kbd "s-C-t") 'ha-tab-bar-new)
|
|
||||||
(global-set-key (kbd "s-C-[") 'tab-bar-switch-to-prev-tab)
|
|
||||||
(global-set-key (kbd "s-C-]") 'tab-bar-switch-to-next-tab)
|
|
||||||
|
|
||||||
(tab-bar-mode 1))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
I want to quickly jump, by the number shown on the tab, to that grouping. The following two functions create leader sequences with the name of the tab group:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(defun ha-tab-bar-update-names (&optional changed-tab)
|
|
||||||
"Create normal-mode keybindings for the tab groupings.
|
|
||||||
This creates `SPC TAB 1' to jump to the first tab, etc."
|
|
||||||
(interactive)
|
|
||||||
;; Remove all previously created keybindings:
|
|
||||||
(ignore-errors
|
|
||||||
(dolist (indx (number-sequence 0 9))
|
|
||||||
(general-nmap :prefix "SPC" (format "<tab> %d" indx) nil)))
|
|
||||||
|
|
||||||
;; Loop through the existing tabs, create keys for each:
|
|
||||||
(seq-do-indexed 'ha-tab-bar-update-tab-keybinding (tab-bar-tabs)))
|
|
||||||
|
|
||||||
(defun ha-tab-bar-update-tab-keybinding (tab-deets indx)
|
|
||||||
"Create a keybinding to jump to tab described by TAB-DEETS.
|
|
||||||
The key sequence, `SPC' `TAB' then INDX."
|
|
||||||
(when (< indx 10)
|
|
||||||
(let ((name (alist-get 'name tab-deets)))
|
|
||||||
(general-nmap :prefix "SPC"
|
|
||||||
;; As indx is starts with 0, we need to create keybindings to
|
|
||||||
;; match the tab labels by incrementing it by one ... unless,
|
|
||||||
;; we are at 10, where we use 0 instead:
|
|
||||||
(format "<tab> %d" (if (= indx 9) 0 (1+ indx)))
|
|
||||||
`(,name . (lambda () (interactive)
|
|
||||||
"Switch to a tab-bar by number"
|
|
||||||
(setq ha-tab-bar-previous
|
|
||||||
(tab-bar-tab-name-current))
|
|
||||||
(tab-bar-select-tab ,(1+ indx))))))))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Any time I create or delete a new tab, we can call =ha-tab-bar-update-names=:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(advice-add #'tab-bar-new-tab :after #'ha-tab-bar-update-names)
|
|
||||||
(advice-add #'tab-bar-close-tab :after #'ha-tab-bar-update-names)
|
|
||||||
(advice-add #'tab-bar-close-other-tabs :after #'ha-tab-bar-update-names)
|
|
||||||
|
|
||||||
(add-hook #'desktop-after-read-hook #'ha-tab-bar-update-names)
|
|
||||||
#+END_SRC
|
|
||||||
*** Add Numbers ot Tab Bar Mode
|
|
||||||
Christian Tietze had a [[https://christiantietze.de/posts/2022/02/emacs-tab-bar-numbered-tabs/][great idea]] for making the tab-bar numbers more distinguished from the labels on the tabs (he has a [[https://christiantietze.de/posts/2022/12/sf-symbols-emacs-tab-numbers/][follow up essay]] about using a different font, but since I need the portability of Unicode for Linux, I may need to synthesize both).
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(defface ha/tab-bar-numbers
|
|
||||||
(if (ha-running-on-macos?)
|
|
||||||
'((t
|
|
||||||
:inherit tab-bar-tab-face
|
|
||||||
:family "SF Compact"
|
|
||||||
:weight light))
|
|
||||||
;; On Linux, we use the dingbat unicode with default font:
|
|
||||||
'((t
|
|
||||||
:inherit tab-bar-tab-face
|
|
||||||
:weight light)))
|
|
||||||
"Face for tab numbers in both active and inactive tabs.")
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
First, create a variable that contains the Unicode values for the numbers. I am using the negative versions of the circle numbers from the Unicode dingbats collection:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(defvar ha/circle-numbers-alist
|
|
||||||
(if (ha-running-on-macos?)
|
|
||||||
'((0 . "")
|
|
||||||
(1 . "")
|
|
||||||
(2 . "")
|
|
||||||
(3 . "")
|
|
||||||
(4 . "")
|
|
||||||
(5 . "")
|
|
||||||
(6 . "")
|
|
||||||
(7 . "")
|
|
||||||
(8 . "")
|
|
||||||
(9 . "")
|
|
||||||
(10 . ""))
|
|
||||||
;; Keeping these around for the Mac just in case.
|
|
||||||
;; '((0 . "⓿")
|
|
||||||
;; (1 . "❶")
|
|
||||||
;; (2 . "❷")
|
|
||||||
;; (3 . "❸")
|
|
||||||
;; (4 . "❹")
|
|
||||||
;; (5 . "❺")
|
|
||||||
;; (6 . "❻")
|
|
||||||
;; (7 . "❼")
|
|
||||||
;; (8 . "❽")
|
|
||||||
;; (9 . "❾"))
|
|
||||||
'((0 . "🄌")
|
|
||||||
(1 . "➊")
|
|
||||||
(2 . "➋")
|
|
||||||
(3 . "➌")
|
|
||||||
(4 . "➍")
|
|
||||||
(5 . "➎")
|
|
||||||
(6 . "➏")
|
|
||||||
(7 . "➐")
|
|
||||||
(8 . "➑")
|
|
||||||
(9 . "➒")))
|
|
||||||
"Alist of integers to strings of circled unicode numbers.")
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
And then use this function to replace the standard =tab-bar-tab-name-format-function=:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(defun ha/tab-bar-tab-name-format-default (tab i)
|
|
||||||
"Replacement for `tab-bar-tab-name-format-function'.
|
|
||||||
Places a special symbol for the initial digit."
|
|
||||||
(let ((current-p (eq (car tab) 'current-tab)))
|
|
||||||
(concat
|
(concat
|
||||||
;; First, add the tab number with a custom face
|
(format " %d: %s%s" ; Shame that the following doesn't work:
|
||||||
(propertize
|
num ; (propertize (number-to-string num) :foreground "#00a0")
|
||||||
(if (and tab-bar-tab-hints (< i 10)) (alist-get i ha/circle-numbers-alist) "")
|
(car names) ; Nor does surrounding the number with underbars.
|
||||||
'face 'ha/tab-bar-numbers)
|
|
||||||
;; Fixed-width gap so active (variable-pitch) and inactive tabs match
|
|
||||||
(propertize " " 'face 'tab-bar)
|
|
||||||
;; Add tab name with the face returned by tab-bar-tab-face-function
|
|
||||||
(propertize
|
|
||||||
(concat
|
|
||||||
" "
|
|
||||||
(alist-get 'name tab)
|
|
||||||
(or (and tab-bar-close-button-show
|
|
||||||
(not (eq tab-bar-close-button-show
|
|
||||||
(if current-p 'non-selected 'selected)))
|
|
||||||
tab-bar-close-button)
|
|
||||||
"")
|
|
||||||
" ")
|
|
||||||
'face (funcall tab-bar-tab-face-function tab)))))
|
|
||||||
|
|
||||||
(setq tab-bar-tab-name-format-function
|
(if (equal (car names) (persp-name (persp-curr))) "*" ""))
|
||||||
#'ha/tab-bar-tab-name-format-default)
|
(ha--persp-label (1+ num) (cdr names)))))
|
||||||
#+END_SRC
|
|
||||||
|
(defun ha-persp-labels ()
|
||||||
|
"Return a string of numbered elements from a list of names."
|
||||||
|
(ha--persp-label 1 (sort (hash-table-keys (perspectives-hash)) 's-less?)))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Build the hydra as well as configure the =perspective= project.
|
||||||
|
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(use-package perspective
|
||||||
|
:custom
|
||||||
|
(persp-modestring-short t)
|
||||||
|
(persp-show-modestring t)
|
||||||
|
|
||||||
|
:config
|
||||||
|
(setq persp-suppress-no-prefix-key-warning t)
|
||||||
|
|
||||||
|
(persp-mode)
|
||||||
|
|
||||||
|
(defhydra hydra-workspace-leader (:color blue :hint nil) "
|
||||||
|
Workspaces- %s(ha-persp-labels)
|
||||||
|
_n_: new project _r_: rename _a_: add buffer _l_: load worksp
|
||||||
|
_]_: next worksp _d_: delete _b_: goto buffer _s_: save worksp
|
||||||
|
_[_: previous _W_: init all _k_: remove buffer _`_: to last worksp "
|
||||||
|
("TAB" persp-switch-quick)
|
||||||
|
("RET" persp-switch)
|
||||||
|
("`" persp-switch-last)
|
||||||
|
("1" (persp-switch-by-number 1))
|
||||||
|
("2" (persp-switch-by-number 2))
|
||||||
|
("3" (persp-switch-by-number 3))
|
||||||
|
("4" (persp-switch-by-number 4))
|
||||||
|
("5" (persp-switch-by-number 5))
|
||||||
|
("6" (persp-switch-by-number 6))
|
||||||
|
("7" (persp-switch-by-number 7))
|
||||||
|
("8" (persp-switch-by-number 8))
|
||||||
|
("9" (persp-switch-by-number 9))
|
||||||
|
("0" (persp-switch-by-number 0))
|
||||||
|
("n" ha-project-persp)
|
||||||
|
("N" persp-switch)
|
||||||
|
("]" persp-next :color pink)
|
||||||
|
("[" persp-prev :color pink)
|
||||||
|
("d" persp-kill)
|
||||||
|
("W" ha-workspace-initialize)
|
||||||
|
("a" persp-add-buffer)
|
||||||
|
("b" persp-switch-to-buffer)
|
||||||
|
("k" persp-remove-buffer)
|
||||||
|
("K" persp-kill-buffer)
|
||||||
|
("m" persp-merge)
|
||||||
|
("u" persp-unmerge)
|
||||||
|
("i" persp-import)
|
||||||
|
("r" persp-rename)
|
||||||
|
("s" persp-state-save)
|
||||||
|
("l" persp-state-load)
|
||||||
|
("w" ha-switch-to-special) ; The most special perspective
|
||||||
|
("q" nil)
|
||||||
|
("C-g" nil)))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Let’s give it a binding:
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(ha-leader "TAB" '("workspaces" . hydra-workspace-leader/body))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
When called, it /can/ look like:
|
||||||
|
|
||||||
|
[[file:screenshots/projects-hydra.png]]
|
||||||
|
|
||||||
|
The /special/ perspective is a nice shortcut to the one I use the most:
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(defun ha-switch-to-special ()
|
||||||
|
"Change to the projects perspective."
|
||||||
|
(interactive)
|
||||||
|
(persp-switch "projects"))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
I often want a workspace dedicated to an /application/, so this function:
|
||||||
|
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(defun ha-app-perspective (name func)
|
||||||
|
"Generate new perspective NAME, automatically running FUNC."
|
||||||
|
(lambda ()
|
||||||
|
(interactive)
|
||||||
|
(let ((already-started? (seq-contains-p (persp-names) name 'equal)))
|
||||||
|
(persp-switch name)
|
||||||
|
(unless already-started?
|
||||||
|
(call-interactively func)))))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
And I can then use it like:
|
||||||
|
#+begin_src emacs-lisp :tangle no
|
||||||
|
(ha-leader "a x" `("to foobar" . ,(ha-app-perspective "foobar" #'foobar)))
|
||||||
|
#+end_src
|
||||||
|
*** Predefined Workspaces
|
||||||
|
Let's describe a list of startup project workspaces. This way, I don't need the clutter of the recent state, but also get back to a state of mental normality.
|
||||||
|
Granted, this list is essentially a list of projects that I'm currently developing, so I expect this to change often.
|
||||||
|
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(defvar ha-workspace-projects-personal nil "List of default projects with a name.")
|
||||||
|
|
||||||
|
(add-to-list 'ha-workspace-projects-personal
|
||||||
|
'("projects" "~/projects" ("breathe.org" "tasks.org")))
|
||||||
|
(add-to-list 'ha-workspace-projects-personal
|
||||||
|
'("personal" "~/personal" ("general.org")))
|
||||||
|
(add-to-list 'ha-workspace-projects-personal
|
||||||
|
'("technical" "~/technical" ("ansible.org")))
|
||||||
|
(add-to-list 'ha-workspace-projects-personal
|
||||||
|
'("hamacs" "~/src/hamacs" ("README.org" "ha-config.org")))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Given a list of information about project-workspaces, can we create them all?
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(defun ha-persp-exists? (name)
|
||||||
|
"Return non-nill if a perspective of NAME exists."
|
||||||
|
(when (fboundp 'perspectives-hash)
|
||||||
|
(seq-contains (hash-table-keys (perspectives-hash)) name)))
|
||||||
|
|
||||||
|
(defun ha-workspace-initialize (&optional projects)
|
||||||
|
"Precreate workspace projects from a PROJECTS list.
|
||||||
|
Each entry in the list is a list containing:
|
||||||
|
- name (as a string)
|
||||||
|
- project root directory
|
||||||
|
- a optional list of files to display"
|
||||||
|
(interactive)
|
||||||
|
(unless projects
|
||||||
|
(setq projects ha-workspace-projects-personal))
|
||||||
|
|
||||||
|
(dolist (project projects)
|
||||||
|
(seq-let (name root files) project
|
||||||
|
(unless (ha-persp-exists? name)
|
||||||
|
(message "Creating workspace: %s (from %s)" name root)
|
||||||
|
(ha-project-persp root name files))))
|
||||||
|
(persp-switch "main"))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Often, but not always, I want a perspective based on an actual Git repository, e.g. a project. Emacs calls these transients.
|
||||||
|
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(defun ha-project-persp (project &optional name files)
|
||||||
|
"Create a new perspective, and then switch to the PROJECT.
|
||||||
|
If NAME is not given, then figure it out based on the name of the
|
||||||
|
PROJECT. If FILES aren't specified, then see if there is a
|
||||||
|
README. Otherwise, pull up Dired."
|
||||||
|
(interactive (list (completing-read "Project: "
|
||||||
|
(project-known-project-roots))))
|
||||||
|
(when (f-directory-p project)
|
||||||
|
(unless name
|
||||||
|
(setq name (f-filename project)))
|
||||||
|
(persp-switch name)
|
||||||
|
|
||||||
|
(let ((recent-files (thread-last recentf-list
|
||||||
|
(--filter (s-starts-with? project it))
|
||||||
|
(-take 3)))
|
||||||
|
(readme-org (f-join project "README.org"))
|
||||||
|
(readme-md (f-join project "README.md"))
|
||||||
|
(readme-rst (f-join project "README.rst")))
|
||||||
|
(cond
|
||||||
|
(files (ha--project-show-files project files))
|
||||||
|
(recent-files (ha--project-show-files project recent-files))
|
||||||
|
((f-exists? readme-org) (find-file readme-org))
|
||||||
|
((f-exists? readme-md) (find-file readme-md))
|
||||||
|
((f-exists? readme-rst) (find-file readme-rst))
|
||||||
|
(t (dired project))))))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
When starting a new perspective, and I specify more than one file, this function splits the window horizontally for each file.
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(defun ha--project-show-files (root files)
|
||||||
|
"Display a list of FILES in a project ROOT directory.
|
||||||
|
Each file gets its own window (so don't make the list of files
|
||||||
|
long)."
|
||||||
|
(when files
|
||||||
|
(let ((default-directory root)
|
||||||
|
(file (car files))
|
||||||
|
(more (cdr files)))
|
||||||
|
(message "Loading files from %s ... %s and %s" root file more)
|
||||||
|
(when (f-exists? file)
|
||||||
|
(find-file file))
|
||||||
|
(when more
|
||||||
|
(split-window-horizontally)
|
||||||
|
(ha--project-show-files root more)))))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
* Pretty Good Encryption
|
* Pretty Good Encryption
|
||||||
For details on using GnuPG in Emacs, see Mickey Petersen’s [[https://www.masteringemacs.org/article/keeping-secrets-in-emacs-gnupg-auth-sources][GnuPG Essay]].
|
For details on using GnuPG in Emacs, see Mickey Petersen’s [[https://www.masteringemacs.org/article/keeping-secrets-in-emacs-gnupg-auth-sources][GnuPG Essay]].
|
||||||
|
|
@ -1258,7 +1166,6 @@ Also, as [[https://www.bytedude.com/gpg-in-emacs/][bytedude]] mentions, I need t
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package epa-file
|
(use-package epa-file
|
||||||
:straight (:type built-in)
|
|
||||||
:custom
|
:custom
|
||||||
(epg-debug t)
|
(epg-debug t)
|
||||||
(auth-source-debug t)
|
(auth-source-debug t)
|
||||||
|
|
@ -1268,18 +1175,22 @@ Also, as [[https://www.bytedude.com/gpg-in-emacs/][bytedude]] mentions, I need t
|
||||||
;; Make sure we prompt in the minibuffer for the password:
|
;; Make sure we prompt in the minibuffer for the password:
|
||||||
(epg-pinentry-mode 'loopback)
|
(epg-pinentry-mode 'loopback)
|
||||||
;; I trust my Emacs session, so I don't bother expiring my pass:
|
;; I trust my Emacs session, so I don't bother expiring my pass:
|
||||||
(auth-source-cache-expiry nil)
|
(auth-source-cache-expiry nil))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Need to make sure that Emacs will handle the prompts, and turn it on:
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(use-package epa-file
|
||||||
:config
|
:config
|
||||||
(setenv "GPG_AGENT_INFO" nil)
|
(setenv "GPG_AGENT_INFO" nil)
|
||||||
(ignore-error (epa-file-enable)))
|
(epa-file-enable))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Since I already (at this point in my file) have Org installed and running, the following code configures the encryption of certain header sections (see [[https://orgmode.org/worg/org-tutorials/encrypting-files.html][this tutorial]]). Headers with a =:crypt:tag (see =org-crypt-tag-matcher= to change it) will be encrypted.
|
Since I already (at this point in my file) have Org installed and running, the following code configures the encryption of certain header sections (see [[https://orgmode.org/worg/org-tutorials/encrypting-files.html][this tutorial]]). Headers with a =:crypt:tag (see =org-crypt-tag-matcher= to change it) will be encrypted.
|
||||||
|
|
||||||
To temporarily read an encrypted part, and call =M-x org-decrypt-entry= when the cursor is inside that section. Saving the file, will re-encrypt it.
|
To temporarily read an encrypted part, and call =M-x org-decrypt-entry= when the cursor is inside that section. Saving the file, will re-encrypt it.
|
||||||
|
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp
|
||||||
(use-package org
|
(use-package org
|
||||||
:config
|
:config
|
||||||
(require 'org-crypt)
|
(require 'org-crypt)
|
||||||
|
|
|
||||||
|
|
@ -145,10 +145,12 @@ The [[https://github.com/emacs-dashboard/emacs-dashboard][emacs-dashboard]] proj
|
||||||
(string-replace "\n" "" smaller-version)))
|
(string-replace "\n" "" smaller-version)))
|
||||||
|
|
||||||
(setq dashboard-startup-banner
|
(setq dashboard-startup-banner
|
||||||
;; Choose a random image from my collection of startup images:
|
(if (ha-emacs-for-work?)
|
||||||
(thread-first "~/src/hamacs/support/dashboard"
|
"~/src/hamacs/support/teal-sticker.png"
|
||||||
(directory-files t (rx ".png"))
|
;; Choose a random image from my collection of startup images:
|
||||||
(seq-random-elt)))
|
(thread-first "~/src/hamacs/support/dashboard"
|
||||||
|
(directory-files t (rx ".png"))
|
||||||
|
(seq-random-elt))))
|
||||||
|
|
||||||
(setq dashboard-banner-logo-title
|
(setq dashboard-banner-logo-title
|
||||||
(format "Emacs %s — %s"
|
(format "Emacs %s — %s"
|
||||||
|
|
@ -167,12 +169,12 @@ The [[https://github.com/emacs-dashboard/emacs-dashboard][emacs-dashboard]] proj
|
||||||
dashboard-set-heading-icons t
|
dashboard-set-heading-icons t
|
||||||
dashboard-footer-messages (list (ha--dad-joke)))
|
dashboard-footer-messages (list (ha--dad-joke)))
|
||||||
|
|
||||||
|
:config
|
||||||
|
(dashboard-setup-startup-hook)
|
||||||
|
|
||||||
;; Real shame that :config is incompatible with :hook, otherwise:
|
;; Real shame that :config is incompatible with :hook, otherwise:
|
||||||
;; :hook (dashboard-after-initialize . ha-dashboard)
|
;; :hook (dashboard-after-initialize . ha-dashboard)
|
||||||
|
(add-hook 'dashboard-after-initialize-hook 'ha-dashboard))
|
||||||
:config
|
|
||||||
(tab-bar-switch-to-tab "main")
|
|
||||||
(dashboard-setup-startup-hook))
|
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
This dashboard project requires [[https://github.com/purcell/page-break-lines][page-break-lines]] (which is a nice project):
|
This dashboard project requires [[https://github.com/purcell/page-break-lines][page-break-lines]] (which is a nice project):
|
||||||
|
|
@ -262,7 +264,6 @@ The =dashboard= project hooks to [[help:emacs-startup-hook][emacs-startup-hook]]
|
||||||
(defun ha-dashboard ()
|
(defun ha-dashboard ()
|
||||||
"Shows the extra stuff with the dashboard."
|
"Shows the extra stuff with the dashboard."
|
||||||
(interactive)
|
(interactive)
|
||||||
(tab-bar-switch-to-tab "main")
|
|
||||||
(switch-to-buffer "*dashboard*")
|
(switch-to-buffer "*dashboard*")
|
||||||
(setq-local mode-line-format nil)
|
(setq-local mode-line-format nil)
|
||||||
(delete-other-windows)
|
(delete-other-windows)
|
||||||
|
|
|
||||||
15
ha-demos.org
15
ha-demos.org
|
|
@ -44,8 +44,8 @@ Once I made demonstrations /within/ Emacs with my [[https://github.com/howardabr
|
||||||
|
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp :tangle no
|
||||||
(use-package demo-it
|
(use-package demo-it
|
||||||
:straight (:local-repo "~/src/demo-it")
|
:load-path "~/src/demo-it"
|
||||||
;; :straight (:host github :repo "howardabrams/demo-it")
|
;; :vc (:url "https://github.com/howardabrams/demo-it")
|
||||||
:commands (demo-it-create demo-it-start demo-it-hide-mode-line
|
:commands (demo-it-create demo-it-start demo-it-hide-mode-line
|
||||||
demo-it--presentation-display-set)
|
demo-it--presentation-display-set)
|
||||||
:custom (demo-it--insert-test-speed :faster))
|
:custom (demo-it--insert-test-speed :faster))
|
||||||
|
|
@ -194,7 +194,7 @@ With some a startup bug that I haven’t been able to resolve, I’m not using i
|
||||||
|
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp :tangle no
|
||||||
(use-package dslide
|
(use-package dslide
|
||||||
:straight (dslide :host github :repo "positron-solutions/dslide")
|
;; :vc (:url "https://github.com/positron-solutions/dslide")
|
||||||
:commands (dslide-deck-start dslide-deck-stop)
|
:commands (dslide-deck-start dslide-deck-stop)
|
||||||
:custom
|
:custom
|
||||||
(dslide-start-from 'point)
|
(dslide-start-from 'point)
|
||||||
|
|
@ -244,7 +244,7 @@ Call the =ha-slide-notes-update= function automatically after updating a slide.
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp :tangle no
|
#+BEGIN_SRC emacs-lisp :tangle no
|
||||||
(use-package dslide
|
(use-package dslide
|
||||||
:straight (dslide :host github :repo "positron-solutions/dslide")
|
;; :vc (:url "https://github.com/positron-solutions/dslide")
|
||||||
:commands (dslide-narrow-hook)
|
:commands (dslide-narrow-hook)
|
||||||
:hook (dslide-narrow . 'ha-slide-notes-update))
|
:hook (dslide-narrow . 'ha-slide-notes-update))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
@ -256,8 +256,7 @@ The [[https://github.com/positron-solutions/moc][Master of Ceremonies]] package
|
||||||
(use-package default-text-scale)
|
(use-package default-text-scale)
|
||||||
|
|
||||||
(use-package moc
|
(use-package moc
|
||||||
:straight (:type git :host github
|
;; :vc (:url "https://github.com/positron-solutions/moc"))
|
||||||
:repo "positron-solutions/moc"))
|
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
Select text, and call =moc-focus= (call =moc-focus-quit= to stop). Highlight more text, and call =moc-focus-highlight= to brighten it, or =moc-focus-obscure= to hide it.
|
Select text, and call =moc-focus= (call =moc-focus-quit= to stop). Highlight more text, and call =moc-focus-highlight= to brighten it, or =moc-focus-obscure= to hide it.
|
||||||
|
|
@ -272,7 +271,7 @@ The [[https://github.com/trevorpogue/topspace][topspace]] project can pad the to
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(use-package topspace
|
(use-package topspace
|
||||||
:straight (:type git :host github :repo "trevorpogue/topspace"))
|
;; :vc (:url "https://github.com/trevorpogue/topspace"))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
*** Showing Something associated with a Headline
|
*** Showing Something associated with a Headline
|
||||||
|
|
@ -307,7 +306,7 @@ To do this, add =:DSLIDE_ACTIONS: dslide-action-highlight-paragraphs= to the pro
|
||||||
|
|
||||||
#+begin_src elisp emacs-lisp :tangle no
|
#+begin_src elisp emacs-lisp :tangle no
|
||||||
(use-package dslide
|
(use-package dslide
|
||||||
:straight (:host github :repo "positron-solutions/dslide")
|
;; :vc (:url "https://github.com/positron-solutions/dslide")
|
||||||
:config
|
:config
|
||||||
(defclass dslide-action-highlight-paragraphs (dslide-action)
|
(defclass dslide-action-highlight-paragraphs (dslide-action)
|
||||||
((overlays :initform nil))
|
((overlays :initform nil))
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ Let's turn off the menu and other settings:
|
||||||
Let's install and load some of packages from the [[https://github.com/hlissner/doom-emacs][Doom Emacs]] project, like [[https://github.com/seagle0128/doom-modeline][doom-modeline]] and maybe the themes:
|
Let's install and load some of packages from the [[https://github.com/hlissner/doom-emacs][Doom Emacs]] project, like [[https://github.com/seagle0128/doom-modeline][doom-modeline]] and maybe the themes:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package doom-modeline
|
(use-package doom-modeline
|
||||||
:config
|
:init
|
||||||
(setq doom-modeline-minor-modes nil
|
(setq doom-modeline-minor-modes nil
|
||||||
doom-modeline-buffer-encoding nil
|
doom-modeline-buffer-encoding nil
|
||||||
doom-modeline-major-mode-color-icon t
|
doom-modeline-major-mode-color-icon t
|
||||||
|
|
@ -56,82 +56,53 @@ Let's install and load some of packages from the [[https://github.com/hlissner/d
|
||||||
doom-modeline-modal 'evil
|
doom-modeline-modal 'evil
|
||||||
doom-modeline-lsp-icon t
|
doom-modeline-lsp-icon t
|
||||||
doom-modeline-percent-position nil)
|
doom-modeline-percent-position nil)
|
||||||
|
|
||||||
;; Weird bug:
|
|
||||||
(defvar helm-ag-show-status-function nil "Workaround")
|
|
||||||
|
|
||||||
(doom-modeline-mode 1))
|
(doom-modeline-mode 1))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
* Window Dimmer
|
* Window Dimmer
|
||||||
To make the active window /more noticeable/, we /dim/ the in-active windows with the [[https://github.com/gonewest818/dimmer.el][dimmer project]].
|
To make the active window /more noticeable/, we /dim/ the in-active windows with the [[https://github.com/gonewest818/dimmer.el][dimmer project]].
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package dimmer
|
(use-package dimmer
|
||||||
:custom (dimmer-adjustment-mode :foreground)
|
:custom (dimmer-adjustment-mode :foreground))
|
||||||
:config
|
#+end_src
|
||||||
;; I get issues with Magit and Dimmer, so let’s turn off this feature in certain windows:
|
|
||||||
(dimmer-configure-which-key) ; Do not dim these special windows
|
|
||||||
(dimmer-configure-hydra)
|
|
||||||
(dimmer-configure-magit)
|
|
||||||
|
|
||||||
(dimmer-mode t))
|
I get issues with Magic and Dimmer, so let’s turn off this feature in certain windows:
|
||||||
|
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(use-package dimmer
|
||||||
|
:config
|
||||||
|
(dimmer-configure-which-key) ; Do not dim these special windows
|
||||||
|
(dimmer-configure-hydra)
|
||||||
|
(dimmer-configure-magit)
|
||||||
|
|
||||||
|
(dimmer-mode t))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
As an interesting alternative, check out the [[https://www.emacs.dyerdwelling.family/emacs/20240208164549-emacs-selected-window-accent-mode-now-on-melpa/][selected-window-accent]] project.
|
As an interesting alternative, check out the [[https://www.emacs.dyerdwelling.family/emacs/20240208164549-emacs-selected-window-accent-mode-now-on-melpa/][selected-window-accent]] project.
|
||||||
** Ultra Scroll
|
** Ultra Scroll
|
||||||
|
|
||||||
The [[https://github.com/jdtsmith/ultra-scroll][ultra-scroll]] project allows smoother scrolling of text and images. While this splits text at the top/bottom of buffer windows, we no longer work within a 80x24 text matrix. Large images would either be "there or not" which resulted large jumps and large distractions.
|
The [[https://github.com/jdtsmith/ultra-scroll][ultra-scroll]] project allows smoother scrolling of text and images. While this splits text at the top/bottom of buffer windows, we no longer work within a 80x24 text matrix. Large images would
|
||||||
|
either be "there or not" which resulted large jumps and large distractions.
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(use-package ultra-scroll
|
(use-package ultra-scroll
|
||||||
:straight (:type git :host github :repo "jdtsmith/ultra-scroll")
|
;; :vc (:url "https://github.com/jdtsmith/ultra-scroll")
|
||||||
:config
|
:config
|
||||||
(setq scroll-conservatively 101 ; important!
|
(setq scroll-conservatively 101 ; important!
|
||||||
pixel-scroll-precision-interpolate-page t
|
scroll-margin 0)
|
||||||
scroll-margin 0)
|
|
||||||
(ultra-scroll-mode 1))
|
(ultra-scroll-mode 1))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
** Find the Bloody Cursor
|
** Find the Bloody Cursor
|
||||||
Large screen, lots of windows, so where is the cursor? While I used to use =hl-line+=, Sacha Chua’s [[https://fediscience.org/@ericsfraga/116279043710841253][suggestion]] solves the problem of the built-in =hl-line= that /highlights paragraphs/ in Org with a single function:
|
Large screen, lots of windows, so where is the cursor? While I used to use =hl-line+=, I found that the prolific [[https://protesilaos.com/][Protesilaos Stavrou]] [[https://protesilaos.com/codelog/2022-03-14-emacs-pulsar-demo/][introduced his Pulsar project]] is just what I need. Specifically, I might /loose the cursor/ and need to have it highlighted (using ~F8~), but also, this automatically highlights the cursor line with specific /actions/ , like changing windows.
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(defun get-visual-line-range ()
|
|
||||||
"Identify current visual line (for highlighting mostly)
|
|
||||||
|
|
||||||
Use the visual line functions to define the range in the buffer
|
|
||||||
that should be highlighted when ~hl-line-mode~ is enabled. By
|
|
||||||
default, the whole line in the file is highlighted. This is not
|
|
||||||
particularly useful in org files where I have whole paragraphs in a
|
|
||||||
single file line."
|
|
||||||
(let (b e)
|
|
||||||
(save-excursion
|
|
||||||
(beginning-of-visual-line)
|
|
||||||
(setq b (point))
|
|
||||||
(end-of-visual-line)
|
|
||||||
(setq e (point)))
|
|
||||||
(cons b e)))
|
|
||||||
|
|
||||||
(setq hl-line-range-function #'get-visual-line-range)
|
|
||||||
|
|
||||||
(global-hl-line-mode 1)
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
The real trick should /visually/ highlight the current line without being too light and interfering with seeing the text or being too obnoxious (see my [[file:ha-theme.org::*Dark Theme][Dark Theme]]).
|
|
||||||
|
|
||||||
I found that the prolific [[https://protesilaos.com/][Protesilaos Stavrou]] [[https://protesilaos.com/codelog/2022-03-14-emacs-pulsar-demo/][introduced his Pulsar project]] is just what I need. Specifically, I might /loose the cursor/ and need to have it highlighted (using ~F8~), but also, this automatically highlights the cursor line with specific /actions/ , like changing windows.
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package pulsar
|
(use-package pulsar
|
||||||
:straight (:host github :repo "protesilaos/pulsar")
|
;; :vc (:url "https://github.com/protesilaos/pulsar")
|
||||||
:after winum ace-window
|
|
||||||
:custom
|
:custom
|
||||||
(pulsar-face 'pulsar-generic)
|
(pulsar-face 'pulsar-generic)
|
||||||
(pulsar-delay 0.15)
|
(pulsar-delay 0.15)
|
||||||
|
|
||||||
:config
|
:config
|
||||||
(set-face-attribute 'pulsar-generic nil :background "orange")
|
|
||||||
|
|
||||||
(dolist (built-in-function '(recenter-top-bottom move-to-window-line-top-bottom reposition-window
|
(dolist (built-in-function '(recenter-top-bottom move-to-window-line-top-bottom reposition-window
|
||||||
bookmark-jump other-window delete-window delete-other-windows
|
bookmark-jump other-window delete-window delete-other-windows
|
||||||
forward-page backward-page scroll-up-command scroll-down-command
|
forward-page backward-page scroll-up-command scroll-down-command
|
||||||
|
|
@ -188,7 +159,7 @@ Am I ever really ever satisfied with any font? I regularly change my font based
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package mixed-pitch
|
(use-package mixed-pitch
|
||||||
;; :straight (:host github :repo "jabranham/mixed-pitch")
|
;; ;; :vc (:url "https://github.com/jabranham/mixed-pitch")
|
||||||
:config
|
:config
|
||||||
(add-to-list 'mixed-pitch-fixed-pitch-faces 'org-property-value)
|
(add-to-list 'mixed-pitch-fixed-pitch-faces 'org-property-value)
|
||||||
(add-to-list 'mixed-pitch-fixed-pitch-faces 'org-special-keyword)
|
(add-to-list 'mixed-pitch-fixed-pitch-faces 'org-special-keyword)
|
||||||
|
|
@ -418,7 +389,7 @@ This project replaces [[https://github.com/domtronn/all-the-icons.el][all-the-ic
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(use-package nerd-icons
|
(use-package nerd-icons
|
||||||
:straight (nerd-icons :type git :host github :repo "rainstormstudio/nerd-icons.el")
|
;; :vc (:url "https://github.com/rainstormstudio/nerd-icons.el")
|
||||||
:custom
|
:custom
|
||||||
;; The Nerd Font you want to use in GUI defaults to fixed-font:
|
;; The Nerd Font you want to use in GUI defaults to fixed-font:
|
||||||
(nerd-icons-font-family ha-fixed-font))
|
(nerd-icons-font-family ha-fixed-font))
|
||||||
|
|
@ -477,15 +448,10 @@ This replaces the /title generator/ for [[file:ha-config.org::*Leader Sequences]
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(setq major-mode-hydra-title-generator
|
(setq major-mode-hydra-title-generator
|
||||||
'(lambda (&optional mode)
|
'(lambda (&optional mode)
|
||||||
(unless mode (setq mode major-mode))
|
(let ((title (major-mode-hydra-title mode)))
|
||||||
(let ((title (major-mode-hydra-title mode))
|
(s-concat ; (s-repeat 5 " ")
|
||||||
(default (nerd-icons-mdicon "nf-md-apple_keyboard_command"
|
(nerd-icons-icon-for-mode (or mode major-mode) :v-adjust 0.05)
|
||||||
:v-adjust 0.05))
|
" " title " Commands"))))
|
||||||
(icon (nerd-icons-icon-for-mode (or mode major-mode)
|
|
||||||
:v-adjust 0.05)))
|
|
||||||
|
|
||||||
(concat (if (eq icon mode) default icon)
|
|
||||||
" " title " Commands"))))
|
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
Transition:
|
Transition:
|
||||||
|
|
@ -672,7 +638,7 @@ In code, if you drop a specific /text/ labels, we can highlight them with [[http
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package hl-todo
|
(use-package hl-todo
|
||||||
:straight (:host github :repo "tarsius/hl-todo")
|
;; :vc (:url "https://github.com/tarsius/hl-todo")
|
||||||
:config
|
:config
|
||||||
(setq hl-todo-keyword-faces
|
(setq hl-todo-keyword-faces
|
||||||
`(("TODO" . ,(face-foreground 'warning))
|
`(("TODO" . ,(face-foreground 'warning))
|
||||||
|
|
@ -688,16 +654,14 @@ Suggests to bind some keys to =hl-todo-next= in order to jump from tag to tag, b
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package consult-todo
|
(use-package consult-todo
|
||||||
:straight (:host github :repo "liuyinz/consult-todo")
|
;; :vc (:url "https://github.com/liuyinz/consult-todo")
|
||||||
:init
|
:init
|
||||||
(defconst consult-todo--narrow
|
(defconst consult-todo--narrow
|
||||||
'((?t . "TODO")
|
'((?t . "TODO")
|
||||||
(?f . "FIXME")
|
(?f . "FIXME")
|
||||||
(?n . "NOTE"))
|
(?n . "NOTE"))
|
||||||
"Mapping of narrow and keywords.")
|
"Mapping of narrow and keywords.")
|
||||||
;; :config
|
:general (:states 'normal "g t" '("jump todos" . consult-todo)))
|
||||||
;; (evil-define-key '(normal) 'global "g t" '("jump todos" . consult-todo))
|
|
||||||
)
|
|
||||||
#+end_src
|
#+end_src
|
||||||
* Full Size Frame
|
* Full Size Frame
|
||||||
Taken from [[https://emacsredux.com/blog/2020/12/04/maximize-the-emacs-frame-on-startup/][this essay]], I figured I would start the initial frame automatically in fullscreen, but not any subsequent frames (as this could be part of the capturing system).
|
Taken from [[https://emacsredux.com/blog/2020/12/04/maximize-the-emacs-frame-on-startup/][this essay]], I figured I would start the initial frame automatically in fullscreen, but not any subsequent frames (as this could be part of the capturing system).
|
||||||
|
|
|
||||||
72
ha-email.org
72
ha-email.org
|
|
@ -36,7 +36,7 @@ Next type ~s~ to view and organize mail I've never seen before. We need to focus
|
||||||
- ~P~ :: receipts go to this *Paper Trail*, which takes a *tag* as the name of the store
|
- ~P~ :: receipts go to this *Paper Trail*, which takes a *tag* as the name of the store
|
||||||
- ~f~ :: mailing lists and other email that might be nice to read go to *The Feed*
|
- ~f~ :: mailing lists and other email that might be nice to read go to *The Feed*
|
||||||
|
|
||||||
* Email Addresses
|
** Email Addresses
|
||||||
The configuration files below expect email addresses (I store passwords and other encrypted information elsewhere). These email addresses are /not/ private, but I figured I would annoy any screenscraping spam-inducing crawlers out there, while still allowing others to follow my lead on configuring Emacs and Email.
|
The configuration files below expect email addresses (I store passwords and other encrypted information elsewhere). These email addresses are /not/ private, but I figured I would annoy any screenscraping spam-inducing crawlers out there, while still allowing others to follow my lead on configuring Emacs and Email.
|
||||||
|
|
||||||
#+name: email-address-1
|
#+name: email-address-1
|
||||||
|
|
@ -54,18 +54,19 @@ The configuration files below expect email addresses (I store passwords and othe
|
||||||
(rot13-string "ubjneq@shmmlgbnfg.pbz")
|
(rot13-string "ubjneq@shmmlgbnfg.pbz")
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
To use these, we set the =:noweb yes= (to pull in the /name/ of the code block) but put a pair of parens after the name to have it evaluated. For instance, the configuration for sending mail through the MUA in Emacs. Note that port 465 requires implicit SSL/TLS (not STARTTLS), so we set =smtpmail-stream-type= to ~ssl~ and let =auth-source= (e.g. =~/.authinfo.gpg=) supply the credentials for =<<email-address-1>>=:
|
To use these, we set the =:noweb yes= (to pull in the /name/ of the code block) but put a pair of parens after the name to have it evaluated. For instance, the configuration for sending mail through the MUA in Emacs:
|
||||||
|
|
||||||
#+begin_src emacs-lisp :noweb yes
|
#+begin_src emacs-lisp :noweb yes
|
||||||
(setq send-mail-function 'smtpmail-send-it
|
(setq send-mail-function 'smtpmail-send-it
|
||||||
message-send-mail-function 'smtpmail-send-it
|
message-send-mail-function 'smtpmail-send-it
|
||||||
smtpmail-smtp-server "smtp.mailbox.org"
|
smtpmail-starttls-credentials '(("smtp.gmail.com" 587 nil nil))
|
||||||
smtpmail-smtp-service 465
|
smtpmail-auth-credentials `(("smtp.gmail.com" 587 ,<<email-address-1>> nil))
|
||||||
smtpmail-stream-type 'ssl)
|
smtpmail-default-smtp-server "smtp.gmail.com"
|
||||||
|
smtpmail-smtp-server "smtp.gmail.com"
|
||||||
|
smtpmail-smtp-service 587)
|
||||||
#+end_src
|
#+end_src
|
||||||
* Sending Mail
|
* Sending Mail
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(require 'smtpmail)
|
(require 'smtpmail)
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
* Installation and Basic Configuration
|
* Installation and Basic Configuration
|
||||||
|
|
@ -131,8 +132,8 @@ Also, let's do some basic configuration of Emacs' mail system:
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Create a special mail perspective:
|
Create a special mail perspective:
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp
|
||||||
(ha-leader "a M" `("mail" . ,(ha-tab-bar-new "mail" #'notmuch)))
|
(ha-leader "a M" `("mail" . ,(ha-app-perspective "mail" #'notmuch)))
|
||||||
#+end_src
|
#+end_src
|
||||||
* Configuration
|
* Configuration
|
||||||
Do I want to sign messages by default? Nope.
|
Do I want to sign messages by default? Nope.
|
||||||
|
|
@ -172,7 +173,6 @@ There are global settings:
|
||||||
#+begin_src conf :tangle ~/.mbsyncrc :noweb yes
|
#+begin_src conf :tangle ~/.mbsyncrc :noweb yes
|
||||||
# Note: We now tangle this file from ~/src/hamacs/ha-email.org
|
# Note: We now tangle this file from ~/src/hamacs/ha-email.org
|
||||||
Create Both
|
Create Both
|
||||||
Expunge Both
|
|
||||||
SyncState *
|
SyncState *
|
||||||
MaxMessages 100
|
MaxMessages 100
|
||||||
Sync All # New ReNew Flags
|
Sync All # New ReNew Flags
|
||||||
|
|
@ -184,41 +184,37 @@ The file generally can have a =Pass= entry for the encrypted passcode, but to sh
|
||||||
#+begin_src conf :tangle ~/.mbsyncrc :noweb yes
|
#+begin_src conf :tangle ~/.mbsyncrc :noweb yes
|
||||||
# PERSONAL ACCOUNT
|
# PERSONAL ACCOUNT
|
||||||
IMAPAccount personal
|
IMAPAccount personal
|
||||||
|
Host imap.gmail.com
|
||||||
|
User <<email-address-1()>> # Substitute your own email address here
|
||||||
|
PassCmd "gpg --quiet --for-your-eyes-only --no-tty --decrypt ~/.mailpass-personal.gpg"
|
||||||
|
SSLType IMAPS
|
||||||
|
AuthMechs LOGIN
|
||||||
|
|
||||||
IMAPStore personal-remote
|
IMAPStore personal-remote
|
||||||
Account personal
|
Account personal
|
||||||
Host imap.mailbox.org
|
|
||||||
User <<email-address-1()>> # Substitute your own email address here
|
|
||||||
PassCmd "gpg --quiet --for-your-eyes-only --no-tty --decrypt ~/.mailpass-personal.gpg"
|
|
||||||
SSLType STARTTLS
|
|
||||||
|
|
||||||
MaildirStore personal-local
|
MaildirStore personal-local
|
||||||
Path ~/.mail/personal/
|
Path ~/.mail/personal/
|
||||||
Inbox ~/.mail/personal/INBOX
|
Inbox ~/.mail/personal/INBOX
|
||||||
SubFolders Verbatim
|
Flatten .
|
||||||
|
|
||||||
Channel mailbox
|
Channel personal-inbox
|
||||||
Master :mailbox-remote:
|
Far :personal-remote:
|
||||||
Slave :mailbox-local:
|
Near :personal-local:
|
||||||
Patterns * !Archives
|
Patterns * !"[Gmail]/Drafts" !"[Gmail]/Spam"
|
||||||
|
Expunge Both
|
||||||
|
# Patterns "inbox"
|
||||||
|
# ExpireUnread no
|
||||||
|
|
||||||
## Channel personal-inbox
|
Channel personal-sent
|
||||||
## Far :personal-remote:
|
Far :personal-remote:"[Gmail]/Sent Mail"
|
||||||
## Near :personal-local:
|
Near :personal-local:sent
|
||||||
## # Patterns * !"[Gmail]/Drafts" !"[Gmail]/Spam"
|
ExpireUnread yes
|
||||||
## Expunge Both
|
|
||||||
## # Patterns "inbox"
|
Channel personal-trash
|
||||||
## # ExpireUnread no
|
Far :personal-remote:"[Gmail]/Trash"
|
||||||
##
|
Near :personal-local:trash
|
||||||
## Channel personal-sent
|
ExpireUnread yes
|
||||||
## # Far :personal-remote:"[Gmail]/Sent Mail"
|
|
||||||
## Near :personal-local:sent
|
|
||||||
## ExpireUnread yes
|
|
||||||
##
|
|
||||||
## Channel personal-trash
|
|
||||||
## # Far :personal-remote:"[Gmail]/Trash"
|
|
||||||
## Near :personal-local:trash
|
|
||||||
## ExpireUnread yes
|
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
I have other email accounts that could use or ignore.
|
I have other email accounts that could use or ignore.
|
||||||
|
|
@ -741,13 +737,13 @@ The idea of linking org documents to email could be nice, however, the =ol-notmu
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp :tangle no
|
||||||
(use-package ol-notmuch
|
(use-package ol-notmuch
|
||||||
:after org
|
:after org
|
||||||
:straight (:type built-in)
|
|
||||||
:config (add-to-list 'org-modules 'ol-notmuch))
|
:config (add-to-list 'org-modules 'ol-notmuch))
|
||||||
#+end_src
|
#+end_src
|
||||||
To use, read a message and save a link to it with ~SPC o l~. Next, in an org document, create a link with ~, l~. Now, you can return to the message from that document with ~, o~. Regardless, I may need to store a local copy when I upgrade Org.
|
To use, read a message and save a link to it with ~SPC o l~. Next, in an org document, create a link with ~, l~. Now, you can return to the message from that document with ~, o~. Regardless, I may need to store a local copy when I upgrade Org.
|
||||||
* Display Configuration
|
* Display Configuration
|
||||||
Using the [[https://github.com/seagle0128/doom-modeline][Doom Modeline]] to add notifications:
|
Using the [[https://github.com/seagle0128/doom-modeline][Doom Modeline]] to add notifications:
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp
|
||||||
(use-package doom-modeline
|
(use-package doom-modeline
|
||||||
:config
|
:config
|
||||||
(setq doom-modeline-mu4e t))
|
(setq doom-modeline-mu4e t))
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ If you find the documentation lacking, I [[http://www.howardism.org/Technical/Em
|
||||||
Tell straight to use the built-in =eshell=:
|
Tell straight to use the built-in =eshell=:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package eshell
|
(use-package eshell
|
||||||
:straight (:type built-in)
|
|
||||||
:hook (eshell-mode . ha-eshell-setup))
|
:hook (eshell-mode . ha-eshell-setup))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
|
|
@ -976,7 +976,7 @@ The problem with this trick is that =rx= outputs an Emacs-compatible regular exp
|
||||||
The [[https://github.com/joddie/pcre2el][pcre2el]] project can convert from a Lisp regular expression to a [[http://www.pcre.org/][PCRE]] (Perl Compatible Regular Expression), acceptable by [[https://github.com/BurntSushi/ripgrep][ripgrep]].
|
The [[https://github.com/joddie/pcre2el][pcre2el]] project can convert from a Lisp regular expression to a [[http://www.pcre.org/][PCRE]] (Perl Compatible Regular Expression), acceptable by [[https://github.com/BurntSushi/ripgrep][ripgrep]].
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package pcre2el
|
(use-package pcre2el
|
||||||
:straight (:host github :repo "joddie/pcre2el")
|
;; :vc (:url "https://github.com/joddie/pcre2el")
|
||||||
:config
|
:config
|
||||||
(defmacro prx (&rest expressions)
|
(defmacro prx (&rest expressions)
|
||||||
"Convert the rx-compatible regular EXPRESSIONS to PCRE.
|
"Convert the rx-compatible regular EXPRESSIONS to PCRE.
|
||||||
|
|
@ -1378,7 +1378,7 @@ The [[https://codeberg.org/akib/emacs-eat][Emulate a Terminal]] project provides
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp :tangle no
|
||||||
(use-package eat
|
(use-package eat
|
||||||
:after eshell
|
:after eshell
|
||||||
:straight (:repo "https://codeberg.org/akib/emacs-eat")
|
;; :vc (:url "https://codeberg.org/akib/emacs-eat")
|
||||||
:hook (eshell-load . eat-eshell-visual-command-mode))
|
:hook (eshell-load . eat-eshell-visual-command-mode))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
|
|
@ -1787,7 +1787,7 @@ Sometimes you need to change something about the current file you are editing...
|
||||||
Here is where we associate all the functions and their hooks with =eshell=, through the magic of =use-package=.
|
Here is where we associate all the functions and their hooks with =eshell=, through the magic of =use-package=.
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package eshell
|
(use-package eshell
|
||||||
:straight (:type built-in)
|
|
||||||
:custom (eshell-banner-message '(ha-eshell-banner))
|
:custom (eshell-banner-message '(ha-eshell-banner))
|
||||||
:init
|
:init
|
||||||
(setq eshell-error-if-no-glob t
|
(setq eshell-error-if-no-glob t
|
||||||
|
|
|
||||||
38
ha-evil.org
38
ha-evil.org
|
|
@ -54,7 +54,9 @@ I split the configuration of Evil mode into sections. First, global settings:
|
||||||
evil-want-keybinding nil ; work with evil-collection
|
evil-want-keybinding nil ; work with evil-collection
|
||||||
evil-want-integration t
|
evil-want-integration t
|
||||||
evil-want-C-u-scroll nil
|
evil-want-C-u-scroll nil
|
||||||
evil-want-C-i-jump nil))
|
evil-want-C-i-jump nil
|
||||||
|
evil-escape-key-sequence "jk"
|
||||||
|
evil-escape-unordered-key-sequence t))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
The Escape key act like ~C-g~ and always go back to normal mode?
|
The Escape key act like ~C-g~ and always go back to normal mode?
|
||||||
|
|
@ -191,29 +193,12 @@ I am not a long term VI user, and don’t have much need for any of its control
|
||||||
;; I have better window control:
|
;; I have better window control:
|
||||||
(kbd "C-w") 'sp-kill-region))
|
(kbd "C-w") 'sp-kill-region))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** Evil Escape
|
|
||||||
I find that myself not noticing I’m in /insert/ mode, and start typing =j= repeatedly to go down a line, and end up typing something like: =jjjjjjjjjjjj=.
|
|
||||||
|
|
||||||
To remedy this, I’m using the [[https://www.emacswiki.org/emacs/KeyChord][KeyChord]] code:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(use-package key-chord
|
|
||||||
:config
|
|
||||||
(setq key-chord-two-keys-delay 0.1)
|
|
||||||
|
|
||||||
(key-chord-define evil-insert-state-map "jk" 'evil-normal-state)
|
|
||||||
(key-chord-define evil-insert-state-map "jj" 'evil-normal-state)
|
|
||||||
(key-chord-define evil-insert-state-map "kk" 'evil-normal-state)
|
|
||||||
|
|
||||||
(key-chord-mode 1))
|
|
||||||
#+END_SRC
|
|
||||||
** Evil Text Object Line
|
** Evil Text Object Line
|
||||||
Delete a line, ~d d~ is in basic VI. Since some commands use text objects, and the basic text object doesn’t include lines, the [[https://github.com/emacsorphanage/evil-textobj-line][evil-textobj-line]] project adds that:
|
Delete a line, ~d d~ is in basic VI. Since some commands use text objects, and the basic text object doesn’t include lines, the [[https://github.com/emacsorphanage/evil-textobj-line][evil-textobj-line]] project adds that:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package evil-textobj-line)
|
(use-package evil-textobj-line)
|
||||||
#+end_src
|
#+end_src
|
||||||
Now ~v i l~ selects the line without initial/trailing whitespace, and ~v a l~ selects the line with spaces, and ~S-v~ selects the line with the final carriage return. Is this much of an improvement? Well, wrapping a line in parenthesis, is just ~y s i l )~
|
Now ~v i l~ and ~v a l~ works as you’d expect, but does this improve on ~S-v~?
|
||||||
** Text Objects based on Indentation
|
** Text Objects based on Indentation
|
||||||
The [[https://github.com/TheBB/evil-indent-plus][evil-indent-plus]] project creates text objects based on the indentation level, similar to how the ~b~ works with “blocks” of code.
|
The [[https://github.com/TheBB/evil-indent-plus][evil-indent-plus]] project creates text objects based on the indentation level, similar to how the ~b~ works with “blocks” of code.
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
|
|
@ -355,16 +340,6 @@ And the keybindings:
|
||||||
|
|
||||||
Why not use ~f~? I’m reserving the ~f~ for a tree-sitter version that is not always available for all modes… yet.
|
Why not use ~f~? I’m reserving the ~f~ for a tree-sitter version that is not always available for all modes… yet.
|
||||||
* Evil Extensions
|
* Evil Extensions
|
||||||
** Anzu
|
|
||||||
[[https://github.com/emacsorphanage/anzu][Anzu]] provides a minor mode which displays current match and total matches information in the mode-line in various search modes.
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(use-package anzu
|
|
||||||
:ensure t
|
|
||||||
:config
|
|
||||||
(global-anzu-mode +1))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
** Evil Exchange
|
** Evil Exchange
|
||||||
I often use the Emacs commands, ~M-t~ and whatnot to exchange words and whatnot, but this requires a drop out of normal state mode. The [[https://github.com/Dewdrops/evil-exchange][evil-exchange]] project attempts to do something similar, but in a VI-way, and the /objects/ do not need to be adjacent.
|
I often use the Emacs commands, ~M-t~ and whatnot to exchange words and whatnot, but this requires a drop out of normal state mode. The [[https://github.com/Dewdrops/evil-exchange][evil-exchange]] project attempts to do something similar, but in a VI-way, and the /objects/ do not need to be adjacent.
|
||||||
|
|
||||||
|
|
@ -427,7 +402,7 @@ Where I like to align, is on variable assignments, e.g.
|
||||||
If you press ~RETURN~ for the /character/ to align, =evil-lion= package simply calls the built-in [[help:align][align]] function. This function chooses a regular expression based on a list of /rules/, and aligning Lisp variables requires a complicated regular expression. Extend [[elisp:(describe-variable 'align-rules-list)][align-rules-list]]:
|
If you press ~RETURN~ for the /character/ to align, =evil-lion= package simply calls the built-in [[help:align][align]] function. This function chooses a regular expression based on a list of /rules/, and aligning Lisp variables requires a complicated regular expression. Extend [[elisp:(describe-variable 'align-rules-list)][align-rules-list]]:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package align
|
(use-package align
|
||||||
:straight (:type built-in)
|
|
||||||
:config
|
:config
|
||||||
(add-to-list 'align-rules-list
|
(add-to-list 'align-rules-list
|
||||||
`("lisp-assignments"
|
`("lisp-assignments"
|
||||||
|
|
@ -514,8 +489,9 @@ Notes:
|
||||||
** Evil Jump, er Better Jump
|
** Evil Jump, er Better Jump
|
||||||
The [[https//github.com/gilbertw1/better-jumper][better-jumper project]] replaces the [[https://github.com/bling/evil-jumper][evil-jumper project]], essentially allowing you jump back to various movements. While I already use ~g ;~ to jump to the last change, this jumps /to the jumps/ … kinda. I’m having a difficult time determining /what jumps/ are remembered.
|
The [[https//github.com/gilbertw1/better-jumper][better-jumper project]] replaces the [[https://github.com/bling/evil-jumper][evil-jumper project]], essentially allowing you jump back to various movements. While I already use ~g ;~ to jump to the last change, this jumps /to the jumps/ … kinda. I’m having a difficult time determining /what jumps/ are remembered.
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp :tangle no
|
||||||
(use-package better-jumper
|
(use-package better-jumper
|
||||||
|
;; :vc (:url "https//github.com/gilbertw1/better-jumper")
|
||||||
:config
|
:config
|
||||||
(better-jumper-mode +1)
|
(better-jumper-mode +1)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ According to Ben Maughan and [[http://pragmaticemacs.com/emacs/to-eww-or-not-to-
|
||||||
|
|
||||||
And some global keys to display them in the =apps= menu:
|
And some global keys to display them in the =apps= menu:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(ha-leader "a f" `("feed reader" . ,(ha-tab-bar-new "elfeed" #'elfeed)))
|
(ha-leader "a f" `("feed reader" . ,(ha-app-perspective "elfeed" #'elfeed)))
|
||||||
#+end_src
|
#+end_src
|
||||||
* The Feeds :elfeed:
|
* The Feeds :elfeed:
|
||||||
The [[https://github.com/remyhonig/elfeed-org][elfeed-org]] project configures =elfeed= to read the RSS feeds from an Org file … like this one!
|
The [[https://github.com/remyhonig/elfeed-org][elfeed-org]] project configures =elfeed= to read the RSS feeds from an Org file … like this one!
|
||||||
|
|
|
||||||
|
|
@ -335,8 +335,9 @@ And the collection of useful operations:
|
||||||
"b O" '("other" . project-switch-buffer-to-other-window)
|
"b O" '("other" . project-switch-buffer-to-other-window)
|
||||||
"b i" '("ibuffer" . ibuffer)
|
"b i" '("ibuffer" . ibuffer)
|
||||||
"b I" '("ibuffer" . ibuffer-other-window)
|
"b I" '("ibuffer" . ibuffer-other-window)
|
||||||
|
"b k" '("persp remove" . persp-remove-buffer)
|
||||||
"b N" '("new" . evil-buffer-new)
|
"b N" '("new" . evil-buffer-new)
|
||||||
"b d" '("delete" . kill-buffer)
|
"b d" '("delete" . persp-kill-buffer*)
|
||||||
"b r" '("revert" . revert-buffer)
|
"b r" '("revert" . revert-buffer)
|
||||||
"b s" '("save" . save-buffer)
|
"b s" '("save" . save-buffer)
|
||||||
"b S" '("save all" . evil-write-all)
|
"b S" '("save all" . evil-write-all)
|
||||||
|
|
@ -360,15 +361,10 @@ And the collection of useful operations:
|
||||||
"b C-g" '(keyboard-escape-quit :which-key t))
|
"b C-g" '(keyboard-escape-quit :which-key t))
|
||||||
#+end_src
|
#+end_src
|
||||||
* Bookmarks
|
* Bookmarks
|
||||||
The built-in behavior of bookmarks doesn’t honor either /projects/ or /perspectives/, but the [[https://codeberg.org/ideasman42/emacs-bookmark-in-project][bookmark-in-project]] package makes =project=-specific bookmarks.
|
Expand on my [[file:ha-config.org::*Bookmarks][Bookmarks]] with the following key sequences:
|
||||||
|
|
||||||
This expands on my global [[file:ha-config.org::*Bookmarks][Bookmarks]]:
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package bookmark-in-project
|
(use-package bookmark-in-project
|
||||||
:bindd
|
|
||||||
(("C-x r m" . bookmark-in-project-toggle)
|
|
||||||
("C-x r M" . ha-bookmark-set))
|
|
||||||
:config
|
:config
|
||||||
(ha-leader
|
(ha-leader
|
||||||
;; Set or delete a bookmark associated with project:
|
;; Set or delete a bookmark associated with project:
|
||||||
|
|
@ -380,31 +376,6 @@ This expands on my global [[file:ha-config.org::*Bookmarks][Bookmarks]]:
|
||||||
"b <down>" '("next mark" . bookmark-in-project-jump-next)
|
"b <down>" '("next mark" . bookmark-in-project-jump-next)
|
||||||
"b <up>" '("previous mark" . bookmark-in-project-jump-previous)))
|
"b <up>" '("previous mark" . bookmark-in-project-jump-previous)))
|
||||||
#+end_src
|
#+end_src
|
||||||
** Annotations
|
|
||||||
Let's try [[https://github.com/bastibe/annotate.el][annotate-mode]], which allows you to drop "notes" displayed /inline/ with the buffer.
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
|
||||||
(use-package annotate
|
|
||||||
:config
|
|
||||||
(ha-leader
|
|
||||||
"t A" '("annotations" . annotate-mode)
|
|
||||||
|
|
||||||
"n" '(:ignore t :which-key "notes")
|
|
||||||
"n a" '("toggle mode" . annotate-mode)
|
|
||||||
"n n" '("annotate" . annotate-annotate)
|
|
||||||
"n d" '("delete" . annotate-delete-annotation)
|
|
||||||
|
|
||||||
;; Can't seem to get anything useful out of this function:
|
|
||||||
"n s" '("summary" . annotate-show-annotation-summary)
|
|
||||||
|
|
||||||
;; The next and previous only work for the current file:
|
|
||||||
"n j" '("next" . annotate-goto-next-annotation)
|
|
||||||
"n k" '("prev" . annotate-goto-previous-annotation)))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
Keep the annotations simple, almost /tag-like/, and then the summary doesn’t wrap. For longer notes, see my [[file:ha-capturing-notes.org::*Code Notes][Code Notes]] section.
|
|
||||||
|
|
||||||
The annotations are stored as s-expressions in the file, =~/.emacs.d/annotations=. Let’s see how well this scales.
|
|
||||||
* Centering
|
* Centering
|
||||||
After reading [[https://mbork.pl/2024-04-15_Improving_recenter-top-bottom_and_reposition-window][this essay]], I got to thinking that it would be nice to position the text in a buffer /near the top/, but show context based on some specific, textual /things/. My thought is to have a function that prompts for the thing (like the current paragraph, function, etc), but also create thing-specific functions.
|
After reading [[https://mbork.pl/2024-04-15_Improving_recenter-top-bottom_and_reposition-window][this essay]], I got to thinking that it would be nice to position the text in a buffer /near the top/, but show context based on some specific, textual /things/. My thought is to have a function that prompts for the thing (like the current paragraph, function, etc), but also create thing-specific functions.
|
||||||
|
|
||||||
|
|
@ -550,7 +521,6 @@ The goal here is toggle switches and other miscellaneous settings.
|
||||||
"t T" '("tramp mode" . tramp-mode)
|
"t T" '("tramp mode" . tramp-mode)
|
||||||
"t v" '("visual" . visual-line-mode)
|
"t v" '("visual" . visual-line-mode)
|
||||||
"t w" '("whitespace" . whitespace-mode)
|
"t w" '("whitespace" . whitespace-mode)
|
||||||
"t <tab>" '("tab-bar" . tab-bar-mode)
|
|
||||||
|
|
||||||
"t <escape>" '(keyboard-escape-quit :which-key t)
|
"t <escape>" '(keyboard-escape-quit :which-key t)
|
||||||
"t C-g" '(keyboard-escape-quit :which-key t))
|
"t C-g" '(keyboard-escape-quit :which-key t))
|
||||||
|
|
@ -576,10 +546,13 @@ And put it on the toggle menu:
|
||||||
(ha-leader "t n" '("narrow" . ha-narrow-dwim))
|
(ha-leader "t n" '("narrow" . ha-narrow-dwim))
|
||||||
#+end_src
|
#+end_src
|
||||||
* Window Operations
|
* Window Operations
|
||||||
While it comes with Emacs, the =tab-bar= feature keeps track of all window configurations within a tab, allowing me to revert situations where I accidentally delete all the windows.
|
While it comes with Emacs, I use [[https://www.emacswiki.org/emacs/WinnerMode][winner-mode]] to undo window-related changes:
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(tab-bar-history-mode)
|
(use-package winner
|
||||||
|
:custom
|
||||||
|
(winner-dont-bind-my-keys t)
|
||||||
|
:config
|
||||||
|
(winner-mode +1))
|
||||||
#+end_src
|
#+end_src
|
||||||
** Ace Window
|
** Ace Window
|
||||||
Use the [[https://github.com/abo-abo/ace-window][ace-window]] project to jump to any window you see.
|
Use the [[https://github.com/abo-abo/ace-window][ace-window]] project to jump to any window you see.
|
||||||
|
|
@ -907,7 +880,7 @@ Remember these keys in the *Help* buffer:
|
||||||
Let's make Info behave a little more VI-like:
|
Let's make Info behave a little more VI-like:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package info
|
(use-package info
|
||||||
:straight (:type built-in)
|
|
||||||
:general
|
:general
|
||||||
(:states 'normal :keymaps 'Info-mode-map
|
(:states 'normal :keymaps 'Info-mode-map
|
||||||
"B" 'Info-bookmark-jump
|
"B" 'Info-bookmark-jump
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ Quick way to start and jump to my IRC world.
|
||||||
|
|
||||||
And some global keys to display them:
|
And some global keys to display them:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(ha-leader "a i" `("irc" . ,(ha-tab-bar-new "irc" #'ha-erc)))
|
(ha-leader "a i" `("irc" . ,(ha-app-perspective "irc" #'ha-erc)))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
And a quick shortcuts to call it:
|
And a quick shortcuts to call it:
|
||||||
|
|
@ -121,7 +121,7 @@ And a quick shortcuts to call it:
|
||||||
#+end_src
|
#+end_src
|
||||||
* Display Configuration
|
* Display Configuration
|
||||||
Using the [[https://github.com/seagle0128/doom-modeline][Doom Modeline]] to add notifications:
|
Using the [[https://github.com/seagle0128/doom-modeline][Doom Modeline]] to add notifications:
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp
|
||||||
(setq doom-modeline-irc t
|
(setq doom-modeline-irc t
|
||||||
doom-modeline-irc-stylize 'identity)
|
doom-modeline-irc-stylize 'identity)
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
#+author: Howard Abrams
|
#+author: Howard Abrams
|
||||||
#+date: 2024-07-07
|
#+date: 2024-07-07
|
||||||
#+filetags: emacs hamacs
|
#+filetags: emacs hamacs
|
||||||
#+lastmod: [2026-08-05 Wed]
|
#+lastmod: [2025-09-09 Tue]
|
||||||
|
|
||||||
A literate programming file for literate programming in Emacs Org Files.
|
A literate programming file for literate programming in Emacs Org Files.
|
||||||
|
|
||||||
|
|
@ -51,7 +51,7 @@ Since we are using Emacs, the downsides of using a literate approach (even for p
|
||||||
** Surround with Org Block
|
** Surround with Org Block
|
||||||
This simple change allows me to highlight any text, and surround it in an org block. We
|
This simple change allows me to highlight any text, and surround it in an org block. We
|
||||||
|
|
||||||
#+BEGIN_SRC snippet :tangle ~/.emacs.d/snippets/org-mode/surround-org-block :eval none
|
#+BEGIN_SRC snippet :tangle ~/.emacs.d/snippets/org-mode/surround-org-block
|
||||||
# name: surround-org-block
|
# name: surround-org-block
|
||||||
# --
|
# --
|
||||||
,#+BEGIN_${1:SRC} ${2:emacs-lisp} $0
|
,#+BEGIN_${1:SRC} ${2:emacs-lisp} $0
|
||||||
|
|
@ -90,7 +90,7 @@ Actually, I probably want all the parameters (if any) to come along for the ride
|
||||||
|
|
||||||
This allows us to now split a block into two parts:
|
This allows us to now split a block into two parts:
|
||||||
|
|
||||||
#+BEGIN_SRC snippet :tangle ~/.emacs.d/snippets/org-mode/split-org-block :eval none
|
#+BEGIN_SRC snippet :tangle ~/.emacs.d/snippets/org-mode/split-org-block
|
||||||
,# -*- mode: snippet -*-
|
,# -*- mode: snippet -*-
|
||||||
,# name: split-org-block
|
,# name: split-org-block
|
||||||
,# key: #split
|
,# key: #split
|
||||||
|
|
@ -712,8 +712,7 @@ Not every header should be a destination, as many of my org files have duplicate
|
||||||
"Install"
|
"Install"
|
||||||
"Overview"
|
"Overview"
|
||||||
"Summary"
|
"Summary"
|
||||||
"Technical Artifacts"
|
"Technical Artifacts"))
|
||||||
"[["))
|
|
||||||
"Regular expression matching headers to purge.")
|
"Regular expression matching headers to purge.")
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
|
|
@ -778,20 +777,12 @@ Before we dive into the implementation of this function, let’s write a test to
|
||||||
'("Somefile∷ Parent﹥ Subparent﹥ Deep Heading"
|
'("Somefile∷ Parent﹥ Subparent﹥ Deep Heading"
|
||||||
"ha-somefile.org" 73)))
|
"ha-somefile.org" 73)))
|
||||||
|
|
||||||
(setq ha-hamacs-edit-prev-head-list '("Parent" "Subparent" "Subby"))
|
|
||||||
|
|
||||||
(should (equal
|
|
||||||
(ha-hamacs-edit--process-entry
|
|
||||||
"ha-somefile.org:73:**** Deep Heading")
|
|
||||||
'("Somefile∷ Parent﹥ Subparent﹥ Subby﹥ Deep Heading"
|
|
||||||
"ha-somefile.org" 73)))
|
|
||||||
|
|
||||||
(setq ha-hamacs-edit-prev-head-list '("Parent" "Subparent"
|
(setq ha-hamacs-edit-prev-head-list '("Parent" "Subparent"
|
||||||
"Subby" "Deepsubby"))
|
"Subby" "Deepsubby"))
|
||||||
(should (equal
|
(should (equal
|
||||||
(ha-hamacs-edit--process-entry
|
(ha-hamacs-edit--process-entry
|
||||||
"ha-somefile.org:73:***** Deepest Heading")
|
"ha-somefile.org:73:***** Deepest Heading")
|
||||||
'("Somefile∷ Parent﹥ Subparent﹥ Subby﹥ Deepsubby﹥ Deepest Heading"
|
'("Somefile∷ ... Deepest Heading"
|
||||||
"ha-somefile.org" 73))))
|
"ha-somefile.org" 73))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
|
|
@ -871,11 +862,13 @@ I would like to make the /filename/ more readable, I use the =s-match= again, to
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defun ha-hamacs-edit--file-title (file)
|
(defun ha-hamacs-edit--file-title (file)
|
||||||
"Return a more readable string from FILE."
|
"Return a more readable string from FILE."
|
||||||
(s-with file
|
(if file
|
||||||
(s-match ha-hamacs-edit-file-to-title)
|
(s-with file
|
||||||
(second)
|
(s-match ha-hamacs-edit-file-to-title)
|
||||||
(s-replace "-" " ")
|
(second)
|
||||||
(s-titleize)))
|
(s-replace "-" " ")
|
||||||
|
(s-titleize))
|
||||||
|
file))
|
||||||
|
|
||||||
(defvar ha-hamacs-edit-file-to-title
|
(defvar ha-hamacs-edit-file-to-title
|
||||||
(rx (optional (or "README-" "ha-"))
|
(rx (optional (or "README-" "ha-"))
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ The RSS needs UUIDs:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp results silent
|
#+BEGIN_SRC emacs-lisp results silent
|
||||||
(use-package uuidgen
|
(use-package uuidgen
|
||||||
:straight (:host github :repo "emacsmirror/uuidgen"))
|
;; :vc (:url "https://github.com/emacsmirror/uuidgen"))
|
||||||
|
|
||||||
(defun org-icalendar-create-uid (&rest ignored)
|
(defun org-icalendar-create-uid (&rest ignored)
|
||||||
"Returns a UUID."
|
"Returns a UUID."
|
||||||
|
|
@ -42,7 +42,7 @@ While the following packages come with Emacs, they aren't necessarily loaded:
|
||||||
|
|
||||||
#+begin_src emacs-lisp :results silent
|
#+begin_src emacs-lisp :results silent
|
||||||
(use-package ox-rss
|
(use-package ox-rss
|
||||||
:straight (:host github :repo "emacsmirror/ox-rss"))
|
;; :vc (:url "https://github.com/emacsmirror/ox-rss"))
|
||||||
|
|
||||||
(use-package org
|
(use-package org
|
||||||
:config
|
:config
|
||||||
|
|
@ -64,7 +64,7 @@ Render my code with my font colors:
|
||||||
Also, we need Jack, and his HTML prowess:
|
Also, we need Jack, and his HTML prowess:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package jack
|
(use-package jack
|
||||||
:straight (:host github :repo "tonyaldon/jack")
|
;; :vc (:url "https://github.com/tonyaldon/jack")
|
||||||
:commands (jack-html))
|
:commands (jack-html))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@
|
||||||
#+author: Howard X. Abrams
|
#+author: Howard X. Abrams
|
||||||
#+date: 2020-09-25
|
#+date: 2020-09-25
|
||||||
#+tags: emacs work
|
#+tags: emacs work
|
||||||
|
|
||||||
A literate program for configuring org files for work-related notes.
|
A literate program for configuring org files for work-related notes.
|
||||||
|
|
||||||
#+begin_src emacs-lisp :exports none
|
#+begin_src emacs-lisp :exports none
|
||||||
;;; org-sprint --- Configuring org files for work-related notes. -*- lexical-binding: t; -*-
|
;;; org-sprint --- Configuring org files for work-related notes. -*- lexical-binding: t; -*-
|
||||||
;;
|
;;
|
||||||
|
|
@ -23,51 +25,31 @@ A literate program for configuring org files for work-related notes.
|
||||||
;;; Code:
|
;;; Code:
|
||||||
#+end_src
|
#+end_src
|
||||||
* Introduction
|
* Introduction
|
||||||
|
|
||||||
At the beginning of each Sprint, I create a new org file dedicated to it. This workflow/technique strikes a balance between a single ever-growing file, and a thousand little ones. This also gives me a sense of continuity, as the filename of each sprint is date-based.
|
At the beginning of each Sprint, I create a new org file dedicated to it. This workflow/technique strikes a balance between a single ever-growing file, and a thousand little ones. This also gives me a sense of continuity, as the filename of each sprint is date-based.
|
||||||
|
|
||||||
I want a single keybinding that always displays the current Sprint note file, regardless of the Sprint. This means, I need to have functions that can calculate what this is.
|
I want a single keybinding that always displays the current Sprint note file, regardless of the Sprint. This means, I need to have functions that can calculate what this is.
|
||||||
|
|
||||||
To have the Org Capture features to be able to write to correct locations in the current file, I need each file to follow a particular format. I create a [[file:templates/sprint.org][sprint note template]] that will be automatically expanded with a new sprint.
|
To have the Org Capture features to be able to write to correct locations in the current file, I need each file to follow a particular format. I create a [[file:templates/sprint.org][sprint note template]] that will be automatically expanded with a new sprint.
|
||||||
|
|
||||||
This template needs the following functions:
|
This template needs the following functions:
|
||||||
- =sprint-current-name= to be both the numeric label as well as the nickname
|
- =sprint-current-name= to be both the numeric label as well as the nickname
|
||||||
- =sprint-date-range= to include a org-formatted date range beginning and ending the sprint
|
- =sprint-date-range= to include a org-formatted date range beginning and ending the sprint
|
||||||
- =sprint-date-from-start= return a date for pre-scheduled and recurring meetings
|
- =sprint-date-from-start= return a date for pre-scheduled and recurring meetings
|
||||||
|
|
||||||
* Naming Sprints
|
* Naming Sprints
|
||||||
|
|
||||||
I give each sprint a nickname, based on a /theme/ of some sorts, alphabetized. Since our sprints are every two weeks, this allows me to go through the alphabet once. Yeah, my group likes to boringly /number/ the sprints, so I do both… for myself.
|
I give each sprint a nickname, based on a /theme/ of some sorts, alphabetized. Since our sprints are every two weeks, this allows me to go through the alphabet once. Yeah, my group likes to boringly /number/ the sprints, so I do both… for myself.
|
||||||
|
|
||||||
At the beginning of the year, I choose a theme, and make a list for the upcoming sprints. In the org file, this is a list, that gets /tangled/ into an actual Emacs LIsp list. This is pretty cool.
|
At the beginning of the year, I choose a theme, and make a list for the upcoming sprints. In the org file, this is a list, that gets /tangled/ into an actual Emacs LIsp list. This is pretty cool.
|
||||||
#+begin_src emacs-lisp :var sprint-names=sprint-names-2026
|
|
||||||
|
#+begin_src emacs-lisp :var sprint-names=sprint-names-2025
|
||||||
(defvar sprint-nicknames sprint-names
|
(defvar sprint-nicknames sprint-names
|
||||||
"List of 26 Sprint Nicknames from A to Z.")
|
"List of 26 Sprint Nicknames from A to Z.")
|
||||||
#+end_src
|
#+end_src
|
||||||
** 2026
|
|
||||||
Migrating to a new team should include something different.
|
|
||||||
#+name: sprint-names-2026
|
|
||||||
- Alchemist’s Amber
|
|
||||||
- Bubblegloop Bogs
|
|
||||||
- Crumpet Crossroads
|
|
||||||
- Dandelion Drifters
|
|
||||||
- Elfin Espresso
|
|
||||||
- Firefly Fandango
|
|
||||||
- Giggling Goblins
|
|
||||||
- Hobgoblin Hideaway
|
|
||||||
- Inkwell Isles
|
|
||||||
- Jester’s Junction
|
|
||||||
- Knapsack Knolls
|
|
||||||
- Lavender Labyrinths
|
|
||||||
- Moonbeam Meadow
|
|
||||||
- Nectar Nibblers
|
|
||||||
- Owl’s Overlook
|
|
||||||
- Pixie Dust Pipeline
|
|
||||||
- Quizzical Quagmires
|
|
||||||
- Runaway Radishes
|
|
||||||
- Starfruit Springs
|
|
||||||
- Thimbleweed Thicket
|
|
||||||
- Unicorn Unwinding
|
|
||||||
- Velvet Valley
|
|
||||||
- Whistling Willows
|
|
||||||
- Xanadu Xylophones
|
|
||||||
- Yawning Yearlings
|
|
||||||
- Zephyr Zigzags
|
|
||||||
** 2025
|
** 2025
|
||||||
This year is the animals representing corporate slogans:
|
This year is the animals representing corporate slogans:
|
||||||
|
|
||||||
#+name: sprint-names-2025
|
#+name: sprint-names-2025
|
||||||
- Accelerated and Adaptive Ant
|
- Accelerated and Adaptive Ant
|
||||||
- Buy-in Bee and Blue-sky Bull
|
- Buy-in Bee and Blue-sky Bull
|
||||||
|
|
@ -126,6 +108,7 @@ How about Tabaxi names this year … especially since my next character has to b
|
||||||
- Zip in the Wind
|
- Zip in the Wind
|
||||||
** 2023
|
** 2023
|
||||||
How about a list of Ent names?
|
How about a list of Ent names?
|
||||||
|
|
||||||
#+name: sprint-names-2023
|
#+name: sprint-names-2023
|
||||||
- ashskin
|
- ashskin
|
||||||
- birchblossom
|
- birchblossom
|
||||||
|
|
@ -154,7 +137,9 @@ How about a list of Ent names?
|
||||||
- yewbiquitous
|
- yewbiquitous
|
||||||
- zelkova
|
- zelkova
|
||||||
** 2022
|
** 2022
|
||||||
|
|
||||||
Fun sprint names for 2022 lists my favorite D&D monsters, also see [[https://list.fandom.com/wiki/List_of_monsters][this list of monsters]] from mythology and other sources:
|
Fun sprint names for 2022 lists my favorite D&D monsters, also see [[https://list.fandom.com/wiki/List_of_monsters][this list of monsters]] from mythology and other sources:
|
||||||
|
|
||||||
#+name: sprint-names-2022
|
#+name: sprint-names-2022
|
||||||
- ankheg
|
- ankheg
|
||||||
- beholder
|
- beholder
|
||||||
|
|
@ -184,6 +169,7 @@ Fun sprint names for 2022 lists my favorite D&D monsters, also see [[https://lis
|
||||||
- zombie
|
- zombie
|
||||||
** 2021
|
** 2021
|
||||||
Choosing Sprint Names based on [[https://www.imagineforest.com/blog/funniest-words-in-the-english-language/][Funny or Silly Words]]:
|
Choosing Sprint Names based on [[https://www.imagineforest.com/blog/funniest-words-in-the-english-language/][Funny or Silly Words]]:
|
||||||
|
|
||||||
#+name: sprint-names-2021
|
#+name: sprint-names-2021
|
||||||
- abibliophobia :: The fear of running out of reading materials to read
|
- abibliophobia :: The fear of running out of reading materials to read
|
||||||
- bamboozled :: To trick or confuse someone
|
- bamboozled :: To trick or confuse someone
|
||||||
|
|
@ -212,7 +198,9 @@ Choosing Sprint Names based on [[https://www.imagineforest.com/blog/funniest-wor
|
||||||
- yerk :: Pull or push something with a sudden movement.
|
- yerk :: Pull or push something with a sudden movement.
|
||||||
- zazzy :: Something that is shiny and flashy
|
- zazzy :: Something that is shiny and flashy
|
||||||
** 2020
|
** 2020
|
||||||
|
|
||||||
New names from [[https://en.m.wikipedia.org/wiki/List_of_dinosaur_genera][list of dinosaurs]].
|
New names from [[https://en.m.wikipedia.org/wiki/List_of_dinosaur_genera][list of dinosaurs]].
|
||||||
|
|
||||||
#+name: sprint-names-2020
|
#+name: sprint-names-2020
|
||||||
- ankylosaurus
|
- ankylosaurus
|
||||||
- brontosaurus
|
- brontosaurus
|
||||||
|
|
@ -240,8 +228,11 @@ New names from [[https://en.m.wikipedia.org/wiki/List_of_dinosaur_genera][list o
|
||||||
- xiaotingia
|
- xiaotingia
|
||||||
- yi
|
- yi
|
||||||
- zuul
|
- zuul
|
||||||
|
|
||||||
** 2019
|
** 2019
|
||||||
|
|
||||||
Came up with a list of somewhat well-known cities throughout the world (at least, they had to have a population of 100,000 or more), but I didn't want any real obvious ones.
|
Came up with a list of somewhat well-known cities throughout the world (at least, they had to have a population of 100,000 or more), but I didn't want any real obvious ones.
|
||||||
|
|
||||||
#+name: sprint-names-2019
|
#+name: sprint-names-2019
|
||||||
- achy-aachen
|
- achy-aachen
|
||||||
- bare-bacabal
|
- bare-bacabal
|
||||||
|
|
@ -269,8 +260,10 @@ Came up with a list of somewhat well-known cities throughout the world (at least
|
||||||
- xenophobic-xichang
|
- xenophobic-xichang
|
||||||
- yellow-yamaguchi
|
- yellow-yamaguchi
|
||||||
- zippy-zinder
|
- zippy-zinder
|
||||||
|
|
||||||
* Sprint Boundaries
|
* Sprint Boundaries
|
||||||
Function to help in calculating dates and other features of a two-week sprint that starts on Tuesday and ends on a Monday… how we work at my job.
|
Function to help in calculating dates and other features of a two-week sprint that starts on Tuesday and ends on a Monday… how we work at my job.
|
||||||
|
|
||||||
Emacs have an internal rep of a time.
|
Emacs have an internal rep of a time.
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defun get-date-time (date)
|
(defun get-date-time (date)
|
||||||
|
|
@ -284,40 +277,81 @@ Emacs have an internal rep of a time.
|
||||||
(apply 'encode-time))
|
(apply 'encode-time))
|
||||||
date))
|
date))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** Sprint Numbering
|
** Sprint Numbering
|
||||||
|
|
||||||
Each year, specify the first day of the first sprint of the year:
|
Each year, specify the first day of the first sprint of the year:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
;; CHANGEME Each year as this should update:
|
(defvar sprint-start-date (get-date-time "2025-01-14")
|
||||||
(defvar sprint-start-date (get-date-time "2026-01-06")
|
|
||||||
"The date of the first day of the first sprint of the year.
|
"The date of the first day of the first sprint of the year.
|
||||||
See `sprint-range'.")
|
See `sprint-range'.")
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
My company has sprints two weeks long that we can calculate from
|
|
||||||
|
My Sprint starts on Tuesday, but this sometimes changed, so let's make this a variable:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defvar sprint-length (* 14 24 60 60)
|
(defvar sprint-starting-day 2 "The day of the week the sprint begins, where 0 is Sunday.")
|
||||||
"The length of a sprint, in seconds. This is 2 weeks long")
|
|
||||||
#+end_src
|
#+end_src
|
||||||
The number of the sprint comes from the number of /bi-weeks/ (14 day increments) from the =sprint-start-date=. We can calculate the number of seconds from this /start date/ and divide it by the =sprint-length=:
|
|
||||||
|
We seem to never start our Sprints correctly, and we seem to like offsets:
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
;; CHANGEME Each year as this never matches:
|
||||||
|
(defvar sprint-offset-value 1 "The number of the first sprint.")
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
We label our sprint based on the week number that it starts. Note that on a Monday, I want to consider that we are still numbering from last week.
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(defun sprint-week-num (&optional date)
|
||||||
|
"Return the week of the current year (or DATE), but starting
|
||||||
|
the week at Tuesday to Monday."
|
||||||
|
(let* ((d (get-date-time date))
|
||||||
|
(dow (nth 6 (decode-time d))) ; Day of the week 0=Sunday
|
||||||
|
(week (thread-last d ; Week number in the year
|
||||||
|
(format-time-string "%U")
|
||||||
|
string-to-number)))
|
||||||
|
(if (>= dow sprint-starting-day)
|
||||||
|
(1+ week)
|
||||||
|
week)))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Let's have these tests to make of this /weekly/ perspective:
|
||||||
|
#+begin_src emacs-lisp :tangle no
|
||||||
|
(ert-deftest sprint-week-num-test ()
|
||||||
|
(should (= (sprint-week-num "2025-01-13") 2)) ; Monday previous week
|
||||||
|
(should (= (sprint-week-num "2025-01-14") 3)) ; Monday previous week
|
||||||
|
|
||||||
|
(should (= (sprint-week-num "2024-01-01") 0)) ; Monday previous week
|
||||||
|
(should (= (sprint-week-num "2024-01-02") 1)) ; Tuesday ... this week
|
||||||
|
(should (= (sprint-week-num "2024-01-09") 2)) ; Monday, next week, part of last
|
||||||
|
(should (= (sprint-week-num "2024-01-10") 3))) ; Tuesday next week
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
My company has sprints two weeks long, we could be see that on even week numbers, the /sprint/ is actually the previous week's number.
|
||||||
|
|
||||||
|
This year, my PM decided to start the sprints sequentially starting with 11, so I’ve decided to follow my own naming convention for my filenames.
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defun sprint-number (&optional date)
|
(defun sprint-number (&optional date)
|
||||||
"Return the number of 14-day intervals since SPRINT-START-DATE to DATE.
|
"Return the current sprint number, with some assumptions that
|
||||||
DATE is a string in YYYY-MM-DD format."
|
each sprint is two weeks long, starting on Tuesday."
|
||||||
(unless date
|
(let* ((num (sprint-week-num date))
|
||||||
(setq date (format-time-string "%Y-%m-%d")))
|
;; Depending on how late we wait to start the sprint, the
|
||||||
(let* ((end-time (get-date-time date))
|
;; new sprint may be on an oddp or evenp week:
|
||||||
(diff-seconds (float-time (time-subtract end-time sprint-start-date))))
|
(bucket (if (cl-oddp num) num (1- num))))
|
||||||
;; After calculating the number of 'bi-weeks' (2 week sprint increments),
|
(thread-first bucket
|
||||||
;; we add one to return the correct sprint for the networking team:
|
;; Make 2 week sprints sequential:
|
||||||
;; (1+ (floor (/ diff-seconds sprint-length)))
|
(/ 2)
|
||||||
;; Or for the platform storage team:
|
;; Sprint offset number:
|
||||||
(1+ (* 2 (floor (/ diff-seconds sprint-length))))))
|
(- sprint-offset-value))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
And some tests to verify that:
|
And some tests to verify that:
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp :tangle no
|
||||||
(ert-deftest sprint-number-test ()
|
(ert-deftest sprint-number-test ()
|
||||||
(should (= (sprint-number "2026-01-12") 0))
|
(should (= (sprint-number "2025-01-13") 0))
|
||||||
(should (= (sprint-number "2026-01-13") 1))
|
(should (= (sprint-number "2025-01-14") 0))
|
||||||
(should (= (sprint-number "2026-01-20") 1)))
|
(should (= (sprint-number "2025-01-27") 0))
|
||||||
|
(should (= (sprint-number "2025-01-29") 1)))
|
||||||
#+end_src
|
#+end_src
|
||||||
** Sprint File Name
|
** Sprint File Name
|
||||||
I create my org-file notes based on the Sprint number.
|
I create my org-file notes based on the Sprint number.
|
||||||
|
|
@ -330,6 +364,7 @@ I create my org-file notes based on the Sprint number.
|
||||||
(format-time-string "%Y" d)
|
(format-time-string "%Y" d)
|
||||||
(sprint-number d)))))
|
(sprint-number d)))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
So given a particular date, I should expect to be able to find the correct Sprint file name:
|
So given a particular date, I should expect to be able to find the correct Sprint file name:
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp :tangle no
|
||||||
(ert-deftest sprint-current-file-test ()
|
(ert-deftest sprint-current-file-test ()
|
||||||
|
|
@ -338,7 +373,9 @@ So given a particular date, I should expect to be able to find the correct Sprin
|
||||||
(should (s-ends-with? "Sprint-2024-13.org" (sprint-current-file "2024-02-01")))
|
(should (s-ends-with? "Sprint-2024-13.org" (sprint-current-file "2024-02-01")))
|
||||||
(should (s-ends-with? "Sprint-2024-14.org" (sprint-current-file "2024-02-13"))))
|
(should (s-ends-with? "Sprint-2024-14.org" (sprint-current-file "2024-02-13"))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Daily note-taking goes into my sprint file notes, so this interactive function makes an easy global short-cut key.
|
Daily note-taking goes into my sprint file notes, so this interactive function makes an easy global short-cut key.
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defun sprint-current-find-file (&optional date)
|
(defun sprint-current-find-file (&optional date)
|
||||||
"Load the `org-mode' note associated with my current sprint."
|
"Load the `org-mode' note associated with my current sprint."
|
||||||
|
|
@ -349,32 +386,33 @@ Daily note-taking goes into my sprint file notes, so this interactive function m
|
||||||
(add-to-list 'org-agenda-files filename)
|
(add-to-list 'org-agenda-files filename)
|
||||||
(find-file filename)))
|
(find-file filename)))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
The /name/ and /nickname/ of the sprint will be used in the =#+TITLE= section, and it looks something like: =Sprint 2019-07 (darling-dadu)=
|
The /name/ and /nickname/ of the sprint will be used in the =#+TITLE= section, and it looks something like: =Sprint 2019-07 (darling-dadu)=
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defun sprint-current-name (&optional date)
|
(defun sprint-current-name (&optional date)
|
||||||
"Return the default name of the current sprint (based on DATE)."
|
"Return the default name of the current sprint (based on DATE)."
|
||||||
(let* ((d (get-date-time date))
|
(let* ((d (get-date-time date))
|
||||||
(sprint-num (sprint-number d))
|
(sprint-num (sprint-number d))
|
||||||
(nickname (nth (/ (1- sprint-num) 2) sprint-nicknames)))
|
(nickname (nth (1- sprint-num) sprint-nicknames)))
|
||||||
(format "Sprint %s-%02d :: %s"
|
(format "Sprint %s-%02d :: %s"
|
||||||
(format-time-string "%Y" d)
|
(format-time-string "%Y" d)
|
||||||
(sprint-number d)
|
(sprint-number d)
|
||||||
nickname)))
|
nickname)))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Current sprint name:
|
|
||||||
#+BEGIN_SRC emacs-lisp :tangle no
|
|
||||||
(sprint-current-name)
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
These test won't pass any more, as the nickname of the sprint changes from year to year.
|
These test won't pass any more, as the nickname of the sprint changes from year to year.
|
||||||
|
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp :tangle no
|
||||||
(ert-deftest sprint-current-name-test ()
|
(ert-deftest sprint-current-name-test ()
|
||||||
(should (equal "Sprint 2024-01 :: Art of Shadows" (sprint-current-name "2024-01-02")))
|
(should (equal "Sprint 2024-01 :: Art of Shadows" (sprint-current-name "2024-01-02")))
|
||||||
(should (equal "Sprint 2024-04 :: Daydream at Night" (sprint-current-name "2024-02-14"))))
|
(should (equal "Sprint 2024-04 :: Daydream at Night" (sprint-current-name "2024-02-14"))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** Sprint Start and End
|
** Sprint Start and End
|
||||||
|
|
||||||
I want to print the beginning and ending of the sprint, where we have a sprint number or a data, and we can give the dates that bound the sprint. This odd function calculates this based on knowing the date of the /first Tuesday/ of the year, so I need to begin the year changing this value. I should fix this.
|
I want to print the beginning and ending of the sprint, where we have a sprint number or a data, and we can give the dates that bound the sprint. This odd function calculates this based on knowing the date of the /first Tuesday/ of the year, so I need to begin the year changing this value. I should fix this.
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defun sprint-range (&optional number-or-date)
|
(defun sprint-range (&optional number-or-date)
|
||||||
"Return a list of three entries, start of the current sprint,
|
"Return a list of three entries, start of the current sprint,
|
||||||
|
|
@ -392,7 +430,9 @@ I want to print the beginning and ending of the sprint, where we have a sprint n
|
||||||
(sprint-end (time-add sprint-next (- day-length))))
|
(sprint-end (time-add sprint-next (- day-length))))
|
||||||
(list sprint-start sprint-end sprint-next)))
|
(list sprint-start sprint-end sprint-next)))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Format the start and end so that we can insert this directly in the org file:
|
Format the start and end so that we can insert this directly in the org file:
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defun sprint-date-range (&optional number-or-date)
|
(defun sprint-date-range (&optional number-or-date)
|
||||||
"Return `org-mode' formatted date range for a given sprint.
|
"Return `org-mode' formatted date range for a given sprint.
|
||||||
|
|
@ -404,6 +444,7 @@ Format the start and end so that we can insert this directly in the org file:
|
||||||
(end (format-time-string formatter sprint-end)))
|
(end (format-time-string formatter sprint-end)))
|
||||||
(format "[%s]--[%s]" start end))))
|
(format "[%s]--[%s]" start end))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
And validate with a test:
|
And validate with a test:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(ert-deftest sprint-date-range ()
|
(ert-deftest sprint-date-range ()
|
||||||
|
|
@ -418,8 +459,11 @@ And validate with a test:
|
||||||
(should (equal (sprint-date-range 5)
|
(should (equal (sprint-date-range 5)
|
||||||
(sprint-date-range "2024-01-31"))))
|
(sprint-date-range "2024-01-31"))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** Pre-scheduled Dates
|
** Pre-scheduled Dates
|
||||||
|
|
||||||
Due to the regularity of the sprint cadence, I can pre-schedule meetings and other deadlines by /counting/ the number of days from the start of the sprint:
|
Due to the regularity of the sprint cadence, I can pre-schedule meetings and other deadlines by /counting/ the number of days from the start of the sprint:
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defun sprint-date-from-start (days &optional formatter)
|
(defun sprint-date-from-start (days &optional formatter)
|
||||||
"Return formatted date string from number of DAYS from the start of the sprint."
|
"Return formatted date string from number of DAYS from the start of the sprint."
|
||||||
|
|
@ -430,14 +474,18 @@ Due to the regularity of the sprint cadence, I can pre-schedule meetings and oth
|
||||||
(format-time-string formatter adate)
|
(format-time-string formatter adate)
|
||||||
(format-time-string "%Y-%m-%d %a" adate))))
|
(format-time-string "%Y-%m-%d %a" adate))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
* Other Date Functions
|
* Other Date Functions
|
||||||
|
|
||||||
The following functions /were/ helpful at times. But I'm not sure I will use them.
|
The following functions /were/ helpful at times. But I'm not sure I will use them.
|
||||||
|
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp :tangle no
|
||||||
(defun sprint-num-days (time-interval)
|
(defun sprint-num-days (time-interval)
|
||||||
"Converts a TIME-INTERVAL to a number of days."
|
"Converts a TIME-INTERVAL to a number of days."
|
||||||
(let ((day-length (* 3600 24)))
|
(let ((day-length (* 3600 24)))
|
||||||
(round (/ (float-time time-interval) day-length))))
|
(round (/ (float-time time-interval) day-length))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp :tangle no
|
||||||
(defun sprint-day-range (&optional date)
|
(defun sprint-day-range (&optional date)
|
||||||
"Returns a list of two values, the number of days from the
|
"Returns a list of two values, the number of days from the
|
||||||
|
|
@ -449,6 +497,7 @@ The following functions /were/ helpful at times. But I'm not sure I will use the
|
||||||
(ending (time-subtract sprint-end now)))
|
(ending (time-subtract sprint-end now)))
|
||||||
(list (sprint-num-days starting) (sprint-num-days ending)))))
|
(list (sprint-num-days starting) (sprint-num-days ending)))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp :tangle no
|
||||||
(ert-deftest sprint-day-range ()
|
(ert-deftest sprint-day-range ()
|
||||||
;; This sprint starts on 2/13 and ends on 2/26
|
;; This sprint starts on 2/13 and ends on 2/26
|
||||||
|
|
@ -456,25 +505,33 @@ The following functions /were/ helpful at times. But I'm not sure I will use the
|
||||||
(should (equal '(-1 12) (sprint-day-range "2020-02-14")))
|
(should (equal '(-1 12) (sprint-day-range "2020-02-14")))
|
||||||
(should (equal '(-13 0) (sprint-day-range "2020-02-26"))))
|
(should (equal '(-13 0) (sprint-day-range "2020-02-26"))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp :tangle no
|
||||||
(defun sprint-day-start (&optional date)
|
(defun sprint-day-start (&optional date)
|
||||||
"Return a relative number of days to the start of the current sprint. For instance, if today was Friday, and the sprint started on Tuesday, this would return -1."
|
"Return a relative number of days to the start of the current sprint. For instance, if today was Friday, and the sprint started on Tuesday, this would return -1."
|
||||||
(first (sprint-day-range date)))
|
(first (sprint-day-range date)))
|
||||||
|
|
||||||
(defun sprint-day-end (&optional date)
|
(defun sprint-day-end (&optional date)
|
||||||
"Return a relative number of days to the end of the current sprint. For instance, if today was Monday, and the sprint will end on Monday, this would return 3."
|
"Return a relative number of days to the end of the current sprint. For instance, if today was Monday, and the sprint will end on Monday, this would return 3."
|
||||||
(second (sprint-day-range date)))
|
(second (sprint-day-range date)))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
* Technical Artifacts :noexport:
|
* Technical Artifacts :noexport:
|
||||||
|
|
||||||
Let's =provide= a name so we can =require= this file:
|
Let's =provide= a name so we can =require= this file:
|
||||||
#+begin_src emacs-lisp :exports none
|
#+begin_src emacs-lisp :exports none
|
||||||
(provide 'ha-org-sprint)
|
(provide 'ha-org-sprint)
|
||||||
;;; ha-org-sprint.el ends here
|
;;; ha-org-sprint.el ends here
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Before you can build this on a new system, make sure that you put the cursor over any of these properties, and hit: ~C-c C-c~
|
Before you can build this on a new system, make sure that you put the cursor over any of these properties, and hit: ~C-c C-c~
|
||||||
|
|
||||||
#+description: A literate program for configuring org files for work-related notes.
|
#+description: A literate program for configuring org files for work-related notes.
|
||||||
|
|
||||||
#+property: header-args:sh :tangle no
|
#+property: header-args:sh :tangle no
|
||||||
#+property: header-args:emacs-lisp :tangle yes
|
#+property: header-args:emacs-lisp :tangle yes
|
||||||
#+property: header-args :results none :eval no-export :comments no mkdirp yes
|
#+property: header-args :results none :eval no-export :comments no mkdirp yes
|
||||||
|
|
||||||
#+options: num:nil toc:t todo:nil tasks:nil tags:nil date:nil
|
#+options: num:nil toc:t todo:nil tasks:nil tags:nil date:nil
|
||||||
#+options: skip:nil author:nil email:nil creator:nil timestamp:nil
|
#+options: skip:nil author:nil email:nil creator:nil timestamp:nil
|
||||||
#+infojs_opt: view:nil toc:t ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
|
#+infojs_opt: view:nil toc:t ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ A literate programming file for making Org file more readable.
|
||||||
#+end_src
|
#+end_src
|
||||||
* Introduction
|
* Introduction
|
||||||
I like having org-mode files look more like a word processor than having it look like programming code. But that is me. The end results:
|
I like having org-mode files look more like a word processor than having it look like programming code. But that is me. The end results:
|
||||||
|
|
||||||
[[file:screenshots/org-as-word-processor.png]]
|
[[file:screenshots/org-as-word-processor.png]]
|
||||||
* General Org Settings
|
* General Org Settings
|
||||||
Since I use ellipsis in my writing… to /change/ how org renders a collapsed heading.
|
Since I use ellipsis in my writing… to /change/ how org renders a collapsed heading.
|
||||||
|
|
@ -101,7 +102,7 @@ And hook this function to Org:
|
||||||
The list of things to try:
|
The list of things to try:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(setq org-hide-leading-stars t) ; or t
|
(setq org-hide-leading-stars nil) ; or t
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
** Markup View
|
** Markup View
|
||||||
|
|
@ -109,7 +110,7 @@ The variable, =org-hide-emphasis-markers=, is key to pretending that Emacs can b
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package org-appear
|
(use-package org-appear
|
||||||
:straight (:type git :host github :repo "awth13/org-appear")
|
;; :vc (:url "https://github.com/awth13/org-appear")
|
||||||
:init (setq org-appear-trigger 'manual)
|
:init (setq org-appear-trigger 'manual)
|
||||||
:hook
|
:hook
|
||||||
((org-mode . (lambda ()
|
((org-mode . (lambda ()
|
||||||
|
|
@ -330,7 +331,7 @@ The [[https://github.com/minad/org-modern][org-modern]] project attempts to do a
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package org-modern
|
(use-package org-modern
|
||||||
:straight (:host github :repo "minad/org-modern")
|
;; :vc (:url "https://github.com/minad/org-modern")
|
||||||
:after org
|
:after org
|
||||||
:hook ((org-mode . org-modern-mode)
|
:hook ((org-mode . org-modern-mode)
|
||||||
(org-agenda-finalize . org-modern-agenda))
|
(org-agenda-finalize . org-modern-agenda))
|
||||||
|
|
@ -338,12 +339,11 @@ The [[https://github.com/minad/org-modern][org-modern]] project attempts to do a
|
||||||
(org-modern-table nil)
|
(org-modern-table nil)
|
||||||
(org-pretty-entities t)
|
(org-pretty-entities t)
|
||||||
(setq org-modern-fold-stars '(("▸" . "▾")
|
(setq org-modern-fold-stars '(("▸" . "▾")
|
||||||
("▹" . "▿")
|
("▹" . "▿")
|
||||||
("▸" . "▾")
|
("▸" . "▾")
|
||||||
("▹" . "▿")
|
("▹" . "▿")
|
||||||
("▸" . "▾")))
|
("▸" . "▾")))
|
||||||
:config
|
:config
|
||||||
(set-face-attribute 'org-modern-todo nil :inverse-video nil)
|
|
||||||
(set-face-attribute 'org-ellipsis nil :inherit 'default :box nil :underline nil)
|
(set-face-attribute 'org-ellipsis nil :inherit 'default :box nil :underline nil)
|
||||||
(set-face-attribute 'org-modern-symbol nil :family "Iosevka"))
|
(set-face-attribute 'org-modern-symbol nil :family "Iosevka"))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
@ -367,7 +367,7 @@ According to an idea by [[https://jft.home.blog/2019/07/17/use-unicode-symbol-to
|
||||||
The [[https://github.com/TonCherAmi/org-padding][org-padding]] project looks places extra space before and after headers and blocks (essentially leading), to create a more word-processor-y experience. Great idea, however, I have spent a lot of extra time entering blank lines before and after my headers and blocks:
|
The [[https://github.com/TonCherAmi/org-padding][org-padding]] project looks places extra space before and after headers and blocks (essentially leading), to create a more word-processor-y experience. Great idea, however, I have spent a lot of extra time entering blank lines before and after my headers and blocks:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package org-padding
|
(use-package org-padding
|
||||||
:straight (:host github :repo "TonCherAmi/org-padding")
|
;; :vc (:url "https://github.com/TonCherAmi/org-padding")
|
||||||
:hook (org-mode . org-padding-mode)
|
:hook (org-mode . org-padding-mode)
|
||||||
:config
|
:config
|
||||||
(setq org-padding-block-begin-line-padding '(0.5 . 0.3)
|
(setq org-padding-block-begin-line-padding '(0.5 . 0.3)
|
||||||
|
|
|
||||||
357
ha-org.org
357
ha-org.org
|
|
@ -3,7 +3,7 @@
|
||||||
#+date: 2020-09-18
|
#+date: 2020-09-18
|
||||||
#+tags: emacs org
|
#+tags: emacs org
|
||||||
#+startup: inlineimages
|
#+startup: inlineimages
|
||||||
#+lastmod: [2026-08-10 Mon]
|
#+lastmod: [2025-09-08 Mon]
|
||||||
|
|
||||||
A literate programming file for configuring org-mode and those files.
|
A literate programming file for configuring org-mode and those files.
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@ Org is a /large/ complex beast with a gazillion settings, so I discuss these lat
|
||||||
#+begin_src emacs-lisp :noweb yes
|
#+begin_src emacs-lisp :noweb yes
|
||||||
(use-package org
|
(use-package org
|
||||||
;; TODO: Using the latest org-mode
|
;; TODO: Using the latest org-mode
|
||||||
;; :straight (:type built-in)
|
;;
|
||||||
:mode (("\\.org" . org-mode))
|
:mode (("\\.org" . org-mode))
|
||||||
:init
|
:init
|
||||||
<<variables>>
|
<<variables>>
|
||||||
|
|
@ -65,7 +65,6 @@ Begin by initializing these org variables:
|
||||||
org-edit-src-content-indentation 2 ; Doom Emacs sets this to 0,
|
org-edit-src-content-indentation 2 ; Doom Emacs sets this to 0,
|
||||||
; but uses a trick to make it
|
; but uses a trick to make it
|
||||||
; appear indented.
|
; appear indented.
|
||||||
org-use-sub-superscripts '{}
|
|
||||||
|
|
||||||
org-imenu-depth 4
|
org-imenu-depth 4
|
||||||
sentence-end-double-space nil ; I jump around by sentences, but seldom have two spaces.
|
sentence-end-double-space nil ; I jump around by sentences, but seldom have two spaces.
|
||||||
|
|
@ -154,7 +153,7 @@ What should we do if we are at the end of a line?
|
||||||
- In a table? Create a new row.
|
- In a table? Create a new row.
|
||||||
- In a list, create a new item.
|
- In a list, create a new item.
|
||||||
|
|
||||||
I should break this function into smaller bits:
|
I should break this function into smaller bits ...
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defun ha-org-special-return (&optional ignore)
|
(defun ha-org-special-return (&optional ignore)
|
||||||
|
|
@ -180,7 +179,7 @@ I should break this function into smaller bits:
|
||||||
(org-return)))))
|
(org-return)))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
How do we know if we are in a list item? Lists end with two empty lines, so we need to make sure we are also not at the beginning of a line to avoid a loop where a new entry gets created with one empty line.
|
How do we know if we are in a list item? Lists end with two blank lines, so we need to make sure we are also not at the beginning of a line to avoid a loop where a new entry gets created with one blank line.
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defun org-really-in-item-p ()
|
(defun org-really-in-item-p ()
|
||||||
"Return item beginning position when in a plain list, nil otherwise.
|
"Return item beginning position when in a plain list, nil otherwise.
|
||||||
|
|
@ -224,11 +223,10 @@ I need to add a /blocked/ state, and wouldn’t /doing/ be better than /in progr
|
||||||
|
|
||||||
(add-hook 'org-after-todo-state-change-hook 'ha-org-clock-todo-change)
|
(add-hook 'org-after-todo-state-change-hook 'ha-org-clock-todo-change)
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
And I would like to have cute little icons for those states:
|
And I would like to have cute little icons for those states:
|
||||||
|
|
||||||
#+name: org-font-lock
|
#+name: org-font-lock
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp
|
||||||
(dolist (m '(org-mode org-journal-mode))
|
(dolist (m '(org-mode org-journal-mode))
|
||||||
(font-lock-add-keywords m ; A bit silly but my headers are now
|
(font-lock-add-keywords m ; A bit silly but my headers are now
|
||||||
`(("^\\*+ \\(TODO\\) " ; shorter, and that is nice canceled
|
`(("^\\*+ \\(TODO\\) " ; shorter, and that is nice canceled
|
||||||
|
|
@ -247,7 +245,6 @@ And I would like to have cute little icons for those states:
|
||||||
("^ +\\([-*]\\) "
|
("^ +\\([-*]\\) "
|
||||||
(0 (prog1 () (compose-region (match-beginning 1) (match-end 1) "•")))))))
|
(0 (prog1 () (compose-region (match-beginning 1) (match-end 1) "•")))))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** Meetings
|
** Meetings
|
||||||
I've notice that while showing a screen while taking meeting notes, I don't always like showing other windows, so I created this function to remove distractions during a meeting.
|
I've notice that while showing a screen while taking meeting notes, I don't always like showing other windows, so I created this function to remove distractions during a meeting.
|
||||||
|
|
||||||
|
|
@ -281,7 +278,7 @@ Came up with a great way to search a project for Org-specific files, and wrote [
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package org-find-file
|
(use-package org-find-file
|
||||||
:straight nil
|
|
||||||
:config
|
:config
|
||||||
(ha-leader "f o" '("load org" . org-find-file)))
|
(ha-leader "f o" '("load org" . org-find-file)))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
@ -412,9 +409,8 @@ And let’s see how it works:
|
||||||
User-Agent: ${user-agent}
|
User-Agent: ${user-agent}
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Results in:
|
#+results:
|
||||||
|
#+begin_src js
|
||||||
#+begin_src js :tangle no
|
|
||||||
{
|
{
|
||||||
"Emacs Lisp": 15327,
|
"Emacs Lisp": 15327,
|
||||||
"Shell": 139
|
"Shell": 139
|
||||||
|
|
@ -435,9 +431,8 @@ And let’s try this:
|
||||||
User-Agent: ${user-agent}
|
User-Agent: ${user-agent}
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Results in the same output:
|
#+results:
|
||||||
|
#+begin_src js
|
||||||
#+begin_src js :tangle no
|
|
||||||
{
|
{
|
||||||
"Emacs Lisp": 15327,
|
"Emacs Lisp": 15327,
|
||||||
"Shell": 139
|
"Shell": 139
|
||||||
|
|
@ -494,8 +489,12 @@ Need to install and configure Emacs to work with [[https://plantuml.com/][PlantU
|
||||||
|
|
||||||
After installing the [[https://github.com/skuro/plantuml-mode][plantuml-mode]], we need to reference the location:
|
After installing the [[https://github.com/skuro/plantuml-mode][plantuml-mode]], we need to reference the location:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
|
(use-package deflate
|
||||||
|
:vc (:url "https://github.com/skuro/deflate.git"))
|
||||||
|
|
||||||
(use-package plantuml-mode
|
(use-package plantuml-mode
|
||||||
:straight (:host github :repo "skuro/plantuml-mode")
|
;; :vc (:url "https://github.com/skuro/plantuml-mode")
|
||||||
|
:after deflate
|
||||||
:init
|
:init
|
||||||
(setq org-plantuml-jar-path (expand-file-name "~/bin/plantuml.jar")))
|
(setq org-plantuml-jar-path (expand-file-name "~/bin/plantuml.jar")))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
@ -548,8 +547,7 @@ Of course, since we are dealing with Emacs, where we assimilate any good idea. J
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package pikchr-mode
|
(use-package pikchr-mode
|
||||||
:straight (:local-repo "~/src/pikchr-mode")
|
;; :vc (:url "https://github.com/kljohann/pikchr-mode")
|
||||||
;; :straight (:host github :repo "kljohann/pikchr-mode")
|
|
||||||
:custom
|
:custom
|
||||||
(pikchr-executable "~/bin/pikchr"))
|
(pikchr-executable "~/bin/pikchr"))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
@ -737,7 +735,7 @@ I have a special version of tweaked [[file:elisp/ox-confluence.el][Confluence ex
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package ox-confluence
|
(use-package ox-confluence
|
||||||
:after org
|
:after org
|
||||||
:straight nil ; Located in my "elisp" directory
|
; Located in my "elisp" directory
|
||||||
:config
|
:config
|
||||||
(ha-leader :keymaps 'org-mode-map
|
(ha-leader :keymaps 'org-mode-map
|
||||||
"o E" '("to confluence" . ox-export-to-confluence)))
|
"o E" '("to confluence" . ox-export-to-confluence)))
|
||||||
|
|
@ -748,7 +746,7 @@ I have a special version of tweaked [[file:elisp/ox-confluence.el][Confluence ex
|
||||||
I’m not afraid of HTML, but I like the idea of doing my HTML work in a Lisp-like way using the [[https://github.com/tonyaldon/jack][jack-html project]]:
|
I’m not afraid of HTML, but I like the idea of doing my HTML work in a Lisp-like way using the [[https://github.com/tonyaldon/jack][jack-html project]]:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package jack
|
(use-package jack
|
||||||
:straight (:host github :repo "tonyaldon/jack")
|
;; :vc (:url "https://github.com/tonyaldon/jack")
|
||||||
:commands (jack-html))
|
:commands (jack-html))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
|
|
@ -796,13 +794,16 @@ Splitting out HTML snippets is often a way that I can transfer org-formatted con
|
||||||
hard-newline))))))
|
hard-newline))))))
|
||||||
#+end_src
|
#+end_src
|
||||||
** Focused Work
|
** Focused Work
|
||||||
|
:LOGBOOK:
|
||||||
|
CLOCK: [2022-02-11 Fri 11:05]--[2022-02-11 Fri 11:21] => 0:16
|
||||||
|
:END:
|
||||||
I've been working on my own [[http://www.howardism.org/Technical/Emacs/focused-work.html][approach to focused work]],
|
I've been working on my own [[http://www.howardism.org/Technical/Emacs/focused-work.html][approach to focused work]],
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package async)
|
(use-package async)
|
||||||
|
|
||||||
(use-package ha-focus
|
(use-package ha-focus
|
||||||
:straight (:type built-in)
|
|
||||||
:config
|
:config
|
||||||
(ha-leader
|
(ha-leader
|
||||||
"o f" '("begin focus" . ha-focus-begin)
|
"o f" '("begin focus" . ha-focus-begin)
|
||||||
|
|
@ -812,49 +813,8 @@ I've been working on my own [[http://www.howardism.org/Technical/Emacs/focused-w
|
||||||
("S-<f12>" . ha-focus-interrupt)
|
("S-<f12>" . ha-focus-interrupt)
|
||||||
("s-<f12>" . ha-focus-timer-left)))
|
("s-<f12>" . ha-focus-timer-left)))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
|
|
||||||
Need to be able to clock out of a task even if no /clock-in/ has occurred.
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
|
||||||
(defun ha-clock-out ()
|
|
||||||
"Safely clock out of the current task.
|
|
||||||
This will not throw an error if no active clock exists."
|
|
||||||
(interactive)
|
|
||||||
(ignore-errors
|
|
||||||
(org-clock-out)
|
|
||||||
(message "Clocked out.")))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
I call this function from [[file:hammerspoon.org::*Clocking out a Task][my Hammerspoon configuration]] to clock out of a task when I close my laptop.
|
|
||||||
|
|
||||||
What was I working on? Opening the laptop after a break seldom answers that, so this reports whatever clock is still running, and when none is, the one I stopped last, which is the more likely question after a break.
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
|
||||||
(defun ha-display-latest-clocked-task ()
|
|
||||||
"Display the task clocked in now, or the one most recently clocked out."
|
|
||||||
(interactive)
|
|
||||||
(cond
|
|
||||||
((org-clocking-p)
|
|
||||||
(message "Currently clocked in: %s"
|
|
||||||
(substring-no-properties (org-clock-get-clock-string))))
|
|
||||||
|
|
||||||
((and org-clock-out-time
|
|
||||||
(stringp org-clock-heading)
|
|
||||||
(not (string-empty-p org-clock-heading)))
|
|
||||||
(let ((seconds (float-time (time-subtract nil org-clock-out-time))))
|
|
||||||
(message "Clocked out of \"%s\" %s ago" org-clock-heading
|
|
||||||
(if (< seconds 60)
|
|
||||||
"less than a minute"
|
|
||||||
(format-seconds "%D %H %M%z" seconds)))))
|
|
||||||
|
|
||||||
(t (message "No task is currently clocked in."))))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
The =%z= at the /end/ of the format drops the larger units while they read zero, turning =0 days 0 hours 25 minutes= into =25 minutes=. It has nothing to lean on when every unit is zero, so the first minute gets phrased by hand:
|
|
||||||
|
|
||||||
** Spell Checking
|
** Spell Checking
|
||||||
Let's hook some spell checking into org files, and actually all text files. I’m making this particularly delicious.
|
Let's hook some spell-checking into org files, and actually all text files. I’m making this particularly delicious.
|
||||||
|
|
||||||
*** abbrev
|
*** abbrev
|
||||||
First, we turn on =abbrev-mode=. While this package comes with Emacs, check out [[https://masteringemacs.org/article/correcting-typos-misspellings-abbrev][Mickey Petersen's overview]] of using this package for auto-correcting typos.
|
First, we turn on =abbrev-mode=. While this package comes with Emacs, check out [[https://masteringemacs.org/article/correcting-typos-misspellings-abbrev][Mickey Petersen's overview]] of using this package for auto-correcting typos.
|
||||||
|
|
@ -874,7 +834,7 @@ The idea is that you can correct a typo /and remember/ it. Perhaps calling [[hel
|
||||||
*** jinx
|
*** jinx
|
||||||
Once upon a time, I used [[https://www.emacswiki.org/emacs/FlySpell][flyspell]] mode to highlight the misspelled words, and the venerable [[https://www.emacswiki.org/emacs/InteractiveSpell][ispell]] for correcting. To be able to correct spelling mistakes /from a distance/, without navigation, I wrote a function that took advantage of Evil’s [[help:evil-prev-flyspell-error][evil-prev-flyspell-error]] to jump back to the last spelling mistake.
|
Once upon a time, I used [[https://www.emacswiki.org/emacs/FlySpell][flyspell]] mode to highlight the misspelled words, and the venerable [[https://www.emacswiki.org/emacs/InteractiveSpell][ispell]] for correcting. To be able to correct spelling mistakes /from a distance/, without navigation, I wrote a function that took advantage of Evil’s [[help:evil-prev-flyspell-error][evil-prev-flyspell-error]] to jump back to the last spelling mistake.
|
||||||
|
|
||||||
Now, I’m using [[https://github.com/minad/jinx][jinx]], as it is the /complete basket/. It spellchecks based on the fontlock face and uses an external [[https://github.com/AbiWord/enchant][enchant program]] (to make spell checking fast and asynchronous). Like =flymake=, Jinx does on-the-fly spell checking of code comments and strings.
|
Now, I’m using [[https://github.com/minad/jinx][jinx]], as it is the /complete basket/. It spellchecks based on the fontlock face and uses an external [[https://github.com/AbiWord/enchant][enchant program]] (to make spell-checking fast and asynchronous). Like =flymake=, Jinx does on-the-fly spellchecking of code comments and strings.
|
||||||
|
|
||||||
I keep =jinx-correct= bound to ~C-;~ à la flyspell because it is so darn helpful. Supports checking documents with mixed languages.
|
I keep =jinx-correct= bound to ~C-;~ à la flyspell because it is so darn helpful. Supports checking documents with mixed languages.
|
||||||
|
|
||||||
|
|
@ -894,7 +854,7 @@ And the Emacs interface to that:
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package jinx
|
(use-package jinx
|
||||||
:straight (:host github :repo "minad/jinx" :files (:defaults "jinx-mod.c" "emacs-module.h"))
|
;; :vc (:url "https://github.com/minad/jinx" :files (:defaults "jinx-mod.c" "emacs-module.h"))
|
||||||
:hook (emacs-startup . global-jinx-mode)
|
:hook (emacs-startup . global-jinx-mode)
|
||||||
:bind (("C-;" . jinx-correct-nearest)
|
:bind (("C-;" . jinx-correct-nearest)
|
||||||
("s-;" . jinx-correct-nearest)
|
("s-;" . jinx-correct-nearest)
|
||||||
|
|
@ -934,6 +894,7 @@ The key-bindings, keystrokes, and key-connections work well with a hyper-command
|
||||||
:bind ("s-t" . powerthesaurus-lookup-dwim))
|
:bind ("s-t" . powerthesaurus-lookup-dwim))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
*** Definitions
|
*** Definitions
|
||||||
Since the /definitions/ do not work, so let's use the [[https://github.com/abo-abo/define-word][define-word]] project:
|
Since the /definitions/ do not work, so let's use the [[https://github.com/abo-abo/define-word][define-word]] project:
|
||||||
|
|
||||||
|
|
@ -979,103 +940,21 @@ Once in the dictionary buffer, acquiesce these keybindings:
|
||||||
|
|
||||||
Also note that the dictionary has links to other pages, so ~n~ and ~TAB~ jumps to the next link and ~RET~ opens that link.
|
Also note that the dictionary has links to other pages, so ~n~ and ~TAB~ jumps to the next link and ~RET~ opens that link.
|
||||||
** Grammar and Prose Linting
|
** Grammar and Prose Linting
|
||||||
Flagging clichés, weak phrasing and other poor grammar choices.
|
Flagging cliches, weak phrasing and other poor grammar choices.
|
||||||
|
|
||||||
We are trying a lot of checkers, as each have their specialties. To do this, we /chain/ them with a call to =flycheck-add-next-checker=:
|
We are trying a lot of checkers, so we need to /chain/ them with a call to =flycheck-add-next-checker=:
|
||||||
|
|
||||||
=write-good= —> =proselint= —> =textlint= —> =jinx= (for spell checking)
|
=write-good= —> =proselint= —> =textlint= —> =languagetool=?
|
||||||
*** Ignoring Code Blocks
|
|
||||||
None of these checkers understand Org, so each one grades my source blocks as if they were English. This becomes especially problematic when a Lisp quote turns off =proselint= for the rest of the file. I solved this issue in Markdown with [[file:ha-programming.org::*Remark][Remark]] and a code-stripping plugin, but the Org checkers have no such pipeline.
|
|
||||||
|
|
||||||
/Every/ checker gets the buffer through one of two functions: =flycheck-process-send-buffer= for the ones reading standard input, and =flycheck-save-buffer-to-file= for the ones handed a temporary file. Both work on whatever buffer happens to be current, so rather than wrap all three tools, I =advise= the pair to cover every checker in the chain, including any I add later.
|
|
||||||
|
|
||||||
The blanking replaces each character with a space instead of deleting it. Line numbers, columns, and character offsets all survive, so the positions the checkers report still line up with the real buffer:
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
|
||||||
(defcustom ha-org-prose-ignored-blocks '("src" "example" "export")
|
|
||||||
"Org block types whose contents the prose checkers should never see.
|
|
||||||
Note the absence of `quote' and `verse', as those hold prose worth
|
|
||||||
grading."
|
|
||||||
:type '(repeat string)
|
|
||||||
:group 'flycheck)
|
|
||||||
|
|
||||||
(defun ha-org--blank-region (start end)
|
|
||||||
"Replace each non-newline character between START and END with a space.
|
|
||||||
|
|
||||||
To keep the line number positions correct, I can't just delete a code block,
|
|
||||||
but need to replace the code block contents with innocuous spaces, but keep
|
|
||||||
the newline characters."
|
|
||||||
(save-excursion
|
|
||||||
(goto-char start)
|
|
||||||
(while (< (point) end)
|
|
||||||
(let ((eol (min end (line-end-position))))
|
|
||||||
(unless (= eol (point))
|
|
||||||
(let ((len (- eol (point))))
|
|
||||||
(delete-region (point) eol)
|
|
||||||
(insert (make-string len ?\s)))))
|
|
||||||
(forward-line 1))))
|
|
||||||
|
|
||||||
(defun ha-org--blank-inline-code ()
|
|
||||||
"Blank inline verbatim and code markup in the current buffer.
|
|
||||||
Only group 2 of `org-verbatim-re', the markup and its delimiters,
|
|
||||||
gets blanked, leaving the surrounding punctuation it also matches."
|
|
||||||
(goto-char (point-min))
|
|
||||||
(while (re-search-forward org-verbatim-re nil t)
|
|
||||||
(ha-org--blank-region (match-beginning 2) (match-end 2))
|
|
||||||
;; Stop short of the trailing character, since it doubles as the
|
|
||||||
;; leading character of whatever construct comes next.
|
|
||||||
(goto-char (match-end 2))))
|
|
||||||
|
|
||||||
(defun ha-org-prose-only-text ()
|
|
||||||
"Return this buffer's text with code blocks and inline code blanked."
|
|
||||||
(let ((text (buffer-substring-no-properties (point-min) (point-max))))
|
|
||||||
(with-temp-buffer
|
|
||||||
(insert text)
|
|
||||||
(goto-char (point-min))
|
|
||||||
(let* ((case-fold-search t)
|
|
||||||
(types (regexp-opt ha-org-prose-ignored-blocks t))
|
|
||||||
(start-re (concat "^[ \t]*#\\+begin_" types "\\b")))
|
|
||||||
(while (re-search-forward start-re nil t)
|
|
||||||
(let ((type (match-string 1))
|
|
||||||
(start (match-beginning 0)))
|
|
||||||
(when (re-search-forward
|
|
||||||
(concat "^[ \t]*#\\+end_" (regexp-quote type) "\\b.*$") nil t)
|
|
||||||
(ha-org--blank-region start (point))))))
|
|
||||||
;; After the blocks, since their contents no longer contain markup.
|
|
||||||
(ha-org--blank-inline-code)
|
|
||||||
(buffer-string))))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
Another issue I encounter (again solved differently for Markdown files using [[file:ha-programming.org::*Remark][Remark]]) involves ignoring these grammar and /proselinters/ for verbatim-formatted code. This allows me to write about =just= as a program, and not have it flagged.
|
|
||||||
|
|
||||||
Both advised functions read the current buffer, so the trick is to make a scratch buffer current that holds the blanked text. The =derived-mode-p= test happens /before/ the temporary buffer exists, since that buffer is in =fundamental-mode=:
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
|
||||||
(defun ha-flycheck--org-prose-only (orig &rest args)
|
|
||||||
"Call ORIG with code blocks blanked, when checking an Org buffer."
|
|
||||||
(if (derived-mode-p 'org-mode)
|
|
||||||
(let ((text (ha-org-prose-only-text)))
|
|
||||||
(with-temp-buffer
|
|
||||||
(insert text)
|
|
||||||
(apply orig args)))
|
|
||||||
(apply orig args)))
|
|
||||||
|
|
||||||
(advice-add 'flycheck-process-send-buffer :around #'ha-flycheck--org-prose-only)
|
|
||||||
(advice-add 'flycheck-save-buffer-to-file :around #'ha-flycheck--org-prose-only)
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
This matters more than tidiness for =proselint=. Since version 0.16, it pairs quote characters across the whole document and silently discards any complaint landing between a pair. Lisp is full of quoted symbols and strings, so those pairings run wild: 80% of this file counts as “quoted”, and the warnings in it never appear. Blanking the code drops that figure to a fraction of a percent.
|
|
||||||
*** Writegood
|
*** Writegood
|
||||||
The [[https://github.com/bnbeckwith/writegood-mode][writegood-mode]] is effective at highlighting passive voice and weasel words.
|
The [[https://github.com/bnbeckwith/writegood-mode][writegood-mode]] is effective at highlighting passive and weasel words.
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(use-package writegood-mode)
|
||||||
|
#+end_src
|
||||||
|
And it reports obnoxious messages.
|
||||||
|
|
||||||
For instance:
|
Note: Instead of hooking the =writegood-mode= to Org files, I will hook it to =flycheck= instead.
|
||||||
|
|
||||||
#+begin_quote
|
|
||||||
The cake was eaten by the children.
|
|
||||||
#+end_quote
|
|
||||||
|
|
||||||
We install the =write-good= npm:
|
|
||||||
|
|
||||||
|
We install the =write-good= NPM:
|
||||||
#+begin_src shell
|
#+begin_src shell
|
||||||
npm install -g write-good
|
npm install -g write-good
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
@ -1085,9 +964,7 @@ And check that the following works:
|
||||||
write-good --text="So it is what it is."
|
write-good --text="So it is what it is."
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Instead of hooking the =writegood-mode= to Org files, I =hook= it to =flycheck= instead.
|
Now, let’s connect it to flycheck:
|
||||||
Note that =markdown-mode= is absent from the list of modes, as Markdown files go through [[file:ha-programming.org::*Remark][Remark]] instead, which runs =write-good= over the prose while ignoring the code blocks:
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package flycheck
|
(use-package flycheck
|
||||||
:config
|
:config
|
||||||
|
|
@ -1097,31 +974,21 @@ Note that =markdown-mode= is absent from the list of modes, as Markdown files go
|
||||||
:standard-input nil
|
:standard-input nil
|
||||||
:error-patterns
|
:error-patterns
|
||||||
((warning line-start (file-name) ":" line ":" column ":" (message) line-end))
|
((warning line-start (file-name) ":" line ":" column ":" (message) line-end))
|
||||||
:modes (org-mode text-mode))
|
:modes (markdown-mode org-mode text-mode))
|
||||||
|
|
||||||
(add-to-list 'flycheck-checkers 'write-good))
|
(add-to-list 'flycheck-checkers 'write-good))
|
||||||
#+end_src
|
#+end_src
|
||||||
*** Proselint
|
*** Proselint
|
||||||
The [[https://github.com/amperser/proselint/][proselint]] project checks for poor English phrasings, clichés, and corporate-speak. For instance:
|
With overlapping goals to =write-good=, the [[https://github.com/amperser/proselint/][proselint]] project, once installed, can check for some English phrasings. I like =write-good= better, but I want this available for its level of /pedantic-ness/.
|
||||||
|
|
||||||
#+begin_quote
|
|
||||||
Let's touch base and circle back.
|
|
||||||
#+end_quote
|
|
||||||
|
|
||||||
Install it on a Mac via:
|
|
||||||
|
|
||||||
#+begin_src sh
|
#+begin_src sh
|
||||||
brew install proselint
|
brew install proselint
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
And on Linux:
|
And on Linux:
|
||||||
|
|
||||||
#+BEGIN_SRC sh
|
#+BEGIN_SRC sh
|
||||||
sudo apt install python3-proselint
|
sudo apt install python3-proselint
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
Next, create a configuration file, =~/.config/proselint/config= file, to turn on/off checks:
|
Next, create a configuration file, =~/.config/proselint/config= file, to turn on/off checks:
|
||||||
|
|
||||||
#+begin_src js :tangle ~/.config/proselint/config.json :mkdirp yes
|
#+begin_src js :tangle ~/.config/proselint/config.json :mkdirp yes
|
||||||
{
|
{
|
||||||
"checks": {
|
"checks": {
|
||||||
|
|
@ -1132,8 +999,7 @@ Next, create a configuration file, =~/.config/proselint/config= file, to turn on
|
||||||
}
|
}
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
And tell [[https://www.flycheck.org/][flycheck]] to use Proselint:
|
And tell [[https://www.flycheck.org/][flycheck]] to use this:
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package flycheck
|
(use-package flycheck
|
||||||
:config
|
:config
|
||||||
|
|
@ -1141,20 +1007,10 @@ And tell [[https://www.flycheck.org/][flycheck]] to use Proselint:
|
||||||
;; And create the chain of checkers so that both work:
|
;; And create the chain of checkers so that both work:
|
||||||
(flycheck-add-next-checker 'write-good 'proselint))
|
(flycheck-add-next-checker 'write-good 'proselint))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Without this block tangling, =write-good= has no /next/ checker, and since it sits at the front of =flycheck-checkers=, it becomes the one Org buffers select. Flycheck then stops there, and every checker chained behind it never runs.
|
|
||||||
*** Textlint
|
*** Textlint
|
||||||
The [[https://textlint.github.io/][textlint]] project comes with =flycheck=, and flags undefined acronyms (which annoyingly display at the beginning of the file), using the wrong punctuation, and “profane” words. For instance:
|
The [[https://textlint.github.io/][textlint]] project comes with =flycheck=, as long as there is an executable:
|
||||||
|
|
||||||
#+begin_quote
|
|
||||||
NASA launched a new rocket ball’s deep to the moon.
|
|
||||||
#+end_quote
|
|
||||||
|
|
||||||
This requires an executable:
|
|
||||||
|
|
||||||
#+begin_src sh :results silent
|
#+begin_src sh :results silent
|
||||||
sudo npm install -g textlint
|
sudo npm install -g textlint
|
||||||
|
|
||||||
# And all the rules
|
# And all the rules
|
||||||
sudo npm install -g textlint-rule-alex
|
sudo npm install -g textlint-rule-alex
|
||||||
sudo npm install -g textlint-rule-diacritics
|
sudo npm install -g textlint-rule-diacritics
|
||||||
|
|
@ -1162,112 +1018,75 @@ This requires an executable:
|
||||||
sudo npm install -g textlint-rule-max-comma
|
sudo npm install -g textlint-rule-max-comma
|
||||||
sudo npm install -g textlint-rule-no-start-duplicated-conjunction
|
sudo npm install -g textlint-rule-no-start-duplicated-conjunction
|
||||||
sudo npm install -g textlint-rule-period-in-list-item
|
sudo npm install -g textlint-rule-period-in-list-item
|
||||||
|
sudo npm install -g textlint-rule-stop-words
|
||||||
sudo npm install -g textlint-rule-terminology
|
sudo npm install -g textlint-rule-terminology
|
||||||
sudo npm install -g textlint-rule-unexpanded-acronym
|
sudo npm install -g textlint-rule-unexpanded-acronym
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
I create a configuration file in my home directory:
|
I create a configuration file in my home directory:
|
||||||
|
|
||||||
#+begin_src js :tangle ~/.textlintrc
|
#+begin_src js :tangle ~/.textlintrc
|
||||||
{
|
{
|
||||||
"filters": {},
|
"filters": {},
|
||||||
"rules": {
|
"rules": {
|
||||||
"abbr-within-parentheses": false,
|
"abbr-within-parentheses": false,
|
||||||
"alex": { "severity": "warning" },
|
"alex": true,
|
||||||
"common-misspellings": false,
|
"common-misspellings": false,
|
||||||
"diacritics": { "severity": "warning" },
|
"diacritics": true,
|
||||||
"en-max-word-count": { "severity": "warning" },
|
"en-max-word-count": true,
|
||||||
"max-comma": { "severity": "warning" },
|
"max-comma": true,
|
||||||
"no-start-duplicated-conjunction": { "severity": "warning" },
|
"no-start-duplicated-conjunction": true,
|
||||||
"period-in-list-item": { "severity": "warning" },
|
"period-in-list-item": true,
|
||||||
"stop-words": false,
|
"stop-words": true,
|
||||||
"terminology": { "severity": "warning" },
|
"terminology": true,
|
||||||
"unexpanded-acronym": { "severity": "warning" },
|
"unexpanded-acronym": true,
|
||||||
"write-good": false
|
"write-good": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Every rule carries an explicit =severity=, as textlint defaults each one to /error/, and =flycheck-parse-eslint= turns that into an Emacs error. Left alone, a quibble over a hyphen would outrank a misspelling. Note the /object/ form: a rule set to =true= takes no options, so the severity has to replace that value rather than sit beside it.
|
Add =textlint= to the /chain/ for Org files:
|
||||||
|
|
||||||
Add =textlint= to the /chain/ for Org files. The built-in checker hands textlint a temporary file named after the buffer, and textlint /silently skips/ any file whose extension it does not know. Since =.org= is not on that list, it reports nothing and exits successfully, which looks identical to a clean buffer…ugh.
|
|
||||||
|
|
||||||
Piping the buffer in under a name textlint accepts avoids that. The =stdin.txt= name is cosmetic, as =flycheck-parse-eslint= overwrites it with the real buffer’s file name:
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package flycheck
|
(use-package flycheck
|
||||||
:config
|
:config
|
||||||
(setq flycheck-textlint-config (format "%s/.textlintrc" (getenv "HOME")))
|
(setq flycheck-textlint-config (format "%s/.textlintrc" (getenv "HOME")))
|
||||||
|
(flycheck-add-next-checker 'proselint 'textlint t))
|
||||||
(flycheck-define-checker org-textlint
|
|
||||||
"A prose checker for Org files using textlint.
|
|
||||||
|
|
||||||
Textlint skips files whose extension it does not know, and `.org' is
|
|
||||||
not among them, so the buffer arrives on standard input under a name
|
|
||||||
the text plugin accepts."
|
|
||||||
:command ("textlint"
|
|
||||||
(config-file "--config" flycheck-textlint-config)
|
|
||||||
"--format" "json"
|
|
||||||
"--plugin" "@textlint/text"
|
|
||||||
"--stdin" "--stdin-filename" "stdin.txt")
|
|
||||||
:standard-input t
|
|
||||||
:error-parser flycheck-parse-eslint
|
|
||||||
:modes (org-mode))
|
|
||||||
|
|
||||||
(add-to-list 'flycheck-checkers 'org-textlint t)
|
|
||||||
(flycheck-add-next-checker 'proselint 'org-textlint t))
|
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Note: The text plugin has no notion of Org, but that no longer matters, since [[*Ignoring Code Blocks][the advice above]] hands it prose with the code already blanked.
|
*** Language Tool
|
||||||
*** Spelling
|
Another flycheck feature is to use [[http://languagetool.org][LanguageTool]] connection to [[https://github.com/emacs-languagetool/flycheck-languagetool][flycheck-languagetool]]:
|
||||||
I like correcting spelling through the [[*jinx][jinx]] interface, but would also like to jump through misspellings using the =flycheck= UI.
|
|
||||||
Note: Since =flycheck= only /reports/ problems (like a misspelling), I still require =jinx-correct= to repair the problem.
|
|
||||||
|
|
||||||
Jinx already does the work and leaves an overlay on each misspelling, so the checker collects them:
|
#+BEGIN_SRC emacs-lisp :tangle no
|
||||||
|
(use-package flycheck-languagetool
|
||||||
|
:ensure t
|
||||||
|
:hook (text-mode . flycheck-languagetool-setup)
|
||||||
|
:init
|
||||||
|
(setq flycheck-languagetool-server-jar (expand-file-name "/opt/homebrew/Cellar/languagetool/6.6/libexec/languagetool-commandline.jar")
|
||||||
|
flycheck-languagetool-server-args (expand-file-name "~/.config/languagetool/config.properties")))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
Where it can read: [[file:~/.config/languagetool/config.properties][config.properties]].
|
||||||
|
|
||||||
|
And connect it to the chain:
|
||||||
|
|
||||||
|
#+BEGIN_SRC emacs-lisp :tangle no
|
||||||
(use-package flycheck
|
(use-package flycheck
|
||||||
:after jinx
|
:config (flycheck-add-next-checker 'textlint 'languagetool t)
|
||||||
:config
|
|
||||||
(defun ha-jinx--flycheck-errors (checker)
|
|
||||||
"Return a `flycheck-error' for each misspelling jinx finds."
|
|
||||||
;; Jinx separates prose from code by font-lock face, and font-lock
|
|
||||||
;; only fontifies what has been displayed. Without this, the rest of
|
|
||||||
;; the buffer arrives faceless and every symbol reads as misspelled.
|
|
||||||
(font-lock-ensure)
|
|
||||||
(jinx--check-region (point-min) (point-max))
|
|
||||||
(mapcar (lambda (ov)
|
|
||||||
(let ((start (overlay-start ov))
|
|
||||||
(end (overlay-end ov)))
|
|
||||||
(flycheck-error-new-at-pos
|
|
||||||
start 'error
|
|
||||||
(format "Misspelled: %s"
|
|
||||||
(buffer-substring-no-properties start end))
|
|
||||||
:id "jinx"
|
|
||||||
:checker checker
|
|
||||||
:end-pos end)))
|
|
||||||
(jinx--get-overlays (point-min) (point-max))))
|
|
||||||
|
|
||||||
(flycheck-define-generic-checker 'jinx
|
;; May have to specify a Java on one of my Mac machines:
|
||||||
"A spell checker reporting what jinx and enchant already found."
|
(when (file-exists-p "/opt/homebrew/opt/openjdk")
|
||||||
:start (lambda (checker callback)
|
(add-to-list 'exec-path "/opt/homebrew/opt/openjdk/bin")))
|
||||||
(condition-case err
|
#+END_SRC
|
||||||
(funcall callback 'finished (ha-jinx--flycheck-errors checker))
|
|
||||||
(error (funcall callback 'errored (error-message-string err)))))
|
|
||||||
;; Without jinx enabled there are no dictionaries to ask.
|
|
||||||
:predicate (lambda () (bound-and-true-p jinx-mode))
|
|
||||||
:modes '(org-mode text-mode markdown-mode gfm-mode))
|
|
||||||
|
|
||||||
(add-to-list 'flycheck-checkers 'jinx t)
|
This check complains about whitespace in Org files (duh), so let’s create a configuration file where we can disable that rule (and any other we can require):
|
||||||
(flycheck-add-next-checker 'org-textlint 'jinx t))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
Note that jinx underlining the words itself, /on top of/ what flycheck now draws. Turning that off would mean would apply that everywhere — including the programming buffers where this prose-chain never runs, and I would need jinx’s flagging. In other words, the doubled underline seems the smaller cost.
|
#+BEGIN_SRC conf :tangle ~/.config/languagetool/config.properties :mkdirp ~/.config/languagetool
|
||||||
|
disabledRuleIds: WHITESPACE
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
Gotta admit that Language Tool doesn’t seem to help much. $ 100 ?
|
||||||
** Perfect Sentence
|
** Perfect Sentence
|
||||||
Jordan Peterson's writing app, [[https://essay.app/guide][Essay]], inspired Chris Malorana’s [[https://www.youtube.com/watch?v=E-yk_V5TnNU][video tutorial]] showing the ability to extrude a single sentence into another buffer, edit different versions of that sentence, and replace one version into the original buffer. Similar to how org-mode edits blocks.
|
Chris Malorana’s [[https://www.youtube.com/watch?v=E-yk_V5TnNU][video tutorial]] demonstrates the ability to extrude a single sentence into another buffer, edit different versions of that sentence, and replace one version into the original buffer. For instance, how org-mode edits blocks.
|
||||||
|
|
||||||
Thought I might work on it, but I want my version more resilient and not as dependent on the context.
|
Malorana based this idea on Jordan Peterson's writing app, [[https://essay.app/guide][Essay]]. Thought I might work on it, but I want my version more resilient and not as dependent on the context.
|
||||||
|
|
||||||
When we create a new buffer, we set the following /buffer-local/ variables, so we know where to return:
|
When we create a new buffer, we set the following /buffer-local/ variables, so we know where to return:
|
||||||
|
|
||||||
|
|
@ -1350,11 +1169,9 @@ In that case, we need to select the text before hitting the ~C-c C-c~ sequence.
|
||||||
(delete-region orig-start orig-end) ; Or call `kill-region' to put on clipboard?
|
(delete-region orig-start orig-end) ; Or call `kill-region' to put on clipboard?
|
||||||
(insert chosen-sentence))))
|
(insert chosen-sentence))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
The [[help:kill-region][kill-region]] function takes the original text and places it on the [[help:kill-ring][kill-ring]] (the clipboard). But since we already copied that when we created the buffer, we call [[help:delete-region][delete-region]] instead. Especially since if we felt like we made a mistake, we could just undo the changes.
|
||||||
|
|
||||||
The [[help:kill-region][kill-region]] function takes the original text and places it on the [[help:kill-ring][kill-ring]] (the clipboard). But since we already copied that when we created the buffer, we call [[help:delete-region][delete-region]] instead. If we felt like we made a mistake, we can undo the changes.
|
With my limited experience, I seldom enter completely difference sentences. Instead, I want to /copy/ the sentence and work on that. Let’s make a function to duplicate it.
|
||||||
|
|
||||||
With my limited experience, I seldom replace the sentence. Instead, I want to /copy/ the sentence and work on that:
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defun ha-sentence-duplicate ()
|
(defun ha-sentence-duplicate ()
|
||||||
(interactive)
|
(interactive)
|
||||||
|
|
@ -1366,7 +1183,7 @@ With my limited experience, I seldom replace the sentence. Instead, I want to /c
|
||||||
(goto-char starting-point))))
|
(goto-char starting-point))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
When creating this new editing buffer, we need keybindings that exist for this buffer, in other words, a [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Defining-Minor-Modes.html][minor mode]]:
|
When creating this new editing buffer, we need keybindings that exist only for this buffer, in other words, a [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Defining-Minor-Modes.html][minor mode]]:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defvar ha-sentence-buffer-mode-map (make-sparse-keymap) "Keymap for `my-mode'.")
|
(defvar ha-sentence-buffer-mode-map (make-sparse-keymap) "Keymap for `my-mode'.")
|
||||||
(define-key ha-sentence-buffer-mode-map (kbd "C-c C-c") #'ha-sentence-choose)
|
(define-key ha-sentence-buffer-mode-map (kbd "C-c C-c") #'ha-sentence-choose)
|
||||||
|
|
@ -1398,9 +1215,7 @@ And something else while in Evil mode:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(ha-leader "x b" '("edit sentence" . ha-sentence-break))
|
(ha-leader "x b" '("edit sentence" . ha-sentence-break))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
Perhaps he might get around to turning [[https://git.chrismaiorana.com/?p=sentinel.git;a=blob;f=sentin.el;h=2738eff6ac2b0877576bafe88878683a7eff3125;hb=refs/heads/master][his code]] into a package. Features needed include:
|
||||||
While Malorana might get around to turning [[https://git.chrismaiorana.com/?p=sentinel.git;a=blob;f=sentin.el;h=2738eff6ac2b0877576bafe88878683a7eff3125;hb=refs/heads/master][his code]] into a package, new features need to include:
|
|
||||||
|
|
||||||
- Adding an overlay to the original text, help:org-src--make-source-overlay
|
- Adding an overlay to the original text, help:org-src--make-source-overlay
|
||||||
|
|
||||||
** Distraction-Free Writing
|
** Distraction-Free Writing
|
||||||
|
|
@ -1438,7 +1253,7 @@ The [[https://github.com/rnkn/olivetti][olivetti project]] sets wide margins and
|
||||||
Trying out [[https://protesilaos.com/][Protesilaos Stavrou]]’s [[https://protesilaos.com/emacs/logos][logos project]] as a replacement for [[https://github.com/joostkremers/writeroom-mode][Writeroom-mode]]:
|
Trying out [[https://protesilaos.com/][Protesilaos Stavrou]]’s [[https://protesilaos.com/emacs/logos][logos project]] as a replacement for [[https://github.com/joostkremers/writeroom-mode][Writeroom-mode]]:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package logos
|
(use-package logos
|
||||||
:straight (:host gitlab :repo "protesilaos/logos")
|
;; :vc (:url "https://gitlab.com/protesilaos/logos")
|
||||||
:init
|
:init
|
||||||
(setq logos-outlines-are-pages t
|
(setq logos-outlines-are-pages t
|
||||||
logos-outline-regexp-alist
|
logos-outline-regexp-alist
|
||||||
|
|
@ -1481,5 +1296,5 @@ Before you can build this on a new system, make sure that you put the cursor ove
|
||||||
#+infojs_opt: view:nil toc:t ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
|
#+infojs_opt: view:nil toc:t ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
|
||||||
|
|
||||||
# Local Variables:
|
# Local Variables:
|
||||||
# jinx-local-words: "Braganza Graphviz Malorana Malorana’s Proselint Somers Textlint Writegood flycheck flyspell fontlock"
|
# jinx-local-words: "Braganza Graphviz Malorana’s Proselint Somers Textlint Writegood flycheck flyspell fontlock"
|
||||||
# End:
|
# End:
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ The obvious keybindings are ~M-h/j/k/l~ … but that is used … well, somewhat.
|
||||||
|
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp :tangle no
|
||||||
(use-package spatial-navigate
|
(use-package spatial-navigate
|
||||||
:straight (:repo "https://codeberg.org/ideasman42/emacs-spatial-navigate")
|
;; :vc (:url "https://codeberg.org/ideasman42/emacs-spatial-navigate")
|
||||||
:config
|
:config
|
||||||
(pretty-hydra-define spatial-navigate (:color amaranth :quit-key "q")
|
(pretty-hydra-define spatial-navigate (:color amaranth :quit-key "q")
|
||||||
("Box"
|
("Box"
|
||||||
|
|
@ -62,7 +62,7 @@ The [[https://github.com/antonj/Highlight-Indentation-for-Emacs][Highlight-Inden
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package highlight-indentation
|
(use-package highlight-indentation
|
||||||
:straight (:host github :repo "antonj/Highlight-Indentation-for-Emacs")
|
;; :vc (:url "https://github.com/antonj/Highlight-Indentation-for-Emacs")
|
||||||
:hook ((yaml-mode . highlight-indentation-mode)
|
:hook ((yaml-mode . highlight-indentation-mode)
|
||||||
(python-mode . highlight-indentation-mode)))
|
(python-mode . highlight-indentation-mode)))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
@ -119,7 +119,7 @@ Allow this mode in Org blocks:
|
||||||
And we hook
|
And we hook
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package yaml-pro
|
(use-package yaml-pro
|
||||||
:straight (:host github :repo "zkry/yaml-pro")
|
;; :vc (:url "https://github.com/zkry/yaml-pro")
|
||||||
:after yaml-mode
|
:after yaml-mode
|
||||||
:hook ((yaml-mode . yaml-pro-mode)))
|
:hook ((yaml-mode . yaml-pro-mode)))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
@ -224,7 +224,7 @@ Do I consider all YAML files an Ansible file needing [[https://github.com/k1LoW/
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package ansible
|
(use-package ansible
|
||||||
:straight (:host gitlab :repo "emacs-ansible/emacs-ansible")
|
;; :vc (:url "https://gitlab.com/emacs-ansible/emacs-ansible")
|
||||||
;; :mode ((rx (or "playbooks" "roles") (one-or-more any) ".y" (optional "a") "ml") . ansible-mode)
|
;; :mode ((rx (or "playbooks" "roles") (one-or-more any) ".y" (optional "a") "ml") . ansible-mode)
|
||||||
:config
|
:config
|
||||||
(setq ansible-vault-password-file "~/.ansible-vault-passfile")
|
(setq ansible-vault-password-file "~/.ansible-vault-passfile")
|
||||||
|
|
@ -242,7 +242,7 @@ Since most Ansible files are a combination of YAML and Jinja, the [[https://gith
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(use-package poly-ansible
|
(use-package poly-ansible
|
||||||
:straight (:host github :repo "emacsmirror/poly-ansible")
|
;; :vc (:url "https://github.com/emacsmirror/poly-ansible")
|
||||||
:after ansible)
|
:after ansible)
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -247,7 +247,7 @@ The /real problem/ is trying to remember all the [[https://github.com/clojure-em
|
||||||
And of course, we want to put this with org blocks:
|
And of course, we want to put this with org blocks:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package ob-clojure
|
(use-package ob-clojure
|
||||||
:straight (:type built-in)
|
|
||||||
:custom
|
:custom
|
||||||
(org-babel-clojure-backend 'cider)
|
(org-babel-clojure-backend 'cider)
|
||||||
:config
|
:config
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,11 @@ And we should extend it with the [[https://github.com/xuchunyang/elisp-demos][el
|
||||||
Wilfred’s [[https://github.com/Wilfred/suggest.el][suggest]] function helps you find the right function. Basically, you type in the parameters of a function, and then the desired output, and it will write the function call.
|
Wilfred’s [[https://github.com/Wilfred/suggest.el][suggest]] function helps you find the right function. Basically, you type in the parameters of a function, and then the desired output, and it will write the function call.
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package suggest)
|
(use-package spinner
|
||||||
|
:vc (:url "https://github.com/Malabarba/spinner.el"))
|
||||||
|
|
||||||
|
(use-package suggest
|
||||||
|
:after spinner)
|
||||||
#+end_src
|
#+end_src
|
||||||
* Navigation
|
* Navigation
|
||||||
** Goto Definitions
|
** Goto Definitions
|
||||||
|
|
@ -280,7 +284,7 @@ These are all good, but the primary keys I need to figure out, are the s-express
|
||||||
Wilfred’s [[https://github.com/Wilfred/emacs-refactor/tree/master#elisp][emacs-refactor]] package can be helpful if you turn on =context-menu-mode= and …
|
Wilfred’s [[https://github.com/Wilfred/emacs-refactor/tree/master#elisp][emacs-refactor]] package can be helpful if you turn on =context-menu-mode= and …
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package emr
|
(use-package emr
|
||||||
;; :straight (:host github :repo "Wilfred/emacs-refactor")
|
;; ;; :vc (:url "https://github.com/Wilfred/emacs-refactor")
|
||||||
:config
|
:config
|
||||||
(pretty-hydra-define+ lisp-refactor nil
|
(pretty-hydra-define+ lisp-refactor nil
|
||||||
("To 𝛌"
|
("To 𝛌"
|
||||||
|
|
@ -366,3 +370,4 @@ Let's =provide= a name so we can =require= this file:
|
||||||
#+options: num:nil toc:t todo:nil tasks:nil tags:nil date:nil
|
#+options: num:nil toc:t todo:nil tasks:nil tags:nil date:nil
|
||||||
#+options: skip:nil author:nil email:nil creator:nil timestamp:nil
|
#+options: skip:nil author:nil email:nil creator:nil timestamp:nil
|
||||||
#+infojs_opt: view:nil toc:t ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
|
#+infojs_opt: view:nil toc:t ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
|
||||||
|
exit
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ The [[https://github.com/mihaimaruseac/hindent][hindent package]] looks interest
|
||||||
* Haskell and Org
|
* Haskell and Org
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package ob-haskell
|
(use-package ob-haskell
|
||||||
:straight (:type built-in)
|
|
||||||
:config
|
:config
|
||||||
(add-to-list 'org-babel-load-languages '(haskell . t)))
|
(add-to-list 'org-babel-load-languages '(haskell . t)))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,8 @@ While Emacs supplies a Python editing environment, we’ll still use =use-packag
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package python
|
(use-package python
|
||||||
:after flycheck
|
:after flycheck
|
||||||
:mode (((rx ".flake8" eol) . conf-mode)
|
:mode ("[./]flake8\\'" . conf-mode)
|
||||||
((rx "Pipfile" eol) . conf-mode)
|
:mode ("/Pipfile\\'" . conf-mode)
|
||||||
((rx ".wsgi" eol) . python-mode))
|
|
||||||
:init
|
:init
|
||||||
(setq python-indent-guess-indent-offset-verbose nil
|
(setq python-indent-guess-indent-offset-verbose nil
|
||||||
flycheck-flake8-maximum-line-length 120)
|
flycheck-flake8-maximum-line-length 120)
|
||||||
|
|
@ -137,14 +136,7 @@ Next, after reading David Vujic’s [[https://davidvujic.blogspot.com/2025/03/ar
|
||||||
# c.InteractiveShellApp.exec_lines = ['%autoreload 2']
|
# c.InteractiveShellApp.exec_lines = ['%autoreload 2']
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
** Isolated Python Environments
|
** Virtual Environment
|
||||||
While the Python community (and my work at my company) had difficulty transitioning from Python 2 to 3, I often run into issues needing a particular Python version and modules. After playing around with different approaches, I’m finding:
|
|
||||||
* Docker environments are nicely isolated, but annoying to work from outside the container
|
|
||||||
* The Builtin =venv= is works well for different library modules, but not for different versions
|
|
||||||
* The =pyenv= deals with different Python versions, but is overkill for library isolation
|
|
||||||
|
|
||||||
While the [[https://github.com/marcwebbie/auto-virtualenv][auto-virtualenv]] project attempts to resolve this, I’m using the [[file:ha-programming.org::*Virtual Environments with direnv][direnv project]] abstraction for situations where I need project-specific isolation in more than just Python.
|
|
||||||
*** Virtual Environments
|
|
||||||
Use the built-in module, venv, to create isolated Python environments for specific projects, enabling you to manage dependencies separately.
|
Use the built-in module, venv, to create isolated Python environments for specific projects, enabling you to manage dependencies separately.
|
||||||
|
|
||||||
Create a virtual environment, either in the project’s directory, or in a global spot:
|
Create a virtual environment, either in the project’s directory, or in a global spot:
|
||||||
|
|
@ -168,8 +160,8 @@ Now, do what you need to do with this isolation:
|
||||||
#+BEGIN_SRC sh :tangle no
|
#+BEGIN_SRC sh :tangle no
|
||||||
pip install -r test-requirements.txt
|
pip install -r test-requirements.txt
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
*** Managing Python Versions
|
** Virtual Environment with new Python Version
|
||||||
[[https://github.com/pyenv/pyenv][Pyenv]] is a tool for managing multiple versions of Python on your machine, allowing you to switch between them easily (see [[https://realpython.com/intro-to-pyenv/][this essay]]). On a Mac, installed it via Homebrew:
|
Pyenv is a tool for managing multiple versions of Python on your machine, allowing you to switch between them easily. On a Mac, installed it via Homebrew:
|
||||||
|
|
||||||
#+BEGIN_SRC sh
|
#+BEGIN_SRC sh
|
||||||
brew install readline xz
|
brew install readline xz
|
||||||
|
|
@ -221,56 +213,16 @@ Also, you need the following in your =~/.config/direnv/direnvrc= file (which I h
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
#+end_src
|
#+end_src
|
||||||
|
** Editing Python Code
|
||||||
Tell Emacs about [[https://github.com/pythonic-emacs/pyenv-mode][pyenv-mode]]:
|
Let’s integrate this [[https://github.com/wbolster/evil-text-object-python][Python support for evil-text-object]] project:
|
||||||
|
#+begin_src emacs-lisp
|
||||||
#+BEGIN_SRC emacs-lisp
|
(when (fboundp 'evil-define-text-object)
|
||||||
(use-package pyenv-mode
|
(use-package evil-text-object-python
|
||||||
:config
|
:hook (python-mode . evil-text-object-python-add-bindings)))
|
||||||
(defun setup-pyenv ()
|
#+end_src
|
||||||
"Pyenv."
|
This allows me to delete a Python “block” using ~dal~.
|
||||||
(setenv "WORKON_HOME" "~/.pyenv/versions")
|
** Docker Environment
|
||||||
(pyenv-mode +1)))
|
Docker really allows you to isolate your project's environment. The downside is that you are using Docker and probably a bloated container. On my work laptop, a Mac, this creates a behemoth virtual machine that immediately spins the fans like a wind tunnel.
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Now specify the =pyenv= Python version by calling [[help:pyenv-mode-set][pyenv-mode-set]]:
|
|
||||||
|
|
||||||
#+begin_example
|
|
||||||
M-x pyenv-mode-set
|
|
||||||
#+end_example
|
|
||||||
|
|
||||||
When you run inferior Python processes (like =run-python=), the process will start inside the specified Python installation. You can unset the current version with:
|
|
||||||
|
|
||||||
#+begin_example
|
|
||||||
M-x pyenv-mode-unset
|
|
||||||
#+end_example
|
|
||||||
|
|
||||||
Or, we can do it automatically when we get into a project (if the project has a =.python-version= file):
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(use-package pyenv-mode
|
|
||||||
:config
|
|
||||||
(defun project-pyenv-mode-set (&rest _)
|
|
||||||
"Set pyenv version matching project name."
|
|
||||||
(ignore-errors
|
|
||||||
(let* ((filename (thread-first
|
|
||||||
(project-current)
|
|
||||||
(project-root)
|
|
||||||
(file-name-concat ".python-version")))
|
|
||||||
(version (when (file-exists-p filename)
|
|
||||||
(with-temp-buffer
|
|
||||||
(insert-file-contents filename)
|
|
||||||
(buffer-string)))))
|
|
||||||
(when version
|
|
||||||
(pyenv-mode-set version)
|
|
||||||
(pyenv-mode-unset)))))
|
|
||||||
|
|
||||||
;; Either set/unset the pyenv version whenever changing tabs:
|
|
||||||
(add-hook 'tab-bar-tab-post-select-functions 'project-pyenv-mode-set))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
*** Docker Environment
|
|
||||||
Docker allows you to isolate your project's environment. The downside is that you are using Docker and probably a bloated container. On my work laptop, a Mac, this creates a behemoth virtual machine that immediately spins the fans like a wind tunnel.
|
|
||||||
|
|
||||||
But, but... think of the dependencies!
|
But, but... think of the dependencies!
|
||||||
|
|
||||||
|
|
@ -284,14 +236,6 @@ Your project's =.envrc= file would contain something like:
|
||||||
|
|
||||||
container_layout
|
container_layout
|
||||||
#+end_src
|
#+end_src
|
||||||
** Editing Python Code
|
|
||||||
Let’s integrate this [[https://github.com/wbolster/evil-text-object-python][Python support for evil-text-object]] project:
|
|
||||||
#+begin_src emacs-lisp
|
|
||||||
(when (fboundp 'evil-define-text-object)
|
|
||||||
(use-package evil-text-object-python
|
|
||||||
:hook (python-mode . evil-text-object-python-add-bindings)))
|
|
||||||
#+end_src
|
|
||||||
This allows me to delete a Python “block” using ~dal~.
|
|
||||||
** Unit Tests
|
** Unit Tests
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package python-pytest
|
(use-package python-pytest
|
||||||
|
|
@ -318,6 +262,64 @@ This allows me to delete a Python “block” using ~dal~.
|
||||||
("Misc"
|
("Misc"
|
||||||
(("t" python-tests/body "Tests..."))))))
|
(("t" python-tests/body "Tests..."))))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
* Elpy
|
||||||
|
The [[https://elpy.readthedocs.io/en/latest/introduction.html][Elpy Project]] expands on the =python-mode=.
|
||||||
|
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(use-package elpy
|
||||||
|
:config
|
||||||
|
(elpy-enable))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
Let’s expand our =major-mode-hydra= with some extras:
|
||||||
|
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(use-package major-mode-hydra
|
||||||
|
:after elpy
|
||||||
|
:config
|
||||||
|
|
||||||
|
(pretty-hydra-define python-evaluate (:color blue :quit-key "q"
|
||||||
|
:title ha-python-eval-title)
|
||||||
|
("Section"
|
||||||
|
(("F" elpy-shell-send-defun "Function")
|
||||||
|
("E" elpy-shell-send-statement "Statement")
|
||||||
|
(";" python-shell-send-string "Expression"))
|
||||||
|
"Entirety"
|
||||||
|
(("B" elpy-shell-send-buffer "Buffer")
|
||||||
|
("r" elpy-shell-send-region-or-buffer "region"))
|
||||||
|
"And Step..."
|
||||||
|
(("f" elpy-shell-send-defun-and-step "Function" :color pink)
|
||||||
|
("e" elpy-shell-send-statement-and-step "Statement" :color pink))))
|
||||||
|
|
||||||
|
(pretty-hydra-define+ python-refactor nil
|
||||||
|
("Elpy"
|
||||||
|
(("r" elpy-refactor-rename "Rename")
|
||||||
|
("i" elpy-refactor-inline "Inline var")
|
||||||
|
("v" elpy-refactor-extract-variable "To variable")
|
||||||
|
("f" elpy-refactor-extract-function "To function")
|
||||||
|
("a" elpy-refactor-mode "All..."))))
|
||||||
|
|
||||||
|
(major-mode-hydra-define+ python-mode (:quit-key "q" :color blue)
|
||||||
|
("Server"
|
||||||
|
(("s" elpy-shell-switch-to-shell "Go to Server")
|
||||||
|
("C" elpy-config "Config Elpy"))
|
||||||
|
"Edit"
|
||||||
|
(("f" elpy-black-fix-code "Fix/format code"))
|
||||||
|
"Docs"
|
||||||
|
(("d" elpy-eldoc-documentation "Describe Symbol")
|
||||||
|
("D" elpy-doc "Docs Symbol")))))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
* Anaconda
|
||||||
|
The [[https://github.com/pythonic-emacs/anaconda-mode][anaconda-mode project]] seems as good as Elpy, but also include Evil keybindings.
|
||||||
|
|
||||||
|
#+BEGIN_SRC emacs-lisp :tangle no
|
||||||
|
(use-mode anaconda-mode
|
||||||
|
:hook ((python-mode . anaconda-mode)
|
||||||
|
(python-mode ../ anaconda-eldoc-mode)))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
Since we are using
|
||||||
* LSP Integration of Python
|
* LSP Integration of Python
|
||||||
** Dependencies
|
** Dependencies
|
||||||
Each Python project's =requirements-dev.txt= file would reference the [[https://pypi.org/project/python-lsp-server/][python-lsp-server]] (not the /unmaintained/ project, =python-language-server=):
|
Each Python project's =requirements-dev.txt= file would reference the [[https://pypi.org/project/python-lsp-server/][python-lsp-server]] (not the /unmaintained/ project, =python-language-server=):
|
||||||
|
|
@ -343,60 +345,100 @@ Each Python project's =requirements-dev.txt= file would reference the [[https://
|
||||||
# ...
|
# ...
|
||||||
#+end_src
|
#+end_src
|
||||||
** Pyright
|
** Pyright
|
||||||
I’m using the Microsoft-supported [[https://github.com/Microsoft/pyright][pyright]] package instead, adding it to the global Python scope.
|
I’m using the Microsoft-supported [[https://github.com/Microsoft/pyright][pyright]] package instead. Adding this to my =requirements.txt= files:
|
||||||
|
|
||||||
#+begin_src conf :tangle no
|
#+begin_src conf :tangle no
|
||||||
pyright
|
pyright
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
While the [[https://github.com/emacs-lsp/lsp-pyright][pyright package]] works with LSP, this isn’t needed, as =eglot= works directly with it.
|
The [[https://github.com/emacs-lsp/lsp-pyright][pyright package]] works with LSP.
|
||||||
** eglot
|
|
||||||
The built-in Eglot package natively supports [[https://github.com/Microsoft/pyright][pyright]] via =eglot-server-programs= (no extra package needed):
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp :tangle no
|
||||||
(use-package eglot
|
(use-package lsp-pyright
|
||||||
:straight nil
|
:hook (python-mode . (lambda () (require 'lsp-pyright)))
|
||||||
:after python
|
:init (when (executable-find "python3")
|
||||||
:defer t
|
(setq lsp-pyright-python-executable-cmd "python3")))
|
||||||
:hook (python-mode . eglot-ensure)
|
|
||||||
:config
|
|
||||||
(add-to-list 'eglot-server-programs
|
|
||||||
'((python-mode python-ts-mode) . ("pyright-langserver" "--stdio"))))
|
|
||||||
#+end_src
|
#+end_src
|
||||||
|
*** Keybindings
|
||||||
Keybindings for the eglot-based LSP sub-menu, wired into the existing =python-mode= hydra:
|
Now that the [[file:ha-programming.org::*Language Server Protocol (LSP) Integration][LSP Integration]] is complete, we can stitch the two projects together, by calling =lsp=. I oscillate between automatically turning on LSP mode with every Python file, but I sometimes run into issues when starting, so I conditionally turn it on.
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defvar ha-python-lsp-title (font-icons 'faicon "python" :title "Python LSP"))
|
(defvar ha-python-lsp-title (font-icons 'faicon "python" :title "Python LSP"))
|
||||||
|
|
||||||
(use-package eglot
|
(defun ha-setup-python-lsp ()
|
||||||
:straight nil
|
"Configure the keybindings for LSP in Python."
|
||||||
:after major-mode-hydra
|
(interactive)
|
||||||
:config
|
|
||||||
(pretty-hydra-define python-lsp (:color blue :quit-key "q"
|
(pretty-hydra-define python-lsp (:color blue :quit-key "q"
|
||||||
:title ha-python-lsp-title)
|
:title ha-python-lsp-title)
|
||||||
("Server"
|
("Server"
|
||||||
(("D" eglot-shutdown "Disconnect")
|
(("D" lsp-disconnect "Disconnect")
|
||||||
("R" eglot-reconnect "Reconnect")
|
("R" lsp-workspace-restart "Restart")
|
||||||
("?" eglot-show-workspace-configuration "Describe"))
|
("S" lsp-workspace-shutdown "Shutdown")
|
||||||
|
("?" lsp-describe-session "Describe"))
|
||||||
"Refactoring"
|
"Refactoring"
|
||||||
(("a" eglot-code-actions "Code Actions")
|
(("a" lsp-execute-code-action "Code Actions")
|
||||||
("o" eglot-code-action-organize-imports "Organize Imports")
|
("o" lsp-organize-imports "Organize Imports")
|
||||||
("r" eglot-rename "Rename"))
|
("l" lsp-avy-lens "Avy Lens"))
|
||||||
"Navigation"
|
"Toggles"
|
||||||
(("." xref-find-definitions "Definition")
|
(("b" lsp-headerline-breadcrumb-mode "Breadcrumbs")
|
||||||
("/" xref-find-references "References")
|
("d" lsp-ui-doc-mode "Documentation Popups")
|
||||||
("t" eglot-find-typeDefinition "Type Definition")
|
("m" lsp-modeline-diagnostics-mode "Modeline Diagnostics")
|
||||||
("i" eglot-find-implementation "Implementation"))
|
("s" lsp-ui-sideline-mode "Sideline Mode"))
|
||||||
"Docs"
|
""
|
||||||
(("d" eldoc-doc-buffer "Eldoc Buffer")
|
(("t" lsp-toggle-on-type-formatting "Type Formatting")
|
||||||
("h" eglot-inlay-hints-mode "Inlay Hints")
|
("h" lsp-toggle-symbol-highlight "Symbol Highlighting")
|
||||||
("=" eglot-format-buffer "Format Buffer"))))
|
("L" lsp-toggle-trace-io "Log I/O"))))
|
||||||
|
|
||||||
|
(pretty-hydra-define+ python-goto (:quit-key "q")
|
||||||
|
("LSP"
|
||||||
|
(("g" lsp-find-definition "Definition")
|
||||||
|
("d" lsp-find-declaration "Declaration")
|
||||||
|
("r" lsp-find-references "References")
|
||||||
|
("t" lsp-find-type-definition "Type Definition"))
|
||||||
|
"Peek"
|
||||||
|
(("D" lsp-ui-peek-find-definitions "Definitions")
|
||||||
|
("I" lsp-ui-peek-find-implementation "Implementations")
|
||||||
|
("R" lsp-ui-peek-find-references "References")
|
||||||
|
("S" lsp-ui-peek-find-workspace-symbol "Symbols"))
|
||||||
|
"LSP+"
|
||||||
|
(("u" lsp-ui-imenu "UI Menu")
|
||||||
|
("i" lsp-find-implementation "Implementations")
|
||||||
|
("h" lsp-treemacs-call-hierarchy "Hierarchy")
|
||||||
|
("E" lsp-treemacs-errors-list "Error List"))))
|
||||||
|
|
||||||
|
(major-mode-hydra-define+ python-mode nil
|
||||||
|
("Server"
|
||||||
|
(("l" python-lsp/body "LSP..."))
|
||||||
|
"Edit"
|
||||||
|
(("=" lsp-format-region "Format"))
|
||||||
|
"Navigate"
|
||||||
|
(("A" lsp-workspace-folders-add "Add Folder")
|
||||||
|
("R" lsp-workspace-folders-remove "Remove Folder"))
|
||||||
|
"Docs"
|
||||||
|
(("D" lsp-describe-thing-at-point "Describe LSP Symbol")
|
||||||
|
("h" lsp-ui-doc-glance "Glance Help")
|
||||||
|
("H" lsp-document-highlight "Highlight"))))
|
||||||
|
|
||||||
|
(call-interactively 'lsp))
|
||||||
|
|
||||||
|
(use-package lsp-mode
|
||||||
|
:config
|
||||||
(major-mode-hydra-define+ python-mode (:quit-key "q")
|
(major-mode-hydra-define+ python-mode (:quit-key "q")
|
||||||
("Server"
|
("Server"
|
||||||
( ;; ("L" eglot-ensure "Start eglot") --> Not needed with eglot-ensure
|
(("L" ha-setup-python-lsp "Start LSP Server")))))
|
||||||
("l" python-lsp/body "LSP...")))))
|
|
||||||
|
;; ----------------------------------------------------------------------
|
||||||
|
;; Missing Symbols to be integrated?
|
||||||
|
;; "0" '("treemacs" . lsp-treemacs-symbols)
|
||||||
|
;; "/" '("complete" . completion-at-point)
|
||||||
|
;; "k" '("check code" . python-check)
|
||||||
|
;; "Fb" '("un-blacklist folder" . lsp-workspace-blacklist-remove)
|
||||||
|
;; "hs" '("signature help" . lsp-signature-activate)
|
||||||
|
;; "tT" '("toggle treemacs integration" . lsp-treemacs-sync-mode)
|
||||||
|
;; "ta" '("toggle modeline code actions" . lsp-modeline-code-actions-mode)
|
||||||
|
;; "th" '("toggle highlighting" . lsp-toggle-symbol-highlight)
|
||||||
|
;; "tl" '("toggle lenses" . lsp-lens-mode)
|
||||||
|
;; "ts" '("toggle signature" . lsp-toggle-signature-auto-activate)
|
||||||
#+end_src
|
#+end_src
|
||||||
* Project Configuration
|
* Project Configuration
|
||||||
I work with a lot of projects with my team where I need to /configure/ the project such that LSP and my Emacs setup works. Let's suppose I could point a function at a project directory, and have it /set it up/:
|
I work with a lot of projects with my team where I need to /configure/ the project such that LSP and my Emacs setup works. Let's suppose I could point a function at a project directory, and have it /set it up/:
|
||||||
|
|
@ -422,9 +464,17 @@ I work with a lot of projects with my team where I need to /configure/ the proje
|
||||||
(shell-command "pipconf --local")
|
(shell-command "pipconf --local")
|
||||||
(shell-command "pip install --upgrade pip"))
|
(shell-command "pip install --upgrade pip"))
|
||||||
|
|
||||||
(message "Configuring pip for eglot/pyright")
|
(message "Configuring pip for LSP")
|
||||||
(with-temp-file "requirements-dev.txt"
|
(with-temp-file "requirements-dev.txt"
|
||||||
(insert "pyright\n"))
|
(insert "python-lsp-server[all]\n")
|
||||||
|
|
||||||
|
;; Let's install these extra packages individually ...
|
||||||
|
(insert "pyls-flake8\n")
|
||||||
|
;; (insert "pylsp-mypy")
|
||||||
|
;; (insert "pyls-isort")
|
||||||
|
;; (insert "python-lsp-black")
|
||||||
|
;; (insert "pyls-memestra")
|
||||||
|
(insert "pylsp-rope\n"))
|
||||||
(shell-command "pip install -r requirements-dev.txt")))
|
(shell-command "pip install -r requirements-dev.txt")))
|
||||||
#+end_src
|
#+end_src
|
||||||
* Major Mode Hydra
|
* Major Mode Hydra
|
||||||
|
|
|
||||||
|
|
@ -229,6 +229,37 @@ The [[https://github.com/r0man/ruby-test-mode][ruby-test-mode]] project aims a r
|
||||||
"t A" '("test all" . ruby-test-run)
|
"t A" '("test all" . ruby-test-run)
|
||||||
"t a" '("retest" . ruby-test-rerun)))
|
"t a" '("retest" . ruby-test-rerun)))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
** Robe
|
||||||
|
The [[https://github.com/dgutov/robe][Robe project]] can be used instead of [[file:ha-programming.org::*Language Server Protocol (LSP) Integration][LSP]].
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(use-package robe
|
||||||
|
|
||||||
|
:config
|
||||||
|
(ha-local-leader 'ruby-mode-map
|
||||||
|
"w" '(:ignore t :which-key "robe")
|
||||||
|
"ws" '("start" . robe-start))
|
||||||
|
|
||||||
|
;; The following leader-like keys, are only available when I have
|
||||||
|
;; started LSP, and is an alternate to Command-m:
|
||||||
|
:general
|
||||||
|
(:states 'normal :keymaps 'robe-mode-map
|
||||||
|
", w r" '("restart" . lsp-reconnect)
|
||||||
|
", w b" '("events" . lsp-events-buffer)
|
||||||
|
", w e" '("errors" . lsp-stderr-buffer)
|
||||||
|
", w q" '("quit" . lsp-shutdown)
|
||||||
|
", w l" '("load file" . ruby-load-file)
|
||||||
|
|
||||||
|
", l r" '("rename" . lsp-rename)
|
||||||
|
", l f" '("format" . lsp-format)
|
||||||
|
", l a" '("actions" . lsp-code-actions)
|
||||||
|
", l i" '("imports" . lsp-code-action-organize-imports)
|
||||||
|
", l d" '("doc" . lsp-lookup-documentation)))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Do we want to load Robe /automatically/?
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(use-package robe :hook (ruby-mode . robe-mode))
|
||||||
|
#+end_src
|
||||||
** Bundler
|
** Bundler
|
||||||
The [[https://github.com/endofunky/bundler.el][Bundler project]] integrates [[https://bundler.io/][bundler]] to install a projects Gems.
|
The [[https://github.com/endofunky/bundler.el][Bundler project]] integrates [[https://bundler.io/][bundler]] to install a projects Gems.
|
||||||
|
|
||||||
|
|
@ -264,34 +295,6 @@ Seems that to understand and edit Cucumber /feature/ definitions, you need [[htt
|
||||||
** RSpec
|
** RSpec
|
||||||
https://github.com/pezra/rspec-mode
|
https://github.com/pezra/rspec-mode
|
||||||
* LSP
|
* LSP
|
||||||
The built-in Eglot package natively supports [[https://github.com/castwide/solargraph][Solargraph]] via =eglot-server-programs= (no extra package needed):
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
|
||||||
(use-package eglot
|
|
||||||
:straight nil
|
|
||||||
:after ruby-mode
|
|
||||||
:defer t
|
|
||||||
:hook (ruby-mode . eglot-ensure))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
Keybindings for the eglot-based LSP sub-menu, wired into the =ruby-mode= local leader:
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
|
||||||
(use-package eglot
|
|
||||||
:straight nil
|
|
||||||
:after ruby-mode
|
|
||||||
:config
|
|
||||||
(ha-local-leader 'ruby-mode-map
|
|
||||||
"w" '(:ignore t :which-key "eglot")
|
|
||||||
"wD" '("disconnect" . eglot-shutdown)
|
|
||||||
"wR" '("reconnect" . eglot-reconnect)
|
|
||||||
"w?" '("describe" . eglot-show-workspace-configuration)
|
|
||||||
"wa" '("code actions" . eglot-code-actions)
|
|
||||||
"wo" '("organize imports" . eglot-code-action-organize-imports)
|
|
||||||
"wr" '("rename" . eglot-rename)
|
|
||||||
"w=" '("format buffer" . eglot-format-buffer)))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
Need to install [[https://github.com/castwide/solargraph][Solargraph]] for the LSP server experience:
|
Need to install [[https://github.com/castwide/solargraph][Solargraph]] for the LSP server experience:
|
||||||
#+begin_src sh
|
#+begin_src sh
|
||||||
gem install solargraph
|
gem install solargraph
|
||||||
|
|
@ -300,7 +303,29 @@ Or add it to your =Gemfile=:
|
||||||
#+begin_src ruby
|
#+begin_src ruby
|
||||||
gem 'solargraph', group: :development
|
gem 'solargraph', group: :development
|
||||||
#+end_src
|
#+end_src
|
||||||
Once installed, [[*eglot][eglot]] finds and talks to =solargraph= automatically; no further Emacs configuration is needed.
|
* XRef Interface with GNU Global
|
||||||
|
The [[http://www.gnu.org/software/global/][GNU Global]] has the ability to generate a tags file for large, multi-project Ruby code bases.
|
||||||
|
|
||||||
|
First, issue these two:
|
||||||
|
#+begin_src sh :dir ~/work/gourmet
|
||||||
|
find . -name .git | while read DOTGIT
|
||||||
|
do
|
||||||
|
REPO=$(dirname $DOTGIT)
|
||||||
|
(cd $REPO && git pull origin master)
|
||||||
|
done
|
||||||
|
|
||||||
|
find . -name "*.rb" > gtags.files
|
||||||
|
gtags --gtagslabel=new-ctags --file gtags.files
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
And now we need the GNU Global for Emacs, we are using the most up-to-date version of [[https://github.com/leoliu/ggtags][ggtags]].
|
||||||
|
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(use-package ggtags
|
||||||
|
:hook ((ruby-mode . #'ggtags-mode)))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Careful observers will note that
|
||||||
* Technical Artifacts :noexport:
|
* Technical Artifacts :noexport:
|
||||||
|
|
||||||
Let's =provide= a name so we can =require= this file:
|
Let's =provide= a name so we can =require= this file:
|
||||||
|
|
|
||||||
|
|
@ -68,10 +68,24 @@ The [[https://github.com/brotzeit/rustic][rustic project]] incorporates [[https:
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package rustic
|
(use-package rustic
|
||||||
:config
|
:init
|
||||||
(setq rustic-lsp-client 'eglot
|
(setq rustic-lsp-client 'eglot
|
||||||
rustic-analyzer-command '("/opt/homebrew/bin/rust-analyzer")
|
rustic-analyzer-command '("/opt/homebrew/bin/rust-analyzer"))
|
||||||
rustic-format-on-save t))
|
;; :bind (:map rustic-mode-map
|
||||||
|
;; ("M-j" . lsp-ui-imenu)
|
||||||
|
;; ("M-?" . lsp-find-references)
|
||||||
|
;; ("C-c C-c l" . flycheck-list-errors)
|
||||||
|
;; ("C-c C-c a" . lsp-execute-code-action)
|
||||||
|
;; ("C-c C-c r" . lsp-rename)
|
||||||
|
;; ("C-c C-c q" . lsp-workspace-restart)
|
||||||
|
;; ("C-c C-c Q" . lsp-workspace-shutdown)
|
||||||
|
;; ("C-c C-c s" . lsp-rust-analyzer-status)
|
||||||
|
;; ("C-c C-c e" . lsp-rust-analyzer-expand-macro)
|
||||||
|
;; ("C-c C-c d" . dap-hydra)
|
||||||
|
;; ("C-c C-c h" . lsp-ui-doc-glance))
|
||||||
|
|
||||||
|
:config
|
||||||
|
(setq rustic-format-on-save t))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** The Playground
|
** The Playground
|
||||||
|
|
@ -88,6 +102,7 @@ The [[https://github.com/dryman/toml-mode.el][toml-mode]] adds syntax highlighti
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package toml-mode)
|
(use-package toml-mode)
|
||||||
#+end_src
|
#+end_src
|
||||||
|
* LSP
|
||||||
* Technical Artifacts :noexport:
|
* Technical Artifacts :noexport:
|
||||||
Let's =provide= a name so we can =require= this file:
|
Let's =provide= a name so we can =require= this file:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ The [[https://www.nongnu.org/geiser/][geiser project]] attempts to be the interf
|
||||||
Do we need a Scheme work for Org Babel? According to [[https://orgmode.org/worg/org-contrib/babel/languages/ob-doc-scheme.html][this document]], we just need to make sure we add the =:session= variable to start the REPL.
|
Do we need a Scheme work for Org Babel? According to [[https://orgmode.org/worg/org-contrib/babel/languages/ob-doc-scheme.html][this document]], we just need to make sure we add the =:session= variable to start the REPL.
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package ob-scheme
|
(use-package ob-scheme
|
||||||
:straight (:type built-in)
|
|
||||||
:config
|
:config
|
||||||
(add-to-list 'org-babel-load-languages '(scheme . t)))
|
(add-to-list 'org-babel-load-languages '(scheme . t)))
|
||||||
|
|
||||||
|
|
@ -122,7 +122,7 @@ While Racket, as a Scheme, should work with Geiser (below), let’s also get [[h
|
||||||
Can we get Racket working with Org?
|
Can we get Racket working with Org?
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package ob-racket
|
(use-package ob-racket
|
||||||
:straight (:host github :repo "DEADB17/ob-racket")
|
;; :vc (:url "https://github.com/DEADB17/ob-racket")
|
||||||
:after org
|
:after org
|
||||||
:config
|
:config
|
||||||
(add-to-list 'org-babel-load-languages '(racket . t)))
|
(add-to-list 'org-babel-load-languages '(racket . t)))
|
||||||
|
|
|
||||||
|
|
@ -28,32 +28,6 @@ A literate programming file for helping me program.
|
||||||
Configuration for programming interfaces and workflows that behave similarly.
|
Configuration for programming interfaces and workflows that behave similarly.
|
||||||
* General
|
* General
|
||||||
The following work for all programming languages.
|
The following work for all programming languages.
|
||||||
** Virtual Environments with Mise
|
|
||||||
The [[https://github.com/eki3z/mise.el][mise.el]] project overcomes the [[https://mise.jdx.dev/ide-integration.html#emacs][need for shims]] and other hoops when working with [[https://mise.jdx.dev/walkthrough.html][Mise]].
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(use-package mise
|
|
||||||
:straight (:type git :host github :repo "eki3z/mise.el")
|
|
||||||
:hook (after-init . global-mise-mode))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Now, per project, create a =mise.toml= that contains tools, environment variables, etc.
|
|
||||||
|
|
||||||
#+BEGIN_SRC toml
|
|
||||||
[env]
|
|
||||||
AWS_DEFAULT_REGION = "us-gov-east-1"
|
|
||||||
|
|
||||||
[tools]
|
|
||||||
python = {version='3', virtualenv='~/.venv/govcloud_lambda'}
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Or create the entries by using CLI commands:
|
|
||||||
|
|
||||||
#+BEGIN_SRC sh :tangle no
|
|
||||||
mise set AWS_DEFAULT_REGION=us-gov-east-1
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
|
|
||||||
** Virtual Environments with direnv
|
** Virtual Environments with direnv
|
||||||
Farm off commands into /virtual environments/:
|
Farm off commands into /virtual environments/:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
|
|
@ -84,7 +58,7 @@ For all programming languages, I would like to now default to absolute line numb
|
||||||
While Emacs has options for viewing and moving around code, sometimes, we could /collapse/ all functions, and then start to expand them one at a time. For this, we could enable the built-in [[https://www.emacswiki.org/emacs/HideShow][hide-show feature]]:
|
While Emacs has options for viewing and moving around code, sometimes, we could /collapse/ all functions, and then start to expand them one at a time. For this, we could enable the built-in [[https://www.emacswiki.org/emacs/HideShow][hide-show feature]]:
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp :tangle no
|
||||||
(use-package hide-show
|
(use-package hide-show
|
||||||
:straight (:type built-in)
|
|
||||||
:init
|
:init
|
||||||
(setq hs-hide-comments t
|
(setq hs-hide-comments t
|
||||||
hs-hide-initial-comment-block t
|
hs-hide-initial-comment-block t
|
||||||
|
|
@ -138,10 +112,10 @@ I appreciate calling =hi-lock-face-symbol-at-point= (or =highlight-symbol-at-poi
|
||||||
:config
|
:config
|
||||||
(setq ahs-idle-interval 0.1)
|
(setq ahs-idle-interval 0.1)
|
||||||
|
|
||||||
(set-face-attribute ahs-face nil :foreground 'unspecified :background 'unspecified
|
(set-face-attribute ahs-face nil :foreground nil :background nil
|
||||||
:weight 'ultra-bold :slant 'italic)
|
:weight 'ultra-bold :slant 'italic)
|
||||||
(set-face-attribute ahs-plugin-default-face nil :foreground 'unspecified
|
(set-face-attribute ahs-plugin-default-face nil :foreground nil
|
||||||
:background 'unspecified :weight 'bold :slant 'normal))
|
:background nil :weight 'bold :slant 'normal))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Instead of calling =global-auto-highlight-symbol-mode=, we should just hook it to the =prog-mode=:
|
Instead of calling =global-auto-highlight-symbol-mode=, we should just hook it to the =prog-mode=:
|
||||||
|
|
@ -238,7 +212,7 @@ Why use [[https://www.flycheck.org/][flycheck]] over the built-in =flymake=? Spe
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package flycheck
|
(use-package flycheck
|
||||||
:straight (:host github :repo "flycheck/flycheck")
|
;; :vc (:url "https://github.com/flycheck/flycheck")
|
||||||
:init
|
:init
|
||||||
(setq next-error-message-highlight t)
|
(setq next-error-message-highlight t)
|
||||||
:bind (:map flycheck-error-list-mode-map
|
:bind (:map flycheck-error-list-mode-map
|
||||||
|
|
@ -370,7 +344,7 @@ Normally, you would need to add all the projects to directory clones in =~/src=
|
||||||
https://github.com/tree-sitter/tree-sitter-go
|
https://github.com/tree-sitter/tree-sitter-go
|
||||||
https://github.com/tree-sitter/tree-sitter-javascript
|
https://github.com/tree-sitter/tree-sitter-javascript
|
||||||
https://github.com/tree-sitter/tree-sitter-templ
|
https://github.com/tree-sitter/tree-sitter-templ
|
||||||
https://github.com/tree-sitter-grammars/tree-sitter-yaml
|
https://github.com/ikatyang/tree-sitter-yaml
|
||||||
https://github.com/tree-sitter/tree-sitter-json
|
https://github.com/tree-sitter/tree-sitter-json
|
||||||
https://github.com/tree-sitter/tree-sitter-css
|
https://github.com/tree-sitter/tree-sitter-css
|
||||||
https://github.com/tree-sitter/tree-sitter-python
|
https://github.com/tree-sitter/tree-sitter-python
|
||||||
|
|
@ -422,11 +396,11 @@ In most cases,the =npm install= /usually/ works, but I may work on some sort of
|
||||||
#+end_src
|
#+end_src
|
||||||
At this point, we can now parse stuff using: =tree-sitter parse <source-code-file>=
|
At this point, we can now parse stuff using: =tree-sitter parse <source-code-file>=
|
||||||
**** Emacs Part
|
**** Emacs Part
|
||||||
Note that Emacs already has the ability to download and install grammars, so following instructions from Mickey Petersen’s essay on [[https://www.masteringemacs.org/article/combobulate-structured-movement-editing-treesitter][using Tree-sitter with Combobulate]]:
|
However, Emacs already has the ability to download and install grammars, so following instructions from Mickey Petersen’s essay on [[https://www.masteringemacs.org/article/combobulate-structured-movement-editing-treesitter][using Tree-sitter with Combobulate]]:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(when (treesit-available-p)
|
(when (treesit-available-p)
|
||||||
(use-package treesit
|
(use-package treesit
|
||||||
:straight (:type built-in)
|
|
||||||
:preface
|
:preface
|
||||||
(setq treesit-language-source-alist
|
(setq treesit-language-source-alist
|
||||||
'((bash "https://github.com/tree-sitter/tree-sitter-bash")
|
'((bash "https://github.com/tree-sitter/tree-sitter-bash")
|
||||||
|
|
@ -460,7 +434,7 @@ Note that Emacs already has the ability to download and install grammars, so fol
|
||||||
(toml "https://github.com/tree-sitter/tree-sitter-toml")
|
(toml "https://github.com/tree-sitter/tree-sitter-toml")
|
||||||
;; (tsx "https://github.com/tree-sitter/tree-sitter-typescript" "master" "tsx/src")
|
;; (tsx "https://github.com/tree-sitter/tree-sitter-typescript" "master" "tsx/src")
|
||||||
;; (typescript "https://github.com/tree-sitter/tree-sitter-typescript" "master" "typescript/src")
|
;; (typescript "https://github.com/tree-sitter/tree-sitter-typescript" "master" "typescript/src")
|
||||||
(yaml "https://github.com/tree-sitter-grammars/tree-sitter-yaml")))
|
(yaml "https://github.com/ikatyang/tree-sitter-yaml")))
|
||||||
|
|
||||||
(defun mp-setup-install-grammars ()
|
(defun mp-setup-install-grammars ()
|
||||||
"Install Tree-sitter grammars if they are absent."
|
"Install Tree-sitter grammars if they are absent."
|
||||||
|
|
@ -506,7 +480,7 @@ I like [[file:ha-programming-elisp.org::*Clever Parenthesis][Clever Parenthesis]
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(when (treesit-available-p)
|
(when (treesit-available-p)
|
||||||
(use-package combobulate
|
(use-package combobulate
|
||||||
:straight (:host github :repo "mickeynp/combobulate")
|
;; :vc (:url "https://github.com/mickeynp/combobulate")
|
||||||
:after treesit
|
:after treesit
|
||||||
:hook ((yaml-ts-mode . combobulate-mode)
|
:hook ((yaml-ts-mode . combobulate-mode)
|
||||||
;; (css-ts-mode . combobulate-mode)
|
;; (css-ts-mode . combobulate-mode)
|
||||||
|
|
@ -683,21 +657,86 @@ I have two different /jumping/ systems, the [[info:emacs#Xref][Xref interface]]
|
||||||
The [[https://microsoft.github.io/language-server-protocol/][LSP]] is a way to connect /editors/ (like Emacs) to /languages/ (like Lisp)… wait, no. While originally designed for VS Code and probably Python, we can abstract away [[https://github.com/davidhalter/jedi][Jedi]] and the [[http://tkf.github.io/emacs-jedi/latest/][Emacs integration to Jedi]] (and duplicate everything for Ruby, and Clojure, and…).
|
The [[https://microsoft.github.io/language-server-protocol/][LSP]] is a way to connect /editors/ (like Emacs) to /languages/ (like Lisp)… wait, no. While originally designed for VS Code and probably Python, we can abstract away [[https://github.com/davidhalter/jedi][Jedi]] and the [[http://tkf.github.io/emacs-jedi/latest/][Emacs integration to Jedi]] (and duplicate everything for Ruby, and Clojure, and…).
|
||||||
|
|
||||||
Emacs has two LSP projects, and while I have used [[LSP Mode]], but since I don’t have heavy IDE requirements, I am finding that [[eglot]] to be simpler.
|
Emacs has two LSP projects, and while I have used [[LSP Mode]], but since I don’t have heavy IDE requirements, I am finding that [[eglot]] to be simpler.
|
||||||
|
*** LSP
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package eglot
|
(use-package lsp-mode
|
||||||
:straight nil
|
:commands (lsp lsp-deferred)
|
||||||
:commands eglot-ensure
|
:init
|
||||||
|
;; Let's make lsp-doctor happy with these settings:
|
||||||
|
(setq gc-cons-threshold (* 100 1024 1024)
|
||||||
|
read-process-output-max (* 1024 1024)
|
||||||
|
company-idle-delay 0.0 ; Are thing fast enough to do this?
|
||||||
|
lsp-keymap-prefix "s-m")
|
||||||
|
|
||||||
:config
|
:config
|
||||||
(setq eglot-autoshutdown t))
|
(global-set-key (kbd "s-m") 'lsp)
|
||||||
|
(ha-local-leader :keymaps 'prog-mode-map
|
||||||
|
"w" '(:ignore t :which-key "lsp")
|
||||||
|
"l" '(:ignore t :which-key "lsp")
|
||||||
|
"ws" '("start" . lsp))
|
||||||
|
|
||||||
|
;; The following leader-like keys, are only available when I have
|
||||||
|
;; started LSP, and is an alternate to Command-m:
|
||||||
|
:general
|
||||||
|
(:states 'normal :keymaps 'lsp-mode-map
|
||||||
|
", w r" '("restart" . lsp-reconnect)
|
||||||
|
", w b" '("events" . lsp-events-buffer)
|
||||||
|
", w e" '("errors" . lsp-stderr-buffer)
|
||||||
|
", w q" '("quit" . lsp-shutdown)
|
||||||
|
", w Q" '("quit all" . lsp-shutdown-all)
|
||||||
|
|
||||||
|
", l r" '("rename" . lsp-rename)
|
||||||
|
", l f" '("format" . lsp-format)
|
||||||
|
", l a" '("actions" . lsp-code-actions)
|
||||||
|
", l i" '("imports" . lsp-code-action-organize-imports)
|
||||||
|
", l d" '("doc" . lsp-lookup-documentation))
|
||||||
|
|
||||||
|
:hook ((lsp-mode . lsp-enable-which-key-integration)))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
I will want to start adding commands under my =,= mode-specific key sequence leader, but in the meantime, all LSP-related keybindings are available under ~⌘-m~. See [[https://emacs-lsp.github.io/lsp-mode/page/keybindings/][this page]] for the default keybindings.
|
||||||
|
|
||||||
The [[https://github.com/flycheck/flycheck-eglot][flycheck-eglot]] package bridges eglot diagnostics into flycheck, so existing checkers and keybindings continue to work unchanged:
|
Using the [[https://github.com/seagle0128/doom-modeline][Doom Modeline]] to add notifications:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package flycheck-eglot
|
(use-package doom-modeline
|
||||||
:after (flycheck eglot)
|
|
||||||
:config
|
:config
|
||||||
(global-flycheck-eglot-mode 1))
|
(setq doom-modeline-lsp t
|
||||||
|
doom-modeline-env-version t))
|
||||||
|
#+end_src
|
||||||
|
**** UI
|
||||||
|
The [[https://github.com/emacs-lsp/lsp-ui][lsp-ui]] project offers much of the display and interface to LSP. Seems to make the screen cluttered.
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(use-package lsp-ui
|
||||||
|
:commands lsp-ui-mode
|
||||||
|
:config
|
||||||
|
(setq lsp-ui-sideline-ignore-duplicate t
|
||||||
|
lsp-ui-sideline-show-hover t
|
||||||
|
lsp-ui-sideline-show-diagnostics t)
|
||||||
|
:hook (lsp-mode . lsp-ui-mode))
|
||||||
|
#+end_src
|
||||||
|
*** Company Completion
|
||||||
|
The [[https://github.com/tigersoldier/company-lsp][company-lsp]] offers a [[http://company-mode.github.io/][company]] completion backend for [[https://github.com/emacs-lsp/lsp-mode][lsp-mode]]:
|
||||||
|
|
||||||
|
#+begin_src emacs-lisp :tangle no
|
||||||
|
(use-package company-lsp
|
||||||
|
:config
|
||||||
|
(push 'company-lsp company-backends))
|
||||||
|
#+end_src
|
||||||
|
To options that might be interesting:
|
||||||
|
- =company-lsp-async=: When set to non-nil, fetch completion candidates asynchronously.
|
||||||
|
- =company-lsp-enable-snippet=: Set it to non-nil if you want to enable snippet expansion on completion. Set it to nil to disable this feature.
|
||||||
|
|
||||||
|
*** LSP iMenu
|
||||||
|
The [[https://github.com/emacs-lsp/lsp-ui/blob/master/lsp-ui-imenu.el][lsp-imenu]] project offers a =lsp-ui-imenu= function for jumping to functions:
|
||||||
|
|
||||||
|
#+begin_src emacs-lisp :tangle no
|
||||||
|
(use-package lsp-ui-imenu
|
||||||
|
|
||||||
|
:after lsp-ui
|
||||||
|
:config
|
||||||
|
(ha-local-leader :keymaps 'prog-mode-map
|
||||||
|
"g" '(:ignore t :which-key "goto")
|
||||||
|
"g m" '("imenu" . lsp-ui-imenu))
|
||||||
|
(add-hook 'lsp-after-open-hook 'lsp-enable-imenu))
|
||||||
#+end_src
|
#+end_src
|
||||||
** General Code Editing
|
** General Code Editing
|
||||||
*** iEdit
|
*** iEdit
|
||||||
|
|
@ -1011,8 +1050,9 @@ So many configuration files to track:
|
||||||
("\\.setup.*\\'" . conf-space-mode)))
|
("\\.setup.*\\'" . conf-space-mode)))
|
||||||
#+end_src
|
#+end_src
|
||||||
** JSON
|
** JSON
|
||||||
I’m interested in the [[https://github.com/emacs-tree-sitter/tree-sitter-langs][tree-sitter]] extensions for JSON, e.g. =json-ts-mode=, that comes with Emacs 29, but instead of /searching/ for particular data in a JSON buffer, what about whittling that data with [[https://jqlang.github.io/jq/][jq]]?
|
While interested in the [[https://github.com/emacs-tree-sitter/tree-sitter-langs][tree-sitter]] extensions for JSON, e.g. =json-ts-mode=, that comes with Emacs 29, I’ll deal with what is bundled now.
|
||||||
|
|
||||||
|
However, what about taking a buffer of JSON data, and whittling it down with [[https://jqlang.github.io/jq/][jq]]?
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defun ha-json-buffer-to-jq (query)
|
(defun ha-json-buffer-to-jq (query)
|
||||||
"Runs JSON buffer with QUERY through an external `jq' program.
|
"Runs JSON buffer with QUERY through an external `jq' program.
|
||||||
|
|
@ -1049,7 +1089,7 @@ This means, that some data like:
|
||||||
"data": {
|
"data": {
|
||||||
"name": "Create And Wait for Service Image",
|
"name": "Create And Wait for Service Image",
|
||||||
"description": "Creates a new Service Image using IMaaS",
|
"description": "Creates a new Service Image using IMaaS",
|
||||||
"long_description": "This job creates a new service image with name ... blah, blah, blah",
|
"long_description": "This job creates a new yawxway service image with name yawxway-howard.abrams-test and docker-dev-artifactory.workday.com/dev/yawxway-service:latest docker url in development folder",
|
||||||
"job_id": "5e077245-0f4a-4dc9-b473-ce3ec0b811ba",
|
"job_id": "5e077245-0f4a-4dc9-b473-ce3ec0b811ba",
|
||||||
"state": "success",
|
"state": "success",
|
||||||
"progress": "100",
|
"progress": "100",
|
||||||
|
|
@ -1093,14 +1133,12 @@ I can type, ~, j~ and then type =.data.timeout.seconds= and end up with:
|
||||||
300
|
300
|
||||||
#+end_src
|
#+end_src
|
||||||
** Markdown
|
** Markdown
|
||||||
Most projects’ =README= files and other documentation use [[https://jblevins.org/projects/markdown-mode/][markdown-mode]]. Note that the /preview/ uses =multimarkdown=, which should be /pre-installed/. For instance:
|
Most project =README= files and other documentation use [[https://jblevins.org/projects/markdown-mode/][markdown-mode]]. Note that the /preview/ is based on =multimarkdown=, when needs to be /pre-installed/, for instance:
|
||||||
|
|
||||||
#+begin_src sh
|
#+begin_src sh
|
||||||
brew install multimarkdown
|
brew install multimarkdown
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Also, I like Markdown to look like a word processor, similarly to my org files:
|
Also, I like Markdown is look like a word processor, similarly to my org files:
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package markdown-mode
|
(use-package markdown-mode
|
||||||
:mode ((rx ".md" string-end) . gfm-mode)
|
:mode ((rx ".md" string-end) . gfm-mode)
|
||||||
|
|
@ -1125,8 +1163,51 @@ Also, I like Markdown to look like a word processor, similarly to my org files:
|
||||||
|
|
||||||
Note that the markdown-specific commands use the ~C-c C-c~ and ~C-c C-s~ prefixes.
|
Note that the markdown-specific commands use the ~C-c C-c~ and ~C-c C-s~ prefixes.
|
||||||
|
|
||||||
*** Rendering with Pandoc
|
Let’s make sure that [[https://www.flycheck.org/en/latest/languages.html#markdown][markdown]] is proper using [[https://pypi.org/project/pymarkdownlnt/][PyMarkdown]]. First, get the script installed globally:
|
||||||
We call both the =markdown-command= and the =markdown-open-command= variables to render (and preview) a Markdown file (~C-c C-c o~), and calls the following scripts (which in turn, call =pandoc= as I depend on this for other org-related features):
|
|
||||||
|
#+begin_src sh
|
||||||
|
pip install pymarkdown
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
And then we can use it. For some reason, the =pymarkdown= (which I need to use from work) doesn’t seem to be part of the version of Flycheck available on Melpa, so…
|
||||||
|
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(use-package markdown-mode
|
||||||
|
:after flycheck
|
||||||
|
:config
|
||||||
|
(setq flycheck-markdown-pymarkdown-config
|
||||||
|
(expand-file-name ".pymarkdown.yml" (getenv "HOME")))
|
||||||
|
(flycheck-may-enable-checker 'markdown-pymarkdown))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Ugh
|
||||||
|
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(flycheck-def-config-file-var flycheck-markdown-pymarkdown-config
|
||||||
|
markdown-pymarkdown nil
|
||||||
|
:package-version '(flycheck . "34"))
|
||||||
|
|
||||||
|
(flycheck-define-checker markdown-pymarkdown
|
||||||
|
"Markdown checker using PyMarkdown.
|
||||||
|
|
||||||
|
See URL `https://pypi.org/project/pymarkdownlnt/'."
|
||||||
|
:command ("pymarkdown"
|
||||||
|
(config-file "--config" flycheck-markdown-pymarkdown-config)
|
||||||
|
"scan"
|
||||||
|
source)
|
||||||
|
:error-patterns
|
||||||
|
((error line-start
|
||||||
|
(file-name) ":" line
|
||||||
|
(? ":" column) ": " (id (one-or-more alnum))
|
||||||
|
": " (message) line-end))
|
||||||
|
:error-filter
|
||||||
|
(lambda (errors)
|
||||||
|
(flycheck-sanitize-errors
|
||||||
|
(flycheck-remove-error-file-names "(string)" errors)))
|
||||||
|
:modes (markdown-mode gfm-mode))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Both the =markdown-command= and the =markdown-open-command= variables are called to render (and preview) a Markdown file (~C-c C-c o~), and calls the following scripts (which in turn, call =pandoc= as I depend on this for other org-related features):
|
||||||
|
|
||||||
#+begin_src sh :tangle ~/bin/markdown :shebang "#!/usr/bin/env bash" :tangle-mode u+x
|
#+begin_src sh :tangle ~/bin/markdown :shebang "#!/usr/bin/env bash" :tangle-mode u+x
|
||||||
pandoc --to=html --from=gfm $*
|
pandoc --to=html --from=gfm $*
|
||||||
|
|
@ -1145,250 +1226,6 @@ We call both the =markdown-command= and the =markdown-open-command= variables to
|
||||||
fi
|
fi
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
*** PyMarkdown
|
|
||||||
My teams have standardized on “linting” markdown files using [[https://pypi.org/project/pymarkdownlnt/][PyMarkdown]], specifying a =.pymarkdown.yml= to configure what is allowed. For instance:
|
|
||||||
|
|
||||||
#+BEGIN_SRC yaml :tangle no
|
|
||||||
---
|
|
||||||
# Enable front-matter extensions, as Hugo needs them.
|
|
||||||
extensions:
|
|
||||||
front-matter:
|
|
||||||
enabled: true
|
|
||||||
plugins:
|
|
||||||
md013:
|
|
||||||
enabled: true
|
|
||||||
line_length: 80
|
|
||||||
heading_line_length: 80
|
|
||||||
code_block_line_length: 160
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Flycheck ships the =markdown-pymarkdown= checker itself, so all it wants is the configuration file.
|
|
||||||
|
|
||||||
Note that PyMarkdown does /not/ look for =.pymarkdown.yml= on its own — it only reads a configuration passed with =--config=. Naming the file without a directory hands the search to flycheck, whose =flycheck-locate-config-file-functions= walks the ancestor directories with =locate-dominating-file= and then falls back to my home directory. A repository that defines its own rules therefore gets them, and everything else gets my defaults:
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
|
||||||
(use-package flycheck
|
|
||||||
:config
|
|
||||||
(setq flycheck-markdown-pymarkdown-config ".pymarkdown.yml"))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
*** Remark
|
|
||||||
The [[https://github.com/remarkjs/remark][Remark]] project can transform markdown with plugins, evaluating and changing through its pipeline. This means I can apply [[file:ha-org.org::*Writegood][Writegood]] intelligently to a Markdown file, ignoring code blocks, etc. Prose is the /only/ job it has here, and [[*Remark and Flycheck][flycheck chains the two together]].
|
|
||||||
|
|
||||||
The =remark= executable itself lives globally, since flycheck calls it by name:
|
|
||||||
|
|
||||||
#+begin_src sh
|
|
||||||
npm install -g remark-cli
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
The /plugins/, however, can not. The configuration file below uses ESM =import= statements, and Node resolves those specifiers relative to /importing the file/, as globally installed packages are not on the path. The =~/.config/remark= directory has to be a self-contained package with its own =node_modules=, with a =package.json= file.
|
|
||||||
|
|
||||||
Note the ="type": "module"= entry belongs here too, otherwise Node parses each =.js= file as CommonJS, fails, and re-parses it as a module while complaining:
|
|
||||||
|
|
||||||
#+begin_src js :tangle ~/.config/remark/package.json :mkdirp yes
|
|
||||||
{
|
|
||||||
"name": "remark-config",
|
|
||||||
"private": true,
|
|
||||||
"type": "module",
|
|
||||||
"dependencies": {
|
|
||||||
"remark-frontmatter": "^5.0.0",
|
|
||||||
"remark-lint-write-good": "^1.2.0",
|
|
||||||
"unist-util-visit": "^5.1.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
After tangling that the above file, we install the plugins /into the =.config/remark= directory/:
|
|
||||||
|
|
||||||
#+begin_src sh :dir ~/.config/remark
|
|
||||||
npm install
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
Let’s create a /code-stripping/ plugin that removes all text but the prose of a Markdown document. We can then validate the prose with =write-good= on the results:
|
|
||||||
|
|
||||||
#+begin_src js :tangle ~/.config/remark/ignore-code.js :mkdirp yes
|
|
||||||
import { visit } from 'unist-util-visit';
|
|
||||||
|
|
||||||
// Bare URLs and autolinks land in `text` nodes, where write-good
|
|
||||||
// grades the path segments as if they were prose. A `[label](url)`
|
|
||||||
// needs no help here, since the url of a link node never reaches
|
|
||||||
// the prose checker anyway.
|
|
||||||
const URL_PATTERN = /\b(?:https?:\/\/|www\.)[^\s<>()[\]{}'"]+/gi;
|
|
||||||
|
|
||||||
/** Spaces of equal length, so line numbers and offsets survive. */
|
|
||||||
const blank = (text) => ' '.repeat(text.length);
|
|
||||||
|
|
||||||
export default function remarkIgnoreCode() {
|
|
||||||
return (tree) => {
|
|
||||||
visit(tree, ['code', 'inlineCode'], (node) => {
|
|
||||||
// Replaces code block and backtick content with spaces of
|
|
||||||
// equal length. This silences linters while preserving line
|
|
||||||
// numbers and offsets.
|
|
||||||
node.value = blank(node.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
visit(tree, 'text', (node) => {
|
|
||||||
node.value = node.value.replace(URL_PATTERN, (match) => {
|
|
||||||
// Trailing punctuation stays, so sentence boundaries are
|
|
||||||
// not merged.
|
|
||||||
const url = match.replace(/[.,;:!?]+$/, '');
|
|
||||||
return blank(url) + match.slice(url.length);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
A path like =/a/very/long/= reads as weasel words to =write-good=, so blanking the URL is what keeps =very= and =obviously= out of the report. The =[label](url)= form is left alone deliberately: its label /is/ prose worth grading, and its url was never visited. Trailing punctuation survives so that =…/foo.= still ends a sentence, rather than running it into the next one.
|
|
||||||
|
|
||||||
**** Configuring the pipeline
|
|
||||||
The other plugins have been made, so Remark’s configuration file (=.config/remark/remarkrc.js=) pulls them in, and creates an ordered plugin pipeline. Note the absence of any =remark-preset-lint-*= entry: those presets check structure, which is PyMarkdown’s half of the work, and they disagree with it often enough to be worth leaving out. Left in, they wanted every ATX heading rewritten as setext:
|
|
||||||
|
|
||||||
#+begin_src js :tangle ~/.config/remark/.remarkrc.js
|
|
||||||
import remarkFrontmatter from 'remark-frontmatter';
|
|
||||||
import remarkWriteGood from 'remark-lint-write-good';
|
|
||||||
import remarkIgnoreCode from './ignore-code.js';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
plugins: [
|
|
||||||
// 1. Recognise YAML front matter, so its metadata is not
|
|
||||||
// read as prose
|
|
||||||
remarkFrontmatter,
|
|
||||||
|
|
||||||
// 2. Erase backticks/code blocks before the prose check
|
|
||||||
remarkIgnoreCode,
|
|
||||||
|
|
||||||
// 3. Check English writing quality, the sole
|
|
||||||
// job remark has, since PyMarkdown already covers
|
|
||||||
// structure and formatting.
|
|
||||||
remarkWriteGood
|
|
||||||
]
|
|
||||||
};
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
The order matters, since plugins run as transformers in sequence: =remarkIgnoreCode= has to blank the code out before =remarkWriteGood= gets a look at what is left.
|
|
||||||
|
|
||||||
And =remarkFrontmatter= earns its place, rather than being tidiness. Without it, a =---= delimiter usually turns the metadata into a setext heading, which =write-good= happens to skip — but only by luck. Put a blank line in the front matter and the first key becomes an ordinary paragraph, at which point =title:= gets graded as prose:
|
|
||||||
|
|
||||||
#+begin_src markdown :tangle no
|
|
||||||
---
|
|
||||||
title: This value is very obviously scanned as prose
|
|
||||||
|
|
||||||
description: short
|
|
||||||
---
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
Worth knowing what =write-good= looks at, since it only visits =paragraph= nodes: headings are never checked, and blockquotes are skipped on purpose, on the reasoning that quoted words are somebody else’s.
|
|
||||||
|
|
||||||
Verify on the command line using the =rc-path= parameter:
|
|
||||||
|
|
||||||
#+begin_src sh :tangle no
|
|
||||||
remark --rc-path ~/.config/remark/.remarkrc.js README.md
|
|
||||||
#+end_src
|
|
||||||
**** Remark and Flycheck
|
|
||||||
Remark’s default report groups messages under a file name header and gives each one a =line:column-line:column= range, which takes more regular expression than I care to write. Instead, let’s hand it a [[https://github.com/vfile/vfile-reporter][vfile reporter]] of our own that prints one message per line in a shape flycheck parses with a single pattern:
|
|
||||||
|
|
||||||
#+begin_src js :tangle ~/.config/remark/flycheck-reporter.js
|
|
||||||
/**
|
|
||||||
* A vfile reporter that emits one message per line in a format that is
|
|
||||||
* trivial for Emacs' flycheck to parse with a single regular expression:
|
|
||||||
*
|
|
||||||
* LINE:COLUMN:SEVERITY:[ruleId] reason
|
|
||||||
*
|
|
||||||
* The file name is deliberately omitted; flycheck lints one buffer at a
|
|
||||||
* time and attributes patterns without a file name to that buffer, which
|
|
||||||
* avoids the mismatch between the buffer's directory and remark's cwd.
|
|
||||||
*/
|
|
||||||
export default function flycheckReporter(files) {
|
|
||||||
const lines = [];
|
|
||||||
|
|
||||||
for (const file of [files].flat()) {
|
|
||||||
for (const message of file.messages) {
|
|
||||||
// `fatal` is true for errors, false for warnings, null/undefined for info.
|
|
||||||
const severity =
|
|
||||||
message.fatal === true
|
|
||||||
? 'error'
|
|
||||||
: message.fatal === false
|
|
||||||
? 'warning'
|
|
||||||
: 'info';
|
|
||||||
const rule = message.ruleId || message.source || 'remark';
|
|
||||||
// Newlines would break the one-message-per-line contract.
|
|
||||||
const reason = String(message.reason).replace(/\s*\n\s*/g, ' ');
|
|
||||||
|
|
||||||
lines.push(
|
|
||||||
`${message.line || 1}:${message.column || 1}:${severity}:[${rule}] ${reason}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines.join('\n');
|
|
||||||
}
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
Which we can check by hand:
|
|
||||||
|
|
||||||
#+begin_src sh
|
|
||||||
remark --no-color --no-stdout \
|
|
||||||
--rc-path ~/.config/remark/.remarkrc.js \
|
|
||||||
--report ~/.config/remark/flycheck-reporter.js README.md
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
Two flags there earn their keep. Remark writes the /transformed/ document to standard output by default, and since =remarkIgnoreCode= replaces code with whitespace, that output is a mangled copy of the file. The =--no-stdout= parameter throws it away, guaranteeing a stray =--output= can never overwrite the real file with it. Also =--no-color= keeps ANSI escapes out of what flycheck has to parse.
|
|
||||||
|
|
||||||
Note that both paths are absolute. Remark resolves a /reporter/ relative to the current directory, but a configuration file relative to the file being linted, so neither would be found from an arbitrary project directory.
|
|
||||||
|
|
||||||
Note that =flycheck-define-checker= only /defines/ the checker; the =add-to-list= is what puts it in rotation. The trailing =t= appends rather than prepends, which is the whole trick to chaining: flycheck picks the /first/ checker in =flycheck-checkers= that claims the buffer, so leaving =markdown-remark= behind the built-in =markdown-pymarkdown= lets PyMarkdown go first and remark follow it.
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
|
||||||
(use-package flycheck
|
|
||||||
:config
|
|
||||||
(flycheck-def-config-file-var flycheck-markdown-remark-config markdown-remark
|
|
||||||
(expand-file-name ".remarkrc.js" "~/.config/remark")
|
|
||||||
:package-version '(flycheck . "34"))
|
|
||||||
|
|
||||||
(defcustom flycheck-markdown-remark-reporter
|
|
||||||
(expand-file-name "flycheck-reporter.js" "~/.config/remark")
|
|
||||||
"Path to the vfile reporter that formats remark messages for flycheck."
|
|
||||||
:type 'string
|
|
||||||
:group 'flycheck)
|
|
||||||
|
|
||||||
(flycheck-define-checker markdown-remark
|
|
||||||
"A Markdown prose checker using remark and write-good.
|
|
||||||
|
|
||||||
See URL `https://github.com/remarkjs/remark'."
|
|
||||||
:command ("remark"
|
|
||||||
"--no-color"
|
|
||||||
"--no-stdout"
|
|
||||||
(config-file "--rc-path" flycheck-markdown-remark-config)
|
|
||||||
"--report" (eval flycheck-markdown-remark-reporter)
|
|
||||||
source)
|
|
||||||
:error-patterns
|
|
||||||
((error line-start line ":" column ":error:["
|
|
||||||
(id (one-or-more (not (any "]")))) "] " (message) line-end)
|
|
||||||
(warning line-start line ":" column ":warning:["
|
|
||||||
(id (one-or-more (not (any "]")))) "] " (message) line-end)
|
|
||||||
(info line-start line ":" column ":info:["
|
|
||||||
(id (one-or-more (not (any "]")))) "] " (message) line-end))
|
|
||||||
:modes (markdown-mode gfm-mode))
|
|
||||||
|
|
||||||
(add-to-list 'flycheck-checkers 'markdown-remark t)
|
|
||||||
|
|
||||||
;; Structure first, then prose, in one report.
|
|
||||||
(flycheck-add-next-checker 'markdown-pymarkdown 'markdown-remark)
|
|
||||||
|
|
||||||
;; Then terminology/consistency, since flycheck's built-in textlint
|
|
||||||
;; checker already declares markdown-mode/gfm-mode and picks the
|
|
||||||
;; @textlint/markdown plugin for them -- no new checker needed.
|
|
||||||
(flycheck-add-next-checker 'markdown-remark 'textlint t))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
Naming =markdown-remark= as a plain symbol, rather than a =(level . checker)= cons cell, means the prose check runs whatever PyMarkdown found. Were it written =(warning . markdown-remark)=, a single formatting error would swallow the prose report along with it.
|
|
||||||
|
|
||||||
Since remark now runs =write-good= over the prose (and more carefully, as it skips the code), the [[file:ha-org.org::*Writegood][write-good]] checker no longer lists =markdown-mode= among its modes, and the two do not double up.
|
|
||||||
|
|
||||||
[[file:ha-org.org::*Textlint][Textlint]] runs last in the chain. Its bundled Markdown plugin parses the AST itself, so it skips fenced code without any =ignore-code.js=-style help, and =flycheck-textlint-plugin-alist= already maps =markdown-mode=/=gfm-mode= to =@textlint/markdown=.
|
|
||||||
|
|
||||||
*** Markdown and Polymode
|
|
||||||
Using [[https://polymode.github.io/][polymode]], let’s add syntax coloring to Markdown code blocks similar to what we do with Org:
|
Using [[https://polymode.github.io/][polymode]], let’s add syntax coloring to Markdown code blocks similar to what we do with Org:
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
|
|
@ -1459,12 +1296,12 @@ While I don't like writing them, I can't get away from them. Check out the goodi
|
||||||
While filename extensions work fine most of the time, I don't like to pre-pend =.sh= to the shell scripts I write, and instead, would like to associate =shell-mode= with all files in a =bin= directory:
|
While filename extensions work fine most of the time, I don't like to pre-pend =.sh= to the shell scripts I write, and instead, would like to associate =shell-mode= with all files in a =bin= directory:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package sh-mode
|
(use-package sh-mode
|
||||||
:straight (:type built-in)
|
|
||||||
:mode (rx (or (seq ".sh" eol)
|
:mode (rx (or (seq ".sh" eol)
|
||||||
"/bin/"))
|
"/bin/"))
|
||||||
:init
|
:init
|
||||||
(setq sh-basic-offset 4
|
(setq sh-basic-offset 2
|
||||||
sh-indentation 4)
|
sh-indentation 2)
|
||||||
:config
|
:config
|
||||||
(ha-auto-insert-file (rx (or (seq ".sh" eol)
|
(ha-auto-insert-file (rx (or (seq ".sh" eol)
|
||||||
"/bin/"))
|
"/bin/"))
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ A literate configuration for accessing remote systems.
|
||||||
[[https://www.emacswiki.org/emacs/TrampMode][Tramp]] allows almost all Emacs features to execute on a remote system.
|
[[https://www.emacswiki.org/emacs/TrampMode][Tramp]] allows almost all Emacs features to execute on a remote system.
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package tramp
|
(use-package tramp
|
||||||
:straight (:type built-in)
|
|
||||||
|
|
||||||
:config
|
:config
|
||||||
;; Use remote PATH on tramp (handy for eshell).
|
;; Use remote PATH on tramp (handy for eshell).
|
||||||
|
|
@ -42,7 +42,7 @@ A literate configuration for accessing remote systems.
|
||||||
Will Schenk has [[https://willschenk.com/articles/2020/tramp_tricks/][a simple extension]] to allow editing of files /inside/ a Docker container:
|
Will Schenk has [[https://willschenk.com/articles/2020/tramp_tricks/][a simple extension]] to allow editing of files /inside/ a Docker container:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package tramp
|
(use-package tramp
|
||||||
:straight (:type built-in)
|
|
||||||
:config
|
:config
|
||||||
(push '("docker" . ((tramp-login-program "docker")
|
(push '("docker" . ((tramp-login-program "docker")
|
||||||
(tramp-login-args (("exec" "-it") ("%h") ("/bin/sh")))
|
(tramp-login-args (("exec" "-it") ("%h") ("/bin/sh")))
|
||||||
|
|
@ -50,19 +50,16 @@ Will Schenk has [[https://willschenk.com/articles/2020/tramp_tricks/][a simple e
|
||||||
(tramp-remote-shell-args ("-i") ("-c"))))
|
(tramp-remote-shell-args ("-i") ("-c"))))
|
||||||
tramp-methods)
|
tramp-methods)
|
||||||
|
|
||||||
(defun ha-tramp-completion-docker (orig-fun &rest args)
|
(defadvice tramp-completion-handle-file-name-all-completions
|
||||||
"Advice for `tramp-completion-handle-file-name-all-completions`.
|
(around dotemacs-completion-docker activate)
|
||||||
Return active Docker container names when completing for `/docker:`."
|
"(tramp-completion-handle-file-name-all-completions \"\" \"/docker:\" returns
|
||||||
(if (equal (nth 1 args) "/docker:")
|
a list of active Docker container names, followed by colons."
|
||||||
|
(if (equal (ad-get-arg 1) "/docker:")
|
||||||
(let* ((command "docker ps --format '{{.Names}}:'")
|
(let* ((command "docker ps --format '{{.Names}}:'")
|
||||||
(dockernames-raw (shell-command-to-string command))
|
(dockernames-raw (shell-command-to-string command))
|
||||||
(dockernames (split-string dockernames-raw "\n" t)))
|
(dockernames (split-string dockernames-raw "\n")))
|
||||||
dockernames)
|
(setq ad-return-value dockernames))
|
||||||
;; Call the original function
|
ad-do-it)))
|
||||||
(apply orig-fun args)))
|
|
||||||
|
|
||||||
(advice-add 'tramp-completion-handle-file-name-all-completions
|
|
||||||
:around #'ha-tramp-completion-docker))
|
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Keep in mind you need to /name/ your Docker session, with the =—name= option. I actually do more docker work on remote systems (as Docker seems to make my fans levitate my laptop over the desk). Granted, the =URL= is a bit lengthy, for instance:
|
Keep in mind you need to /name/ your Docker session, with the =—name= option. I actually do more docker work on remote systems (as Docker seems to make my fans levitate my laptop over the desk). Granted, the =URL= is a bit lengthy, for instance:
|
||||||
|
|
@ -75,7 +72,7 @@ Which means, I need to put it as a link in an org file.
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package tramp-sh
|
(use-package tramp-sh
|
||||||
:after tramp
|
:after tramp
|
||||||
:straight (:type built-in)
|
|
||||||
:custom (tramp-use-ssh-controlmaster-options nil))
|
:custom (tramp-use-ssh-controlmaster-options nil))
|
||||||
#+end_src
|
#+end_src
|
||||||
* Remote Terminals
|
* Remote Terminals
|
||||||
|
|
@ -157,37 +154,18 @@ VTerm has an issue (at least for me) with ~M-Backspace~ not deleting the previou
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
The advantage of running terminals in Emacs is the ability to copy text without a mouse. For that, hit ~C-c C-t~ to enter a special copy-mode. If I go into this mode, I might as well also go into normal mode to move the cursor. To exit the copy-mode (and copy the selected text to the clipboard), hit ~Return~.
|
The advantage of running terminals in Emacs is the ability to copy text without a mouse. For that, hit ~C-c C-t~ to enter a special copy-mode. If I go into this mode, I might as well also go into normal mode to move the cursor. To exit the copy-mode (and copy the selected text to the clipboard), hit ~Return~.
|
||||||
|
|
||||||
An odd use case I have: working in a Terminal session in one buffer window, and reading instructions in another. I want to copy commands from the document into the session using Avy to mark the command. Normally, I would use ~s-g~ to call [[file:ha-config.org::*Jump with Avy][avy-goto-char-timer]], and typing the /beginning of the command/, but before /jumping/, I press ~N~ to copy the line into the clipboard (kill-ring), and then /paste/ that into the Terminal using ~C-y~. One-too-many keystrokes.
|
|
||||||
|
|
||||||
Now, I can type: ~s-G~ (with the shift), select the line with Avy, and type ~N~ to have it copy/paste into the buffer:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(use-package vterm
|
|
||||||
:config
|
|
||||||
(defun ha-vterm-avy-paste-line ()
|
|
||||||
"docstring"
|
|
||||||
(interactive)
|
|
||||||
(let ((buf (current-buffer)))
|
|
||||||
(call-interactively 'avy-goto-char-timer)
|
|
||||||
|
|
||||||
(when (equal buf (current-buffer))
|
|
||||||
(vterm-yank))))
|
|
||||||
|
|
||||||
(general-def 'vterm-mode-map "s-G" 'ha-vterm-avy-paste-line))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
** Eat
|
** Eat
|
||||||
While not as fast as [[https://github.com/akermu/emacs-libvterm][vterm]], the [[https://codeberg.org/akib/emacs-eat][Emulate a Terminal]] project (eat) is fast enough, and doesn’t require a dedicate library that requires re-compilation. While offering [[https://elpa.nongnu.org/nongnu-devel/doc/eat.html][online documentation]], I’m glad for an [[info:eat#Top][Info version]].
|
While not as fast as [[https://github.com/akermu/emacs-libvterm][vterm]], the [[https://codeberg.org/akib/emacs-eat][Emulate a Terminal]] project (eat) is fast enough, and doesn’t require a dedicate library that requires re-compilation. While offering [[https://elpa.nongnu.org/nongnu-devel/doc/eat.html][online documentation]], I’m glad for an [[info:eat#Top][Info version]].
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp :tangle no
|
#+BEGIN_SRC emacs-lisp :tangle no
|
||||||
(use-package eat
|
(use-package eat
|
||||||
:straight (:host codeberg :repo "akib/emacs-eat"
|
;; :straight (:host codeberg :repo "akib/emacs-eat"
|
||||||
:files ("*.el" ("term" "term/*.el") "*.texi"
|
;; :files ("*.el" ("term" "term/*.el") "*.texi"
|
||||||
"*.ti" ("terminfo/e" "terminfo/efo/e/*")
|
;; "*.ti" ("terminfo/e" "terminfo/efo/e/*")
|
||||||
("terminfo/65" "terminfo/65/*")
|
;; ("terminfo/65" "terminfo/65/*")
|
||||||
("integration" "integration/*")
|
;; ("integration" "integration/*")
|
||||||
(:exclude ".dir-locals.el" "*-tests.el")))
|
;; (:exclude ".dir-locals.el" "*-tests.el")))
|
||||||
|
;; :vc (:url "https://codeberg.org/akib/emacs-eat")
|
||||||
:commands (eat eat-make eat-project)
|
:commands (eat eat-make eat-project)
|
||||||
:bind (:map eat-semi-char-mode-map
|
:bind (:map eat-semi-char-mode-map
|
||||||
("C-c C-t" . ha-eat-narrow-to-shell-prompt-dwim))
|
("C-c C-t" . ha-eat-narrow-to-shell-prompt-dwim))
|
||||||
|
|
@ -237,6 +215,14 @@ Let's begin by defining some variables used for communication between the functi
|
||||||
See =ha-ssh-add-favorite-host= for easily adding to this list.")
|
See =ha-ssh-add-favorite-host= for easily adding to this list.")
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
|
Also, let's make it easy for me to change my default shell:
|
||||||
|
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(defvar ha-shell "bash" ;; Eat works better with Bash/Zsh
|
||||||
|
;; (string-trim (shell-command-to-string "type -p fish"))
|
||||||
|
"The executable to the shell I want to use locally.")
|
||||||
|
#+end_src
|
||||||
|
|
||||||
** Terminal Abstractions
|
** Terminal Abstractions
|
||||||
Could I abstract the different ways I start terminals in Emacs? The =ha-ssh-term= starts either a [[VTerm]]
|
Could I abstract the different ways I start terminals in Emacs? The =ha-ssh-term= starts either a [[VTerm]]
|
||||||
or [[Eat]] terminals, depending on what is available. This replaces (wraps) the default [[help:make-term][make-term]].
|
or [[Eat]] terminals, depending on what is available. This replaces (wraps) the default [[help:make-term][make-term]].
|
||||||
|
|
@ -247,11 +233,11 @@ or [[Eat]] terminals, depending on what is available. This replaces (wraps) the
|
||||||
The PROGRAM, if non-nil, is executed, otherwise, this is `ha-shell'.
|
The PROGRAM, if non-nil, is executed, otherwise, this is `ha-shell'.
|
||||||
STARTFILE is the initial text given to the PROGRAM, and the
|
STARTFILE is the initial text given to the PROGRAM, and the
|
||||||
SWITCHES are the command line options."
|
SWITCHES are the command line options."
|
||||||
|
(unless program (setq program ha-shell))
|
||||||
(cond
|
(cond
|
||||||
((fboundp 'vterm) (progn (vterm name)
|
((fboundp 'vterm) (progn (vterm name)
|
||||||
(when program
|
(vterm-send-string (append program switches))
|
||||||
(vterm-send-string (append program switches))
|
(vterm-send-return)))
|
||||||
(vterm-send-return))))
|
|
||||||
((fboundp 'eat) (progn (switch-to-buffer
|
((fboundp 'eat) (progn (switch-to-buffer
|
||||||
(apply 'eat-make (append (list name program startfile)
|
(apply 'eat-make (append (list name program startfile)
|
||||||
switches)))
|
switches)))
|
||||||
|
|
@ -315,7 +301,7 @@ For the sake of my demonstrations, I use =ha-shell= to start a terminal with a p
|
||||||
default-directory)))
|
default-directory)))
|
||||||
(buf-name (format "*%s*" win-name)))
|
(buf-name (format "*%s*" win-name)))
|
||||||
(setq ha-latest-ssh-window-name buf-name)
|
(setq ha-latest-ssh-window-name buf-name)
|
||||||
(ha-make-term win-name))) ; Lisp-2 FTW!?
|
(ha-make-term win-name ha-shell))) ; Lisp-2 FTW!?
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Now that Emacs can /host/ a Terminal shell, I would like to /programmatically/ send commands to the running terminal, e.g. =(ha-shell-send "ls *.py")= I would really like to be able to send and execute a command in a terminal from a script.
|
Now that Emacs can /host/ a Terminal shell, I would like to /programmatically/ send commands to the running terminal, e.g. =(ha-shell-send "ls *.py")= I would really like to be able to send and execute a command in a terminal from a script.
|
||||||
|
|
@ -340,6 +326,8 @@ Now that Emacs can /host/ a Terminal shell, I would like to /programmatically/ s
|
||||||
(t (progn
|
(t (progn
|
||||||
(insert command)
|
(insert command)
|
||||||
(term-send-input))))))
|
(term-send-input))))))
|
||||||
|
|
||||||
|
(ha-shell-send "exit")
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Let's have a quick way to bugger out of the terminal:
|
Let's have a quick way to bugger out of the terminal:
|
||||||
|
|
|
||||||
27
ha-theme.org
27
ha-theme.org
|
|
@ -311,6 +311,7 @@ Can we *see* our colors?
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
* Dark Theme
|
* Dark Theme
|
||||||
|
|
||||||
Let’s make a /theme/:
|
Let’s make a /theme/:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
|
@ -322,20 +323,15 @@ Let’s make a /theme/:
|
||||||
'hamacs
|
'hamacs
|
||||||
`(default ((t (:foreground ,default-fg :background ,default-bg))))
|
`(default ((t (:foreground ,default-fg :background ,default-bg))))
|
||||||
`(fringe ((t :background ,default-bg)))
|
`(fringe ((t :background ,default-bg)))
|
||||||
|
`(tab-bar ((t :foreground ,default-fg :background ,default-bg)))
|
||||||
|
`(tab-line ((t :foreground ,default-fg :background ,default-bg)))
|
||||||
`(window-divider ((t :foreground "black")))
|
`(window-divider ((t :foreground "black")))
|
||||||
`(cursor ((t (:foreground ,gray-10 :background ,cursor))))
|
`(cursor ((t (:foreground ,gray-10 :background ,cursor))))
|
||||||
`(region ((t (:background ,region))))
|
`(region ((t (:background ,region))))
|
||||||
`(hl-line ((t (:background ,gray-20))))
|
|
||||||
|
|
||||||
`(mode-line ((t (:background ,active :foreground "white"))))
|
`(mode-line ((t (:background ,active :foreground "white"))))
|
||||||
`(mode-line-active ((t (:background ,active))))
|
`(mode-line-active ((t (:background ,active))))
|
||||||
`(mode-line-inactive ((t (:background ,inactive))))
|
`(mode-line-inactive ((t (:background ,inactive))))
|
||||||
`(which-func ((t (:foreground ,orange-lt))))
|
|
||||||
|
|
||||||
`(tab-bar ((t :foreground ,default-fg :background ,inactive :inherit variable-pitch)))
|
|
||||||
`(tab-line ((t :foreground ,default-fg :background ,inactive)))
|
|
||||||
`(tab-bar-tab ((t (:inherit variable-pitch :background ,active :weight bold))))
|
|
||||||
`(tab-bar-tab-inactive ((t (:inherit variable-pitch :background ,inactive :weight normal))))
|
|
||||||
|
|
||||||
`(doom-modeline-buffer-path ((t (:foreground ,almond))))
|
`(doom-modeline-buffer-path ((t (:foreground ,almond))))
|
||||||
`(doom-modeline-buffer-file ((t (:foreground "white" :weight bold))))
|
`(doom-modeline-buffer-file ((t (:foreground "white" :weight bold))))
|
||||||
|
|
@ -458,24 +454,7 @@ Let’s make a /theme/:
|
||||||
`(elfeed-search-tag-face ((t (:foreground ,slate))))))
|
`(elfeed-search-tag-face ((t (:foreground ,slate))))))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
[[file:ha-theme-results.png]]
|
[[file:ha-theme-results.png]]
|
||||||
|
|
||||||
To update a feature live with all attributes, do something like:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp :tangle no
|
|
||||||
(set-face-attribute 'tab-bar-tab-inactive nil
|
|
||||||
:inherit variable-pitch
|
|
||||||
:background "#462200")
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Or, you can use the helper functions to change a feature directly:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp :tangle no
|
|
||||||
(set-face-background 'tab-bar-tab-inactive "#462200")
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
|
|
||||||
* Technical Artifacts :noexport:
|
* Technical Artifacts :noexport:
|
||||||
|
|
||||||
Let's =provide= a name so we can =require= this file:
|
Let's =provide= a name so we can =require= this file:
|
||||||
|
|
|
||||||
373
hammerspoon.org
373
hammerspoon.org
|
|
@ -1,373 +0,0 @@
|
||||||
#+title: Hammerspoon Configuration
|
|
||||||
#+author: Howard X. Abrams
|
|
||||||
#+date: 2025-11-24
|
|
||||||
#+filetags: emacs hamacs
|
|
||||||
#+lastmod: [2026-08-12 Wed]
|
|
||||||
|
|
||||||
A literate programming file for configuring Hammerspoon.
|
|
||||||
|
|
||||||
* Introduction
|
|
||||||
Ever since I got a Mac, I’ve used various tools to /script/ it. With my limited number of applications, my UI needs are simple (I mean, what more do you need once Emacs is in full screen). That said, I’ve been using [[https://hammerspoon.org][Hammerspoon]] for those few needs. While I refer to the [[https://www.hammerspoon.org/docs/index.html][standard documentation]], I often steal snippets of code from others.
|
|
||||||
|
|
||||||
Simple to use. Use =hs.execute= to run a script, =ha.alert.show= to print a small message on the screen, and for something longer:
|
|
||||||
#+BEGIN_SRC lua :tangle no
|
|
||||||
hs.notify.new({title="Hammerspoon", informativeText="Hello World"}):send()
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
** The Command Line
|
|
||||||
The =hs= binary talks to a running Hammerspoon over a Mach port, and that port only exists while the =hs.ipc= module is loaded. Without this line, the tool answers every request with /can’t access Hammerspoon message port/, however well it is installed:
|
|
||||||
|
|
||||||
#+BEGIN_SRC lua
|
|
||||||
require("hs.ipc")
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
For instance, using the [[*Zoom][Zoom spoon]], I can toggle the mute button from a script:
|
|
||||||
|
|
||||||
#+BEGIN_SRC sh :tangle no
|
|
||||||
hs -c "spoon.Zoom:toggleMute()" # your Zoom spoon, from anywhere
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Or callable from Emacs.
|
|
||||||
|
|
||||||
Other ideas:
|
|
||||||
|
|
||||||
1. Tail the Hammerspoon console in a terminal with =hs -C=, which shows anything called with =printf=.
|
|
||||||
|
|
||||||
2. While I already built out [[https://howardism.org/Technical/Emacs/beep-for-emacs.html][a notification system]], I could use Hammerspoon to replace =beep= CLI:
|
|
||||||
#+BEGIN_SRC sh
|
|
||||||
make build; hs -c 'hs.notify.new({title="Build", informativeText="done"}):send()'
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
3. Query Hammerspoon to query the system:
|
|
||||||
#+BEGIN_SRC bash :results replace raw output
|
|
||||||
hs -c 'return hs.wifi.currentNetwork()'
|
|
||||||
#+END_SRC
|
|
||||||
This should return =iTerm2=, or =Emacs= when running from my literate config:
|
|
||||||
#+BEGIN_SRC sh :results replace raw output
|
|
||||||
hs -c "return hs.application.frontmostApplication():name()"
|
|
||||||
#+END_SRC
|
|
||||||
Or screwing with my monitors:
|
|
||||||
#+BEGIN_SRC sh :results replace raw output
|
|
||||||
hs -c "return hs.screen.mainScreen():name()"
|
|
||||||
#+END_SRC
|
|
||||||
Or the volume level as a float value:
|
|
||||||
#+BEGIN_SRC sh :results replace raw output
|
|
||||||
hs -c "return hs.audiodevice.defaultOutputDevice():volume()"
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Flags worth knowing, from the binary's usage text:
|
|
||||||
* =-i= interactive REPL
|
|
||||||
* =-q= quiet (only the result, ideal for scripting)
|
|
||||||
* =-c= repeatable
|
|
||||||
* =-A= auto-launch Hammerspoon if not running, and a bare file path to run a =.lua= file.
|
|
||||||
|
|
||||||
* Shortcut Keybindings
|
|
||||||
I’ve created left and right ~Meh~ keys on my Moonlander keyboard:
|
|
||||||
- Left Meh Key: ~C-M-S-s~ (Control Option/Meta Command Shift)
|
|
||||||
- Right Meh Key: ~C-M-S~ (Control Option/Meta Shift)
|
|
||||||
|
|
||||||
To create specific key bindings for starting applications, I can:
|
|
||||||
#+BEGIN_SRC lua
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
-- Launcher replaces iCanHazShortcuts
|
|
||||||
|
|
||||||
hs.hotkey.bind({"alt", "ctrl", "shift"}, "T", function()
|
|
||||||
hs.application.launchOrFocus("iTerm")
|
|
||||||
end)
|
|
||||||
|
|
||||||
hs.hotkey.bind({"alt", "ctrl", "shift"}, "S", function()
|
|
||||||
hs.application.launchOrFocus("Slack")
|
|
||||||
end)
|
|
||||||
|
|
||||||
hs.hotkey.bind({"alt", "ctrl", "shift"}, "F", function()
|
|
||||||
hs.application.launchOrFocus("Vivaldi")
|
|
||||||
end)
|
|
||||||
|
|
||||||
hs.hotkey.bind({"alt", "ctrl", "shift"}, "C", function()
|
|
||||||
-- hs.osascript.applescriptFromFile("~/bin/chrome.scr")
|
|
||||||
hs.execute("~/bin/chrome.scr")
|
|
||||||
end)
|
|
||||||
|
|
||||||
hs.hotkey.bind({"alt", "ctrl", "shift"}, "B", function()
|
|
||||||
hs.application.launchOrFocus("Microsoft Outlook")
|
|
||||||
end)
|
|
||||||
|
|
||||||
hs.hotkey.bind({"alt", "ctrl", "shift"}, "Z", function()
|
|
||||||
hs.application.launchOrFocus("zoom.us")
|
|
||||||
end)
|
|
||||||
|
|
||||||
hs.hotkey.bind({"alt", "ctrl", "shift"}, "Q", function()
|
|
||||||
hs.application.launchOrFocus("KeepassXC")
|
|
||||||
end)
|
|
||||||
|
|
||||||
hs.hotkey.bind({"alt", "ctrl", "shift"}, "G", function()
|
|
||||||
hs.application.launchOrFocus("Ghostty")
|
|
||||||
end)
|
|
||||||
|
|
||||||
hs.hotkey.bind({"alt", "ctrl", "shift"}, "W", function()
|
|
||||||
hs.application.launchOrFocus("VLC")
|
|
||||||
end)
|
|
||||||
|
|
||||||
hs.hotkey.bind({"alt", "ctrl", "shift"}, "E", function()
|
|
||||||
hs.execute("FOR_WORK=yes open -a /Applications/Emacs.app")
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- Special Emacs Guys
|
|
||||||
-- Right Meh key:
|
|
||||||
hs.hotkey.bind({"alt", "ctrl", "shift"}, "X", function()
|
|
||||||
hs.execute("~/bin/emacs-capture")
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- Left Meh key:
|
|
||||||
hs.hotkey.bind({"cmd", "alt", "ctrl", "shift"}, "X", function()
|
|
||||||
hs.execute("~/bin/emacs-capture-clock")
|
|
||||||
end)
|
|
||||||
|
|
||||||
hs.hotkey.bind({"alt", "ctrl", "shift"}, "M", function()
|
|
||||||
hs.execute("~/bin/emacs-capture-meeting")
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- Current music system is actually in Emacs:
|
|
||||||
hs.hotkey.bind({"alt", "ctrl", "shift"}, "R", function()
|
|
||||||
hs.execute("/opt/homebrew/bin/emacsclient -s work -e '(ready-player-toggle-play-stop)'")
|
|
||||||
end)
|
|
||||||
#+END_SRC
|
|
||||||
* Zoom
|
|
||||||
/Spoons/ are library extensions to Hammerspoon: usally small Lua scripts, like =init.lua=, stored in the =Spoons= subdirectory. Installable from a =git clone=, typically. To use the Zoom spoon, clone it:
|
|
||||||
|
|
||||||
#+BEGIN_SRC sh :dir ~/.hammerspoon/Spoons :tangle no :results silent
|
|
||||||
git clone https://github.com/jpf/Zoom.spoon.git
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
I noticed that this library does most of its work by calling Zoom’s menus, and with the latest version of Zoom, they changed the /case/ of the menu, meaning that I needed to create a pull request.
|
|
||||||
|
|
||||||
From this [[https://developer.okta.com/blog/2020/10/22/set-up-a-mute-indicator-light-for-zoom-with-hammerspoon][nice essay]], we create a menu bar item that shows the status, as well as allowing me to click it to toggle:
|
|
||||||
|
|
||||||
#+BEGIN_SRC lua
|
|
||||||
zoomStatusMenuBarItem = hs.menubar.new(true)
|
|
||||||
zoomStatusMenuBarItem:setClickCallback(function()
|
|
||||||
spoon.Zoom:toggleMute()
|
|
||||||
end)
|
|
||||||
|
|
||||||
updateZoomStatus = function(event)
|
|
||||||
hs.printf("updateZoomStatus(%s)", event)
|
|
||||||
if (event == "from-running-to-meeting") then
|
|
||||||
zoomStatusMenuBarItem:returnToMenuBar()
|
|
||||||
elseif (event == "muted") then
|
|
||||||
zoomStatusMenuBarItem:setTitle("🔴")
|
|
||||||
elseif (event == "unmuted") then
|
|
||||||
zoomStatusMenuBarItem:setTitle("🟢")
|
|
||||||
elseif (event == "from-meeting-to-running") then
|
|
||||||
zoomStatusMenuBarItem:removeFromMenuBar()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Now we can load, instantiate it, as well as create a callback loop to call =updateZoomStatus=:
|
|
||||||
|
|
||||||
#+BEGIN_SRC lua
|
|
||||||
hs.loadSpoon("Zoom")
|
|
||||||
spoon.Zoom:setStatusCallback(updateZoomStatus)
|
|
||||||
spoon.Zoom:start()
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
And bind a key to the mute ability that works good for both keyboards:
|
|
||||||
|
|
||||||
#+BEGIN_SRC lua
|
|
||||||
hs.hotkey.bind({"cmd", "alt", "ctrl", "shift"}, "M", function()
|
|
||||||
spoon.Zoom:toggleMute()
|
|
||||||
end)
|
|
||||||
|
|
||||||
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "M", function()
|
|
||||||
spoon.Zoom:toggleMute()
|
|
||||||
end)
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Lovely bit of code and shows the true power of Hammerspoon to fix the various app issues.
|
|
||||||
* Automatically Adjust Volume
|
|
||||||
Leaving my home office and hopping on the train can be socially awkward when my laptop suddenly screams, so if I leave my home, I just readjust the volume and turn it off:
|
|
||||||
|
|
||||||
#+BEGIN_SRC lua
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
-- When leaving house, turn off the volume:
|
|
||||||
|
|
||||||
wifiWatcher = nil
|
|
||||||
homeSSID = "Intertubes"
|
|
||||||
workSSID = "workdaysecure"
|
|
||||||
hotspotSSID = "Pixie Dust"
|
|
||||||
lastSSID = hs.wifi.currentNetwork()
|
|
||||||
|
|
||||||
function ssidChangedCallback()
|
|
||||||
newSSID = hs.wifi.currentNetwork()
|
|
||||||
|
|
||||||
if newSSID == homeSSID and lastSSID ~= homeSSID then
|
|
||||||
-- joined our home WiFi network
|
|
||||||
hs.audiodevice.defaultOutputDevice():setVolume(25)
|
|
||||||
elseif newSSID == workSSID and lastSSID ~= workSSID then
|
|
||||||
-- joined our work WiFi network
|
|
||||||
hs.audiodevice.defaultOutputDevice():setVolume(0)
|
|
||||||
elseif newSSID == hotspotSSID and lastSSID ~= hotspotSSID then
|
|
||||||
-- joined our hotspot WiFi network
|
|
||||||
hs.audiodevice.defaultOutputDevice():setVolume(0)
|
|
||||||
elseif newSSID ~= homeSSID and lastSSID == homeSSID then
|
|
||||||
-- departed our home WiFi network
|
|
||||||
hs.audiodevice.defaultOutputDevice():setVolume(0)
|
|
||||||
end
|
|
||||||
|
|
||||||
lastSSID = newSSID
|
|
||||||
end
|
|
||||||
|
|
||||||
wifiWatcher = hs.wifi.watcher.new(ssidChangedCallback)
|
|
||||||
wifiWatcher:start()
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Why yes, I’m /looking for features/ to use Hammerspoon.
|
|
||||||
* Clocking out a Task
|
|
||||||
I want to use Org’s task clocking ability, but I often forget to /clock out/. This code allows me to clock out whenever my computer goes to sleep … which works well when closing the laptop lid:
|
|
||||||
|
|
||||||
#+BEGIN_SRC lua
|
|
||||||
function sleepWatch(eventType)
|
|
||||||
if (eventType == hs.caffeinate.watcher.systemWillSleep) then
|
|
||||||
if hs.application.find("Emacs") then
|
|
||||||
hs.execute("/opt/homebrew/bin/emacsclient --socket work --eval '(ha-clock-out)'")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
sleepWatcher = hs.caffeinate.watcher.new(sleepWatch)
|
|
||||||
sleepWatcher:start()
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Discovered a subtle bug, solving it meant assigning the =watcher= to a variable, rather than starting it and dropping the value. For instance, if I call the =start= this way:
|
|
||||||
|
|
||||||
#+BEGIN_SRC lua :tangle no
|
|
||||||
hs.caffeinate.watcher.new(sleepWatch):start()
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Since nothing else refers to that object once the line finishes, Lua may collect it, and a collected watcher stops delivering events without a word about it.
|
|
||||||
|
|
||||||
Note if I want to do something for Wake:
|
|
||||||
|
|
||||||
#+BEGIN_SRC lua :tangle no
|
|
||||||
if (eventType == hs.caffeinate.watcher.systemDidWake) then
|
|
||||||
...
|
|
||||||
end
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Oh, and here is a helper for [[file:laptop_keyboard.kbd][kanata]] to put the display to sleep:
|
|
||||||
|
|
||||||
#+BEGIN_SRC lua
|
|
||||||
hs.hotkey.bind({}, "F16", function()
|
|
||||||
hs.execute("pmset displaysleepnow")
|
|
||||||
end)
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
* Monitors
|
|
||||||
My company gave me a nice monitor … maybe a little too nice, as I don’t care to shift my neck to the extreme sides (serious first-world problem), so here I can /center/ a window Keeping it my field of view:
|
|
||||||
|
|
||||||
#+BEGIN_SRC lua
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
-- Centering a window on the large monitors at Work:
|
|
||||||
|
|
||||||
function centerWindow()
|
|
||||||
local win = hs.window.focusedWindow()
|
|
||||||
if not win then return end -- Safety check in case no window is focused
|
|
||||||
|
|
||||||
local app = win:application()
|
|
||||||
local screen = hs.screen.find("DELL P3424WE")
|
|
||||||
local maxBounds = screen:frame() -- This gets the absolute coordinates of that specific monitor
|
|
||||||
|
|
||||||
-- 2. Define your desired size
|
|
||||||
local desiredW = 2200
|
|
||||||
local desiredH = 1470
|
|
||||||
|
|
||||||
if app then
|
|
||||||
local name = app:name()
|
|
||||||
if name == "Slack" or name == "iTerm2" then
|
|
||||||
desiredH = 1200
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- 3. Calculate relative position based on the external monitor's bounds
|
|
||||||
-- This centers the window perfectly on the target screen
|
|
||||||
local f = {
|
|
||||||
x = maxBounds.x + ((maxBounds.w - desiredW) / 2),
|
|
||||||
y = maxBounds.y, -- + ((maxBounds.h - desiredH) / 2),
|
|
||||||
w = desiredW,
|
|
||||||
h = desiredH
|
|
||||||
}
|
|
||||||
|
|
||||||
win:setFrame(f)
|
|
||||||
hs.alert.show("Centered Window")
|
|
||||||
end
|
|
||||||
|
|
||||||
hs.hotkey.bind({"cmd", "alt", "ctrl", "shift"}, "Y", centerWindow)
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Similarly, I sometimes want a window as large as possible, but flushed to the edges of the monitor (a maximized frame, not macOS' native /full screen/):
|
|
||||||
|
|
||||||
#+BEGIN_SRC lua
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
-- Making a window as large as possible on its current monitor:
|
|
||||||
|
|
||||||
function fullWindow()
|
|
||||||
local win = hs.window.focusedWindow()
|
|
||||||
if not win then return end -- Safety check in case no window is focused
|
|
||||||
|
|
||||||
local screen = win:screen()
|
|
||||||
win:setFrame(screen:frame())
|
|
||||||
hs.alert.show("Full Window")
|
|
||||||
end
|
|
||||||
|
|
||||||
hs.hotkey.bind({"cmd", "alt", "ctrl", "shift"}, "U", fullWindow)
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
* Auto Reload Configuration
|
|
||||||
Whenever my configuration file is altered (or with a ~Meh-R~ key), I reload the configuration:
|
|
||||||
|
|
||||||
#+BEGIN_SRC lua
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
-- Automatically reload the Hammerspoon configuration
|
|
||||||
|
|
||||||
hs.hotkey.bind({"cmd", "alt", "ctrl", "shift"}, "R", function()
|
|
||||||
hs.reload()
|
|
||||||
hs.alert.show("Reloaded Hammerspoon Config")
|
|
||||||
end)
|
|
||||||
|
|
||||||
function reloadConfig(files)
|
|
||||||
doReload = false
|
|
||||||
for _,file in pairs(files) do
|
|
||||||
if file:sub(-4) == ".lua" then
|
|
||||||
doReload = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if doReload then
|
|
||||||
hs.reload()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
configWatcher = hs.pathwatcher.new(os.getenv("HOME") .. "/.hammerspoon/", reloadConfig)
|
|
||||||
configWatcher:start()
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Every watcher in this file now lives in a variable of its own, for the reason described under [[*Clocking out a Task][clocking out]]. This one failing is the quietest of the three, as a dead configuration watcher looks exactly like a file that saved without incident.
|
|
||||||
|
|
||||||
* Technical Artifacts :noexport:
|
|
||||||
#+BEGIN_SRC lua
|
|
||||||
hs.alert.show("Hammerspoon Configuration")
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
|
|
||||||
#+DESCRIPTION: Literate Hammerspoon configuration
|
|
||||||
|
|
||||||
#+PROPERTY: header-args:sh :tangle no
|
|
||||||
#+PROPERTY: header-args:lua :tangle ~/.hammerspoon/init.lua
|
|
||||||
#+PROPERTY: header-args :results none :eval no-export :comments no mkdirp yes
|
|
||||||
|
|
||||||
#+OPTIONS: num:nil toc:nil todo:nil tasks:nil tags:nil date:nil
|
|
||||||
#+OPTIONS: skip:nil author:nil email:nil creator:nil timestamp:nil
|
|
||||||
#+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
|
|
||||||
|
|
||||||
# Local Variables:
|
|
||||||
# jinx-local-words: "Lua"
|
|
||||||
# End:
|
|
||||||
67
initialize
67
initialize
|
|
@ -23,15 +23,6 @@ gpg --homedir ~/.emacs.d/elpa/gnupg --receive-keys 066DAFCB81E42C40
|
||||||
cat > "$HAMACS_DEST/early-init.el" <<EOF
|
cat > "$HAMACS_DEST/early-init.el" <<EOF
|
||||||
;;; early-init.el --- Hamacs Early Init -*- lexical-binding: t; -*-
|
;;; early-init.el --- Hamacs Early Init -*- lexical-binding: t; -*-
|
||||||
;;
|
;;
|
||||||
;; ▄████████ ▄▄▄▄███▄▄▄▄ ▄████████ ▄████████ ▄████████
|
|
||||||
;; ███ ███ ▄██▀▀▀███▀▀▀██▄ ███ ███ ███ ███ ███ ███
|
|
||||||
;; ███ █▀ ███ ███ ███ ███ ███ ███ █▀ ███ █▀
|
|
||||||
;; ▄███▄▄▄ ███ ███ ███ ███ ███ ███ ███
|
|
||||||
;; ▀▀███▀▀▀ ███ ███ ███▀███████████ ███ ▀███████████
|
|
||||||
;; ███ █▄ ███ ███ ███ ███ ███ ███ █▄ ███
|
|
||||||
;; ███ ███ ███ ███ ███ ███ ███ ███ ███ ▄█ ███
|
|
||||||
;; ██████████ ▀█ ███ █▀ ███ █▀ ████████▀ ▄████████▀
|
|
||||||
;;
|
|
||||||
;;; Commentary:
|
;;; Commentary:
|
||||||
;;
|
;;
|
||||||
;; This is my early Emacs configuration file. See init.el for the real
|
;; This is my early Emacs configuration file. See init.el for the real
|
||||||
|
|
@ -78,14 +69,6 @@ echo "Created $HAMACS_DEST/early-init.el"
|
||||||
cat > "$HAMACS_DEST/init.el" <<EOF
|
cat > "$HAMACS_DEST/init.el" <<EOF
|
||||||
;;; init.el --- Hamacs Init -*- lexical-binding: t; -*-
|
;;; init.el --- Hamacs Init -*- lexical-binding: t; -*-
|
||||||
;;
|
;;
|
||||||
;; :::::::::: :::: :::: ::: :::::::: ::::::::
|
|
||||||
;; :+: +:+:+: :+:+:+ :+: :+: :+: :+: :+: :+:
|
|
||||||
;; +:+ +:+ +:+:+ +:+ +:+ +:+ +:+ +:+
|
|
||||||
;; +#++:++# +#+ +:+ +#+ +#++:++#++: +#+ +#++:++#++
|
|
||||||
;; +#+ +#+ +#+ +#+ +#+ +#+ +#+
|
|
||||||
;; #+# #+# #+# #+# #+# #+# #+# #+# #+#
|
|
||||||
;; ########## ### ### ### ### ######## ########
|
|
||||||
;;
|
|
||||||
;;; Commentary:
|
;;; Commentary:
|
||||||
;;
|
;;
|
||||||
;; This is my Emacs Bootloader. Simply put, I initialize the package
|
;; This is my Emacs Bootloader. Simply put, I initialize the package
|
||||||
|
|
@ -96,8 +79,23 @@ cat > "$HAMACS_DEST/init.el" <<EOF
|
||||||
|
|
||||||
(defvar hamacs-source-dir "$HAMACS_DIR" "Where we be.")
|
(defvar hamacs-source-dir "$HAMACS_DIR" "Where we be.")
|
||||||
|
|
||||||
|
(defvar hamacs-package-repos
|
||||||
|
(file-name-concat user-emacs-directory "repos")
|
||||||
|
"Repository location for all packages.")
|
||||||
|
|
||||||
|
(defun load-path-repo (repo)
|
||||||
|
(expand-file-name repo hamacs-package-repos))
|
||||||
|
|
||||||
|
(defun hamacs-package-repos-update-load-path ()
|
||||||
|
"Update the \`load-path' with clone repositories."
|
||||||
|
(interactive)
|
||||||
|
(dolist (repo (directory-files hamacs-package-repos 'full "[A-z].*"))
|
||||||
|
(add-to-list 'load-path repo nil 'string-equal)))
|
||||||
|
|
||||||
|
(hamacs-package-repos-update-load-path)
|
||||||
|
|
||||||
;; Bug fixes for ORG (there always seems to be something):
|
;; Bug fixes for ORG (there always seems to be something):
|
||||||
(defvar native-comp-deferred-compilation-deny-list nil)
|
;; (defvar native-comp-deferred-compilation-deny-list nil)
|
||||||
|
|
||||||
;; Allow the installation of unsigned packages, but verify the
|
;; Allow the installation of unsigned packages, but verify the
|
||||||
;; signature if possible:
|
;; signature if possible:
|
||||||
|
|
@ -115,38 +113,7 @@ cat > "$HAMACS_DEST/init.el" <<EOF
|
||||||
(add-to-list 'package-archives
|
(add-to-list 'package-archives
|
||||||
'("elpa-dev" . "https://elpa.gnu.org/devel/"))
|
'("elpa-dev" . "https://elpa.gnu.org/devel/"))
|
||||||
|
|
||||||
;; Configure straight https://github.com/raxod502/straight.el#getting-started
|
(setq use-package-always-ensure nil)
|
||||||
|
|
||||||
(defvar bootstrap-version)
|
|
||||||
(let ((bootstrap-file
|
|
||||||
(expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
|
|
||||||
(bootstrap-version 6))
|
|
||||||
(unless (file-exists-p bootstrap-file)
|
|
||||||
(with-current-buffer
|
|
||||||
(url-retrieve-synchronously
|
|
||||||
"https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
|
|
||||||
'silent 'inhibit-cookies)
|
|
||||||
(goto-char (point-max))
|
|
||||||
(eval-print-last-sexp)))
|
|
||||||
(load bootstrap-file nil 'nomessage))
|
|
||||||
|
|
||||||
(straight-use-package 'use-package)
|
|
||||||
|
|
||||||
;; While that enables the :straight t extension to use-package, let's just have that be the default:
|
|
||||||
(use-package straight
|
|
||||||
:custom (straight-use-package-by-default t
|
|
||||||
straight-default-vc 'git)
|
|
||||||
|
|
||||||
:init
|
|
||||||
;; Eglot and its dependencies ship with Emacs. Tell straight to treat them as
|
|
||||||
;; built-in so nothing (e.g. flycheck-eglot's dependency header) re-pulls the
|
|
||||||
;; GNU-ELPA copies, which would shadow the built-ins and trip eglot's
|
|
||||||
;; `require-with-check' ("Feature `project' is now provided by a different file").
|
|
||||||
(setq straight-built-in-pseudo-packages
|
|
||||||
(append '(eglot project flymake xref jsonrpc external-completion eldoc)
|
|
||||||
straight-built-in-pseudo-packages)))
|
|
||||||
|
|
||||||
;; See the details in https://dev.to/jkreeftmeijer/emacs-package-management-with-straight-el-and-use-package-3oc8
|
|
||||||
|
|
||||||
(use-package org
|
(use-package org
|
||||||
;; TODO: Using the latest org-mode
|
;; TODO: Using the latest org-mode
|
||||||
|
|
|
||||||
|
|
@ -1,132 +0,0 @@
|
||||||
;; -*- mode:lisp; -*-
|
|
||||||
;;
|
|
||||||
;; Here is a keyboard setup for an Apple Macbook keyboard to work with
|
|
||||||
;; "home row mods" where holding down keys on the home row can act
|
|
||||||
;; like a modifier, so holding down `d` and hitting `p` results in a
|
|
||||||
;; capital `P` being entered.
|
|
||||||
;;
|
|
||||||
;; This also includes a dedicated Hyper and Mega modifier layers,
|
|
||||||
;; where holding down the `m' key and striking `a' creates a
|
|
||||||
;; Control+Shift+Option+a key combination. This is useful for fancy
|
|
||||||
;; hotkeys for Hammerspoon and the like.
|
|
||||||
;;
|
|
||||||
;; Installation:
|
|
||||||
;; brew install kanata
|
|
||||||
;;
|
|
||||||
;; Running:
|
|
||||||
;; sudo kanata --cfg ~/src/hamacs/laptop_keyboard.kbd
|
|
||||||
|
|
||||||
(deflocalkeys-macos
|
|
||||||
ì 13)
|
|
||||||
|
|
||||||
;; While this works on both my Macbook running MacOS and Linux, I have
|
|
||||||
;; yet to figure out how to get the same file to work in both, as I
|
|
||||||
;; have to modify the code in the `defcfg' section below.
|
|
||||||
|
|
||||||
(defcfg
|
|
||||||
;; linux-dev-names-include ("Apple Internal Keyboard / Trackpad")
|
|
||||||
macos-dev-names-include ("Apple Internal Keyboard / Trackpad"))
|
|
||||||
|
|
||||||
;; What keys are we re-defining? This is my Macbook Pro layouts, as
|
|
||||||
;; both laptops are this hardware.
|
|
||||||
;;
|
|
||||||
;; Notice lmet/rmet = Apple's Command Key
|
|
||||||
;; lalt/ralt = Option, or a real meta key
|
|
||||||
|
|
||||||
(defsrc
|
|
||||||
esc f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12
|
|
||||||
` 1 2 3 4 5 6 7 8 9 0 - = bspc
|
|
||||||
tab q w e r t y u i o p [ ] \
|
|
||||||
caps a s d f g h j k l ; ' ret
|
|
||||||
lsft z x c v b n m , . / rsft
|
|
||||||
fn lctl lalt lmet spc rmet ralt)
|
|
||||||
|
|
||||||
(defvar
|
|
||||||
tap-time 200
|
|
||||||
hold-time 200)
|
|
||||||
|
|
||||||
;; Also includes a symbol layer accessible while holding down either
|
|
||||||
;; the `h' or `g' keys.
|
|
||||||
|
|
||||||
(defalias
|
|
||||||
;; Home row mods
|
|
||||||
a (tap-hold $tap-time $hold-time a lmet)
|
|
||||||
s (tap-hold $tap-time $hold-time s lctl)
|
|
||||||
d (tap-hold $tap-time $hold-time d lsft)
|
|
||||||
f (tap-hold $tap-time $hold-time f lalt)
|
|
||||||
g (tap-hold $tap-time $hold-time g (layer-while-held SYMBOLS))
|
|
||||||
h (tap-hold $tap-time $hold-time h (layer-while-held SYMBOLS))
|
|
||||||
j (tap-hold $tap-time $hold-time j ralt)
|
|
||||||
k (tap-hold $tap-time $hold-time k rsft)
|
|
||||||
l (tap-hold $tap-time $hold-time l rctl)
|
|
||||||
; (tap-hold $tap-time $hold-time ; rmet)
|
|
||||||
caps (tap-hold $tap-time $hold-time esc lctl)
|
|
||||||
|
|
||||||
;; ----------------------------------------------------
|
|
||||||
;; HYPER ALIASES (Shift + Ctrl + Option)
|
|
||||||
;; ----------------------------------------------------
|
|
||||||
hq (multi lsft lctl lalt q) hw (multi lsft lctl lalt w) he (multi lsft lctl lalt e) hr (multi lsft lctl lalt r) ht (multi lsft lctl lalt t)
|
|
||||||
hy (multi lsft lctl lalt y) hu (multi lsft lctl lalt u) hi (multi lsft lctl lalt i) ho (multi lsft lctl lalt o) hp (multi lsft lctl lalt p)
|
|
||||||
|
|
||||||
ha (multi lsft lctl lalt a) hs (multi lsft lctl lalt s) hd (multi lsft lctl lalt d) hf (multi lsft lctl lalt f) hg (multi lsft lctl lalt g)
|
|
||||||
hh (multi lsft lctl lalt h) hj (multi lsft lctl lalt j) hk (multi lsft lctl lalt k) hl (multi lsft lctl lalt l) h; (multi lsft lctl lalt ;)
|
|
||||||
|
|
||||||
hz (multi lsft lctl lalt z) hx (multi lsft lctl lalt x) hc (multi lsft lctl lalt c) hv (multi lsft lctl lalt v) hb (multi lsft lctl lalt b)
|
|
||||||
hn (multi lsft lctl lalt n) hm (multi lsft lctl lalt m) h, (multi lsft lctl lalt ,) h. (multi lsft lctl lalt .) h/ (multi lsft lctl lalt /)
|
|
||||||
|
|
||||||
;; ----------------------------------------------------
|
|
||||||
;; MEGA ALIASES (Shift + Ctrl + Option + Cmd)
|
|
||||||
;; ----------------------------------------------------
|
|
||||||
mq (multi lsft lctl lalt lmet q) mw (multi lsft lctl lalt lmet w) me (multi lsft lctl lalt lmet e) mr (multi lsft lctl lalt lmet r) mt (multi lsft lctl lalt lmet t)
|
|
||||||
my (multi lsft lctl lalt lmet y) mu (multi lsft lctl lalt lmet u) mi (multi lsft lctl lalt lmet i) mo (multi lsft lctl lalt lmet o) mp (multi lsft lctl lalt lmet p)
|
|
||||||
|
|
||||||
ma (multi lsft lctl lalt lmet a) ms (multi lsft lctl lalt lmet s) md (multi lsft lctl lalt lmet d) mf (multi lsft lctl lalt lmet f) mg (multi lsft lctl lalt lmet g)
|
|
||||||
mh (multi lsft lctl lalt lmet h) mj (multi lsft lctl lalt lmet j) mk (multi lsft lctl lalt lmet k) ml (multi lsft lctl lalt lmet l) m; (multi lsft lctl lalt lmet ;)
|
|
||||||
|
|
||||||
mz (multi lsft lctl lalt lmet z) mx (multi lsft lctl lalt lmet x) mc (multi lsft lctl lalt lmet c) mv (multi lsft lctl lalt lmet v) mb (multi lsft lctl lalt lmet b)
|
|
||||||
mn (multi lsft lctl lalt lmet n) mm (multi lsft lctl lalt lmet m) m, (multi lsft lctl lalt lmet ,) m. (multi lsft lctl lalt lmet .) m/ (multi lsft lctl lalt lmet /)
|
|
||||||
|
|
||||||
m (tap-hold $tap-time $hold-time m (layer-while-held HYPER))
|
|
||||||
v (tap-hold $tap-time $hold-time v (layer-while-held MEGA)))
|
|
||||||
|
|
||||||
;; The base layer is fairly normal, except we all out aliases defined
|
|
||||||
;; above, os the @a is both a `tap-hold' feature as well as a regular
|
|
||||||
;; `a` key:
|
|
||||||
|
|
||||||
(deflayer base
|
|
||||||
esc f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12
|
|
||||||
` 1 2 3 4 5 6 7 8 9 0 - = bspc
|
|
||||||
tab q w e r t y u i o p [ ] \
|
|
||||||
@caps @a @s @d @f @g @h @j @k @l @; ' ret
|
|
||||||
lsft z x c @v b n @m , . / rsft
|
|
||||||
@h lctl lalt lmet spc rmet ralt)
|
|
||||||
|
|
||||||
;; The other layer is our `symbols' which allows me to hold down the
|
|
||||||
;; `g' key to invoke a magical VI-like h/j/k/l arrow keys:
|
|
||||||
|
|
||||||
(deflayer SYMBOLS
|
|
||||||
esc 🔅 🔆 F3 F4 f5 f16 ◀◀ ▶⏸ ▶▶ 🔇 🔉 🔊
|
|
||||||
_ f11 f12 f13 f14 f15 f16 f17 f18 f19 f20 _ _ _
|
|
||||||
_ S-1 S-2 { } S-\ F16 F17 F18 F19 pgup _ _ _
|
|
||||||
_ S-3 S-4 S-9 S-0 ` left down up right pgdn _ _
|
|
||||||
_ S-5 S-6 [ ] S-` F11 F12 F13 F14 F15 _
|
|
||||||
_ _ _ _ _ _ _)
|
|
||||||
|
|
||||||
;; The other layer is our `magic' which allows me to hold down the
|
|
||||||
;; `m' key to invoke a magical VI-like h/j/k/l arrow keys:
|
|
||||||
|
|
||||||
(deflayer HYPER
|
|
||||||
_ _ _ _ _ _ _ _ _ _ _ _ _
|
|
||||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
||||||
_ @hq @hw @he @hr @ht @hy @hu @hi @ho @hp _ _ _
|
|
||||||
_ @ha @hs @hd @hf @hg @hh @hj @hk @hl @h; _ _
|
|
||||||
_ @hz @hx @hc @hv @hb @hn @hm _ _ _ _
|
|
||||||
_ _ _ _ _ _ _)
|
|
||||||
|
|
||||||
(deflayer MEGA
|
|
||||||
_ _ _ _ _ _ _ _ _ _ _ _ _
|
|
||||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
||||||
_ @mq @mw @me @mr @mt @my @mu @mi @mo @mp _ _ _
|
|
||||||
_ @ma @ms @md @mf @mg @mh @mj @mk @ml @m; _ _
|
|
||||||
_ @mz @mx @mc @mv @mb @mn @mm _ _ _ _
|
|
||||||
_ _ _ _ _ _ _)
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 449 KiB After Width: | Height: | Size: 308 KiB |
|
|
@ -2,4 +2,4 @@
|
||||||
# name: title
|
# name: title
|
||||||
# key: title
|
# key: title
|
||||||
# --
|
# --
|
||||||
#+TITLE: ${0}
|
#+title: ${0}
|
||||||
105
tmux.org
105
tmux.org
|
|
@ -1,105 +0,0 @@
|
||||||
#+title: Tmux Configuration
|
|
||||||
#+author: Howard X. Abrams
|
|
||||||
#+date: 2026-01-15
|
|
||||||
#+filetags: emacs hamacs
|
|
||||||
#+lastmod: [2026-01-15 Thu]
|
|
||||||
|
|
||||||
I use [[https://github.com/tmux/tmux/wiki][tmux]] infrequently as I'm usually running a bunch of Terminals from within Emacs. Laugh all you want, but Emacs is my terminal multiplexer. That said, frequent network connectivity and long-running jobs make a =tmux= helpful on a remote server.
|
|
||||||
|
|
||||||
* Remote Installation
|
|
||||||
Log in an install =tmux= on a RedHat-based system with a =yum= command:
|
|
||||||
|
|
||||||
#+BEGIN_SRC sh :dir /ssh:docker:
|
|
||||||
sudo yum install -y tmux
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
And on my Ubuntu systems:
|
|
||||||
|
|
||||||
#+BEGIN_SRC sh :dir /ssh:docker:
|
|
||||||
sudo dpkg install tmux
|
|
||||||
#+END_SRC
|
|
||||||
* Remote Configuration
|
|
||||||
Copy the following to =$HOME/~/.tmux.conf=:
|
|
||||||
|
|
||||||
#+BEGIN_SRC conf :tangle /ssh:docker:/home/vagrant/.tmux.conf
|
|
||||||
set-option -g prefix C-`
|
|
||||||
|
|
||||||
set -g status-right "| docker "
|
|
||||||
set-option -g allow-rename off
|
|
||||||
|
|
||||||
# Start windows and panes at 1, not 0
|
|
||||||
set -g base-index 1
|
|
||||||
set -g pane-base-index 1
|
|
||||||
|
|
||||||
# Bind function keys.
|
|
||||||
bind -n F1 select-window -t 1
|
|
||||||
bind -n F2 select-window -t 2
|
|
||||||
bind -n F3 select-window -t 3
|
|
||||||
bind -n F4 select-window -t 4
|
|
||||||
bind -n F5 select-window -t 5
|
|
||||||
bind -n F6 select-window -t 6
|
|
||||||
bind -n F7 select-window -t 7
|
|
||||||
bind -n F8 select-window -t 8
|
|
||||||
bind -n F9 select-window -t 9
|
|
||||||
bind -n F10 select-window -t 10
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Not sure why =screen= and =tmux= chose such awful prefix keybindings as ~C-a~ and ~C-b~, so I change this to ~C-`~.
|
|
||||||
|
|
||||||
I'm not a tmux power user, as changing windows is about all I do, so I bind the function keys to change tabs.
|
|
||||||
|
|
||||||
The following =~/.bash_aliases= enhance tmux:
|
|
||||||
|
|
||||||
#+BEGIN_SRC sh :tangle /ssh:docker:/home/vagrant/.bash_aliases
|
|
||||||
#!/bin/sh
|
|
||||||
# The following aliases have been installed on this machine.
|
|
||||||
# - `nw` to create a new Tmux window. Give ‘er a command.
|
|
||||||
alias nw='tmux new-window'
|
|
||||||
|
|
||||||
# - `root` for a TMUX window as the root user.
|
|
||||||
alias root='nw -n root "sudo su -" '
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
When using Docker sessions on the remote system, the =go= and =logs= opens a new window:
|
|
||||||
|
|
||||||
#+BEGIN_SRC sh
|
|
||||||
# - `go` to connect to a docker container
|
|
||||||
function go {
|
|
||||||
nw -n "$1" "sudo docker exec -it $1 bash"
|
|
||||||
}
|
|
||||||
|
|
||||||
# - `logs` for displaying the logs from a container
|
|
||||||
function logs {
|
|
||||||
nw -n $1-logs "sudo docker logs -f $*"
|
|
||||||
}
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
After writing a number of helper aliases, I find it helpful to see those aliases when I roll into a remote server, so put this in the =.bashrc= file:
|
|
||||||
|
|
||||||
#+BEGIN_SRC sh
|
|
||||||
if [ "$HOME/.bash_aliases" ]
|
|
||||||
then
|
|
||||||
source "$HOME/.bash_aliases"
|
|
||||||
grep '^# ' "$HOME/.bash_aliases" | sed 's/^# *//'
|
|
||||||
echo
|
|
||||||
fi
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Note: this can interfere with Tramp.
|
|
||||||
* Local Access
|
|
||||||
For a collection of long-running and highly verbose programs (like a remote Anisble run), I use iTerm on a MacOS with a special binding to create Mac-widgets in tmux.
|
|
||||||
|
|
||||||
#+BEGIN_SRC sh
|
|
||||||
ssh -t laptop 'tmux -CC attach || tmux -CC'
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
|
|
||||||
#+DESCRIPTION: Code to be used for configuring Tmux.
|
|
||||||
|
|
||||||
#+PROPERTY: header-args:sh :tangle no
|
|
||||||
#+PROPERTY: header-args:emacs-lisp :tangle yes
|
|
||||||
#+PROPERTY: header-args :results none :eval no-export :comments no mkdirp yes
|
|
||||||
|
|
||||||
#+OPTIONS: num:nil toc:nil todo:nil tasks:nil tags:nil date:nil
|
|
||||||
#+OPTIONS: skip:nil author:nil email:nil creator:nil timestamp:nil
|
|
||||||
#+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
|
|
||||||
187
zshell.org
187
zshell.org
|
|
@ -21,7 +21,6 @@ Regardless, I keep my shell configuration conspicuously light.
|
||||||
Let’s create the following files, and the configuration below will be injected into one of them:
|
Let’s create the following files, and the configuration below will be injected into one of them:
|
||||||
|
|
||||||
- =~/.zshenv= :: Usually run for every zsh
|
- =~/.zshenv= :: Usually run for every zsh
|
||||||
- =~/.zprofile= :: Usually run for login shells (this includes the system-wide =/etc/zprofile=)
|
|
||||||
- =~/.zshrc= :: Run for interactive shells … default file when tangling
|
- =~/.zshrc= :: Run for interactive shells … default file when tangling
|
||||||
- =~/.zlogin= :: Run for login shells … seems to run as often as =.zshrc=
|
- =~/.zlogin= :: Run for login shells … seems to run as often as =.zshrc=
|
||||||
|
|
||||||
|
|
@ -44,24 +43,8 @@ Let’s create the following files, and the configuration below will be injected
|
||||||
* Path
|
* Path
|
||||||
The all important =PATH= environment variable, needs my special =bin= directory.
|
The all important =PATH= environment variable, needs my special =bin= directory.
|
||||||
|
|
||||||
#+BEGIN_SRC zsh :tangle ~/.zshenv
|
#+BEGIN_SRC zsh :export ~/.zshenv
|
||||||
export PATH=$HOME/bin:$HOME/.local/bin:$PATH
|
export PATH=$HOME/bin:/usr/local/bin:$PATH
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
My Apple Macbook screws up my =PATH= by having =/etc/profile= (that runs after my =~/.zshenv=) /pre-pend/ system directories like =/bin= and =/usr/bin= /after/ I’ve set up my =PATH= environment variable. So, in my own =.zprofile= (which runs afterwards), I reverse it using the lovely =tac= program:
|
|
||||||
|
|
||||||
#+BEGIN_SRC sh :tangle ~/.zprofile :shebang #!/bin/zsh
|
|
||||||
if [[ -f /etc/zprofile ]]
|
|
||||||
then
|
|
||||||
# Reverse the PATH variable
|
|
||||||
reversed_path=$(echo $PATH | tr ':' '\n' | tac | tr '\n' ':')
|
|
||||||
|
|
||||||
# Reset the path after removing the trailing colon:
|
|
||||||
export PATH=${reversed_path%:}
|
|
||||||
|
|
||||||
# Output the reversed PATH
|
|
||||||
# echo "Reversed PATH: $reversed_path"
|
|
||||||
fi
|
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
* Options
|
* Options
|
||||||
|
|
@ -111,10 +94,7 @@ setopt MENU_COMPLETE
|
||||||
When using Homebrew on a Mac, we need to add its =PATH=:
|
When using Homebrew on a Mac, we need to add its =PATH=:
|
||||||
|
|
||||||
#+BEGIN_SRC zsh :tangle ~/.zshenv
|
#+BEGIN_SRC zsh :tangle ~/.zshenv
|
||||||
if [[ -d /opt/homebrew ]]
|
eval $(/opt/homebrew/bin/brew shellenv zsh)
|
||||||
then
|
|
||||||
eval $(/opt/homebrew/bin/brew shellenv zsh)
|
|
||||||
fi
|
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
This adds the following environment variables, along with expanding the =PATH=.
|
This adds the following environment variables, along with expanding the =PATH=.
|
||||||
|
|
@ -218,35 +198,24 @@ The [[https://github.com/zsh-users/zsh-syntax-highlighting][ZShell Syntax Highli
|
||||||
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
|
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
Using the =colorize= plugin (see [[Plugins][plugins section below]]), we customize with this variable setting:
|
|
||||||
|
|
||||||
#+BEGIN_SRC zsh
|
|
||||||
export ZSH_COLORIZE_STYLE="coffee"
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
|
|
||||||
** Language Support
|
** Language Support
|
||||||
Anything special for particular languages.
|
Anything special for particular languages.
|
||||||
*** Python
|
*** Python
|
||||||
Not overly impressed, for to get =pyenv= to work, we need to add this code:
|
Not overly impressed, for to get =pyenv= to work, we need to add this code:
|
||||||
|
|
||||||
#+BEGIN_SRC zsh :tangle ~/.zshenv
|
#+BEGIN_SRC zsh
|
||||||
export PYENV_ROOT="$HOME/.pyenv"
|
export PYENV_ROOT="$HOME/.pyenv"
|
||||||
export PATH="$PYENV_ROOT/bin:$PATH"
|
export PATH="$PYENV_ROOT/bin:$PATH"
|
||||||
|
|
||||||
|
eval "$(pyenv init --path)"
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
And call the =pyenv= to initialize it:
|
|
||||||
|
|
||||||
#+BEGIN_SRC zsh
|
|
||||||
# eval "$(pyenv init --path)"
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
** Plugins
|
** Plugins
|
||||||
Configure the plugins, making sure to not use =git=, as the aliases are a pain to remember when I already have a superior Git interface in Emacs.
|
Configure the plugins, making sure to not use =git=, as the aliases are a pain to remember when I already have a superior Git interface in Emacs.
|
||||||
|
|
||||||
- [[https://github.com/hsienjan/colorize][colorize]] :: syntax-highlight file contents, so install [[https://pygments.org/download/][Pygments]] first. Then call =ccat= and =cless= (see [[Aliases]]).
|
- [[https://github.com/hsienjan/colorize][colorize]] :: syntax-highlight file contents, so install [[https://pygments.org/download/][Pygments]] first. Then call =ccat= and =cless= (see [[Aliases]]).
|
||||||
- [[https://github.com/ptavares/zsh-direnv][direnv]] :: to support the [[https://direnv.net/][direnv]] virtual environment project.
|
- [[https://github.com/ptavares/zsh-direnv][direnv]] :: to support the [[https://direnv.net/][direnv]] virtual environment project.
|
||||||
- [[https://github.com/ohmyzsh/ohmyzsh/blob/master/plugins/mise/mise.plugin.zsh][mise]] :: to support the [[https://mise.jdx.dev/getting-started.html][mise]] virtual environment and tool project (instead of direnv). See the [[https://mise.jdx.dev/walkthrough.html][walk-through]].
|
|
||||||
- [[https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/gnu-utils][gnu-utils]] :: bind the GNU flavor for standard utils, like =gfind= to the normal version, e.g. =find=.
|
- [[https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/gnu-utils][gnu-utils]] :: bind the GNU flavor for standard utils, like =gfind= to the normal version, e.g. =find=.
|
||||||
- [[https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/iterm2][iterm2]] :: while fully configured below, configures the interaction with the MacOS application, [[https://www.iterm2.com/][iTerm2]].
|
- [[https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/iterm2][iterm2]] :: while fully configured below, configures the interaction with the MacOS application, [[https://www.iterm2.com/][iTerm2]].
|
||||||
- [[https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/macos][macos]] :: adds new functions that work better with MacOS terminals and the Finder. I like:
|
- [[https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/macos][macos]] :: adds new functions that work better with MacOS terminals and the Finder. I like:
|
||||||
|
|
@ -259,7 +228,7 @@ Configure the plugins, making sure to not use =git=, as the aliases are a pain t
|
||||||
To have a plugin /install/, add its name to the =plugins= array variable /before/ we =source= the OMZ script:
|
To have a plugin /install/, add its name to the =plugins= array variable /before/ we =source= the OMZ script:
|
||||||
|
|
||||||
#+begin_SRC zsh
|
#+begin_SRC zsh
|
||||||
plugins=(colorize direnv gnu-utils iterm2 macos mise virtualenv zbell zsh-syntax-highlighting)
|
plugins=(colorize direnv gnu-utils iterm2 macos pyenv virtualenv zbell zsh-syntax-highlighting)
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
Notice the =iterm2= plugin as well as the =macos= plugins that would be nice to figure out how to make them optionally added (although I believe they check themselves).
|
Notice the =iterm2= plugin as well as the =macos= plugins that would be nice to figure out how to make them optionally added (although I believe they check themselves).
|
||||||
|
|
@ -273,26 +242,6 @@ The trick is to install some base-level plugins, and then, on my work computer,
|
||||||
fi
|
fi
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
I would like to have a history /per project/, so that when I start a session for a project, my history has where I left off /for that project/ and not everything. I guess I’m not the only one who [[https://github.com/ivan-cukic/zsh-per-project-history][thought of this idea]]. We first need to add his plugin to the list of supplied plugins, so do this once:
|
|
||||||
|
|
||||||
#+BEGIN_SRC zsh :tangle no
|
|
||||||
git clone https://github.com/ivan-cukic/zsh-per-project-history ~/.oh-my-zsh/plugins/per-project-history
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
His idea is to have a variable array, =PER_PROJECT_HISTORY_TAGS= that lists files that should identify the start of a project, and while his default seems sufficient, I am not found of the spammy message, so:
|
|
||||||
|
|
||||||
#+BEGIN_SRC zsh
|
|
||||||
declare -a PER_PROJECT_HISTORY_TAGS
|
|
||||||
export PER_PROJECT_HISTORY_TAGS=(.envrc .git)
|
|
||||||
declare -r PER_PROJECT_HISTORY_TAGS
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
we just need to add =per-history= to the list of plugins:
|
|
||||||
|
|
||||||
#+BEGIN_SRC zsh
|
|
||||||
plugins+=(per-project-history)
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Now that I’ve filled in the =plugins= variable, load OMZ and the plugins:
|
Now that I’ve filled in the =plugins= variable, load OMZ and the plugins:
|
||||||
|
|
||||||
#+BEGIN_SRC zsh
|
#+BEGIN_SRC zsh
|
||||||
|
|
@ -325,80 +274,6 @@ Oh use the absolute /over-the-top/ bling associated with Oh My Zshell’s /theme
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
I keep the prompt simple since all of the /gunk/ we typically put in a prompt is better placed in [[https://iterm2.com/documentation-status-bar.html][iTerm2's Status Bar]].
|
I keep the prompt simple since all of the /gunk/ we typically put in a prompt is better placed in [[https://iterm2.com/documentation-status-bar.html][iTerm2's Status Bar]].
|
||||||
* Homebrew
|
|
||||||
When using Homebrew on a Mac, we need to add its =PATH= and environment variables. This is typically done by running the command:
|
|
||||||
|
|
||||||
#+BEGIN_SRC zsh :tangle no
|
|
||||||
eval $(brew shellenv zsh)
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
We want to add the path and environment variables into the =~/.zshenv= file, but this file should not contain any logic or code. So, let’s run the command /from Emacs/, and store the results in the file.
|
|
||||||
|
|
||||||
The full script to run is:
|
|
||||||
|
|
||||||
#+BEGIN_SRC zsh :tangle no :results file :file ~/.zshenv_brew
|
|
||||||
echo '# -*- mode:sh; -*-'
|
|
||||||
if which brew >/dev/null
|
|
||||||
then
|
|
||||||
if [[ -d /opt/homebrew ]]
|
|
||||||
then
|
|
||||||
/opt/homebrew/bin/brew shellenv zsh
|
|
||||||
else
|
|
||||||
brew shellenv zsh
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Seems that if I want the GNU versions (instead of the old ones supplied by Apple), I have to do it myself:
|
|
||||||
|
|
||||||
#+BEGIN_SRC zsh :tangle no :results file :file ~/.zshenv_gnu
|
|
||||||
echo '# -*- mode:sh; -*-'
|
|
||||||
if which brew >/dev/null
|
|
||||||
then
|
|
||||||
for PKG in binutils gettext unzip openssl texinfo mysql-client openjdk
|
|
||||||
do
|
|
||||||
if PKG_INSTALL=$(brew --prefix $PKG)
|
|
||||||
then
|
|
||||||
echo export PATH=$PKG_INSTALL/bin:'$PATH'
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
for PKG in coreutils ed findutils gnu-indent gnu-sed gnu-tar grep make
|
|
||||||
do
|
|
||||||
if PKG_INSTALL=$(brew --prefix $PKG)
|
|
||||||
then
|
|
||||||
echo export PATH=$PKG_INSTALL/libexec/gnubin:'$PATH'
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
And linking all the GNU libraries:
|
|
||||||
|
|
||||||
#+BEGIN_SRC zsh :tangle no :results file :file ~/.zshenv_lib
|
|
||||||
echo '# -*- mode:sh; -*-'
|
|
||||||
|
|
||||||
if which brew >/dev/null
|
|
||||||
then
|
|
||||||
for PKG in readline openssl xz binutils ctags libgccjit imagemagick
|
|
||||||
do
|
|
||||||
if PKG_INSTALL=$(brew --prefix $PKG)
|
|
||||||
echo export LDFLAGS=\"-L$PKG_INSTALL/lib '$LDFLAGS'\"
|
|
||||||
echo export CPPFLAGS=\"-I$PKG_INSTALL/include '$CPPFLAGS'\"
|
|
||||||
done
|
|
||||||
echo export LDFLAGS=\"'$LDFLAGS' -L$(brew --prefix)/lib\"
|
|
||||||
echo export CPPFLAGS=\"'$CPPFLAGS' -I$(brew --prefix)/include\"
|
|
||||||
fi
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
And pull in all the results into the =~/.zshenv= file (why yes, this could be inlined):
|
|
||||||
|
|
||||||
#+BEGIN_SRC zsh :tangle ~/.zshenv
|
|
||||||
[[ -f $HOME/.zshenv_brew ]] && source $HOME/.zshenv_brew
|
|
||||||
[[ -f $HOME/.zshenv_gnu ]] && source $HOME/.zshenv_gnu
|
|
||||||
[[ -f $HOME/.zshenv_lib ]] && source $HOME/.zshenv_lib
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
* iTerm2
|
* iTerm2
|
||||||
On Mac systems, I like the [[https://www.iterm2.com/][iTerm2 application]], and we can enable [[https://iterm2.com/documentation-shell-integration.html][shell integration]], either via the old school way, or just rely on [[https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/iterm2][the /plugin/ ]]above:
|
On Mac systems, I like the [[https://www.iterm2.com/][iTerm2 application]], and we can enable [[https://iterm2.com/documentation-shell-integration.html][shell integration]], either via the old school way, or just rely on [[https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/iterm2][the /plugin/ ]]above:
|
||||||
|
|
||||||
|
|
@ -417,25 +292,15 @@ Favorite feature is the [[https://iterm2.com/documentation-status-bar.html][Stat
|
||||||
Currently, I show the currently defined Kube namespace.
|
Currently, I show the currently defined Kube namespace.
|
||||||
|
|
||||||
#+BEGIN_SRC zsh
|
#+BEGIN_SRC zsh
|
||||||
function iterm2_python_version() {
|
|
||||||
echo $(pyenv version-name):$(echo "$VIRTUAL_ENV" | sed "
|
|
||||||
s|^$HOME|~|
|
|
||||||
s|^~/src/wpc-gerrit.inday.io/||
|
|
||||||
s|^~/work/||
|
|
||||||
s|^~/.venv/||
|
|
||||||
s|/\.venv$||
|
|
||||||
s|\.venv$||")
|
|
||||||
}
|
|
||||||
|
|
||||||
function iterm2_print_user_vars() {
|
function iterm2_print_user_vars() {
|
||||||
# iterm2_set_user_var kubecontext $($ yq '.users[0].name' ~/.kube/config):$(kubectl config view --minify --output 'jsonpath={..namespace}')
|
# iterm2_set_user_var kubecontext $($ yq '.users[0].name' ~/.kube/config):$(kubectl config view --minify --output 'jsonpath={..namespace}')
|
||||||
|
|
||||||
# Correct version:
|
# Correct version:
|
||||||
# iterm2_set_user_var kubecontext $(kubectl config current-context):$(kubectl config view --minify --output 'jsonpath={..namespace}')
|
# iterm2_set_user_var kubecontext $(kubectl config current-context):$(kubectl config view --minify --output 'jsonpath={..namespace}')
|
||||||
# Faster version:
|
# Faster version:
|
||||||
iterm2_set_user_var kubecontext $(awk '/^current-context:/{print $2;exit;}' <~/.kube/config)
|
iterm2_set_user_var kubecontext $(awk '/^current-context:/{print $2;exit;}' <~/.kube/config)
|
||||||
|
|
||||||
iterm2_set_user_var pycontext $(iterm2_python_version)
|
iterm2_set_user_var pycontext "$(pyenv version-name):$(echo $VIRTUAL_ENV | sed 's/.*.venv\///')"
|
||||||
}
|
}
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
|
@ -459,12 +324,10 @@ While it /should/ figure out (as Emacs keybindings are the default), this is how
|
||||||
bindkey -e
|
bindkey -e
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
Where be the =emacsclient=? It should, at this point, be in our path.
|
Where be the =emacsclient=, and how should we call it?
|
||||||
|
|
||||||
And how should we call it?
|
|
||||||
|
|
||||||
#+BEGIN_SRC zsh :tangle ~/.zshenv
|
#+BEGIN_SRC zsh :tangle ~/.zshenv
|
||||||
export EMACS_SOCKET_NAME=personal
|
export EMACS="emacsclient --socket-name personal"
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
Which needs to be overwritten on my Work computer:
|
Which needs to be overwritten on my Work computer:
|
||||||
|
|
@ -472,15 +335,15 @@ Which needs to be overwritten on my Work computer:
|
||||||
#+BEGIN_SRC zsh :tangle ~/.zshenv
|
#+BEGIN_SRC zsh :tangle ~/.zshenv
|
||||||
if hostname | grep AL33 >/dev/null
|
if hostname | grep AL33 >/dev/null
|
||||||
then
|
then
|
||||||
export EMACS_SOCKET_NAME=work
|
export EMACS="emacsclient --socket-name work"
|
||||||
fi
|
fi
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
The =EDITOR= variable that some programs use to edit files from the command line:
|
The =EDITOR= variable that some programs use to edit files from the command line:
|
||||||
|
|
||||||
#+BEGIN_SRC zsh :tangle ~/.zshenv
|
#+BEGIN_SRC zsh :tangle ~/.zshenv
|
||||||
export EDITOR="emacsclient --tty"
|
export EDITOR="$EMACS --tty"
|
||||||
export VISUAL="emacsclient --create-frame"
|
export VISUAL="$EMACS --create-frame"
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
With these variables defined, we can create simple aliases:
|
With these variables defined, we can create simple aliases:
|
||||||
|
|
@ -488,8 +351,8 @@ With these variables defined, we can create simple aliases:
|
||||||
#+BEGIN_SRC zsh
|
#+BEGIN_SRC zsh
|
||||||
alias e="$EDITOR"
|
alias e="$EDITOR"
|
||||||
alias te="$EDITOR"
|
alias te="$EDITOR"
|
||||||
alias ee="emacsclient --create-frame"
|
alias ee="$EMACS --create-frame"
|
||||||
alias eee="emacsclient --create-frame --no-wait"
|
alias eee="$EMACS --create-frame --no-wait"
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
** Vterm
|
** Vterm
|
||||||
|
|
@ -544,7 +407,7 @@ For instance:
|
||||||
Assuming we’ve installed [[https://github.com/lsd-rs/lsd][lsd]], let’s make an alias for it:
|
Assuming we’ve installed [[https://github.com/lsd-rs/lsd][lsd]], let’s make an alias for it:
|
||||||
|
|
||||||
#+BEGIN_SRC zsh
|
#+BEGIN_SRC zsh
|
||||||
if which lsd >/dev/null
|
if whence lsd >/dev/null
|
||||||
then
|
then
|
||||||
alias ls=lsd
|
alias ls=lsd
|
||||||
fi
|
fi
|
||||||
|
|
@ -568,13 +431,6 @@ And an abstraction for transitory endpoints over SSH:
|
||||||
alias ossh="ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o loglevel=ERROR"
|
alias ossh="ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o loglevel=ERROR"
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
And other ones that I use:
|
|
||||||
|
|
||||||
#+BEGIN_SRC zsh
|
|
||||||
alias os=openstack
|
|
||||||
alias k=kubectl
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
* Final Message
|
* Final Message
|
||||||
For sensitive work-related environment variables, store them elsewhere, and load them:
|
For sensitive work-related environment variables, store them elsewhere, and load them:
|
||||||
|
|
||||||
|
|
@ -590,7 +446,6 @@ To let us know we read the =~/.zshrc= file:
|
||||||
|
|
||||||
#+description: A literate programming file for configuring Zshell.
|
#+description: A literate programming file for configuring Zshell.
|
||||||
#+property: header-args:zsh :tangle ~/.zshrc
|
#+property: header-args:zsh :tangle ~/.zshrc
|
||||||
#+property: header-args:sh :tangle no
|
|
||||||
#+property: header-args :results none :eval no-export :comments no mkdirp yes
|
#+property: header-args :results none :eval no-export :comments no mkdirp yes
|
||||||
#+options: num:nil toc:t todo:nil tasks:nil tags:nil date:nil
|
#+options: num:nil toc:t todo:nil tasks:nil tags:nil date:nil
|
||||||
#+options: skip:nil author:nil email:nil creator:nil timestamp:nil
|
#+options: skip:nil author:nil email:nil creator:nil timestamp:nil
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue