Compare commits

..

160 Commits

Author SHA1 Message Date
d2b7817dc1 Add optional region support to aimenu
This change modifies the function aimenu-get-buffer-with-line-numbers to accept optional start and end arguments, allowing it to add line numbers to a specified region instead of the entire buffer. Additionally, it adjusts usage in aimenu-show-outline to handle active region if present.
2024-08-15 00:04:09 -04:00
e2580c7abe Fix aimenu use-package declaration 2024-08-13 23:30:12 -04:00
555d78eadc Set unique-buffer-name-style 2024-08-13 20:15:07 -04:00
4d022e32d2 Refine commit message prompt to be more specific
Updated the prompt text to include "Be specific" to improve clarity and detail in generated commit messages.
2024-08-12 16:31:54 -04:00
6b4430b854 Optimize Emacs startup and UI settings
Streamlined startup by optimizing garbage collection, UI elements, and performance settings. Moved redundant configurations from init.el to early-init.el for better startup efficiency. Made adjustments to init-ui.el for better tool-bar handling post-init.
2024-08-12 16:31:11 -04:00
f32fbd670b Add optional instruction input for custom outlines
Modified aimenu function to accept an optional instruction argument, allowing users to provide specific instructions for generating the outline. Updated related logic to handle the new argument and adjusted the system message accordingly.
2024-08-12 12:34:36 -04:00
73897be017 Use gpt-4o-mini for aimenu 2024-08-12 11:49:00 -04:00
862c55dfdc Require gptel 2024-08-12 11:48:53 -04:00
76bea3d41a Add keybinding for gptel-quick to ai-map 2024-08-12 11:39:07 -04:00
71861b026a Add aimenu for AI-powered imenu outlines
This commit introduces aimenu, a new package that generates imenu-like outlines using a language model. The new package includes functions for generating and handling outlines, managing cache, and interacting with the user to select headers.
2024-08-12 11:38:59 -04:00
c622de4d80 Add gemma2:2b model option 2024-08-10 22:58:47 -04:00
7c9f4475ce Add swift config 2024-08-10 22:58:17 -04:00
4b7e74afa5 Don't use first person in system prompt 2024-08-07 13:10:54 -04:00
5b0cab188e Move backend selection into interactive call 2024-08-07 13:09:13 -04:00
cc12ad22b4 Expand gptel-directive for org-mode with more guidelines
Updated the gptel-directive for org-mode to provide detailed instructions on how to return Emacs Lisp, Python, and shell-script code blocks. Additionally, included several examples to illustrate the expected code block format and evaluation criteria.
2024-08-07 13:01:35 -04:00
6f353f7e45 Load init-navigation later 2024-08-06 11:59:32 -04:00
5873205a25 Enable Markdown export from org-mode 2024-08-06 11:59:16 -04:00
f4ff08e0ba Add keybinding setup for ace-link in eww mode
Configured ace-link keybinding for eww mode to ensure it works correctly by defining it within a setup function and adding a hook for eww-mode.
2024-08-06 11:59:05 -04:00
82bd3725a2 Generate PR diffs properly 2024-08-06 11:58:50 -04:00
f0a47640e5 Open AWS console links in default browser 2024-08-06 11:58:23 -04:00
2f02bc6e1b Add new directives for code review and prompt generation
This commit introduces two new directives into the gptel configuration: one for code review and another for prompt generation. The code review directive provides guidelines for giving feedback on code snippets, while the prompt generation directive outlines steps for creating effective prompts.
2024-08-05 10:03:30 -04:00
c0f2631b00 Add directive for stable diffusion prompt generation
This change adds a new directive to the gptel configuration for creating detailed prompts for stable diffusion image generators. The directive specifies how to translate a simple natural language description into a more detailed and descriptive prompt to ensure high-quality image generation.
2024-07-25 21:31:47 -04:00
38db89e9be Open ComfyUI in default browser 2024-07-25 21:31:24 -04:00
8f6b50d84e Remove llama configuration from init-ai.el 2024-07-25 21:10:26 -04:00
a47f4e537d Add gptel-chat-with-buffer function and keybinding
This commit introduces the gptel-chat-with-buffer function, allowing users to start a chat with the buffer content. It also updates keybindings, assigning "b" to the new function and moving gptel-select-backend to "B".
2024-07-25 21:10:14 -04:00
e63466ca1f Extract org-mode LLM directive into variable 2024-07-25 16:22:18 -04:00
27faface1b Add ob-async 2024-07-25 16:22:10 -04:00
71d26e5e05 Add imenu for eww
Add support for Imenu integration to EWW mode by defining a custom index function that uses HTML headings from the current buffer as menu items, and then add this hook to eww-mode-hook so that it can be triggered automatically when eww buffers are opened. This change includes code borrowed from 3b46f9c0e0
2024-07-25 16:15:31 -04:00
a00ecb3fc0 Enable shell source code blocks in Org-mode Babel 2024-07-25 00:28:07 -04:00
ccf97514f6 Update gptel--system-message for automatic code evaluation
Removed the need for confirmation before evaluating code in Org-Mode source blocks, allowing automatic execution.
2024-07-25 00:27:56 -04:00
ed63a1ea0f Add gptel-org function
Implemented a gptel-org function to allow interacting with GPTel within an Org-Mode buffer, setting the appropriate system message for context. Updated init-org.el to load Emacs-Lisp and Python Babel languages and disabled org-confirm-babel-evaluate for smoother code execution.
2024-07-25 00:10:48 -04:00
a7b98cad02 Add keybinding for gptel-abort 2024-07-24 16:19:58 -04:00
8d7dec78f5 Add shell command directive to gptel configuration
This change introduces a new directive for generating shell commands to the gptel configuration. The directive specifies that the response should be plain text with no additional description or formatting.
2024-07-24 16:09:16 -04:00
0adc712d2f Update ollama models
Changed the model names from "llama3:latest" and "mistral:latest" to "llama3.1:latest" and "mistral-nemo:latest" respectively in the GPTel Ollama backend configuration.
2024-07-24 16:00:22 -04:00
28da68fcd1 Prevent SVG rendering in EWW buffer
Added functionality to ignore SVG files during rendering in EWW to address rendering issues associated with SVGs.
2024-07-24 16:00:10 -04:00
a37891f4fc Configure webjump 2024-07-24 14:21:08 -04:00
906560dae0 Add key binding for generating git commit messages 2024-07-24 13:01:19 -04:00
0973393714 Add vterm AWS profile switch command and keybinding
Added a new function `vterm-aws-switch-profile` to switch AWS profiles in the current vterm buffer and associated it with the keybinding `C-c a` for quick access. This facilitates easier management of AWS profiles within vterm sessions.
2024-07-24 12:08:14 -04:00
8ad4c70070 Add bookmark support and keybindings to EWW in Emacs
This commit adds the ability to access EWW bookmarks. It modifies the init-eww.el configuration file by introducing a function to visit bookmarks and updating the keybindings to include commands for accessing and listing bookmarks.
2024-07-24 11:41:07 -04:00
4a7ccb378d Only load embark target finder for forge in Magit buffers 2024-07-24 11:26:17 -04:00
05d94e2646 Refines commit message prompt directive
Updated the guidelines for generating commit messages to avoid using the word 'enhance' and user experience references, and request plain text responses without syntax.
2024-07-24 10:42:04 -04:00
77138e6dc9 Handle AWS SSO login buffer cleanup
Added process sentinel to close the *aws-sso-login* buffer when the AWS SSO login process finishes
2024-07-24 10:40:08 -04:00
f9b49ef7b6 Autoload gptel-request 2024-07-24 10:28:22 -04:00
3141955f9c Add language detection to YAML scalar editing
Integrated language-detection to automatically detect and set major modes based on buffer/string content. Enhanced YAML editing with language detection for buffer initialization.
2024-07-24 10:26:33 -04:00
d60fa4e873 Move eww init to its own file and add syntax highlighting to pre tags 2024-07-24 00:25:57 -04:00
ef62f0c172 Set up evil-collection keybindings for ibuffer 2024-07-23 16:40:46 -04:00
a4f90da304 Add consult-line binding to embark-general-map
Added the "/" key binding in embark-general-map to trigger consult-line, enhancing quick line searching functionality.
2024-07-23 13:18:30 -04:00
454ffb7d33 Add EWW functions for URL handling and buffer renaming
This commit introduces three new custom functions for EWW: `eww-rename-buffer-to-title` for renaming buffers based on page titles, `eww-capture-urls-on-page` to capture all links on the current page, `eww-visit-url-on-page` to select and visit links using completion, and `eww-jump-to-url-on-page` to jump to URLs on the page. These enhancements improve the usability and navigation of EWW.
2024-07-23 13:18:09 -04:00
f3353bb124 Add eww configuration and custom URL prompt function
This commit adds configuration for the built-in `eww` web browser in Emacs. It introduces a custom prompting function that suggests URLs or keywords based on the browsing history, current URL, and other suggestions, enhancing the user experience. Additionally, a key binding for the `eww` function is added in `eww-mode-map`.
2024-07-23 11:00:57 -04:00
f49adb3b73 Update OpenAI reference to ChatGPT in init-ai.el 2024-07-21 08:06:51 -04:00
9fb96b3099 Refactor gptel backend selection and configuration
Reorganized backend definitions and selection process for gptel, introducing `gptel-backend-openai` and `gptel-backend-ollama` variables. Simplified backend selection logic and improved the `gptel-select-backend` function to dynamically prompt for models.
2024-07-21 00:49:29 -04:00
70599ad053 Add ace-link navigation configuration 2024-07-19 23:45:01 -04:00
ea7185c359 Get rid of spotify git hosts 2024-07-19 23:40:35 -04:00
ac0b59b569 Add eww and arc-mode setup to evil-collection 2024-07-19 23:40:21 -04:00
91ce6952ea Set avy-style to pre 2024-07-19 23:40:14 -04:00
6b9161a797 Add AI keybindings 2024-07-19 23:40:04 -04:00
31220b41b9 Support multiple GPT backends in gptel config
Enhanced the gptel configuration to support multiple backends (Ollama and OpenAI) and added a function to select the desired backend. Default backend is set to OpenAI.
2024-07-16 22:42:22 -04:00
9360580a94 Add Forge diff-for-pr function for pull request creation
This commit adds a new function forge-diff-for-pr to generate a Git diff for use in pull request creation. The function is called automatically when creating a pull request using Forge, providing an easy way to view changes between the source and target branches.
2024-07-16 14:24:30 -04:00
3537134c23 Add Terraform console functionality to Emacs.
This commit adds the ability to run a Terraform console at a specified path in a comint buffer, making it easier to interactively explore and manage Terraform state.
2024-07-16 14:24:16 -04:00
be07e673f9 Update gptel-commit-message prompt 2024-07-16 14:23:02 -04:00
bcb8d468a6 Add function to generate commit messages with gptel 2024-07-15 16:16:11 -04:00
0b8be7b2f7 Add function to jump to tf plan output 2024-07-15 16:16:11 -04:00
8b75209521 Remove casual-calc 2024-07-13 09:27:45 -04:00
57203210b8 Source local zshrc if exists 2024-07-11 11:30:22 -04:00
e29116f2f6 Add utility function to render ansi colors in a file 2024-07-09 13:20:24 -04:00
f394365ff9 Replace pixel-scroll with ultra-scroll-mac 2024-07-09 13:20:13 -04:00
fbc12a5434 Add casual-calc 2024-07-09 10:42:36 -04:00
6a33416fad Add "ask AI" mu4e action 2024-07-08 11:06:39 -04:00
8ac32f0955 Add posframe 2024-07-03 16:15:18 -04:00
e0f367faad Add gptel-quick 2024-07-03 16:09:55 -04:00
ccaf60abbd Add dbg macro 2024-07-03 15:21:40 -04:00
893a673616 Add gptel 2024-07-03 15:21:34 -04:00
9acc16c9aa Add some utility AI replacement functions 2024-07-02 15:07:57 -04:00
11755d6201 Add spinner indicating when the AI is responding, and inhibit input during that time 2024-07-02 14:41:14 -04:00
211904a190 Fix terraform devdocs eldoc docs 2024-06-28 13:58:29 -04:00
f8c3f76cbd Add ollama-copilot-mode to switch to local LLM for copilot 2024-06-28 13:21:47 -04:00
c140740a32 When prompting about a whole buffer, don't put whole buffer in chat 2024-06-27 21:49:16 -05:00
78bc060018 Open a new Llama chat if you call llama-ask* without an existing chat 2024-06-18 16:00:09 -04:00
93bc223e5b Fix plist header 2024-06-18 15:03:14 -04:00
b13cc823f5 Add witchcraft script injector 2024-06-18 15:02:50 -04:00
f3c529781c Fix delete-file implementation 2024-06-18 12:35:05 -04:00
68955452a5 Add dape for debugging 2024-06-13 13:49:30 -04:00
7aea6199cd Fix org-daily capture template 2024-06-13 13:49:23 -04:00
5f63c40b97 Use quit-window instead of bury-buffer 2024-06-13 13:49:10 -04:00
33cd6207f9 Enable evil-mode in the minibuffer 2024-06-06 16:04:19 -04:00
2960fc5ad1 Set up Avy 2024-06-06 11:49:48 -04:00
78dc31a1d9 Use pixel-scroll-precision-mode to scroll by pixel instead of by line. 2024-06-06 11:49:31 -04:00
5362bb63e3 Don't let switch-to-buffer mess with window layout 2024-06-06 09:50:17 -04:00
f993466d81 Run rspec in spec root if found 2024-06-06 09:50:08 -04:00
5169285be0 Add comfy-ui command 2024-06-05 13:13:57 -04:00
f1698e3247 Remove eval, add filter mechanism 2024-05-29 14:36:26 -04:00
d36967e371 Minor tweaks 2024-05-24 01:03:44 -04:00
8db9cc7e8f Make a version of llama-replace-in-region that does not have context 2024-05-23 22:59:43 -04:00
6191654511 Give the AI the ability to evaluate elisp 2024-05-23 18:31:13 -04:00
cdd674fdcd Many more llama improvement 2024-05-23 13:28:24 -04:00
e91b1d6480 Make llama chat nicer 2024-05-23 10:49:23 -04:00
8ae6544709 Use correct straight-use-package recipe format for a local repo 2024-05-23 00:53:32 -04:00
cdd892d4e1 Switch out llm for llama 2024-05-23 00:53:18 -04:00
5faa99158d Make the default llm chat aware that its inside of Emacs 2024-05-21 11:01:13 -04:00
b231ec9327 Add new autoloaded commands to use-package declaration and set default model 2024-05-21 10:27:02 -04:00
3d39b0c3df Fix llm output buffer sentinal formatting and add autoloads 2024-05-21 10:26:45 -04:00
df79eee678 Add llm-chat command and improve prompt code structure 2024-05-20 11:33:32 -04:00
c618c4d3ce Use llama3 as default llm 2024-05-17 13:30:27 -04:00
3c09eb3cbd Enable llm.el to work with ollama models 2024-05-17 13:30:09 -04:00
71c86332c5 Enable copilot in yaml- and json-ts-modes 2024-05-17 13:29:52 -04:00
87584357a9 Set some customizations for llm integration 2024-05-16 10:37:26 -04:00
f4da3fc9e2 Add interface to the llm command-line tool 2024-05-15 13:28:36 -04:00
ef0a072d18 Extend daily note to be able to prompt for date 2024-05-14 11:54:58 -04:00
33c8999722 Support more than 2 captured treesit nodes 2024-05-11 12:28:11 -04:00
9f4e0f88ad Use an alist to encapsulate terraform block metadata 2024-05-10 23:40:36 -04:00
3ba2b5940a Revert "Add recursive tf init function"
This reverts commit 6564c857cf.
2024-05-10 16:07:05 -04:00
e72ae38079 Fix terraform browse documentation command 2024-05-10 16:06:45 -04:00
da98498fd4 Add command to browse a URL or google the symbol at point 2024-05-10 16:06:41 -04:00
232f8053f0 Differentiate between resources and data for terraform devdocs eldoc 2024-05-10 12:31:41 -04:00
b0b5048176 Add devdocs-based eldoc for terraform 2024-05-09 14:38:14 -04:00
6564c857cf Add recursive tf init function 2024-05-09 12:33:59 -04:00
d7d2b7cb1f Add terraform to Brewfile 2024-05-08 13:26:22 -04:00
055bc788e5 Use gfm-mode for markdown files 2024-05-08 13:26:12 -04:00
9d967bfa7f Tell copilot that commit messages and forge posts are markdown 2024-05-08 13:25:56 -04:00
a274d3ba9b Add hcl and sql treesit parsers 2024-05-08 13:25:41 -04:00
650a90b812 Make origami-treesit a proper minor mode 2024-05-08 10:59:18 -04:00
d46352276a Add vcv rack to brewfile 2024-05-08 10:36:57 -04:00
889abf2606 Add folding for ruby hash pairs and fix folding for calls 2024-05-08 10:36:41 -04:00
a0c0f6d1e2 Add ruby support to origami-treesit and make folding level configurable 2024-05-07 16:58:51 -04:00
0209cf16c5 Make which-key more useful 2024-05-07 11:12:22 -04:00
5d1eae825f Add coreutils to Brewfile 2024-05-07 11:12:13 -04:00
86779c79bc Delete old-emacs 2024-05-07 11:12:06 -04:00
12c22a5dfb Include rake tasks without descriptions in run-command recipe 2024-05-06 15:47:36 +00:00
e1e27ab1a2 Add yaml-pro keybinds 2024-05-06 11:04:58 -04:00
81c2ad6963 Fix navi behavior when path doesn't exist and autoloads 2024-05-06 10:02:59 -04:00
a9ea6b4b2f Enable treesit folding in regular yaml-mode 2024-05-05 20:36:28 -04:00
a544811fae Benchmark init and optimize it a bit 2024-05-04 23:05:38 -04:00
9fdc85de65 Fix straight use-package invocation for navi 2024-05-04 22:03:40 -04:00
f7c927bca1 Make origami-treesit more generic 2024-05-04 22:02:51 -04:00
81106aae04 Autoload Navi class definitions 2024-05-03 21:50:11 -04:00
7c67af0b5f Write a treesit-based origami parser for yaml 2024-05-03 21:40:25 -04:00
178b7038ca Add proper parent to embark navi keymap 2024-05-03 21:40:12 -04:00
3a74967ee3 Use yaml-ts-mode and yaml-pro-mode instead of yaml-language-server 2024-05-03 21:39:54 -04:00
3f7dc1c744 Switch to origami instead of hideshow 2024-05-03 21:39:39 -04:00
b80b256786 Add ielm config to buffer-display-alist 2024-05-03 15:29:39 -04:00
7a74212089 Write a navi integration 2024-05-03 15:29:31 -04:00
3108252710 Set some options more sensibly 2024-05-02 09:26:03 -04:00
d097754395 Add evil-string-inflection 2024-05-01 16:57:29 -04:00
b63422050d Add some console roguelikes 2024-04-29 16:24:06 -04:00
7fb37be85f Add keybinding to send arbitrary keystrokes to vterm 2024-04-29 16:23:53 -04:00
86caa02c47 Configure plantuml and mermaid 2024-04-29 12:25:43 -04:00
2482d0d278 buffer-display-alist config for info 2024-04-29 10:11:32 -04:00
b3ef3caed9 Add neovide 2024-04-29 10:11:26 -04:00
2d35ed3405 Add function to jump between Ruby source and spec file 2024-04-29 10:11:10 -04:00
b8a895808d Set up hippie-expand 2024-04-29 10:10:57 -04:00
43204e2ced Improve target handling for forge embark target finder 2024-04-23 12:00:27 -04:00
d6fdb649ce Restore xref embark actions being the default only in prog modes 2024-04-23 12:00:19 -04:00
071aa788c8 Open help-like buffers in side window 2024-04-22 20:01:27 -04:00
5e7421d36c Add postman to Brewfile 2024-04-22 17:50:18 -04:00
357fb6b56b Prefer splitting windows horizontally 2024-04-22 17:47:48 -04:00
f36747325c Add buffer/window management 2024-04-22 17:19:32 -04:00
80d3130492 Configure re-builder 2024-04-22 17:19:25 -04:00
2e13524a2f Add embark keybindings for password-store buffer 2024-04-22 13:22:12 -04:00
6b13ad12d3 Don't mess with the embark target finders order 2024-04-22 12:53:10 -04:00
1005bb6feb Add embark keymap for Forge actions 2024-04-22 12:52:58 -04:00
52 changed files with 2892 additions and 7327 deletions

View File

@ -0,0 +1,329 @@
;; -*- lexical-binding: t; -*-
(defvar ai-map (make-sparse-keymap))
(define-key leader-map "a" (cons "ai" ai-map))
(use-package gptel
:commands (gptel-request)
:config
(defvar gptel-backend-openai (gptel-make-openai "ChatGPT"
:key #'gptel-api-key-from-auth-source
:stream t
:models '("gpt-4o"
"gpt-4o-mini")))
(defvar gptel-backend-ollama (gptel-make-ollama "Ollama"
:stream t
:models '("llama3.1:latest"
"mistral-nemo:latest"
"gemma2:2b")))
(setq gptel-backend gptel-backend-openai
gptel-model "gpt-4o")
(defun gptel-select-backend (backend)
(interactive (list (cl-loop
for (name . backend) in gptel--known-backends
nconc (cl-loop for model in (gptel-backend-models backend)
collect (list (concat name ":" model) backend model))
into models-alist finally return
(cdr (assoc (completing-read "Backend: " models-alist nil t)
models-alist)))))
(setq gptel-backend (car backend)
gptel-model (cadr backend)))
(add-to-list 'gptel-directives '(shell-command . "You are a command line helper. Generate shell commands that do what is requested, without any additional description or explanation. Reply in plain text with no Markdown or other syntax. Reply with the command only."))
(add-to-list 'gptel-directives '(org-mode . "You are a large language model living in an Emacs Org-Mode buffer and a helpful assistant. You may evaluate Emacs Lisp, Python, and shell-script code when necessary by outputting an Org-Mode source block. You don't need to ask for confirmation before evaluating code. The user will execute the source block and display the results in the buffer. Respond concisely.
Some notes on code evaluation:
Source code blocks should have the following format:
#+begin_src <lang>
<code>
#+end_src
where <lang> is the language of the code block, e.g., emacs-lisp, python, or sh.
Do not return the results of the source block - the user will evaluate the code and display the results in the buffer.
You can use Emacs Lisp code blocks to evaluate code in the Emacs process you are running in, for example to open files for the user. For Emacs Lisp code blocks, the return value will be whatever the last expression in the block evaluates to, e.g.:
#+begin_src emacs-lisp
(+ 1 2)
#+end_src
#+RESULTS:
: 3
For shell-script code blocks, the return value will be the output of the script, e.g.:
#+begin_src sh
echo foo
echo bar
#+end_src
#+RESULTS:
| foo |
| bar |
For Python code blocks, you only have access to the Python standard library, and cannot use any third-party libraries. Additionally, the return value needs to be explicitly returned using the return keyword, e.g.:
#+begin_src python
return 1 + 2
#+end_src
#+RESULTS:
: 3
Here are some examples of your task:
User: What's the current date and time?
Assistant:
#+begin_src emacs-lisp
(format-time-string \"%Y-%m-%d %H:%M:%S\")
#+end_src
User:
#+RESULTS:
: 2024-08-07 15:26:55
User: Can you find the square root of 144 in Python?
Assistant:
#+begin_src python
import math
return math.sqrt(144)
#+end_src
User:
#+RESULTS:
: 12.0
User: List all files in the current directory.
Assistant:
#+begin_src sh
ls
#+end_src
User:
#+RESULTS:
| Dockerfile |
| Gemfile |
| Gemfile.lock |
| README.md |
| Rakefile |
| app |
| bin |
| db |
| demo |
| docker |
| docs |
| lib |
User: What is the capital of France?
Assistant: The capital of France is Paris.
User:
Convert 68 degrees F to C
Assistant:
#+begin_src python
def fahrenheit_to_celsius(f):
return (f - 32) * 5.0/9.0
return fahrenheit_to_celsius(68)
#+end_src
User:
#+RESULTS:
: 20.0
User: How do I search for a string in Emacs?
Assistant: You can search for a string in Emacs by using =C-s= (Control + s) to start an incremental search. As you type the string you want to search, Emacs will highlight matches in real-time. To find the next occurrence, press =C-s= again. If you want to search backwards, use =C-r= (Control + r)."))
(add-to-list 'gptel-directives '(stable-diffusion . "You are an AI assistant specialized in creating precise and detailed prompts for stable diffusion image generators. When given a natural language input describing a desired image, you will generate a clear, concise, and highly descriptive prompt that includes key elements such as subjects, actions, environments, styles, lighting, and other relevant details to ensure high-quality image generation.
Example input: \"A fantasy landscape with a castle and dragons\"
Example output: \"A majestic medieval castle perched on a hilltop, surrounded by flying dragons, under a moonlit sky, with a serene forest in the background. Fantasy art style, detailed architecture, and vibrant colors.\""))
(add-to-list 'gptel-directives '(code-review . "You are a code reviewer. Provide feedback on the code snippet below. Highlight any issues, suggest improvements, and provide explanations for your suggestions. Respond in plain text with no Markdown or other syntax."))
(add-to-list 'gptel-directives '(prompt-generator . "
You are an advanced language model designed to generate effective, clear, and contextually appropriate prompts for other language models. Your goals are to:
1. Understand the specific use case or goal provided.
2. Generate prompts that are clear, specific, and actionable.
3. Ensure that prompts are open-ended enough to allow for creativity, yet focused enough to produce relevant responses.
4. Maintain a formal and professional tone unless instructed otherwise.
5. Tailor prompts to maximize the potential and unique capabilities of the language models they are intended for.
Heres an example of your task:
User: Write a prompt to write a creative short story involving a dragon.
Assistant: Write a short story about a dragon who discovers a hidden talent that surprises everyone in the dragon kingdom. Describe the dragon's journey and the reactions of those around it."))
(defun gptel-org (buffer &optional _ initial interactivep)
(interactive (let* ((backend (default-value 'gptel-backend))
(backend-name
(format "*%s*" (gptel-backend-name backend))))
(list (read-buffer "Create or choose gptel buffer: "
(generate-new-buffer-name backend-name) nil ; DEFAULT and REQUIRE-MATCH
(lambda (b) ; PREDICATE
(let ((buffer (get-buffer (or (car-safe b) b))))
(and
(with-current-buffer buffer (eq major-mode 'org-mode))
(buffer-local-value 'gptel-mode buffer)))))
(condition-case nil
(gptel--get-api-key
(gptel-backend-key backend))
((error user-error)
(setq gptel-api-key
(read-passwd
(format "%s API key: " backend-name)))))
(and (use-region-p)
(buffer-substring (region-beginning)
(region-end)))
t)))
(let ((gptel-default-mode #'org-mode))
(gptel buffer nil initial interactivep))
(with-current-buffer buffer
(setq-local gptel--system-message (alist-get 'org-mode gptel-directives))))
(defun gptel-chat-with-buffer (&optional arg interactivep)
(interactive (list current-prefix-arg t))
(let* ((name (format "*gptel: %s*"(buffer-name)))
(buffer (if arg
(generate-new-buffer name)
name)))
(gptel buffer nil (buffer-string) interactivep)
(with-current-buffer buffer
(goto-char (point-max))
(newline)
(insert (gptel-prompt-prefix-string)))))
:general
("C-c RET" #'gptel-send
"C-c C-<return>" #'gptel-menu)
(ai-map
"g" #'gptel
"o" #'gptel-org
"s" #'gptel-send
"m" #'gptel-menu
"b" #'gptel-chat-with-buffer
"B" #'gptel-select-backend
"a" #'gptel-context-add
"f" #'gptel-context-add-file
"k" #'gptel-abort))
(use-package gptel-quick
:straight (:type git :host github :repo "karthink/gptel-quick")
:commands gptel-quick
:general
(embark-general-map
"?" #'gptel-quick)
(ai-map
"?" #'gptel-quick))
(use-package aimenu
:straight `(:local-repo ,(expand-file-name "packages/aimenu" user-emacs-directory) :type nil)
:defer t
:config
(setq aimenu-gptel-backend gptel-backend-openai
aimenu-gptel-model "gpt-4o-mini")
:general
(ai-map "i" #'aimenu))
(defun gptel-commit-message ()
"Generate a commit message via gptel."
(interactive)
(unless git-commit-mode
(user-error "Not in a git commit buffer!"))
(let* ((diff-buf (magit-get-mode-buffer 'magit-diff-mode))
(diff (with-current-buffer diff-buf
(buffer-substring-no-properties
(point-min)
;; Skip the last line, which is just the [back] button
(save-excursion
(goto-char (point-max))
(forward-line -1)
(point)))))
(prompt (format "%s\n\nWrite a clear, concise commit message for this diff. The first line should succinctly summarize the changes made and should be no more than 50 characters. If additional context is needed, include it in an additional paragraph separate by a blank line from the first line. Do not use the word 'enhance' or talk about the user experience. Be specific. Reply in plain text with no Markdown or other syntax. Reply with the commit message only." diff)))
(message "Generating commit message...")
(gptel-request prompt
:stream t
:system "You are a professional software engineer.")))
(with-eval-after-load 'git-commit
(keymap-set git-commit-mode-map "C-c RET" #'gptel-commit-message))
(defvar comfy-ui-path (expand-file-name "~/ComfyUI")
"Path to ComfyUI source repository.")
(defvar comfy-ui-command (list "pipenv" "run" "python" "main.py")
"Command to run ComfyUI server.")
(defvar-local comfy-ui--url nil
"URL for this buffer's ComfyUI process.")
(defun comfy-ui-process-filter (proc string)
(when-let ((match (s-match "To see the GUI go to: \\(.*\\)" string)))
(with-current-buffer (process-buffer proc)
(setq comfy-ui--url (nth 1 match))
(let ((browse-url-browser-function #'browse-url-default-browser))
(browse-url comfy-ui--url))))
(when (buffer-live-p (process-buffer proc))
(with-current-buffer (process-buffer proc)
(let ((moving (= (point) (process-mark proc))))
(save-excursion
;; Insert the text, advancing the process marker.
(goto-char (process-mark proc))
(insert string)
(set-marker (process-mark proc) (point)))
(if moving (goto-char (process-mark proc)))))))
(defun comfy-ui ()
"Launch Comfy UI in a subprocess and opens the web UI."
(interactive)
(unless (file-exists-p (expand-file-name (f-join comfy-ui-path "main.py")))
(user-error "Could not find ComfyUI installation!"))
(if-let ((proc (get-process "comfy-ui")))
(with-current-buffer (process-buffer proc)
(browse-url comfy-ui--url))
(with-temp-buffer
(cd comfy-ui-path)
(make-process :name "comfy-ui"
:buffer "*ComfyUI*"
:command comfy-ui-command
:filter #'comfy-ui-process-filter))))
(defvar ollama-copilot-proxy-port 11435
"Port for the Ollama Copilot proxy server.")
(defvar ollama-copilot-model "codellama:code"
"Model for the Ollama Copilot proxy server.")
(defun ollama-copilot-ensure ()
"Start the Ollama Copilot proxy server if it's not already running."
(let ((proc-name "ollama-copilot"))
(unless (get-process proc-name)
(unless (executable-find "ollama-copilot")
(user-error "Could not find ollama-copilot executable!"))
(make-process :name proc-name
:buffer (format "*%s*" proc-name)
:command `("ollama-copilot"
"-proxy-port" ,(format ":%s" ollama-copilot-proxy-port)
"-model" ,ollama-copilot-model)))))
(defvar ollama-copilot--proxy-cache nil
"Internal variable to cache the old proxy value.")
(define-minor-mode ollama-copilot-mode
"Minor mode to use ollama-copilot as a local Copilot proxy."
:global t
(require 'copilot)
(if ollama-copilot-mode
(progn
(ollama-copilot-ensure)
(setq ollama-copilot--proxy-cache copilot-network-proxy)
(setq copilot-network-proxy `(:host "127.0.0.1"
:port ,ollama-copilot-proxy-port
:rejectUnauthorized :json-false))
(copilot-diagnose))
(setq copilot-network-proxy ollama-copilot--proxy-cache)
(copilot-diagnose)))
(provide 'init-ai)

View File

@ -28,9 +28,27 @@
(vterm-send-string (format "export AWS_PROFILE=%s\n" profile))) (vterm-send-string (format "export AWS_PROFILE=%s\n" profile)))
(setq aws-current-profile profile)) (setq aws-current-profile profile))
(defun vterm-aws-switch-profile (profile)
"Switch the AWS profile for the current vterm command."
(interactive (list (completing-read "Profile: " (aws-list-profiles))))
(vterm-send-C-a)
(vterm-send-string (format "AWS_PROFILE=%s " profile)))
(with-eval-after-load 'vterm
(define-key vterm-mode-map (kbd "C-c a") #'vterm-aws-switch-profile))
(defun aws-sso-login () (defun aws-sso-login ()
(interactive) (interactive)
(async-shell-command "AWS_PROFILE=default aws sso login")) (let ((buffer "*aws-sso-login*"))
(async-shell-command "AWS_PROFILE=default aws sso login" buffer)
(with-current-buffer buffer
(let ((proc (get-buffer-process (current-buffer))))
(set-process-sentinel
proc
(lambda (proc sentinel)
(when (string-match-p "finished" sentinel)
(with-selected-window (get-buffer-window (process-buffer proc) t)
(quit-window)))))))))
(add-hook 'emacs-startup-hook (add-hook 'emacs-startup-hook
(lambda () (lambda ()

View File

@ -29,7 +29,7 @@
(use-package shell (use-package shell
:straight (:type built-in) :straight (:type built-in)
:general :general
(normal shell-mode-map "q" #'bury-buffer)) (normal shell-mode-map "q" #'quit-window))
(use-package ediff (use-package ediff
:defer t :defer t
@ -53,16 +53,21 @@
:defer t :defer t
:straight (:type built-in)) :straight (:type built-in))
(use-package hideshow
:straight (:type built-in)
:hook (prog-mode . hs-minor-mode)
:init
(general-def 'normal "zM" #'hs-hide-level))
(use-package man (use-package man
:straight (:type built-in) :straight (:type built-in)
:commands (man) :commands (man)
:custom :custom
(manual-program "gman")) (manual-program "gman"))
(use-package re-builder
:straight (:type built-in)
:commands (re-builder)
:custom
(reb-re-syntax 'string))
(use-package hippie-expand
:straight (:type built-in)
:general
([remap dabbrev-expand] #'hippie-expand))
(provide 'init-built-ins) (provide 'init-built-ins)

View File

@ -62,9 +62,11 @@
;; Embark adds context actions to completion candidates (and other things!) ;; Embark adds context actions to completion candidates (and other things!)
(use-package embark (use-package embark
:config :config
;; Make sure identifier at point is the first target ;; Put xref targets at the top of the list in programming modes
(setq embark-target-finders (remove 'embark-target-identifier-at-point embark-target-finders)) (defun embark-prod-mode-hook ()
(add-to-list 'embark-target-finders 'embark-target-identifier-at-point) (setq-local embark-target-finders (remove #'embark-target-identifier-at-point embark-target-finders))
(add-to-list 'embark-target-finders #'embark-target-identifier-at-point))
(add-hook 'prog-mode-hook #'embark-prod-mode-hook)
(defun embark-which-key-indicator () (defun embark-which-key-indicator ()
"An embark indicator that displays keymaps using which-key. "An embark indicator that displays keymaps using which-key.
The which-key help message will show the type and value of the The which-key help message will show the type and value of the
@ -99,10 +101,18 @@ targets."
"n" #'straight-normalize-package "n" #'straight-normalize-package
"m" #'straight-merge-package) "m" #'straight-merge-package)
(add-to-list 'embark-keymap-alist '(straight . embark-straight-map)) (add-to-list 'embark-keymap-alist '(straight . embark-straight-map))
(with-eval-after-load 'pass
(defun embark-target-finder-pass ()
"Identify password-store entries at point."
(when-let ((entry (and (eq major-mode 'pass-mode)
(pass-closest-entry))))
`(password-store . ,entry)))
(add-to-list 'embark-target-finders #'embark-target-finder-pass)
(defvar-keymap embark-password-store-actions (defvar-keymap embark-password-store-actions
:doc "Keymap for actions for password-store." :doc "Keymap for actions for password-store."
:parent embark-general-map :parent embark-general-map
"c" #'password-store-copy "c" #'password-store-copy
"RET" #'password-store-copy
"f" #'password-store-copy-field "f" #'password-store-copy-field
"i" #'password-store-insert "i" #'password-store-insert
"I" #'password-store-generate "I" #'password-store-generate
@ -110,13 +120,36 @@ targets."
"e" #'password-store-edit "e" #'password-store-edit
"k" #'password-store-remove "k" #'password-store-remove
"U" #'password-store-url) "U" #'password-store-url)
(add-to-list 'embark-keymap-alist '(password-store . embark-password-store-actions)) (add-to-list 'embark-keymap-alist '(password-store . embark-password-store-actions)))
(add-to-list 'embark-target-injection-hooks '(xref-find-references embark--ignore-target)) (add-to-list 'embark-target-injection-hooks '(xref-find-references embark--ignore-target))
(defun embark-target-finder-forge ()
"Identify Forge commits/issues/PRs at point."
(when-let ((target (and
(fboundp 'forge--browse-target)
(forge--browse-target))))
`(forge ,(or
(and (stringp target) target)
(ignore-errors (forge-get-url target))
(s-chomp (thing-at-point 'line t)))
,(line-beginning-position)
. ,(line-end-position))))
(defun embark-forge-magit-setup ()
(make-local-variable 'embark-target-finders)
(add-to-list 'embark-target-finders #'embark-target-finder-forge))
(add-hook 'magit-mode-hook #'embark-forge-magit-setup)
(defvar-keymap embark-forge-actions
:doc "Keymap for actions for forge."
:parent embark-general-map
"RET" #'forge-browse
"y" #'forge-copy-url-at-point-as-kill)
(add-to-list 'embark-keymap-alist '(forge . embark-forge-actions))
:general :general
((emacs normal motion insert visual) "C-." #'embark-act) ((emacs normal motion insert visual) "C-." #'embark-act)
((emacs normal motion insert visual) "M-." #'embark-dwim) ((emacs normal motion insert visual) "M-." #'embark-dwim)
("C-." #'embark-act) ("C-." #'embark-act)
("M-." #'embark-dwim) ("M-." #'embark-dwim)
(embark-general-map "C-k" #'browse-url-or-search
"/" #'consult-line)
(embark-file-map "s" #'sudo-edit-find-file (embark-file-map "s" #'sudo-edit-find-file
"l" #'vlf "l" #'vlf
"g" #'magit-file-dispatch) "g" #'magit-file-dispatch)

View File

@ -1,7 +1,7 @@
;; -*- lexical-binding: t; -*- ;; -*- lexical-binding: t; -*-
;; Icons! ;; Icons!
(use-package all-the-icons) (use-package nerd-icons)
;; A neat dashboard when you open Emacs ;; A neat dashboard when you open Emacs
(use-package dashboard (use-package dashboard
@ -12,6 +12,8 @@
(setq dashboard-startup-banner pic))) (setq dashboard-startup-banner pic)))
(dashboard-setup-startup-hook) (dashboard-setup-startup-hook)
:custom :custom
(dashboard-display-icons-p t)
(dashboard-icon-type 'nerd-icons)
(dashboard-items '((projects . 5) (recents . 5))) (dashboard-items '((projects . 5) (recents . 5)))
(dashboard-set-heading-icons t) (dashboard-set-heading-icons t)
(dashboard-set-file-icons t) (dashboard-set-file-icons t)

View File

@ -23,7 +23,9 @@
(enable-recursive-minibuffers t) (enable-recursive-minibuffers t)
(vc-follow-symlinks t) (vc-follow-symlinks t)
(display-line-numbers-type 'visual) (display-line-numbers-type 'visual)
(even-window-sizes nil)) (even-window-sizes nil)
(fill-column 110)
(unique-buffer-name-style 'forward))
(provide 'init-defaults) (provide 'init-defaults)

View File

@ -5,7 +5,28 @@
devdocs-lookup devdocs-lookup
devdocs-install devdocs-install
devdocs-delete devdocs-delete
devdocs-update-all) devdocs-update-all
devdocs-as-string)
:config
(defvar devdocs-entry-aliases nil
"Alist mapping expected DevDocs entry names to their actual names.")
(defun devdocs-as-string (entry doc &optional type-rx)
"Returns the devdocs for ENTRY in DOC as a string."
(let* ((entry (if (symbolp entry) (symbol-name entry) entry))
(entry (or (alist-get entry devdocs-entry-aliases nil nil #'equal) entry))
(docs (devdocs--doc-metadata doc))
(entries (devdocs--entries (list docs))))
(when-let ((entry-data (seq-find (lambda (e)
(let ((case-fold-search t)
(type (alist-get 'type (devdocs--get-data e))))
(if type-rx
(and (string-match type-rx type)
(string-match entry e))
(string-match entry e))))
entries)))
(with-current-buffer (devdocs--render (devdocs--get-data entry-data))
(buffer-string)))))
:general :general
(help-map "D" #'devdocs-lookup) (help-map "D" #'devdocs-lookup)
('normal devdocs-mode-map ('normal devdocs-mode-map

View File

@ -7,6 +7,26 @@
:config :config
(ctrlf-mode 1)) (ctrlf-mode 1))
(use-package avy
:config
(with-eval-after-load 'embark
(defun avy-action-embark (point)
(unwind-protect
(save-excursion
(goto-char point)
(embark-act))
(select-window
(cdr (ring-ref avy-ring 0))))
t)
(setf (alist-get ?. avy-dispatch-alist)
#'avy-action-embark)
(add-to-list 'avy-dispatch-alist '(?\C-. . avy-action-embark)))
:bind
("M-f" . avy-goto-char-timer)
:custom
(avy-style 'pre))
;; "pair" management, where pairs are parentheses, braces, etc. ;; "pair" management, where pairs are parentheses, braces, etc.
(use-package smartparens (use-package smartparens
:init :init
@ -134,4 +154,102 @@
(normal "g M-+" #'evil-numbers/inc-at-pt-incremental) (normal "g M-+" #'evil-numbers/inc-at-pt-incremental)
(normal "g M--" #'evil-numbers/dec-at-pt-incremental)) (normal "g M--" #'evil-numbers/dec-at-pt-incremental))
;; Manipulate string inflection, e.g. camelCase -> snake_case
(use-package evil-string-inflection
:general
(normal "gc" #'evil-operator-string-inflection))
(use-package origami
:hook ((prog-mode . origami-mode)
(text-mode . origami-mode))
:config
(defun origami-toggle-all-nodes-same-level (buffer point)
(interactive (list (current-buffer) (point)))
(when-let ((tree (origami-get-fold-tree buffer)))
(when-let ((path (origami-fold-find-path-containing tree point)))
(let ((parent (origami-fold-parent path)))
(dolist (fold (origami-fold-children parent))
(origami-toggle-node buffer (origami-fold-beg fold)))))))
(defun origami-setup ()
(evil-local-set-key 'normal (kbd "zM") #'origami-toggle-all-nodes-same-level))
(add-hook 'origami-mode-hook #'origami-setup))
;; Language detection of arbitrary strings/buffers
(use-package language-detection
:commands (language-detection-buffer
language-detection-string)
:init
(defun language-detection-detect-mode (string)
(let* ((map '((ada ada-mode)
(awk awk-mode)
(c c-mode)
(cpp c++-mode)
(clojure clojure-mode lisp-mode)
(csharp csharp-mode java-mode)
(css css-mode)
(dart dart-mode)
(delphi delphi-mode)
(emacslisp emacs-lisp-mode)
(erlang erlang-mode)
(fortran fortran-mode)
(fsharp fsharp-mode)
(go go-mode)
(groovy groovy-mode)
(haskell haskell-mode)
(html html-mode)
(java java-mode)
(javascript javascript-mode)
(json json-mode javascript-mode)
(latex latex-mode)
(lisp lisp-mode)
(lua lua-mode)
(matlab matlab-mode octave-mode)
(objc objc-mode c-mode)
(perl perl-mode)
(php php-mode)
(prolog prolog-mode)
(python python-mode)
(r r-mode)
(ruby ruby-mode)
(rust rust-mode)
(scala scala-mode)
(shell shell-script-mode)
(smalltalk smalltalk-mode)
(sql sql-mode)
(swift swift-mode)
(visualbasic visual-basic-mode)
(xml sgml-mode)))
(language (language-detection-string string))
(modes (cdr (assoc language map)))
(mode (cl-loop for mode in modes
when (fboundp mode)
return mode)))
(when (fboundp mode)
mode)))
(defun fontify-with-mode (mode text)
(with-temp-buffer
(insert text)
(delay-mode-hooks (funcall mode))
(font-lock-default-function mode)
(font-lock-default-fontify-region (point-min) (point-max) nil)
(buffer-string)))
(defun fontify-using-faces (text)
(let ((pos 0))
(while (setq next (next-single-property-change pos 'face text))
(put-text-property pos next 'font-lock-face (get-text-property pos 'face text) text)
(setq pos next))
(add-text-properties 0 (length text) '(fontified t) text)
text))
(defun language-detection-fontify-region (start end)
(interactive "r")
(let* ((text (buffer-substring-no-properties start end))
(mode (language-detection-detect-mode text))
(fontified (fontify-using-faces (fontify-with-mode mode text))))
(delete-region start end)
(insert fontified)))
(defun language-detection-fontify-buffer ()
(interactive)
(language-detection-fontify-region (point-min) (point-max))))
(provide 'init-editing) (provide 'init-editing)

View File

@ -1,10 +1,19 @@
;; Email in Emacs via mu4e ;; Email in Emacs via mu4e
(use-package mu4e (use-package mu4e
:straight `(:local-repo ,(directory-file-name (file-name-directory (locate-library "mu4e"))) :type built-in) :straight `(:local-repo ,(directory-file-name (file-name-directory (locate-library "mu4e"))) :type built-in)
:defer 2
:if (locate-library "mu4e") :if (locate-library "mu4e")
:commands (mu4e mu4e-update-mail-and-index) :commands (mu4e mu4e-update-mail-and-index)
:hook (after-init . (lambda () (mu4e t)))
:config :config
(defun mu4e-action-ai (msg)
(let* ((subject (mu4e-message-field msg :subject))
(body (mu4e-view-message-text msg))
(prompt (read-string "AI prompt: " "Summarize the key points from this email in bullet points"))
(buffer (generate-new-buffer-name (format "*%s*" subject))))
(gptel buffer nil (format "%s\n\n%s" body prompt))
(with-current-buffer buffer
(gptel-send))
(display-buffer buffer)))
(setq (setq
;; General ;; General
mu4e-maildir (expand-file-name "~/.mail") mu4e-maildir (expand-file-name "~/.mail")
@ -21,7 +30,12 @@
mu4e-view-actions '(("capture message" . mu4e-action-capture-message) mu4e-view-actions '(("capture message" . mu4e-action-capture-message)
("view as pdf" . mu4e-action-view-as-pdf) ("view as pdf" . mu4e-action-view-as-pdf)
("show this thread" . mu4e-action-show-thread) ("show this thread" . mu4e-action-show-thread)
("View in browser" . mu4e-action-view-in-browser)) ("View in browser" . mu4e-action-view-in-browser)
("ask AI" . mu4e-action-ai))
mu4e-headers-actions '(("capture message" . mu4e-action-capture-message)
("browse online archive" . mu4e-action-browse-list-archive)
("show this thread" . mu4e-action-show-thread)
("ask AI" . mu4e-action-ai))
;; Bookmarked searches ;; Bookmarked searches
mu4e-bookmarks '((:name "Inbox" mu4e-bookmarks '((:name "Inbox"
:query "maildir:/jeremy-dormitzer-gmail-com/Inbox OR maildir:/jeremydormitzer-hummingbird-co/Inbox" :query "maildir:/jeremy-dormitzer-gmail-com/Inbox OR maildir:/jeremydormitzer-hummingbird-co/Inbox"
@ -509,6 +523,7 @@ If given prefix arg ARG, skips markdown conversion."
"t" #'mu4e-view-mark-thread "t" #'mu4e-view-mark-thread
"s" mu4e-search-map)) "s" mu4e-search-map))
(add-hook 'mu4e-headers-mode-hook #'my-mu4e-headers-mode-setup) (add-hook 'mu4e-headers-mode-hook #'my-mu4e-headers-mode-setup)
(add-hook 'mu4e-view-mode-hook #'my-mu4e-view-mode-setup)) (add-hook 'mu4e-view-mode-hook #'my-mu4e-view-mode-setup)
(mu4e t))
(provide 'init-email) (provide 'init-email)

View File

@ -50,6 +50,13 @@
eshell eshell
prodigy prodigy
docker docker
devdocs))) devdocs
vertico
minibuffer
eww
arc
ibuffer))
:custom
(evil-collection-setup-minibuffer t))
(provide 'init-evil) (provide 'init-evil)

View File

@ -0,0 +1,160 @@
;; -*- lexical-binding: t; -*-
(use-package eww
:straight (:type built-in)
:config
(defun eww-before-advice (&rest args)
(interactive
(let* ((uris (eww-suggested-uris))
(browser-history (mapcar (lambda (h) (plist-get h :url)) eww-history))
(bookmarks (mapcar (lambda (b) (plist-get b :url)) eww-bookmarks))
(suggestions (delete-dups (append uris eww-prompt-history browser-history)))
(current-uri (plist-get eww-data :url)))
(list (completing-read "URL or keywords: " suggestions nil nil current-uri 'eww-prompt-history)
current-prefix-arg))))
(advice-add 'eww :before #'eww-before-advice)
(defun eww-rename-buffer-to-title ()
(rename-buffer (format "*eww: %s*" (plist-get eww-data :title)) t))
(add-hook 'eww-after-render-hook #'eww-rename-buffer-to-title)
;; Copied from https://protesilaos.com/emacs/dotemacs#h:abc20037-7a4f-4555-809a-dc4165c5db6a
(defun eww-capture-urls-on-page (&optional position)
"Capture all the links on the current web page.
Return a list of strings. Strings are in the form LABEL @ URL.
When optional argument POSITION is non-nil, include position info
in the strings too, so strings take the form
LABEL @ URL ~ POSITION."
(let (links match)
(save-excursion
(goto-char (point-max))
;; NOTE 2021-07-25: The first clause in the `or' is meant to
;; address a bug where if a URL is in `point-min' it does not get
;; captured.
(while (setq match (text-property-search-backward 'shr-url))
(let* ((raw-url (prop-match-value match))
(start-point-prop (prop-match-beginning match))
(end-point-prop (prop-match-end match))
(url (when (stringp raw-url)
(propertize raw-url 'face 'link)))
(label (replace-regexp-in-string "\n" " " ; NOTE 2021-07-25: newlines break completion
(buffer-substring-no-properties
start-point-prop end-point-prop)))
(point start-point-prop)
(line (line-number-at-pos point t))
(column (save-excursion (goto-char point) (current-column)))
(coordinates (propertize
(format "%d,%d (%d)" line column point)
'face 'shadow)))
(when url
(if position
(push (format "%-15s ~ %s @ %s"
coordinates label url)
links)
(push (format "%s @ %s"
label url)
links))))))
links))
(defun eww-visit-url-on-page (&optional arg)
"Visit URL from list of links on the page using completion.
With optional prefix ARG (\\[universal-argument]) open URL in a
new EWW buffer."
(interactive "P")
(when (derived-mode-p 'eww-mode)
(let* ((links (eww-capture-urls-on-page))
(selection (completing-read "Browse URL: " links nil t))
(url (replace-regexp-in-string ".*@ " "" selection)))
(eww url (when arg 4)))))
(defun eww-jump-to-url-on-page (&optional arg)
"Jump to URL position on the page using completion."
(interactive "P")
(when (derived-mode-p 'eww-mode)
(let* ((links (eww-capture-urls-on-page t))
(prompt (format "Jump to URL: "))
(selection (completing-read prompt links nil t))
(position (replace-regexp-in-string "^.*(\\([0-9]+\\))[\s\t]+~" "\\1" selection))
(point (string-to-number position)))
(goto-char point))))
(defun eww-visit-bookmark (bookmark &optional arg)
"Visit BOOKMARK in EWW."
(interactive (list (let* ((bookmarks (mapcar (lambda (b)
(cons (format "%s | %s"
(plist-get b :title)
(plist-get b :url))
(plist-get b :url)))
eww-bookmarks))
(bookmark (completing-read "Bookmark: " bookmarks)))
(cdr (assoc bookmark bookmarks)))
current-prefix-arg))
(eww bookmark arg))
;; Don't try to render SVGs, for some reason they are not rendered correctly
(add-to-list 'shr-external-rendering-functions
'(svg . ignore))
;; https://github.com/alphapapa/unpackaged.el/commit/3b46f9c0e0195d78df8c4ca6e1953b69539e2844
(defun imenu-eww-headings ()
"Return alist of HTML headings in current EWW buffer for Imenu.
Suitable for `imenu-create-index-function'."
(let ((faces '(shr-h1 shr-h2 shr-h3 shr-h4 shr-h5 shr-h6 shr-heading)))
(save-excursion
(save-restriction
(widen)
(goto-char (point-min))
(cl-loop for next-pos = (next-single-property-change (point) 'face)
while next-pos
do (goto-char next-pos)
for face = (get-text-property (point) 'face)
when (cl-typecase face
(list (cl-intersection face faces))
(symbol (member face faces)))
collect (cons (buffer-substring (point-at-bol) (point-at-eol)) (point))
and do (forward-line 1))))))
(add-hook 'eww-mode-hook
(lambda ()
(setq-local imenu-create-index-function #'imenu-eww-headings)))
:general
(leader-map "E" #'eww)
(normal eww-mode-map
"go" #'eww
"gb" #'eww-visit-bookmark
"gB" #'eww-list-bookmarks
"gJ"#'eww-jump-to-url-on-page
"gV" #'eww-visit-url-on-page))
(use-package shr-tag-pre-highlight
:ensure t
:after shr
:config
(add-to-list 'shr-external-rendering-functions
'(pre . shr-tag-pre-highlight)))
(use-package browse-url
:straight (:type built-in)
:config
(defun browse-url-or-search (url-or-symbol)
"If URL-OR-SYMBOL is a URL, browse it. Otherwise, search for it."
(interactive (list (thing-at-point 'symbol)))
(if (ffap-url-p url-or-symbol)
(browse-url url-or-symbol)
(browse-url (format "https://www.google.com/search?q=%s" url-or-symbol))))
:custom
(browse-url-browser-function 'eww-browse-url)
(browse-url-handlers '(("\\`https?://docs.aws.amazon.com" . browse-url-default-browser)
("\\`https?://github.com" . browse-url-default-browser)
("\\`https?://.*.console.aws.amazon.com" . browse-url-default-browser))))
(use-package webjump
:straight (:type built-in)
:init
(defalias 'web-search #'webjump)
:general
(leader-map "S" #'web-search)
:custom
(webjump-sites '(("Emacs Wiki" . [simple-query "www.emacswiki.org" "www.emacswiki.org/cgi-bin/wiki/" ""])
("DuckDuckGo" . [simple-query "duckduckgo.com" "duckduckgo.com/?q=" ""])
("Wikipedia" . [simple-query "wikipedia.org" "https://www.wikipedia.org/search-redirect.php?language=en&go=Go&search=%s" ""])
("Google" . [simple-query "google.com" "https://www.google.com/search?ie=utf-8&oe=utf-8&q=" ""])
("StackOverflow" . [simple-query "stackoverflow.com" "https://stackoverflow.com/search?q=" ""])
("AWS Docs" . [simple-query "docs.aws.amazon.com" "https://docs.aws.amazon.com/search/doc-search.html?searchPath=documentation&searchQuery=" ""]))))
(provide 'init-eww)

View File

@ -2,15 +2,16 @@
;; File-related configuration ;; File-related configuration
(defun jdormit-delete-file (file) (defun my/delete-file (file)
(interactive (list (read-file-name "Delete file: " (interactive (list (read-file-name "Delete file: "
nil nil
nil nil
t t
(buffer-file-name)))) (file-name-nondirectory (buffer-file-name)))))
(let ((file (expand-file-name file)))
(delete-file file) (delete-file file)
(when (string-equal (buffer-file-name) file) (when (string-equal (buffer-file-name) file)
(bury-buffer))) (quit-window))))
(defun rename-this-file (new-name) (defun rename-this-file (new-name)
(interactive (list (read-file-name "Rename file: " (interactive (list (read-file-name "Rename file: "
@ -35,7 +36,7 @@
"f" '(nil :which-key "file") "f" '(nil :which-key "file")
"ff" #'find-file "ff" #'find-file
"fs" #'save-buffer "fs" #'save-buffer
"fD" '(jdormit-delete-file :which-key "delete") "fD" '(my/delete-file :which-key "delete")
"fr" '(rename-this-file :which-key "rename") "fr" '(rename-this-file :which-key "rename")
"fd" #'dired) "fd" #'dired)

View File

@ -0,0 +1,41 @@
;; -*- lexical-binding: t; -*-
(defun roguelike-evil-insert ()
(interactive)
(vterm-reset-cursor-point)
(call-interactively #'evil-insert))
(defun roguelike-buffer-setup ()
(setq-local cursor-type 'box
blink-cursor-mode nil
evil-insert-state-cursor 'box
evil-normal-state-cursor 'hollow
evil-motion-state-cursor 'hollow
left-fringe-width 0
right-fringe-width 0)
(evil-local-set-key 'normal (kbd "i") #'roguelike-evil-insert))
(defun play-roguelike (exe)
(unless (executable-find exe)
(user-error "%s is not installed" exe))
(let ((buffer (format "*%s*" exe)))
(when (get-buffer buffer)
(kill-buffer buffer))
(vterm buffer)
(with-current-buffer buffer
(roguelike-buffer-setup)
(vterm-send-string (format "%s; exit" exe))
(vterm-send-return))
(switch-to-buffer buffer)))
(defun nethack ()
(interactive)
(play-roguelike "nethack"))
(defvar crawl-executable (expand-file-name "~/crawl/crawl-ref/source/crawl"))
(defun crawl ()
(interactive)
(play-roguelike crawl-executable))
(provide 'init-games)

View File

@ -62,14 +62,16 @@
"git.jeremydormitzer.com/api/v1" "git.jeremydormitzer.com/api/v1"
"git.jeremydormitzer.com" "git.jeremydormitzer.com"
forge-gitea-repository)) forge-gitea-repository))
(add-to-list 'forge-alist '("ghe.spotify.net"
"ghe.spotify.net/api/v3"
"ghe.spotify.net"
forge-github-repository))
;; forge-topic-at-point really should just return nil if the buffer is not readable ;; forge-topic-at-point really should just return nil if the buffer is not readable
(advice-add 'forge-topic-at-point :around (advice-add 'forge-topic-at-point :around
(lambda (oldfn &rest args) (lambda (oldfn &rest args)
(ignore-errors (apply oldfn args)))) (ignore-errors (apply oldfn args))))
(defun forge-diff-for-pr ()
(interactive)
(let ((target forge--buffer-base-branch)
(source forge--buffer-head-branch))
(magit-diff-range (format "%s..%s" target source))))
(add-hook 'forge-create-pullreq-hook #'forge-diff-for-pr)
:custom :custom
(forge-owned-accounts '((jdormit . (remote-name "jdormit")))) (forge-owned-accounts '((jdormit . (remote-name "jdormit"))))
:general :general

View File

@ -235,12 +235,38 @@
(ruby-ts-mode . eglot-ensure) (ruby-ts-mode . eglot-ensure)
(sh-mode . eglot-ensure) (sh-mode . eglot-ensure)
(bash-ts-mode . eglot-ensure) (bash-ts-mode . eglot-ensure)
(yaml-mode . eglot-ensure)
(astro-ts-mode . eglot-ensure) (astro-ts-mode . eglot-ensure)
:custom :custom
(eglot-confirm-server-initiated-edits nil) (eglot-confirm-server-initiated-edits nil)
(eglot-connect-timeout nil)) (eglot-connect-timeout nil))
;; Debug adapter protocol
(use-package dape
:config
(add-to-list 'dape-configs
'(rdbg
modes (ruby-mode ruby-ts-mode)
ensure dape-ensure-command
command "bundle"
command-args ("exec" "rdbg" "-O" "--host" "0.0.0.0" "--port" :autoport "-c" "--" :-c)
fn (lambda (config)
(plist-put config 'command-args
(mapcar (lambda (arg)
(if (eq arg :-c)
(plist-get config '-c)
arg))
(plist-get config 'command-args))))
port :autoport
command-cwd dape-command-cwd
:type "Ruby"
;; -- examples:
;; rails server
;; bundle exec ruby foo.rb
;; bundle exec rake test
-c (lambda ()
(format "ruby %s"
(or (dape-buffer-default) ""))))))
;; Some compilation-mode conveniences ;; Some compilation-mode conveniences
(use-package compile (use-package compile
:straight (:type built-in) :straight (:type built-in)
@ -266,11 +292,15 @@
:straight (:host github :repo "copilot-emacs/copilot.el" :files ("dist" "*.el")) :straight (:host github :repo "copilot-emacs/copilot.el" :files ("dist" "*.el"))
:hook ((prog-mode . copilot-mode) :hook ((prog-mode . copilot-mode)
(yaml-mode . copilot-mode) (yaml-mode . copilot-mode)
(yaml-ts-mode . copilot-mode)
(json-mode . copilot-mode) (json-mode . copilot-mode)
(json-ts-mode . copilot-mode)
(forge-post-mode . copilot-mode) (forge-post-mode . copilot-mode)
(git-commit-mode . copilot-mode)) (git-commit-mode . copilot-mode))
:config :config
(add-to-list 'warning-suppress-types '(copilot)) (add-to-list 'warning-suppress-types '(copilot))
(add-to-list 'copilot-major-mode-alist '("forge-post" . "markdown"))
(add-to-list 'copilot-major-mode-alist '("git-commit" . "markdown"))
:general :general
(prog-mode-map "C-c <tab>" #'copilot-complete) (prog-mode-map "C-c <tab>" #'copilot-complete)
(copilot-completion-map "C-n" #'copilot-next-completion (copilot-completion-map "C-n" #'copilot-next-completion

View File

@ -11,7 +11,12 @@
:prefix-map 'leader-map)) :prefix-map 'leader-map))
(use-package which-key (use-package which-key
:hook (after-init . (lambda () (which-key-mode 1)))) :hook (after-init . (lambda () (which-key-mode 1)))
:custom
(which-key-show-operator-state-maps t)
:general
(leader-map "," #'which-key-show-major-mode)
(leader-map "\\" #'which-key-show-top-level))
(leader-def-key "SPC" #'execute-extended-command) (leader-def-key "SPC" #'execute-extended-command)

View File

@ -21,7 +21,7 @@
(general-def kubectl-log-mode-map (general-def kubectl-log-mode-map
"C-c k" #'kubectl--log-kill-process) "C-c k" #'kubectl--log-kill-process)
(general-def 'normal kubectl-log-mode-map (general-def 'normal kubectl-log-mode-map
"q" #'bury-buffer) "q" #'quit-window)
(general-def 'normal kubectl-pods-mode-map (general-def 'normal kubectl-pods-mode-map
"c" #'kubectl-choose-context "c" #'kubectl-choose-context
"s" #'kubectl-pods-choose-namespace "s" #'kubectl-pods-choose-namespace

View File

@ -43,6 +43,8 @@
(use-package ht) (use-package ht)
(use-package posframe)
;; Elisp utilities ;; Elisp utilities
(defun make-process-sentinel (success err) (defun make-process-sentinel (success err)
"Makes a process sentinel that calls `success` on success and `err` on error" "Makes a process sentinel that calls `success` on success and `err` on error"
@ -134,4 +136,10 @@
(kill-new (message rand))) (kill-new (message rand)))
rand)) rand))
(defmacro dbg (form)
"Print the value of FORM and return it."
`(progn
(message (format "%s: %s" ',form ,form))
,form))
(provide 'init-lib) (provide 'init-lib)

View File

@ -34,6 +34,10 @@
(add-hook 'after-init-hook #'theme-timer-hook) (add-hook 'after-init-hook #'theme-timer-hook)
(use-package ultra-scroll-mac
:straight (:type git :host github :repo "jdtsmith/ultra-scroll-mac")
:hook (after-init . ultra-scroll-mac-mode))
(unbind-key "C-<tab>" global-map) (unbind-key "C-<tab>" global-map)
(unbind-key "C-S-<tab>" global-map) (unbind-key "C-S-<tab>" global-map)

View File

@ -0,0 +1,7 @@
;; -*- lexical-binding: t; -*-
(use-package markdown-mode
:mode (("\\.md\\'" . gfm-mode)
("\\.markdown\\'" . gfm-mode)))
(provide 'init-markdown)

View File

@ -0,0 +1,6 @@
;; -*- lexical-binding: t; -*-
(use-package mermaid-mode
:defer t)
(provide 'init-mermaid)

View File

@ -0,0 +1,38 @@
;; -*- lexical-binding: t; -*-
(use-package navi
:straight `(:local-repo ,(expand-file-name "packages/navi" user-emacs-directory) :type nil)
:defer t
:commands (navi
navi-by-tags
navi-matching-current-directory
navi-visit-cheat-file
navi-all-cheats
navi-cheats-for-tags
navi-cheats-matching-filename
navi-cheat-summary)
:init
(defvar-keymap embark-navi-map
:doc "Keymap for actions on Navi cheats"
:parent embark-general-map
"f" #'navi-visit-cheat-file)
(with-eval-after-load 'embark
(add-to-list 'embark-keymap-alist '(navi . embark-navi-map)))
(defun run-command-recipe-navi ()
(let* ((dir (or (projectile-project-root) default-directory))
(cheat-files
(append
(navi-cheats-matching-filename (regexp-quote dir)))))
(-mapcat
(lambda (cheat-file)
(-map (lambda (cheat)
(list :command-name (navi-cheat-summary cheat)
:command-line (lambda () (navi-cheat-render cheat))
:working-dir dir))
(oref cheat-file cheats)))
cheat-files)))
(with-eval-after-load 'run-command
(add-to-list 'run-command-recipes 'run-command-recipe-navi)))
(provide 'init-navi)

View File

@ -0,0 +1,18 @@
;; lexical-binding: t; -*-
(use-package ace-link
:init
(defun ace-link-eww-setup ()
;; For some reason this keybinding wasn't working unless it's set here
(evil-define-key 'normal eww-mode-map "o" #'ace-link-eww))
(add-hook 'eww-mode-hook #'ace-link-eww-setup)
:general
(normal Info-mode-map "o" #'ace-link-info)
(normal help-mode-map "o" #'ace-link-help)
(normal woman-mode-map "o" #'ace-link-woman)
(normal eww-mode-map "o" #'ace-link-eww)
(normal compilation-mode-map "o" #'ace-link-compilation)
(normal custom-mode-map "o" #'ace-link-custom)
(normal org-mode-map "M-o" #'ace-link-org))
(provide 'init-navigation)

View File

@ -7,30 +7,41 @@
:init :init
(leader-def-key "o" '(nil :which-key "org")) (leader-def-key "o" '(nil :which-key "org"))
:config :config
(require 'ox-md)
(add-hook 'org-mode-hook #'olivetti-mode) (add-hook 'org-mode-hook #'olivetti-mode)
(add-hook 'org-mode-hook (lambda () (require 'org-attach))) (add-hook 'org-mode-hook (lambda () (require 'org-attach)))
(defun org-scratch () (defun org-scratch ()
(interactive) (interactive)
(switch-to-buffer "*org-scratch*") (switch-to-buffer "*org-scratch*")
(org-mode)) (org-mode))
(defun org-find-daily-note () (cl-defun org-find-daily-note (date)
(interactive) "Find the daily note for DATE, creating it if necessary.
(let* ((filename (expand-file-name
(format "~/org/daily/%s.org" DATE should be in the format \"YYYY-MM-DD\". If called interactively, default to today's date. If called with\
a prefix argument, prompt for the date."
(interactive (list (if current-prefix-arg
(org-read-date nil nil nil "Date: ")
(format-time-string "%Y-%m-%d")))) (format-time-string "%Y-%m-%d"))))
(let* ((filename (expand-file-name
(format "~/org/daily/%s.org" date)))
(file-exists (file-exists-p filename))) (file-exists (file-exists-p filename)))
(find-file filename) (find-file filename)
(goto-char (point-min)) (goto-char (point-min))
(unless file-exists (unless file-exists
(org-id-get-create) (org-id-get-create)
(goto-char (point-max)) (goto-char (point-max))
(insert (format "#+title: %s daily note" (insert (format "#+title: %s daily note" date))
(format-time-string "%Y-%m-%d")))
(newline)) (newline))
(goto-char (point-max)))) (goto-char (point-max))))
(defun org-capture-daily-note ()
(org-find-daily-note (format-time-string "%Y-%m-%d")))
(org-babel-do-load-languages (org-babel-do-load-languages
'org-babel-load-languages 'org-babel-load-languages
'((plantuml . t))) '((emacs-lisp . t)
(plantuml . t)
(python . t)
(shell . t)))
:custom :custom
(org-modules '(ol-doi (org-modules '(ol-doi
ol-w3m ol-w3m
@ -54,7 +65,7 @@
"DONE(d)" "DONE(d)"
"CANCELLED(c)"))) "CANCELLED(c)")))
(org-capture-templates `(("d" "Daily note" plain (org-capture-templates `(("d" "Daily note" plain
(function org-find-daily-note) (function org-capture-daily-note)
"* %<%I:%M %p>") "* %<%I:%M %p>")
("n" "Org-roam note" plain (function org-roam-capture)) ("n" "Org-roam note" plain (function org-roam-capture))
("h" "Hummingbird task" entry ("h" "Hummingbird task" entry
@ -81,13 +92,25 @@
("\\.x?html?\\'" . default))) ("\\.x?html?\\'" . default)))
(org-plantuml-exec-mode 'jar) (org-plantuml-exec-mode 'jar)
(org-plantuml-jar-path (expand-file-name "~/plantuml/plantuml.jar")) (org-plantuml-jar-path (expand-file-name "~/plantuml/plantuml.jar"))
(org-confirm-babel-evaluate nil)
:general :general
(leader-map "oa" #'org-agenda) (leader-map "oa" #'org-agenda)
(leader-map "oc" #'org-capture) (leader-map "oc" #'org-capture)
(leader-map "ol" #'org-store-link) (leader-map "ol" #'org-store-link)
(leader-map "od" #'org-find-daily-note) (leader-map "od" #'org-find-daily-note)
(normal org-mode-map "<return>" #'org-return) (normal org-mode-map "<return>" #'org-return)
(normal org-mode-map "T" #'org-insert-todo-heading)) (normal org-mode-map "T" #'org-insert-todo-heading)
(org-read-date-minibuffer-local-map "C-k" (lambda () (interactive)
(org-eval-in-calendar '(calendar-backward-week 1)))
"C-j" (lambda () (interactive)
(org-eval-in-calendar '(calendar-forward-week 1)))
"C-h" (lambda () (interactive)
(org-eval-in-calendar '(calendar-backward-day 1)))
"C-l" (lambda () (interactive)
(org-eval-in-calendar '(calendar-forward-day 1)))))
(use-package ob-async
:after org)
(use-package evil-org (use-package evil-org
:after org :after org
@ -177,6 +200,6 @@
:config :config
(general-def 'normal org-roam-backlinks-mode-map (general-def 'normal org-roam-backlinks-mode-map
"RET" #'org-open-at-point "RET" #'org-open-at-point
"q" #'bury-buffer)) "q" #'quit-window))
(provide 'init-org) (provide 'init-org)

View File

@ -4,7 +4,7 @@
("\\.plantuml\\'" . plantuml-mode) ("\\.plantuml\\'" . plantuml-mode)
("\\.puml\\'" . plantuml-mode)) ("\\.puml\\'" . plantuml-mode))
:custom :custom
(plantuml-default-exec-mode 'jar) (plantuml-default-exec-mode 'executable)
(plantuml-jar-path (expand-file-name "~/plantuml/plantuml.jar"))) (plantuml-executable-path (executable-find "plantuml")))
(provide 'init-plantuml) (provide 'init-plantuml)

View File

@ -12,7 +12,32 @@
("Guardfile\\'" . ruby-mode) ("Guardfile\\'" . ruby-mode)
("Capfile\\'" . ruby-mode) ("Capfile\\'" . ruby-mode)
("Vagrantfile\\'" . ruby-mode)) ("Vagrantfile\\'" . ruby-mode))
:hook (ruby-mode . highlight-indent-guides-mode)) :hook (ruby-mode . highlight-indent-guides-mode)
:config
(defun toggle-spec-file ()
"Find the spec file for the current non-spec buffer or the file under test for the current spec buffer."
(interactive)
(let* ((dir (if-let ((proj (project-current)))
(project-root proj)
default-directory))
(ext (file-name-extension (buffer-file-name)))
(filename-base (file-name-base (buffer-file-name)))
(spec? (s-ends-with? "_spec" filename-base))
(rx (if spec?
(format "%s.%s$" (s-chop-suffix "_spec" filename-base) ext)
(format "%s_spec.%s$" filename-base ext)))
(candidates (directory-files-recursively dir rx)))
(cond ((= (length candidates) 0)
(message "No matching file found"))
((= (length candidates) 1)
(find-file (car candidates)))
(t
(let ((choice (consult--read candidates
:prompt "Find file: "
:category 'file)))
(find-file choice))))))
:general
(ruby-mode-map "C-c C-t" #'toggle-spec-file))
(use-package inf-ruby (use-package inf-ruby
:commands (inf-ruby inf-ruby-console-auto) :commands (inf-ruby inf-ruby-console-auto)

View File

@ -383,7 +383,7 @@
cmds cmds
(run-command-rake--cache-cmds (run-command-rake--cache-cmds
rake-dir rake-dir
(->> (shell-command-to-string "rake -T") (->> (shell-command-to-string "rake -AT")
(s-split "\n") (s-split "\n")
(-map (lambda (s) (s-split-up-to " " s 2))) (-map (lambda (s) (s-split-up-to " " s 2)))
(-map (lambda (l) (s-join " " (-take 2 l)))) (-map (lambda (l) (s-join " " (-take 2 l))))
@ -432,7 +432,9 @@
(save-excursion (save-excursion
(goto-char (point-min)) (goto-char (point-min))
(re-search-forward "RSpec" nil t))) (re-search-forward "RSpec" nil t)))
(let ((root-dir (or (projectile-project-root) default-directory))) (let ((root-dir (or (locate-dominating-file default-directory "spec")
(projectile-project-root)
default-directory)))
(-concat (-concat
(list (list
(list :command-name "test this file" (list :command-name "test this file"

View File

@ -0,0 +1,10 @@
;; -*- lexical-binding: t; -*-
(use-package swift-mode
:mode "\\.swift\\'"
:config
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs '(swift-mode . ("sourcekit-lsp")))
(add-hook 'swift-mode-hook 'eglot-ensure)))
(provide 'init-swift)

View File

@ -2,6 +2,89 @@
;; Configuration as code! ;; Configuration as code!
(use-package terraform-mode (use-package terraform-mode
:mode "\\.tf\\'") :mode "\\.tf\\'"
:config
(defun devdocs-terraform-resource-string (resource type)
"Returns the devdocs for Terraform resource RESOURCE of type TYPE (\"data\" or \"resource\")as a string."
(devdocs-as-string resource "terraform" type))
(with-eval-after-load 'devdocs
(add-to-list 'devdocs-entry-aliases '("aws_alb" . "aws_lb")))
(defun terraform-block-metadata-at-pos (pos)
(let* ((parser (treesit-parser-create 'hcl))
(current-node (treesit-node-at pos parser))
(block (treesit-node-top-level current-node "block"))
(capture (when block
(treesit-query-capture block
'((block (identifier) @type (:match "^\\(data\\|resource\\)$" @type)
(string_lit (_) (template_literal) @name (_)))))))
(capture (let ((-compare-fn (lambda (a b) (eq (car a) (car b)))))
(-distinct capture))))
(-map (lambda (r) (cons (car r) (treesit-node-text (cdr r) t))) capture)))
(defun terraform-block-metadata-at-point ()
(terraform-block-metadata-at-pos (point)))
(defun terraform-devdocs-eldoc-function (cb)
(when-let* ((metadata (terraform-block-metadata-at-point))
(resource-name (alist-get 'name metadata))
(resource-type (alist-get 'type metadata))
(doc (devdocs-terraform-resource-string resource-name resource-type))
(summary (with-temp-buffer
(insert doc)
(goto-char (point-min))
(forward-line)
(while (and (looking-at (rx space) t) (not (eobp)))
(forward-line))
(buffer-substring-no-properties (line-beginning-position) (line-end-position)))))
(funcall cb doc :echo summary)))
(defun terraform-setup-eldoc ()
(add-hook 'eldoc-documentation-functions #'terraform-devdocs-eldoc-function nil t))
(add-hook 'terraform-mode-hook #'terraform-setup-eldoc)
(defun find-terraform-root ()
"Returns the highest-level parent directory containing a .tf file."
(let ((current-dir default-directory)
(root nil))
(while (and (not (string-match locate-dominating-stop-dir-regexp current-dir))
(f-glob "*.tf" current-dir))
(setq root current-dir)
(setq current-dir (f-parent current-dir)))
(when root (file-name-as-directory root))))
;; Use root terraform module to resolve providers
(defun terraform-provider-ns-advice (oldfn &rest args)
(let ((dir (or (find-terraform-root) default-directory)))
(let ((default-directory dir))
(apply oldfn args))))
(defvar terraform-default-aws-profile "default"
"The default AWS profile to use when running terraform commands.")
(defun terraform-console (path)
"Run a Terraform console at `path' in a comint buffer."
(interactive (list (read-directory-name "Path: " (find-terraform-root))))
(let* ((process-environment (cons (format "AWS_PROFILE=%s" terraform-default-aws-profile)
process-environment))
(default-directory (expand-file-name path))
(program "terraform")
(switches (list "console"))
(expected-buf-name (apply #'make-comint program program nil switches)))
(comint-run "terraform" (list "console"))
(with-current-buffer expected-buf-name
(setq-local comint-prompt-read-only t
comint-process-echoes t))))
(advice-add 'terraform--get-resource-provider-namespace :around #'terraform-provider-ns-advice))
(defun terraform-jump-to-plan ()
"Jump to the top of the plan output in a buffer showing a terraform plan."
(interactive)
(when (eq major-mode 'vterm-mode)
(vterm-copy-mode))
(search-backward "Terraform will perform the following actions:"))
(provide 'init-terraform) (provide 'init-terraform)

View File

@ -20,7 +20,9 @@
(tsx "https://github.com/tree-sitter/tree-sitter-typescript" "v0.20.3" "tsx/src") (tsx "https://github.com/tree-sitter/tree-sitter-typescript" "v0.20.3" "tsx/src")
(typescript "https://github.com/tree-sitter/tree-sitter-typescript" "v0.20.3" "typescript/src") (typescript "https://github.com/tree-sitter/tree-sitter-typescript" "v0.20.3" "typescript/src")
(yaml "https://github.com/ikatyang/tree-sitter-yaml") (yaml "https://github.com/ikatyang/tree-sitter-yaml")
(astro "https://github.com/virchau13/tree-sitter-astro"))) (astro "https://github.com/virchau13/tree-sitter-astro")
(sql "https://github.com/DerekStride/tree-sitter-sql" "gh-pages" "src")
(hcl "https://github.com/tree-sitter-grammars/tree-sitter-hcl")))
(defun treesit-install-all-grammars () (defun treesit-install-all-grammars ()
(interactive) (interactive)
@ -109,4 +111,13 @@
(-map #'cdr) (-map #'cdr)
(-mapcat (lambda (n) (treesit-node-text n t))))))) (-mapcat (lambda (n) (treesit-node-text n t)))))))
(use-package origami-treesit
:straight (:type built-in)
:ensure nil
:load-path "packages/origami-treesit"
:after origami
:hook (origami-mode . origami-treesit-mode)
:custom
(origami-treesit-level 4))
(provide 'init-treesit) (provide 'init-treesit)

View File

@ -1,10 +1,11 @@
;; -*- lexical-binding: t; -*- ;; -*- lexical-binding: t; -*-
;; Don't show ugly graphical UI elements ;; For some reason, the tool-bar-mode disabling in early-init.el was leaving the frame title too tall, so
(when (window-system) ;; toggle it on and off
(tool-bar-mode -1) (add-hook 'after-init-hook
(scroll-bar-mode -1) (lambda ()
(tooltip-mode -1)) (tool-bar-mode)
(tool-bar-mode -1)))
(when (fboundp 'mac-auto-operator-composition-mode) (when (fboundp 'mac-auto-operator-composition-mode)
(setq mac-auto-operator-composition-characters (setq mac-auto-operator-composition-characters
@ -14,8 +15,8 @@
;; Doom themes are the best themes! ;; Doom themes are the best themes!
(use-package doom-themes) (use-package doom-themes)
(use-package doom-modeline (use-package doom-modeline
:config :hook
(doom-modeline-mode 1) (after-init . doom-modeline-mode)
:custom :custom
(doom-modeline-env-version nil) (doom-modeline-env-version nil)
(doom-modeline-env-enable-python nil) (doom-modeline-env-enable-python nil)
@ -25,9 +26,6 @@
(doom-modeline-env-enable-elixir nil) (doom-modeline-env-enable-elixir nil)
(doom-modeline-env-enable-rust nil)) (doom-modeline-env-enable-rust nil))
;; Load custom themes
(add-to-list 'custom-theme-load-path (expand-file-name "config/themes" user-emacs-directory))
;; A small package to hide the mode line ;; A small package to hide the mode line
(use-package hide-mode-line (use-package hide-mode-line
:commands hide-mode-line-mode) :commands hide-mode-line-mode)

View File

@ -8,4 +8,9 @@
(message formatted) (message formatted)
formatted))) formatted)))
(defun display-ansi-colors ()
(interactive)
(require 'ansi-color)
(ansi-color-apply-on-region (point-min) (point-max)))
(provide 'init-utils) (provide 'init-utils)

View File

@ -47,6 +47,8 @@
(leader-map "v" #'vterm) (leader-map "v" #'vterm)
(vterm-mode-map "C-x C-e" #'vterm-edit-zsh-command-line (vterm-mode-map "C-x C-e" #'vterm-edit-zsh-command-line
"C-c C-g" #'vterm-send-C-g) "C-c C-g" #'vterm-send-C-g)
((normal motion insert) vterm-mode-map
"C-q" #'vterm-send-next-key)
:custom :custom
(vterm-max-scrollback 10000) (vterm-max-scrollback 10000)
(vterm-environment '("TYPEWRITTEN_CURSOR=terminal")) (vterm-environment '("TYPEWRITTEN_CURSOR=terminal"))

View File

@ -28,6 +28,8 @@
(general-def evil-window-map (general-def evil-window-map
"m" #'delete-other-windows "m" #'delete-other-windows
"S" #'split-root-window-below
"V" #'split-root-window-right
"u" #'winner-undo "u" #'winner-undo
"C-r" #'winner-redo) "C-r" #'winner-redo)
@ -37,5 +39,39 @@
which-key-replacement-alist) which-key-replacement-alist)
(push '((nil . "select-window-[1-9]") . t) which-key-replacement-alist)) (push '((nil . "select-window-[1-9]") . t) which-key-replacement-alist))
;; Buffer and window layout management
(setopt switch-to-buffer-in-dedicated-window 'pop
split-height-threshold 116
window-sides-slots '(1 0 0 1)
display-buffer-alist '(("\\*Help\\*"
(display-buffer-reuse-window
display-buffer-in-side-window)
(side . right)
(slot . 0)
(window-width . 85))
("\\*helpful .*\\*"
(display-buffer-reuse-mode-window
display-buffer-in-side-window)
(side . right)
(slot . 0)
(window-width . 85))
("\\*eldoc\\*"
(display-buffer-reuse-window
display-buffer-in-side-window)
(side . right)
(slot . 0)
(window-width . 85))
("\\*vterm.*\\*"
(display-buffer-reuse-mode-window
display-buffer-pop-up-window)
(mode vterm-mode vterm-copy-mode))
("\\*ielm\\*"
(display-buffer-reuse-mode-window
display-buffer-pop-up-window)
(mode vterm-mode vterm-copy-mode))
("\\*Messages\\*" display-buffer-no-window (allow-no-window . t))
("\\*Warnings\\*" display-buffer-no-window (allow-no-window . t))
("\\*info\\*" (display-buffer-reuse-window
display-buffer-pop-up-window))))
(provide 'init-windows) (provide 'init-windows)

View File

@ -1,9 +1,32 @@
;; -*- lexical-binding: t; -*- ;; -*- lexical-binding: t; -*-
;; YAML - literally the worst but still holds an important place in my life ;; YAML - literally the worst but still holds an important place in my life
(use-package yaml-mode (use-package yaml-ts-mode
:mode ("\\.yaml\\'" "\\.yml\\'") :mode ("\\.yaml\\'" "\\.yml\\'")
:config :config
(add-hook 'yaml-mode-hook #'highlight-indent-guides-mode)) (add-hook 'yaml-ts-mode-hook #'highlight-indent-guides-mode))
(use-package yaml-pro
:when (treesit-ready-p 'yaml)
:hook (yaml-ts-mode . yaml-pro-ts-mode)
:config
(defun yaml-pro-edit-initialize-buffer-filter-args-advice (args)
(if-let ((mode (language-detection-detect-mode (buffer-string))))
(cl-destructuring-bind (parent-buffer buffer initial-text type initialize path) args
(let ((init-func (lambda ()
(funcall mode)
(when initialize
(call-interactively initialize)))))
(list parent-buffer buffer initial-text type init-func path)))
args))
(advice-add 'yaml-pro-edit-initialize-buffer :filter-args #'yaml-pro-edit-initialize-buffer-filter-args-advice)
:general
(yaml-pro-ts-mode-map [remap evil-backward-section-begin] #'yaml-pro-ts-prev-subtree
[remap evil-forward-section-begin] #'yaml-pro-ts-next-subtree
[remap evil-shift-right] #'yaml-pro-ts-indent-subtree
[remap evil-shift-left] #'yaml-pro-ts-unindent-subtree
"C-c j" #'yaml-pro-ts-move-subtree-down
"C-c k" #'yaml-pro-ts-move-subtree-up
"C-c f" #'yaml-pro-format))
(provide 'init-yaml) (provide 'init-yaml)

View File

@ -1,7 +1,222 @@
;; -*- lexical-binding: t; -*- ;; -*- no-byte-compile: t; lexical-binding: t; -*-
;; Borrowed from https://github.com/jamescherti/minimal-emacs.d/blob/main/early-init.el
;;; Variables
(defvar minimal-emacs-debug nil
"Non-nil to enable debug.")
(defvar minimal-emacs-gc-cons-threshold (* 16 1024 1024)
"The value of `gc-cons-threshold' after Emacs startup.")
(defvar minimal-emacs-frame-title-format "%b Emacs"
"Template for displaying the title bar of visible and iconified frame.")
;;; Misc
(set-language-environment "UTF-8")
;; Set-language-environment sets default-input-method, which is unwanted.
(setq default-input-method nil)
;;; Garbage collection
;; Garbage collection significantly affects startup times. This setting delays
;; garbage collection during startup but will be reset later.
(setq gc-cons-threshold most-positive-fixnum)
(add-hook 'emacs-startup-hook
(lambda ()
(setq gc-cons-threshold minimal-emacs-gc-cons-threshold)))
;;; Performance
;; Prefer loading newer compiled files
(setq load-prefer-newer t) (setq load-prefer-newer t)
;; Some startup time optimizations stolen from Doom emacs ;; Increase how much is read from processes in a single chunk (default is 4kb).
(setq gc-cons-threshold most-positive-fixnum ; 2^61 bytes (setq read-process-output-max (* 256 1024)) ; 256kb
gc-cons-percentage 0.6)
;; Reduce rendering/line scan work by not rendering cursors or regions in
;; non-focused windows.
(setq-default cursor-in-non-selected-windows nil)
(setq highlight-nonselected-windows nil)
;; Disable warnings from the legacy advice API. They aren't useful.
(setq ad-redefinition-action 'accept)
;; Ignore warnings about "existing variables being aliased".
(setq warning-suppress-types '((defvaralias) (lexical-binding)))
;; Don't ping things that look like domain names.
(setq ffap-machine-p-known 'reject)
;; By default, Emacs "updates" its ui more often than it needs to
(setq idle-update-delay 1.0)
;; Font compacting can be very resource-intensive, especially when rendering
;; icon fonts on Windows. This will increase memory usage.
(setq inhibit-compacting-font-caches t)
(unless (daemonp)
(let ((old-value (default-toplevel-value 'file-name-handler-alist)))
(set-default-toplevel-value
'file-name-handler-alist
;; Determine the state of bundled libraries using calc-loaddefs.el.
;; If compressed, retain the gzip handler in `file-name-handler-alist`.
;; If compiled or neither, omit the gzip handler during startup for
;; improved startup and package load time.
(if (eval-when-compile
(locate-file-internal "calc-loaddefs.el" load-path))
nil
(list (rassq 'jka-compr-handler old-value))))
;; Ensure the new value persists through any current let-binding.
(set-default-toplevel-value 'file-name-handler-alist
file-name-handler-alist)
;; Remember the old value to reset it as needed.
(add-hook 'emacs-startup-hook
(lambda ()
(set-default-toplevel-value
'file-name-handler-alist
;; Merge instead of overwrite to preserve any changes made
;; since startup.
(delete-dups (append file-name-handler-alist old-value))))
101))
(unless noninteractive
(progn
;; Disable mode-line-format during init
(defun minimal-emacs--reset-inhibited-vars-h ()
(setq-default inhibit-redisplay nil
;; Inhibiting `message' only prevents redraws and
inhibit-message nil)
(redraw-frame))
(setq-default mode-line-format nil)
(defun minimal-emacs--startup-load-user-init-file (fn &rest args)
"Around advice for startup--load-user-init-file to reset mode-line-format."
(let (init)
(unwind-protect
(progn
(apply fn args) ; Start up as normal
(setq init t))
(unless init
;; If we don't undo inhibit-{message, redisplay} and there's an
;; error, we'll see nothing but a blank Emacs frame.
(minimal-emacs--reset-inhibited-vars-h))
(unless (default-toplevel-value 'mode-line-format)
(setq-default mode-line-format
minimal-emacs--default-mode-line-format)))))
(advice-add 'startup--load-user-init-file :around
#'minimal-emacs--startup-load-user-init-file))
;; Without this, Emacs will try to resize itself to a specific column size
(setq frame-inhibit-implied-resize t)
;; A second, case-insensitive pass over `auto-mode-alist' is time wasted.
;; No second pass of case-insensitive search over auto-mode-alist.
(setq auto-mode-case-fold nil)
;; Reduce *Message* noise at startup. An empty scratch buffer (or the
;; dashboard) is more than enough, and faster to display.
(setq inhibit-startup-screen t
inhibit-startup-echo-area-message user-login-name)
(setq initial-buffer-choice nil
inhibit-startup-buffer-menu t
inhibit-x-resources t)
;; Disable bidirectional text scanning for a modest performance boost.
(setq-default bidi-display-reordering 'left-to-right
bidi-paragraph-direction 'left-to-right)
;; Give up some bidirectional functionality for slightly faster re-display.
(setq bidi-inhibit-bpa t)
;; Remove "For information about GNU Emacs..." message at startup
(advice-add #'display-startup-echo-area-message :override #'ignore)
;; Suppress the vanilla startup screen completely. We've disabled it with
;; `inhibit-startup-screen', but it would still initialize anyway.
(advice-add #'display-startup-screen :override #'ignore)
;; Shave seconds off startup time by starting the scratch buffer in
;; `fundamental-mode'
(setq initial-major-mode 'fundamental-mode
initial-scratch-message nil)))
;;; Native compilation and Byte compilation
(if (and (featurep 'native-compile)
(fboundp 'native-comp-available-p)
(native-comp-available-p))
;; Activate `native-compile'
(setq native-comp-deferred-compilation t
package-native-compile t)
;; Deactivate the `native-compile' feature if it is not available
(setq features (delq 'native-compile features)))
;; Suppress compiler warnings and don't inundate users with their popups.
(setq native-comp-async-report-warnings-errors
(or minimal-emacs-debug 'silent))
(setq native-comp-warning-on-missing-source minimal-emacs-debug)
(setq debug-on-error minimal-emacs-debug
jka-compr-verbose minimal-emacs-debug)
(setq byte-compile-warnings minimal-emacs-debug)
(setq byte-compile-verbose minimal-emacs-debug)
;;; Disable unneeded UI elements
;; Disable startup screens and messages
(setq inhibit-splash-screen t)
;; I intentionally avoid calling `menu-bar-mode', `tool-bar-mode', and
;; `scroll-bar-mode' because manipulating frame parameters can trigger or queue
;; a superfluous and potentially expensive frame redraw at startup, depending
;; on the window system. The variables must also be set to `nil' so users don't
;; have to call the functions twice to re-enable them.
(push '(menu-bar-lines . 0) default-frame-alist)
(push '(tool-bar-lines . 0) default-frame-alist)
(push '(vertical-scroll-bars) default-frame-alist)
(push '(horizontal-scroll-bars) default-frame-alist)
(setq tool-bar-mode nil
scroll-bar-mode nil)
(when (bound-and-true-p tooltip-mode)
(tooltip-mode -1))
;; Disable GUIs because theyr are inconsistent across systems, desktop
;; environments, and themes, and they don't match the look of Emacs.
(setq use-file-dialog nil)
(setq use-dialog-box nil)
(unless (memq window-system '(mac ns))
;; (menu-bar-mode -1)
(setq menu-bar-mode nil))
(when (fboundp 'horizontal-scroll-bar-mode)
(horizontal-scroll-bar-mode -1))
;; Allow for shorter responses: "y" for yes and "n" for no.
(if (boundp 'use-short-answers)
(setq use-short-answers t)
(advice-add #'yes-or-no-p :override #'y-or-n-p))
(defalias #'view-hello-file #'ignore) ; Never show the hello file
;;; package.el
;; Since Emacs 27, package initialization occurs before `user-init-file' is
;; loaded, but after `early-init-file'.
(setq package-enable-at-startup t)
(setq package-quickstart nil)
(setq frame-title-format minimal-emacs-frame-title-format
icon-title-format minimal-emacs-frame-title-format)
(provide 'early-init)
;;; early-init.el ends here

View File

@ -1,14 +1,5 @@
;; -*- lexical-binding: t; -*- ;; -*- lexical-binding: t; -*-
;; Some startup time optimizations stolen from Doom emacs
(defvar file-name-handler-alist-backup file-name-handler-alist)
(setq file-name-handler-alist nil)
(add-hook 'emacs-startup-hook
(lambda ()
(setq gc-cons-threshold 16777216 ; 16mb
gc-cons-percentage 0.1
file-name-handler-alist file-name-handler-alist-backup)))
;; Start the server after init ;; Start the server after init
(autoload 'server-running-p "server") (autoload 'server-running-p "server")
(defun server-start-if-not-running () (defun server-start-if-not-running ()
@ -37,6 +28,12 @@
(straight-use-package 'use-package) (straight-use-package 'use-package)
(setq straight-use-package-by-default t) (setq straight-use-package-by-default t)
(use-package benchmark-init
:ensure t
:config
;; To disable collection of benchmark data after init is done.
(add-hook 'after-init-hook 'benchmark-init/deactivate))
;; "Garbage Collection Magic Hack" ;; "Garbage Collection Magic Hack"
(use-package gcmh (use-package gcmh
:demand t :demand t
@ -56,7 +53,6 @@
(exec-path-from-shell-check-startup-files nil) (exec-path-from-shell-check-startup-files nil)
(exec-path-from-shell-arguments '("-l"))) (exec-path-from-shell-arguments '("-l")))
;; Don't use this file as the custom-file ;; Don't use this file as the custom-file
(setq custom-file (expand-file-name "custom.el" user-emacs-directory)) (setq custom-file (expand-file-name "custom.el" user-emacs-directory))
@ -97,11 +93,14 @@
(require 'init-js) (require 'init-js)
(require 'init-ruby) (require 'init-ruby)
(require 'init-rust) (require 'init-rust)
(require 'init-swift)
(require 'init-web) (require 'init-web)
(require 'init-markdown)
(require 'init-astro) (require 'init-astro)
(require 'init-xml) (require 'init-xml)
(require 'init-yaml) (require 'init-yaml)
(require 'init-groovy) (require 'init-groovy)
(require 'init-devdocs)
(require 'init-terraform) (require 'init-terraform)
(require 'init-docker) (require 'init-docker)
(require 'init-nix) (require 'init-nix)
@ -114,13 +113,18 @@
(require 'init-kubernetes) (require 'init-kubernetes)
(require 'init-epub) (require 'init-epub)
(require 'init-pdf) (require 'init-pdf)
(require 'init-eww)
(require 'init-navigation)
(require 'init-homebrew) (require 'init-homebrew)
(require 'init-devdocs)
(require 'init-elfeed) (require 'init-elfeed)
(require 'init-1pass) (require 'init-1pass)
(require 'init-wallabag) (require 'init-wallabag)
(require 'init-plantuml) (require 'init-plantuml)
(require 'init-mermaid)
(require 'init-games)
(require 'handwriting) (require 'handwriting)
(require 'init-navi)
(require 'init-ai)
(when (string-equal system-type "darwin") (when (string-equal system-type "darwin")
(require 'init-mac)) (require 'init-mac))

View File

@ -0,0 +1,246 @@
;;; aimenu.el --- imenu-like outline generated by an LLM -*- lexical-binding: t; -*-
;; Copyright (C) 2024 Jeremy Dormitzer
;; Author: Jeremy Dormitzer <jeremy.dormitzer@gmail.com>
;; Keywords: tools
;; Package-Requires: ((emacs "25.1") (gptel "0.9.0"))
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; This package provides an imenu-like outline interface for any buffer, powered by an LLM.
;;; Code:
(require 'json)
(require 'gptel)
(defvar aimenu-gptel-backend (gptel-make-openai "aimenu-openai"
:key #'gptel-api-key-from-auth-source
:models '("gpt-4o-mini"))
"The gptel backend to use for aimenu requests.")
(defvar aimenu-gptel-model "gpt-4o-mini"
"The gptel model to use for aimenu requests.")
(defvar aimenu-outline-cache (make-hash-table :test 'equal)
"Cache for storing outline responses based on prompt hash.")
;;;###autoload
(defun aimenu-bust-cache ()
"Bust the aimenu cache."
(interactive)
(clrhash aimenu-outline-cache))
(defun aimenu-hash-string (str)
"Return a hash for the given string STR using SHA-256."
(secure-hash 'sha256 str))
(defun aimenu-get-buffer-with-line-numbers (buffer &optional start end)
"Return the contents of BUFFER with line numbers added to each line."
(with-temp-buffer
(insert-buffer-substring buffer start end)
(goto-char (point-min))
(let ((line-number (1+ (or (when start
(with-current-buffer buffer
(line-number-at-pos start)))
0))))
(while (not (eobp))
(insert (format "%d: " line-number))
(forward-line 1)
(setq line-number (1+ line-number))))
(buffer-string)))
(defun aimenu-parse-and-strip-response (response)
"Parse and strip markdown code fences from LLM RESPONSE."
(with-temp-buffer
(insert response)
(goto-char (point-min))
(while (re-search-forward "```\\(json\\)*" nil t)
(replace-match ""))
(buffer-string)))
(defun aimenu-header-completions (outline headers buffer)
(lambda (string pred action)
(if (eq action 'metadata)
(list 'metadata
(cons 'display-sort-function
(lambda (headers)
(sort headers
(lambda (a b) (< (alist-get a outline) (alist-get b outline))))))
(cons 'annotation-function
(lambda (header)
(let* ((line-number (alist-get header outline nil nil #'equal))
(line (with-current-buffer buffer
(save-excursion
(goto-char (point-min))
(forward-line line-number)
(thing-at-point 'line)))))
(format "%s%s: %s"
(propertize " " 'display '(space :align-to center))
line-number
(string-trim line))))))
(complete-with-action action headers string pred))))
(defun aimenu-handle-outline-response (outline)
"Handle the outline response by prompting the user to select a header.
OUTLINE is the parsed JSON response."
(let* ((current-buffer (current-buffer))
(headers (mapcar #'car outline))
(chosen-header (completing-read "Choose an outline header: " (aimenu-header-completions outline headers current-buffer)))
(line-number (alist-get chosen-header outline nil nil #'equal)))
(funcall-interactively #'goto-line line-number)))
;;;###autoload
(defun aimenu (arg)
"Generate an outline of the current buffer using an LLM.
If ARG is non-nil, prompt for an instruction for generating the outline."
(interactive "P")
(let* ((instruction (if arg
(read-string "Instruction: ")
nil))
(buffer-contents
(apply #'aimenu-get-buffer-with-line-numbers (current-buffer)
(when (region-active-p)
(list (region-beginning) (region-end)))))
(prompt (if instruction
(concat buffer-contents "\n\nInstruction: " instruction)
buffer-contents))
(prompt-hash (aimenu-hash-string prompt))
(cached-response (gethash prompt-hash aimenu-outline-cache))
(gptel-backend aimenu-gptel-backend)
(gptel-model aimenu-gptel-model))
(if cached-response
(aimenu-handle-outline-response cached-response)
(gptel-request
prompt
:system "Create an outline of the buffer. The user may provide an instruction on how to generate the outline. If no instruction is provided, generate the outline based on the overall structure of the buffer.
Return a JSON object where the keys are the outline headers and the values are the line numbers that correspond to each outline header. Reply only in valid JSON without any code fences or additional text.
Here are some examples of your task:
USER:
1: * Outline header 1
2: Some content here.
3: ** Subheader 1.1
4: More content.
5: ** Subheader 1.2
6: Even more content.
7: * Outline header 2
8: And some more content.
9: ** Subheader 2.1
10: Still more content.
11: *** Outline header 3
12: Final bit of content.
ASSISTANT:
{
\"Outline header 1\": 1,
\"Subheader 1.1\": 3,
\"Subheader 1.2\": 5,
\"Outline header 2\": 7,
\"Subheader 2.1\": 9,
\"Outline header 3\": 11
}
USER:
1: # Main Header
2: Some introductory text.
3: ## Subheader 1
4: More details here.
5: ## Subheader 2
6: Additional information.
7: ### Sub-subheader 2.1
8: Finer details.
ASSISTANT:
{
\"Main Header\": 1,
\"Subheader 1\": 3,
\"Subheader 2\": 5,
\"Sub-subheader 2.1\": 7
}
USER:
1: def main_function():
2: # Main function logic
3: pass
4:
5: def helper_function():
6: # Helper function logic
7: pass
ASSISTANT:
{
\"main_function\": 1,
\"helper_function\": 5
}
USER:
1: main_section:
2: key1: value1
3: key2: value2
4: sub_section1:
5: sub_key1: sub_value1
6: sub_section2:
7: sub_key2: sub_value2
8: sub_sub_section:
9: sub_sub_key: sub_sub_value
ASSISTANT:
{
\"main_section\": 1,
\"sub_section1\": 4,
\"sub_section2\": 6,
\"sub_sub_section\": 8
}
USER:
1: SELECT * FROM users;
2: -- This query fetches all columns from the users table
3: INSERT INTO users (name, email)
4: VALUES ('John Doe', 'john@example.com');
5: -- This query adds a new user to the users table
Instruction: lines with comments
ASSISTANT:
{
\"-- This query fetches all columns from the users table\": 2,
\"-- This query adds a new user to the users table\": 5}
}"
:callback (lambda (response info)
(if response
(let* ((stripped-response (aimenu-parse-and-strip-response response))
(response-json (ignore-errors (let ((json-key-type 'string))
(json-read-from-string stripped-response))))
(outline (if (json-alist-p response-json)
response-json
(error "Invalid response format: %s" stripped-response)))
(buffer (plist-get info :buffer)))
(puthash (aimenu-hash-string prompt)
outline
aimenu-outline-cache)
(with-current-buffer buffer
(aimenu-handle-outline-response outline))
(pop-to-buffer buffer))
(message "gptel-request failed with message: %s" (plist-get info :status))))))))
(provide 'aimenu)
;;; aimenu.el ends here

View File

@ -0,0 +1,435 @@
;;; llama.el --- AI-assisted Emacs -*- lexical-binding: t; -*-
;; Copyright (C) 2024 Jeremy Isaac Dormitzer
;; Author: Jeremy Isaac Dormitzer <jeremy.dormitzer@gmail.com>
;; Package-Requires: ((emacs "28.1") (llm "0.15") (markdown-mode "2.7") (s "1.13") (spinner "1.7.4"))
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; Provides AI assistance features via llm.el.
;;; Code:
(require 'llm)
(require 's)
(require 'spinner)
;; for font-locking
(require 'markdown-mode)
(defvar llama-llm-provider nil
"The llm.el provider to use.")
(defvar llama-chat-prompt-symbol "> "
"The symbol used to indicate the user's input in the chat buffer.")
(defvar llama-chat-default-name "*llama-chat*"
"The default name for the chat buffer.")
(defvar llama-chat-default-initial-prompt-args
'("Briefly greet the user without mentioning your name and ask how you can help."
:context "You are a helpful AI assistant running inside the Emacs text editor.")
"The arguments for the default initial chat prompt.")
(defvar llama-spinner-type 'progress-bar
"The mode-line spinner type from spinner.el to use when waiting for the AI.")
(defvar-local llama-current-chat-prompt nil
"Chat prompt object for the current buffer.")
(defvar-local llama-current-chat-filter nil
"Filter function for the current chat buffer.")
(defvar-local llama-user-input-begin-marker nil
"Marker for the beginning of the user's input.")
(defvar-local llama-user-input-end-marker nil
"Marker for the end of the user's input.")
(defvar-local llama-ai-response-begin-marker nil
"Marker for the AI's response.")
(defvar-local llama-ai-response-end-marker nil
"Marker for the AI's response.")
(defvar-local llama-waiting-for-ai nil
"True if we are waiting for the AI to respond.")
(defun llama-chat-streaming-to-chat-buffer (provider prompt buffer point finish-callback)
"A version of `llm-chat-streaming-to-point' that sets inhibit-read-only to t in the insertion callback."
(with-current-buffer buffer
(save-excursion
(let ((start (make-marker))
(end (make-marker)))
(set-marker start point)
(set-marker end point)
(set-marker-insertion-type start nil)
(set-marker-insertion-type end t)
(setq llama-waiting-for-ai t)
(spinner-start llama-spinner-type)
(cl-flet ((insert-text (text)
;; Erase and insert the new text between the marker cons.
(with-current-buffer (marker-buffer start)
(let ((inhibit-read-only t))
(save-excursion
(goto-char start)
(delete-region start end)
(insert text))))))
(llm-chat-streaming provider prompt
(lambda (text) (insert-text text))
(lambda (text)
(spinner-stop)
(insert-text text)
(funcall finish-callback text)
(setq llama-waiting-for-ai nil))
(lambda (_ msg)
(spinner-stop)
(setq llama-waiting-for-ai nil)
(error "Error calling the LLM: %s" msg))))))))
(defun llama-chat-eldoc-function (cb)
(cond
((markdown-link-p) (funcall cb (markdown-link-url) :thing (nth 2 (markdown-link-at-pos (point)))))))
(defun llama-chat-mode-initialize ()
"Set up a new chat buffer."
(setq llama-user-input-begin-marker (make-marker)
llama-user-input-end-marker (make-marker)
llama-ai-response-begin-marker (make-marker)
llama-ai-response-end-marker (make-marker))
(set-marker-insertion-type llama-user-input-end-marker t)
(set-marker-insertion-type llama-ai-response-end-marker t)
(set-marker llama-ai-response-begin-marker (point-max))
(set-marker llama-ai-response-end-marker (point-max)))
(defun llama-chat-self-insert-advice (&rest _)
"Makes sure that the point is within the user input zone whenever typing input."
(when (and (eq major-mode 'llama-chat-mode)
(< (point) (marker-position llama-user-input-begin-marker)))
(goto-char llama-user-input-end-marker)))
(advice-add 'self-insert-command :before #'llama-chat-self-insert-advice)
(define-derived-mode llama-chat-mode text-mode "Llama"
"Major mode for chatting with the AI."
:interactive nil
:group 'llama
;; Use markdown-mode for font-locking
(setq font-lock-defaults
'(markdown-mode-font-lock-keywords
nil nil nil nil
(font-lock-multiline . t)
(font-lock-syntactic-face-function . markdown-syntactic-face)
(font-lock-extra-managed-props
. (composition display invisible keymap help-echo mouse-face))))
(add-hook 'eldoc-documentation-functions #'llama-chat-eldoc-function nil t)
(setq-local window-point-insertion-type t)
(llama-chat-mode-initialize))
(defun llama-chat-buffer-name ()
"*llama-chat*")
(cl-defun llama-ai-response-finished-callback (&key callback)
(lambda (text)
(put-text-property llama-ai-response-begin-marker
llama-ai-response-end-marker
'read-only t)
(let ((inhibit-read-only t))
(save-excursion
(goto-char (point-max))
(insert (propertize "\n\n" 'read-only t))
(insert (propertize llama-chat-prompt-symbol 'read-only t 'rear-nonsticky '(read-only)))
(set-marker llama-user-input-begin-marker (point))
(set-marker llama-user-input-end-marker llama-user-input-begin-marker)
(set-marker llama-ai-response-begin-marker (point))
(set-marker llama-ai-response-end-marker llama-ai-response-begin-marker)))
(goto-char llama-user-input-begin-marker)
(when callback (funcall callback text))))
(cl-defun llama-chat-send-prompt (name prompt &key filter callback)
"Send the PROMPT to the chat buffer named NAME.
If FILTER is provided, it should be a function that accepts the raw AI response
and two callback arguments `insert' and `send'. In the filter function, call
`insert' to insert text into the chat buffer or `send' to send additional text
to the AI (e.g. to provide function call results).
If CALLBACK is provided, it will be called with the raw AI response text after
it has been inserted into the chat buffer."
(with-current-buffer name
(if filter
(cl-flet ((insert (text)
(let ((inhibit-read-only t))
(save-excursion
(goto-chat llama-ai-response-begin-marker)
(insert text)))
(funcall (llama-ai-response-finished-callback :callback callback) text))
(send (text)
(llm-chat-prompt-append-response prompt text)
(llama-chat-send-prompt name prompt :filter filter)))
(llm-chat-async llama-llm-provider prompt
(lambda (response)
(funcall filter response insert send))))
(llama-chat-streaming-to-chat-buffer llama-llm-provider
prompt
(current-buffer)
llama-ai-response-begin-marker
(llama-ai-response-finished-callback :callback callback)))))
(cl-defun llama-chat-buffer (name prompt &key provider filter callback)
(let ((buffer (get-buffer-create name)))
(with-current-buffer buffer
(unless (eq major-mode 'llama-chat-mode)
(llama-chat-mode)
(when provider
(setq-local llama-llm-provider provider))
(when filter
(setq-local llama-current-chat-filter filter))
(setq llama-current-chat-prompt prompt)
(llama-chat-send-prompt name prompt :filter filter :callback callback)))
buffer))
(defun llama-chat-send ()
(interactive)
(unless (eq major-mode 'llama-chat-mode)
(error "Not in a llama-chat buffer"))
(let ((input (s-trim
(buffer-substring-no-properties llama-user-input-begin-marker
llama-user-input-end-marker))))
(when (s-present? input)
(llm-chat-prompt-append-response llama-current-chat-prompt input)
(save-excursion
(let ((inhibit-read-only t))
(goto-char llama-user-input-end-marker)
(insert (propertize "\n\n" 'read-only t))
(set-marker llama-ai-response-begin-marker (point))))
(llama-chat-send-prompt (current-buffer)
llama-current-chat-prompt
:filter llama-current-chat-filter))))
(defun llama-chat-follow-link ()
(interactive)
(cond
((button-at (point)) (push-button (point)))
((markdown-link-p) (markdown-follow-link-at-point))))
(defun llama-chat-context-action ()
"Perform a contextual action in the chat buffer based on the point:
* follows the link at point
* submits the input if point is within the user input zone"
(interactive)
(cond
((markdown-link-p) (llama-chat-follow-link))
((and (>= (point) (marker-position llama-user-input-begin-marker))
(<= (point) (marker-position llama-user-input-end-marker))
(not llama-waiting-for-ai))
(llama-chat-send))))
(defun llama-chat-next-prompt ()
"Jump to the next prompt in the chat buffer."
(interactive)
(let ((found (save-excursion
(next-line)
(search-forward-regexp (rx line-start (literal llama-chat-prompt-symbol)) nil t))))
(when found
(goto-char found)
(beginning-of-line))))
(defun llama-chat-previous-prompt ()
"Jump to the previous prompt in the chat buffer."
(interactive)
(let ((found (save-excursion
(previous-line)
(search-backward-regexp (rx line-start (literal llama-chat-prompt-symbol)) nil t))))
(when found
(goto-char found)
(beginning-of-line))))
(defun llama-chat ()
"Start a chat with the AI."
(interactive)
(pop-to-buffer (llama-chat-buffer
llama-chat-default-name
(apply #'llm-make-chat-prompt llama-chat-default-initial-prompt-args))))
(defun llama-doctor ()
"Start a psycotherapy session with the AI."
(interactive)
(pop-to-buffer (llama-chat-buffer
"*llama-doctor*"
(llm-make-chat-prompt
"Briefly greet the client without mentioning your name and ask how you can help."
:context "You are an empathetic therapist."))))
(keymap-set llama-chat-mode-map "RET" #'llama-chat-context-action)
(keymap-set llama-chat-mode-map "S-<return>" #'newline)
(keymap-set llama-chat-mode-map "C-j" #'newline)
(keymap-set llama-chat-mode-map "C-c C-c" #'llama-chat-send)
(keymap-set llama-chat-mode-map "C-c C-n" #'llama-chat-next-prompt)
(keymap-set llama-chat-mode-map "C-c C-p" #'llama-chat-previous-prompt)
(keymap-set llama-chat-mode-map "M-n" #'markdown-next-link)
(keymap-set llama-chat-mode-map "M-p" #'markdown-previous-link)
(when (featurep 'evil)
(evil-define-key 'normal llama-chat-mode-map
(kbd "RET") #'llama-chat-follow-link
"[[" #'llama-chat-previous-prompt
"]]" #'llama-chat-next-prompt))
(cl-defun llama-send-string-to-chat (name string &key user-visible-string initial-prompt)
"Send STRING to the chat named NAME.
If USER-VISIBLE-STRING is provided, display that as the user input in the chat
buffer instead of the original string."
(cl-flet ((send (&rest _args)
(with-current-buffer name
(save-excursion
(let ((inhibit-read-only t))
(goto-char llama-user-input-begin-marker)
(insert (or user-visible-string string))
(goto-char llama-user-input-end-marker)
(insert (propertize "\n\n" 'read-only t))
(set-marker llama-ai-response-begin-marker (point))))
(llm-chat-prompt-append-response llama-current-chat-prompt string)
(llama-chat-send-prompt name llama-current-chat-prompt :filter llama-current-chat-filter))))
(if (get-buffer name)
(send)
(pop-to-buffer (llama-chat-buffer
name
(apply #'llm-make-chat-prompt llama-chat-default-initial-prompt-args)
:callback #'send)))))
(defun llama-ask-region (start end prompt &optional name)
"Ask the AI in buffer NAME the PROMPT about the region between START and END.
NAME defaults to `llama-chat-default-name'."
(interactive (list (region-beginning)
(region-end)
(read-string "Prompt: ")
(if current-prefix-arg
(read-string "Chat buffer: ")
llama-chat-default-name)))
(let ((input (format "\n%s\n\n%s" (buffer-substring-no-properties start end) prompt)))
(llama-send-string-to-chat
(or name llama-chat-default-name)
input)
(display-buffer (or name llama-chat-default-name))))
(defun llama-ask-buffer (buffer prompt &optional name)
"Ask the AI in buffer NAME the PROMPT about the BUFFER (interactively, the current buffer).
NAME defaults to `llama-chat-default-name'."
(interactive (list (current-buffer)
(read-string "Prompt: ")
(if current-prefix-arg
(read-string "Chat buffer: ")
llama-chat-default-name)))
(let* ((input (format "%s\n\n%s" (buffer-substring-no-properties (point-min) (point-max)) prompt))
(buf (current-buffer))
(button (buttonize
(format "<Buffer: %s>" (current-buffer))
(lambda (_)
(pop-to-buffer buf)))))
(llama-send-string-to-chat
(or name llama-chat-default-name)
input
:user-visible-string (format "%s\n\n%s" button prompt))
(display-buffer (or name llama-chat-default-name))))
(defun llama-replace-in-region (start end prompt)
"Replace the region between START and END with the AI's response to PROMPT. Requires confirmation."
(interactive "r\nsPrompt: ")
(let ((buffer (current-buffer))
(llm-prompt (llm-make-chat-prompt (format "PROMPT:\n%s\n\nINPUT:\n%s\n" prompt (buffer-substring-no-properties start end))
:context "You are an AI assistant tasked with generating replacement text based on some input text and a prompt. You will be given a PROMPT and an INPUT, and must produce a REPLACEMENT that replaces the original input and an EXPLANATION that explains why the replacement was chosen. Format your answer like this:
EXPLANATION:
<explanation>
REPLACEMENT:
<replacement>
Do not include any additonal notes or commentary outside of the explanation section - all text following the REPLACEMENT: label should be the verbatim replacement."
:examples '(("PROMPT:\nCan you fix the grammar in this sentence?\n\nINPUT:\nI loves to eat pizza!\n"
.
"EXPLANATION:\nThe correct conjugation for the verb \"love\" in first person singular is \"I love\".\nREPLACEMENT:\nI love to eat pizza!")
("PROMPT:\nLowercase all the keys of this JSON object\n\nINPUT:\n{\"Foo\": \"bar\", \"Baz\": \"qux\"}\n"
.
"EXPLANATION:\nI made all the keys of the JSON object lowercase\nREPLACEMENT:\n{\"foo\": \"bar\", \"baz\": \"qux\"}")
("PROMPT:\nRewrite this into a list of bullet points\n\nINPUT:\nWilliam Barry Wood, Jr. (May 4, 1910 March 9, 1971) was an American football player and medical educator. Wood played quarterback for Harvard during the 19291931 seasons and was one of the most prominent football players of his time. He was elected to the College Football Hall of Fame in 1980.\n"
.
"EXPLANATION:\nHere is the rewritten text in a list of bullet points\nREPLACEMENT:\n• William Barry Wood, Jr. (May 4, 1910 March 9, 1971) was an American football player and medical educator.
He played quarterback for Harvard University during the seasons:
+ 1929
+ 1930
+ 1931
He was one of the most prominent football players of his time.
Wood was elected to the College Football Hall of Fame in 1980.")))))
(spinner-start llama-spinner-type)
(llm-chat-async llama-llm-provider
llm-prompt
(lambda (response)
(with-current-buffer buffer
(spinner-stop))
(with-temp-buffer
(insert response)
(goto-char (point-min))
(let* ((exp-start (save-excursion
(when (search-forward "EXPLANATION:")
(point))))
(replace-start (save-excursion
(when (search-forward "REPLACEMENT:")
(point))))
(exp-end (when replace-start (- replace-start (length "REPLACEMENT:"))))
(explanation (when (and exp-start exp-end)
(s-trim (buffer-substring-no-properties exp-start exp-end))))
(replacement (when replace-start
(s-trim (buffer-substring-no-properties replace-start (point-max))))))
(unless replacement
(error "LLM did not return a valid replacement"))
(when (y-or-n-p (format "Explanation:\n%s\n\nReplacment:\n%s\nAccept AI replacement?"
explanation
replacement))
(with-current-buffer buffer
(save-excursion
(delete-region start end)
(goto-char start)
(insert replacement)))))))
(lambda (_ msg) (error "Error calling the LLM: %s" msg)))))
(defun llama-add-comments (start end)
"Add explanatory comments to the code between START and END."
(interactive "r")
(llama-replace-in-region
start
end
"Add concise comments explaining parts of this code that would be otherwise difficult to interpret. Comments belong either on a line by themselves above the code they explain, or inline with the code at the end of the line. Answer with the complete code including the comments. Do not wrap your response in code fences or other markup."))
(defun llama-fill (start end)
"Replace the //fill keyword with the missing logic between START and END."
(interactive "r")
(llama-replace-in-region
start
end
"Replace the keyword //fill in the input with the missing logic. Answer with the complete code including filled-in logic. Do not wrap your response in code fences or other markup."))
(defun llama-rename-symbols (start end)
"Rename code symbols between START and END for clarity and expressiveness."
(interactive "r")
(llama-replace-in-region
start
end
"Rename code symbols (function, variable, class) for clarity and expressiveness. Answer with only the complete code. Do not wrap your response in code fences or other markup."))
(provide 'llama)
;;; llama.el ends here

View File

@ -0,0 +1,254 @@
;;; llm.el --- An Emacs interface to the LLM command-line tool -*- lexical-binding: t; -*-
;; Copyright (C) 2024 Jeremy Isaac Dormitzer
;; Author: Jeremy Isaac Dormitzer <jeremydormitzer@hummingbird.co>
;; Version: 0.1
;; Package-Requires: ((emacs "24.3") (s "1.13") (markdown-mode "2.7"))
;; Keywords: tools
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; This package provides an Emacs interface to the LLM command-line tool.
;;; Code:
(require 's)
(require 'markdown-mode)
(defcustom llm-executable "llm"
"Path to the llm executable."
:type 'string
:group 'llm)
(defcustom llm-model nil
"The llm model to use."
:type 'string
:group 'llm)
(defcustom llm-max-tokens 5000
"The maximum number of tokens to generate."
:type 'integer
:group 'llm)
(defun llm--ensure-executable ()
"Ensure that the llm executable is available."
(unless (executable-find llm-executable)
(error
"llm executable not found: see https://llm.datasette.io/en/stable/index.html for installation instructions")))
(defun llm--process-filter (proc string)
(let* ((buffer (process-buffer proc))
(window (get-buffer-window buffer))
(string (replace-regexp-in-string "\r\n" "\n" string)))
(when (buffer-live-p buffer)
(with-current-buffer buffer
(if (not (mark)) (push-mark))
(exchange-point-and-mark) ;Use the mark to represent the cursor location
(dolist (char (append string nil))
(cond ((char-equal char ?\r)
(move-beginning-of-line 1))
((char-equal char ?\n)
(move-end-of-line 1) (newline))
(t
(if (/= (point) (point-max)) ;Overwrite character
(delete-char 1))
(insert char))))
(exchange-point-and-mark)))
(if window
(with-selected-window window
(goto-char (point-max))))))
(define-derived-mode llm-mode markdown-mode "llm"
"Major mode for LLM output.")
(define-key llm-mode-map
(kbd "q") #'quit-window)
(when (fboundp #'evil-define-key)
(evil-define-key 'normal llm-mode-map
(kbd "q") #'quit-window))
(defun llm--run-async-process-sentinal (proc string)
(with-current-buffer (process-buffer proc)
(goto-char (point-max))
(newline)
(newline)
(insert (format "[llm %s]" (s-trim string)))))
(defun llm--run-async (name buffer-name &rest llm-args)
"Run llm with LLM-ARGS asynchronously.
The process is named NAME and runs in BUFFER-NAME."
(llm--ensure-executable)
(when-let ((existing-buffer (get-buffer buffer-name)))
(kill-buffer existing-buffer))
(let ((proc (make-process :name name
:buffer buffer-name
:command (cons llm-executable llm-args)
:filter #'llm--process-filter)))
(with-current-buffer (process-buffer proc)
(llm-mode))
(set-process-sentinel proc #'llm--run-async-process-sentinal)))
(cl-defun llm--prompt-args (&key prompt system-prompt options extra-args)
"Construct the arguments to prompt LLM with PROMPT."
(let* ((opts (-mapcat (lambda (pair)
(list "-o" (car pair) (cdr pair)))
options))
(sys (when system-prompt
(list "-s" system-prompt)))
(model (when llm-model
(list "-m" llm-model))))
(append (list "prompt") model sys opts extra-args (list prompt))))
(cl-defun llm--prompt-async (&key prompt system-prompt options extra-args name buffer-name)
"Prompt LLM asynchronously with PROMPT and other options."
(let* ((name (or name "llm-prompt"))
(buffer-name (or buffer-name (format "*%s*" name)))
(args (llm--prompt-args :prompt prompt
:system-prompt system-prompt
:options options
:extra-args extra-args)))
(apply #'llm--run-async name buffer-name args)))
;;;###autoload
(cl-defun llm-call (callback &rest llm-args)
"Call llm with LLM-ARGS and call CALLBACK with the result."
(when-let ((buf (get-buffer " *llm-call*")))
(kill-buffer buf))
(let ((proc (apply #'llm--run-async "llm-call" " *llm-call*" llm-args)))
(set-process-sentinel proc
(lambda (proc event)
(unless (string= event "finished\n")
(error "llm-call failed: %s" (s-trim event)))
(with-current-buffer (process-buffer proc)
(goto-char (point-min))
(funcall callback (s-trim
(buffer-substring-no-properties
(point)
(point-max)))))
(kill-buffer (process-buffer proc))))))
;;;###autoload
(defun llm-set-model (model)
"Set the LLM model to MODEL."
(interactive (list (let* ((model-strings
(split-string (shell-command-to-string
(format "%s models" (executable-find llm-executable)))
"\n" t " "))
(models (mapcar
(lambda (s)
(cons s
(cadr
(s-match ".*?: \\(.*?\\)\\(?:[[:blank:]]\\|$\\)" s))))
model-strings))
(selected (completing-read "Model: " models)))
(alist-get selected models nil nil #'equal))))
(setq llm-model model))
(defvar llm-model-options-alist
`(("Meta-Llama-3-8B-Instruct" . (("max_tokens" . ,(number-to-string llm-max-tokens)))))
"Alist mapping model names to options to pass to llm.")
(defun llm--model-options (&optional model)
"Get the extra arguments for MODEL."
(let ((model (or model llm-model)))
(alist-get model llm-model-options-alist nil nil #'equal)))
;;;###autoload
(defun llm-prompt (query &optional system-prompt)
"Prompt llm with the QUERY and optionally SYSTEM-PROMPT."
(interactive (list (read-string "Query: " nil nil)
(when current-prefix-arg
(read-string "System prompt: " nil nil))))
(llm--prompt-async :prompt query :options (llm--model-options))
(switch-to-buffer "*llm-prompt*"))
;;;###autoload
(defun llm-prompt-buffer (system-prompt)
"Prompt llm with the contents of the current buffer and the SYSTEM-PROMPT."
(interactive "sSystem prompt: ")
(llm--prompt-async :prompt (buffer-substring-no-properties (point-min) (point-max))
:system-prompt system-prompt
:options (llm--model-options)
:name "llm-prompt-buffer"
:buffer-name "*llm-prompt-buffer*")
(switch-to-buffer "*llm-prompt-buffer*"))
(defun llm-prompt-region (system-prompt)
"Prompt llm with the contents of the region and the SYSTEM-PROMPT."
(interactive "sSystem prompt: ")
(llm--prompt-async :prompt (buffer-substring-no-properties (region-beginning) (region-end))
:system-prompt system-prompt
:options (llm--model-options)
:name "llm-prompt-region"
:buffer-name "*llm-prompt-region*")
(switch-to-buffer "*llm-prompt-region*"))
(defvar llm-chat-mode-map
(make-sparse-keymap)
"Keymap for `llm-chat-mode'.")
(defvar llm-chat-prompt-regexp "^> "
"Regexp to match the prompt in `llm-chat-mode'.")
(define-derived-mode llm-chat-mode comint-mode "llm-chat"
"Major mode for chatting with llm."
(setq comint-prompt-regexp llm-chat-prompt-regexp)
(setq comint-prompt-read-only t)
(setq comint-process-echoes t))
(cl-defun llm--chat-args (&key system-prompt options)
(let ((opts (-mapcat (lambda (pair)
(list "-o" (car pair) (cdr pair)))
options))
(sys (when system-prompt
(list "-s" system-prompt)))
(model (when llm-model
(list "-m" llm-model))))
(append (list "chat") model sys opts)))
;;;###autoload
(defun llm-chat (system-prompt &optional name)
"Start a chat session with llm, prompting it with SYSTEM-PROMPT, naming the process and buffer NAME."
(interactive (list (read-string "System prompt: " "You are a helpful AI assistant running inside the Emacs text editor.")
"llm-chat"))
(let* ((name (or name "llm-chat"))
(buffer-name (format "*%s*" name))
(buffer (get-buffer-create buffer-name))
(proc-alive (comint-check-proc buffer))
(process (get-buffer-process buffer)))
(unless proc-alive
(with-current-buffer buffer
(apply #'make-comint-in-buffer
name
buffer
llm-executable
nil
(llm--chat-args :system-prompt system-prompt
:options (llm--model-options)))
(llm-chat-mode)))
(when buffer
(pop-to-buffer buffer))))
;;;###autoload
(defun llm-doctor ()
"Start a psychotherapy session with llm."
(interactive)
(llm-chat "You are an empathetic therapist." "llm-doctor"))
(provide 'llm)
;;; llm.el ends here

View File

@ -0,0 +1,316 @@
;;; navi.el --- Emacs interface to the Navi shell cheatsheat utility -*- lexical-binding: t; -*-
;; Copyright (C) 2024 Jeremy Isaac Dormitzer
;; Author: Jeremy Isaac Dormitzer <jeremydormitzer@hummingbird.co>
;; Keywords: tools
;; Package-Requires: ((emacs "25.1") (s "1.13") (ht "2.4"))
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(require 'cl-lib)
(require 'eieio)
(require 's)
(require 'ht)
;;;###autoload
(defclass navi-cheat-registry ()
((cheat-files :initarg :cheat-files
:initform (make-hash-table :test 'equal)
:type hash-table
:documentation "Maps tag lists to a list of cheat files with those tags.")
(tags-index :initarg :tags-index
:initform (make-hash-table :test 'equal)
:type hash-table
:documentation "Maps individual tags to cheat files."))
"A registry of Navi cheat files.")
;;;###autoload
(defclass navi-cheat-file ()
((filename :initarg :filename
:initform ""
:type string
:documentation "The name of the cheat file.")
(tags :initarg :tags
:initform nil
:type list
:documentation "Tags for the cheat file.")
(generators :initarg :generators
:initform (make-hash-table :test 'equal)
:type hash-table
:documenation "Commands that generate possible values for variables.")
(imports :initarg :imports
:initform nil
:type list
:documentation "A list of lists of tags identifying other cheat files to import.")
(cheats :initarg :cheats
:initform nil
:type list
:documentation "A list of navi-cheat objects.")
(registry :initarg :registry
:type navi-cheat-registry
:documentation "The registry this cheat file belongs to."))
"A Navi cheat file.")
;;;###autoload
(defclass navi-cheat ()
((description :initarg :description
:initform ""
:type string
:documentation "Command description.")
(command :initarg :command
:initform ""
:type string
:documentation "The template of the command to run.")
(cheat-file :initarg :cheat-file
:type navi-cheat-file
:documentation "The cheat file this command belongs to."))
"A Navi command cheat template.")
(cl-defmethod navi-cheat-registry-add-cheat-file ((registry navi-cheat-registry) (cheat-file navi-cheat-file))
"Add CHEAT-FILE to REGISTRY."
(let ((tags (oref cheat-file tags)))
(puthash tags (append (gethash tags (oref registry cheat-files)) (list cheat-file)) (oref registry cheat-files))
(dolist (tag tags)
(puthash tag (append (gethash tag (oref registry tags-index)) (list cheat-file)) (oref registry tags-index)))))
(cl-defmethod navi-cheat-registry-tags ((registry navi-cheat-registry))
"Return a list of all tags in REGISTRY."
(hash-table-keys (oref registry tags-index)))
(cl-defmethod navi-cheat-registry-cheat-files ((registry navi-cheat-registry) &optional tags)
(if (seq-empty-p tags)
(-flatten (hash-table-values (oref registry cheat-files)))
(apply #'append (mapcar (lambda (tag) (gethash tag (oref registry tags-index))) tags))))
(cl-defmethod navi-cheat-file-generators ((cheat-file navi-cheat-file))
(let ((imported-generators (make-hash-table :test 'equal))
(imports (oref cheat-file imports))
(registry (oref cheat-file registry)))
(dolist (tags imports)
(let ((imported-files (gethash tags (oref registry cheat-files))))
(dolist (imported-file imported-files)
;; TODO: this doesn't handle cycles
(ht-update! imported-generators (navi-cheat-file-generators imported-file)))))
(ht-merge imported-generators (oref cheat-file generators))))
(cl-defmethod navi-cheat-render ((cheat navi-cheat))
"Render the command for CHEAT."
(let* ((cmd (oref cheat command))
(cheat-file (oref cheat cheat-file))
(generators (navi-cheat-file-generators cheat-file))
(var-values (make-hash-table :test 'equal)))
(-navi--interpolate-vars cmd var-values generators)))
(defun -navi--run-generator (generator)
(with-temp-buffer
(if (not (eq (call-process-shell-command generator nil t) 0))
(error "%s" (buffer-string))
(split-string (buffer-string) "\n" t " "))))
(defun -navi--interpolate-vars (str vars generators)
(let ((var-matches (s-match-strings-all "<\\(.*?\\)>" str)))
(dolist (match var-matches)
(let* ((placeholder (car match))
(var (cadr match))
(cached-value (gethash var vars))
(value (if cached-value
cached-value
(let* ((generator (gethash var generators))
(generator (when generator (-navi--interpolate-vars generator vars generators)))
(value (if generator
(consult--read (-navi--run-generator generator)
:prompt (format "%s: " var)
:category (cond
((string-match-p "^\\(find\\|ls\\)" generator) 'file)))
(read-string (format "%s: " var)))))
(puthash var value vars)
value))))
(setq str (replace-regexp-in-string (regexp-quote placeholder) value str t t)))))
str)
(defun navi-parse-cheat-file (file registry)
"Parse the cheat file FILE and returns a navi-cheat-file object."
(let ((cheat-file (navi-cheat-file :filename file :registry registry)))
(with-temp-buffer
(insert-file-contents file)
(goto-char (point-min))
(while (not (eobp))
(let* ((line (buffer-substring-no-properties (line-beginning-position) (line-end-position)))
(prefix (when (not (string-empty-p line))
(substring line 0 2))))
(cond
((string-empty-p line) (forward-line))
((string= prefix "% ")
(let ((tags (split-string (substring line 2) ", " t " ")))
(oset cheat-file :tags (append (oref cheat-file tags) tags))
(forward-line)))
((string= prefix "# ")
(let ((desc (substring line 2))
(cmd (progn
(forward-line)
(-navi--parse-multiline-string))))
(object-add-to-list cheat-file
:cheats
(navi-cheat :description desc :command cmd :cheat-file cheat-file)
t)
(forward-line)))
((string= prefix "; ") (forward-line))
((string= prefix "$ ")
(let* ((generator-def (substring line 2))
(split (split-string generator-def ": " t " "))
(var (car split))
(cmd (cadr split)))
(puthash var cmd (oref cheat-file generators))
(forward-line)))
((string= prefix "@ ")
(let ((tags (split-string (substring line 2) ", " t " ")))
(object-add-to-list cheat-file :imports tags t)
(forward-line)))
;; default: assume it's a command
(t (let ((cmd (-navi--parse-multiline-string)))
(object-add-to-list cheat-file :cheats (navi-cheat :command cmd :cheat-file cheat-file) t)
(forward-line)))))))
cheat-file))
(defun -navi--parse-multiline-string ()
(cl-loop with cmd = ""
until (or (string-empty-p (buffer-substring-no-properties
(line-beginning-position)
(line-end-position)))
(eobp))
do (setq cmd (concat cmd
(buffer-substring-no-properties
(line-beginning-position)
(line-end-position))
"\n"))
do (forward-line)
finally return (s-trim cmd)))
(defvar -navi--cheat-cache (make-hash-table :test 'equal)
"Map of cheat file checksums to navi-cheat-file objects.")
(defun -navi--cheat-file-checksum (file)
(let ((cksum (shell-command-to-string
(format "%s %s"
(or (executable-find "cksum")
(error "cksum not found"))
file))))
(string-trim (car (split-string cksum " ")))))
(defun -navi--get-or-cache-cheat-file (file registry)
(let ((checksum (-navi--cheat-file-checksum file)))
(unless (gethash checksum -navi--cheat-cache)
(puthash checksum (navi-parse-cheat-file file registry) -navi--cheat-cache))
(gethash checksum -navi--cheat-cache)))
(defun navi-cheat-files (&optional path)
"Return a navi-registry of navi-cheat-file objects in PATH, which defaults to $NAVI_PATH."
(let* ((registry (navi-cheat-registry))
(navi-path (or path (getenv "NAVI_PATH")))
(dirs (when navi-path (split-string navi-path ":" t " "))))
(when dirs
(dolist (file (->> dirs
(-filter #'file-directory-p)
(-map (lambda (dir) (directory-files dir t ".*\\.cheat$")))
(-flatten)
(-map (lambda (file) (navi-parse-cheat-file file registry)))))
(navi-cheat-registry-add-cheat-file registry file)))
registry))
;;;###autoload
(defun navi-all-cheats ()
"Returns all Navi cheats on $NAVI_PATH."
(navi-cheat-registry-cheat-files (navi-cheat-files)))
;;;###autoload
(defun navi-cheats-for-tags (tags)
"Returns all Navi cheats on $NAVI_PATH tagged with TAGS."
(navi-cheat-registry-cheat-files (navi-cheat-files) tags))
;;;###autoload
(defun navi-cheats-matching-filename (rx)
"Returns all Navi cheats on $NAVI_PATH whose filenames match RX."
(seq-filter
(lambda (cheat-file)
(string-match-p rx (oref cheat-file filename)))
(navi-all-cheats)))
;;;###autoload
(cl-defmethod navi-cheat-summary ((cheat navi-cheat))
"Return a summary of CHEAT."
(format "%s: %s [%s]"
(oref cheat description)
(oref cheat command)
(s-join " " (oref (oref cheat cheat-file) tags))))
(defun -navi--build-completion-table (cheats)
(-map
(lambda (cheat)
(cons (navi-cheat-summary cheat)
cheat))
cheats))
(defun -navi-interactive (cheat-files)
(let* ((cheats (->> cheat-files
(-mapcat (lambda (cheat-file) (oref cheat-file cheats)))
(-navi--build-completion-table)))
(cheat (consult--read cheats
:prompt "Command: "
:category 'navi
:require-match t))
(cmd (navi-cheat-render (alist-get cheat cheats nil nil 'equal))))
(let ((compilation-buffer-name-function (lambda (_) (format "*%s*" cmd))))
(compile cmd))))
;;;###autoload
(defun navi ()
"Run a command from a Navi cheatsheet."
(interactive)
(-navi-interactive (navi-all-cheats)))
;;;###autoload
(defun navi-by-tags (tags)
"Run a command from a Navi cheatsheet tagged with TAGS."
(interactive (list (completing-read-multiple "Tags: " (navi-cheat-registry-tags (navi-cheat-files)))))
(-navi-interactive (navi-cheats-for-tags tags)))
;;;###autoload
(defun navi-matching-current-directory ()
"Run a command from a Navi cheatsheet whose filename matches the current directory."
(interactive)
(-navi-interactive
(navi-cheats-matching-filename (regexp-quote (file-name-directory default-directory)))))
;;;###autoload
(defun navi-visit-cheat-file (cheat)
"Visit the cheat file for CHEAT."
(interactive (list (let* ((cheats (-navi--build-completion-table
(-mapcat (lambda (cheat-file)
(oref cheat-file cheats))
(navi-all-cheats))))
(selected (consult--read cheats
:prompt "Cheat file: "
:category 'navi
:require-match t)))
(alist-get selected cheats nil nil 'equal))))
(find-file (oref (oref cheat cheat-file) filename)))
(provide 'navi)
;;; navi.el ends here

View File

@ -0,0 +1,223 @@
;;; origami-treesit.el --- treesit.el integration with origami.el text folding -*- lexical-binding: t; -*-
;; Copyright (C) 2024 Jeremy Dormitzer
;; Author: Jeremy Dormitzer <jeremy.dormitzer@gmail.com>
;; Version: 0.1.0
;; Package-Requires: ((emacs "29.1") (origami "1.0") (dash "2.19.1") (cl-lib "1.0"))
;; Keywords: tools
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; Adds origami parsers based on tree-sitter.
;;; Code:
(require 'treesit)
(require 'cl-lib)
(require 'dash)
(defcustom origami-treesit-level 2
"The level of folding to use for origami-treesit."
:type 'integer
:group 'origami
:options '(1 2 3 4))
(cl-defun make-treesit-parser (create
lang
&key
node-matcher-fn
fold-fn)
(lambda (content)
(let* ((parser (treesit-parser-create lang))
(root (treesit-parser-root-node parser)))
(cl-labels ((get-node-folds (node)
(if (funcall node-matcher-fn node origami-treesit-level)
(cl-destructuring-bind
(start end offset child-nodes &optional sibling-nodes)
(funcall fold-fn node)
(cons (funcall create start end offset (-mapcat #'get-node-folds child-nodes))
(-mapcat #'get-node-folds sibling-nodes)))
(-mapcat #'get-node-folds (treesit-node-children node)))))
(get-node-folds root)))))
(defun origami-yaml-treesit-parser (create)
(make-treesit-parser create 'yaml
:node-matcher-fn (lambda (node _level)
(and (treesit-node-check node 'named)
(member (treesit-node-type node)
'("block_mapping_pair"
"flow_mapping"
"flow_pair"
"flow_sequence"))))
:fold-fn (lambda (node)
(pcase (treesit-node-type node)
((or "block_mapping_pair" "flow_pair")
(let* ((key-node (treesit-node-child-by-field-name node "key"))
(value-node (treesit-node-child-by-field-name node "value"))
(line-beginning (save-excursion
(goto-char (treesit-node-start node))
(line-beginning-position)))
(on-own-line? (= (save-excursion
(goto-char (treesit-node-start node))
(back-to-indentation)
(point))
(treesit-node-start node)))
(start (save-excursion
(goto-char (treesit-node-start node))
(if on-own-line?
line-beginning
(treesit-node-start node))))
(end (treesit-node-end node))
(offset (+ 1 (if on-own-line?
(- (treesit-node-end key-node) line-beginning)
(length (treesit-node-text key-node t)))))
(child-nodes (treesit-node-children value-node)))
(list start end offset child-nodes)))
((or "flow_mapping" "flow_sequence")
(let* ((start (treesit-node-start node))
(end (- (treesit-node-end node) 1))
(offset 1)
(child-nodes (treesit-node-children node)))
(list start end offset child-nodes)))))))
(defun origami-ruby-treesit-parser (create)
(make-treesit-parser create 'ruby
:node-matcher-fn
(lambda (node level)
(let ((foldable-node-types '()))
(when (>= level 1)
(setq foldable-node-types
(append foldable-node-types
'("class" "method" "do_block" "hash" "array"))))
(when (>= level 2)
(setq foldable-node-types
(append foldable-node-types
'("block" "then" "else" "pair"))))
(when (>= level 3)
(setq foldable-node-types
(append foldable-node-types
'("call" "assignment"))))
(and (treesit-node-check node 'named)
(member (treesit-node-type node) foldable-node-types)
(or (not (member (treesit-node-type node)
'("class"
"method"
"do_block"
"block")))
(treesit-node-child-by-field-name node "body"))
(or (not (equal (treesit-node-type node) "hash"))
(treesit-search-subtree
node
(lambda (n)
(equal (treesit-node-type n) "pair"))))
(or (not (equal (treesit-node-type node) "call"))
(treesit-node-child-by-field-name node "arguments"))
(or (not (member (treesit-node-type node) '("then" "else")))
(treesit-node-children node))
(or (not (equal (treesit-node-type node) "pair"))
(treesit-node-child-by-field-name node "value")))))
:fold-fn
(lambda (node)
(pcase (treesit-node-type node)
("class" (let* ((start (treesit-node-start node))
(end (- (treesit-node-end node) 4))
(body (treesit-node-child-by-field-name node "body"))
(offset (save-excursion
(goto-char start)
(forward-line)
(back-to-indentation)
(- (point) start)))
(child-nodes (treesit-node-children node)))
(list start end offset child-nodes)))
((or "method" "do_block" "block")
(let* ((body
(treesit-node-child-by-field-name node "body"))
(start (treesit-node-start node))
(end (treesit-node-end body))
(offset (- (treesit-node-start body) start))
(child-nodes (treesit-node-children node)))
(list start end offset child-nodes)))
("hash" (let* ((pairs (-filter (lambda (n)
(equal (treesit-node-type n) "pair"))
(treesit-node-children node)))
(start (treesit-node-start node))
(end (treesit-node-end (car (last pairs))))
(offset (- (treesit-node-start (first pairs)) start))
(child-nodes (treesit-node-children node)))
(list start end offset child-nodes)))
("array" (let* ((start (treesit-node-start node))
(end (- (treesit-node-end node) 1))
(offset 1)
(child-nodes (treesit-node-children node)))
(list start end offset child-nodes)))
("call" (let* ((args (treesit-node-child-by-field-name node "arguments"))
(method (treesit-node-child-by-field-name node "method"))
(start (treesit-node-start node))
(end (if (equal (char-before (treesit-node-end args)) ?\))
(- (treesit-node-end args) 1)
(treesit-node-end args)))
(offset (if (equal (char-after (treesit-node-start args)) ?\()
(1+ (- (treesit-node-start args) start))
(- (treesit-node-start args) start)))
(child-nodes (-filter (lambda (n)
(not (equal (treesit-node-field-name n)
"block")))
(treesit-node-children node)))
(sibling-nodes (when-let
((block
(treesit-node-child-by-field-name node "block")))
(list block))))
(list start end offset child-nodes sibling-nodes)))
((or "then" "else") (let* ((child-nodes (treesit-node-children node))
(start (treesit-node-start node))
(end (treesit-node-end (car (last child-nodes))))
(offset (- (treesit-node-start (first child-nodes)) start)))
(list start end offset child-nodes)))
("assignment" (let* ((right (treesit-node-child-by-field-name node "right"))
(start (treesit-node-start node))
(end (treesit-node-end node))
(offset (- (treesit-node-start right) start))
(child-nodes (list right)))
(list start end offset child-nodes)))
("pair" (let* ((value (treesit-node-child-by-field-name node "value"))
(start (treesit-node-start node))
(end (treesit-node-end node))
(offset (- (treesit-node-start value) start))
(child-nodes (list value)))
(list start end offset child-nodes)))))))
(defvar origami-treesit-parser-alist
'((yaml-ts-mode . origami-yaml-treesit-parser)
(yaml-mode . origami-yaml-treesit-parser)
(ruby-ts-mode . origami-ruby-treesit-parser)
(ruby-mode . origami-ruby-treesit-parser))
"Alist mapping major-mode to origami-treesit parser function.")
;;;###autoload
(define-minor-mode origami-treesit-mode
"Minor mode for folding code using tree-sitter."
:group 'origami
:lighter nil
:init-value nil
(if origami-treesit-mode
(setq-local origami-parser-alist
(append origami-treesit-parser-alist origami-parser-alist))
(setq-local origami-parser-alist
(-difference origami-parser-alist origami-treesit-parser-alist))))
(provide 'origami-treesit)
;;; origami-treesit.el ends here

View File

@ -8,6 +8,7 @@ tap "railwaycat/emacsmacport"
tap "ryleelyman/seamstress" tap "ryleelyman/seamstress"
brew "clojure/tools/clojure" brew "clojure/tools/clojure"
brew "cmake" brew "cmake"
brew "coreutils"
brew "direnv" brew "direnv"
brew "ffmpeg" brew "ffmpeg"
brew "git" brew "git"
@ -48,6 +49,7 @@ brew "seamstress"
brew "starship" brew "starship"
brew "stow" brew "stow"
brew "sqlite" brew "sqlite"
brew "terraform"
brew "terraform-ls" brew "terraform-ls"
brew "texinfo" brew "texinfo"
brew "typescript-language-server" brew "typescript-language-server"
@ -66,8 +68,10 @@ cask "google-chrome"
cask "google-cloud-sdk" cask "google-cloud-sdk"
cask "intellij-idea" cask "intellij-idea"
cask "iterm2" cask "iterm2"
cask "neovide"
cask "notion" cask "notion"
cask "notion-calendar" cask "notion-calendar"
cask "postman"
cask "protonvpn" cask "protonvpn"
cask "railwaycat/emacsmacport/emacs-mac" cask "railwaycat/emacsmacport/emacs-mac"
cask "rectangle" cask "rectangle"
@ -75,3 +79,4 @@ cask "slack"
cask "spotify" cask "spotify"
cask "syncthing" cask "syncthing"
cask "the-unarchiver" cask "the-unarchiver"
cask "vcv-rack"

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Applice//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>Label</key> <key>Label</key>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Applice//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>local.jdormit.witchcraft</string>
<key>ProgramArguments</key>
<array>
<string>sh</string>
<string>-c</string>
<string>/usr/bin/python3 -m http.server -d ~/witchcraft-scripts 5743</string>
</array>
<key>KeepAlive</key>
<true/>
</dict>
</plist>

View File

@ -1,41 +0,0 @@
;; package setup
(require 'package)
(add-to-list 'package-archives '("gnu" . "https://elpa.gnu.org/packages/"))
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))
(package-initialize)
(unless (package-installed-p 'use-package)
(package-refresh-contents)
(package-install 'use-package))
(eval-when-compile (require 'use-package))
(setq use-package-always-ensure t)
;; evil mode
(use-package evil
:init
(setq evil-want-keybinding nil)
:config
(evil-mode 1)
(setq evil-want-fine-undo t))
(use-package evil-collection
:after (evil)
:config
(evil-collection-init))
;; which-key
(use-package which-key
:config
(which-key-mode))
;; ivy
(use-package counsel
:config
(ivy-mode 1)
(setq ivy-wrap t))
(use-package ivy-hydra
:after counsel)
;; magit
(use-package magit
:commands (magit-status magit-blame magit-find-file))
(use-package evil-magit
:after magit)

View File

@ -1,8 +0,0 @@
alias root cd (projectile-project-root)
alias php-debug php -d xdebug.remote_enable=on -d xdebug.remote_host=127.0.0.1 -d xdebug.remote_port=9000 -d xdebug.remote_handler=dbgp -d xdebug.idekey=geben -d xdebug.remote_autostart=On $*
alias kns kubens $*
alias k kubectl $*
alias kctx kubectx $*
alias sortpom mvn com.github.ekryd.sortpom:sortpom-maven-plugin:sort -Dsort.keepBlankLines -Dsort.sortDependencies=scope,groupId,artifactId -Dsort.createBackupFile=false $*
alias helm /usr/local/bin/helm $*
alias tf terraform $*

View File

@ -1,35 +0,0 @@
;;; -*- lexical-binding: t; -*-
;; If ~/.emacs.d/config/base.el exists, just load it. Otherwise,
;; first bootstrap Straight and load Org to make sure we end up with
;; the right Org version, then tangle ~/.emacs.d/init.org to
;; ~/.emacs.d/config/base.el
(defvar init-org-file (expand-file-name "~/.emacs.old/init.org"))
(defvar config-base-file (expand-file-name "~/.emacs.old/config/base.el"))
(when (not (file-exists-p "~/.emacs.old/config"))
(make-directory "~/.emacs.old/config"))
(when (not (file-exists-p config-base-file))
(message "Bootstrapping init file...")
(defvar bootstrapping-init t)
(defvar bootstrap-version)
(let ((bootstrap-file
(expand-file-name "straight/repos/straight.el/bootstrap.el"
user-emacs-directory))
(bootstrap-version 5))
(unless (file-exists-p bootstrap-file)
(with-current-buffer
(url-retrieve-synchronously
"https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
'silent 'inhibit-cookies)
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil 'nomessage))
(straight-use-package 'org-plus-contrib)
(require 'org)
(org-babel-tangle-file init-org-file))
(load-file config-base-file)

View File

@ -1,35 +0,0 @@
;;; -*- lexical-binding: t; -*-
;; If ~/.emacs.d/config/base.el exists, just load it. Otherwise,
;; first bootstrap Straight and load Org to make sure we end up with
;; the right Org version, then tangle ~/.emacs.d/init.org to
;; ~/.emacs.d/config/base.el
(defvar init-org-file (expand-file-name "~/.emacs.d/init.org"))
(defvar config-base-file (expand-file-name "~/.emacs.d/config/base.el"))
(when (not (file-exists-p "~/.emacs.d/config"))
(make-directory "~/.emacs.d/config"))
(when (not (file-exists-p config-base-file))
(message "Bootstrapping init file...")
(defvar bootstrapping-init t)
(defvar bootstrap-version)
(let ((bootstrap-file
(expand-file-name "straight/repos/straight.el/bootstrap.el"
user-emacs-directory))
(bootstrap-version 5))
(unless (file-exists-p bootstrap-file)
(with-current-buffer
(url-retrieve-synchronously
"https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
'silent 'inhibit-cookies)
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil 'nomessage))
(straight-use-package 'org-plus-contrib)
(require 'org)
(org-babel-tangle-file init-org-file))
(load-file config-base-file)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
console.log("Witchcraft loaded!");

View File

@ -267,7 +267,7 @@ setopt PROMPT_SUBST
PROMPT=$PROMPT'%{$(vterm_prompt_end)%}' PROMPT=$PROMPT'%{$(vterm_prompt_end)%}'
[[ -z "$tw_prompt" ]] || tw_prompt=$tw_prompt'%{$(vterm_prompt_end)%}' [[ -z "$tw_prompt" ]] || tw_prompt=$tw_prompt'%{$(vterm_prompt_end)%}'
[[ -f "$HOME/.zshrc-spotify" ]] && source "$HOME/.zshrc-spotify" [[ -f "$HOME/.zshrc.local" ]] && source "$HOME/.zshrc.local"
type navi > /dev/null && eval "$(navi widget zsh)" type navi > /dev/null && eval "$(navi widget zsh)"