Compare commits
No commits in common. "d2b7817dc1ab7478141c9b42cdffae6ab27dc531" and "2b1a8280215bd81ddbd36c0f751029f872da7503" have entirely different histories.
d2b7817dc1
...
2b1a828021
@ -1,329 +0,0 @@
|
||||
;; -*- 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.
|
||||
|
||||
Here’s 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)
|
@ -28,27 +28,9 @@
|
||||
(vterm-send-string (format "export AWS_PROFILE=%s\n" 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 ()
|
||||
(interactive)
|
||||
(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)))))))))
|
||||
(async-shell-command "AWS_PROFILE=default aws sso login"))
|
||||
|
||||
(add-hook 'emacs-startup-hook
|
||||
(lambda ()
|
||||
|
@ -29,7 +29,7 @@
|
||||
(use-package shell
|
||||
:straight (:type built-in)
|
||||
:general
|
||||
(normal shell-mode-map "q" #'quit-window))
|
||||
(normal shell-mode-map "q" #'bury-buffer))
|
||||
|
||||
(use-package ediff
|
||||
:defer t
|
||||
@ -53,21 +53,16 @@
|
||||
:defer t
|
||||
: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
|
||||
:straight (:type built-in)
|
||||
:commands (man)
|
||||
:custom
|
||||
(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)
|
||||
|
@ -62,11 +62,9 @@
|
||||
;; Embark adds context actions to completion candidates (and other things!)
|
||||
(use-package embark
|
||||
:config
|
||||
;; Put xref targets at the top of the list in programming modes
|
||||
(defun embark-prod-mode-hook ()
|
||||
(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)
|
||||
;; Make sure identifier at point is the first target
|
||||
(setq embark-target-finders (remove 'embark-target-identifier-at-point embark-target-finders))
|
||||
(add-to-list 'embark-target-finders 'embark-target-identifier-at-point)
|
||||
(defun embark-which-key-indicator ()
|
||||
"An embark indicator that displays keymaps using which-key.
|
||||
The which-key help message will show the type and value of the
|
||||
@ -101,18 +99,10 @@ targets."
|
||||
"n" #'straight-normalize-package
|
||||
"m" #'straight-merge-package)
|
||||
(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
|
||||
:doc "Keymap for actions for password-store."
|
||||
:parent embark-general-map
|
||||
"c" #'password-store-copy
|
||||
"RET" #'password-store-copy
|
||||
"f" #'password-store-copy-field
|
||||
"i" #'password-store-insert
|
||||
"I" #'password-store-generate
|
||||
@ -120,36 +110,13 @@ targets."
|
||||
"e" #'password-store-edit
|
||||
"k" #'password-store-remove
|
||||
"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))
|
||||
(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
|
||||
((emacs normal motion insert visual) "C-." #'embark-act)
|
||||
((emacs normal motion insert visual) "M-." #'embark-dwim)
|
||||
("C-." #'embark-act)
|
||||
("M-." #'embark-dwim)
|
||||
(embark-general-map "C-k" #'browse-url-or-search
|
||||
"/" #'consult-line)
|
||||
(embark-file-map "s" #'sudo-edit-find-file
|
||||
"l" #'vlf
|
||||
"g" #'magit-file-dispatch)
|
||||
|
@ -1,7 +1,7 @@
|
||||
;; -*- lexical-binding: t; -*-
|
||||
|
||||
;; Icons!
|
||||
(use-package nerd-icons)
|
||||
(use-package all-the-icons)
|
||||
|
||||
;; A neat dashboard when you open Emacs
|
||||
(use-package dashboard
|
||||
@ -12,8 +12,6 @@
|
||||
(setq dashboard-startup-banner pic)))
|
||||
(dashboard-setup-startup-hook)
|
||||
:custom
|
||||
(dashboard-display-icons-p t)
|
||||
(dashboard-icon-type 'nerd-icons)
|
||||
(dashboard-items '((projects . 5) (recents . 5)))
|
||||
(dashboard-set-heading-icons t)
|
||||
(dashboard-set-file-icons t)
|
||||
|
@ -23,9 +23,7 @@
|
||||
(enable-recursive-minibuffers t)
|
||||
(vc-follow-symlinks t)
|
||||
(display-line-numbers-type 'visual)
|
||||
(even-window-sizes nil)
|
||||
(fill-column 110)
|
||||
(unique-buffer-name-style 'forward))
|
||||
(even-window-sizes nil))
|
||||
|
||||
|
||||
(provide 'init-defaults)
|
||||
|
@ -5,28 +5,7 @@
|
||||
devdocs-lookup
|
||||
devdocs-install
|
||||
devdocs-delete
|
||||
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)))))
|
||||
devdocs-update-all)
|
||||
:general
|
||||
(help-map "D" #'devdocs-lookup)
|
||||
('normal devdocs-mode-map
|
||||
|
@ -7,26 +7,6 @@
|
||||
:config
|
||||
(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.
|
||||
(use-package smartparens
|
||||
:init
|
||||
@ -154,102 +134,4 @@
|
||||
(normal "g M-+" #'evil-numbers/inc-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)
|
||||
|
@ -1,19 +1,10 @@
|
||||
;; Email in Emacs via mu4e
|
||||
(use-package mu4e
|
||||
:straight `(:local-repo ,(directory-file-name (file-name-directory (locate-library "mu4e"))) :type built-in)
|
||||
:defer 2
|
||||
:if (locate-library "mu4e")
|
||||
:commands (mu4e mu4e-update-mail-and-index)
|
||||
:hook (after-init . (lambda () (mu4e t)))
|
||||
: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
|
||||
;; General
|
||||
mu4e-maildir (expand-file-name "~/.mail")
|
||||
@ -30,12 +21,7 @@
|
||||
mu4e-view-actions '(("capture message" . mu4e-action-capture-message)
|
||||
("view as pdf" . mu4e-action-view-as-pdf)
|
||||
("show this thread" . mu4e-action-show-thread)
|
||||
("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))
|
||||
("View in browser" . mu4e-action-view-in-browser))
|
||||
;; Bookmarked searches
|
||||
mu4e-bookmarks '((:name "Inbox"
|
||||
:query "maildir:/jeremy-dormitzer-gmail-com/Inbox OR maildir:/jeremydormitzer-hummingbird-co/Inbox"
|
||||
@ -523,7 +509,6 @@ If given prefix arg ARG, skips markdown conversion."
|
||||
"t" #'mu4e-view-mark-thread
|
||||
"s" mu4e-search-map))
|
||||
(add-hook 'mu4e-headers-mode-hook #'my-mu4e-headers-mode-setup)
|
||||
(add-hook 'mu4e-view-mode-hook #'my-mu4e-view-mode-setup)
|
||||
(mu4e t))
|
||||
(add-hook 'mu4e-view-mode-hook #'my-mu4e-view-mode-setup))
|
||||
|
||||
(provide 'init-email)
|
||||
|
@ -50,13 +50,6 @@
|
||||
eshell
|
||||
prodigy
|
||||
docker
|
||||
devdocs
|
||||
vertico
|
||||
minibuffer
|
||||
eww
|
||||
arc
|
||||
ibuffer))
|
||||
:custom
|
||||
(evil-collection-setup-minibuffer t))
|
||||
devdocs)))
|
||||
|
||||
(provide 'init-evil)
|
||||
|
@ -1,160 +0,0 @@
|
||||
;; -*- 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)
|
@ -2,16 +2,15 @@
|
||||
|
||||
;; File-related configuration
|
||||
|
||||
(defun my/delete-file (file)
|
||||
(defun jdormit-delete-file (file)
|
||||
(interactive (list (read-file-name "Delete file: "
|
||||
nil
|
||||
nil
|
||||
t
|
||||
(file-name-nondirectory (buffer-file-name)))))
|
||||
(let ((file (expand-file-name file)))
|
||||
(buffer-file-name))))
|
||||
(delete-file file)
|
||||
(when (string-equal (buffer-file-name) file)
|
||||
(quit-window))))
|
||||
(bury-buffer)))
|
||||
|
||||
(defun rename-this-file (new-name)
|
||||
(interactive (list (read-file-name "Rename file: "
|
||||
@ -36,7 +35,7 @@
|
||||
"f" '(nil :which-key "file")
|
||||
"ff" #'find-file
|
||||
"fs" #'save-buffer
|
||||
"fD" '(my/delete-file :which-key "delete")
|
||||
"fD" '(jdormit-delete-file :which-key "delete")
|
||||
"fr" '(rename-this-file :which-key "rename")
|
||||
"fd" #'dired)
|
||||
|
||||
|
@ -1,41 +0,0 @@
|
||||
;; -*- 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)
|
@ -62,16 +62,14 @@
|
||||
"git.jeremydormitzer.com/api/v1"
|
||||
"git.jeremydormitzer.com"
|
||||
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
|
||||
(advice-add 'forge-topic-at-point :around
|
||||
(lambda (oldfn &rest 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
|
||||
(forge-owned-accounts '((jdormit . (remote-name "jdormit"))))
|
||||
:general
|
||||
|
@ -235,38 +235,12 @@
|
||||
(ruby-ts-mode . eglot-ensure)
|
||||
(sh-mode . eglot-ensure)
|
||||
(bash-ts-mode . eglot-ensure)
|
||||
(yaml-mode . eglot-ensure)
|
||||
(astro-ts-mode . eglot-ensure)
|
||||
:custom
|
||||
(eglot-confirm-server-initiated-edits 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
|
||||
(use-package compile
|
||||
:straight (:type built-in)
|
||||
@ -292,15 +266,11 @@
|
||||
:straight (:host github :repo "copilot-emacs/copilot.el" :files ("dist" "*.el"))
|
||||
:hook ((prog-mode . copilot-mode)
|
||||
(yaml-mode . copilot-mode)
|
||||
(yaml-ts-mode . copilot-mode)
|
||||
(json-mode . copilot-mode)
|
||||
(json-ts-mode . copilot-mode)
|
||||
(forge-post-mode . copilot-mode)
|
||||
(git-commit-mode . copilot-mode))
|
||||
:config
|
||||
(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
|
||||
(prog-mode-map "C-c <tab>" #'copilot-complete)
|
||||
(copilot-completion-map "C-n" #'copilot-next-completion
|
||||
|
@ -11,12 +11,7 @@
|
||||
:prefix-map 'leader-map))
|
||||
|
||||
(use-package which-key
|
||||
: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))
|
||||
:hook (after-init . (lambda () (which-key-mode 1))))
|
||||
|
||||
(leader-def-key "SPC" #'execute-extended-command)
|
||||
|
||||
|
@ -21,7 +21,7 @@
|
||||
(general-def kubectl-log-mode-map
|
||||
"C-c k" #'kubectl--log-kill-process)
|
||||
(general-def 'normal kubectl-log-mode-map
|
||||
"q" #'quit-window)
|
||||
"q" #'bury-buffer)
|
||||
(general-def 'normal kubectl-pods-mode-map
|
||||
"c" #'kubectl-choose-context
|
||||
"s" #'kubectl-pods-choose-namespace
|
||||
|
@ -43,8 +43,6 @@
|
||||
|
||||
(use-package ht)
|
||||
|
||||
(use-package posframe)
|
||||
|
||||
;; Elisp utilities
|
||||
(defun make-process-sentinel (success err)
|
||||
"Makes a process sentinel that calls `success` on success and `err` on error"
|
||||
@ -136,10 +134,4 @@
|
||||
(kill-new (message rand)))
|
||||
rand))
|
||||
|
||||
(defmacro dbg (form)
|
||||
"Print the value of FORM and return it."
|
||||
`(progn
|
||||
(message (format "%s: %s" ',form ,form))
|
||||
,form))
|
||||
|
||||
(provide 'init-lib)
|
||||
|
@ -34,10 +34,6 @@
|
||||
|
||||
(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-S-<tab>" global-map)
|
||||
|
||||
|
@ -1,7 +0,0 @@
|
||||
;; -*- lexical-binding: t; -*-
|
||||
|
||||
(use-package markdown-mode
|
||||
:mode (("\\.md\\'" . gfm-mode)
|
||||
("\\.markdown\\'" . gfm-mode)))
|
||||
|
||||
(provide 'init-markdown)
|
@ -1,6 +0,0 @@
|
||||
;; -*- lexical-binding: t; -*-
|
||||
|
||||
(use-package mermaid-mode
|
||||
:defer t)
|
||||
|
||||
(provide 'init-mermaid)
|
@ -1,38 +0,0 @@
|
||||
;; -*- 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)
|
@ -1,18 +0,0 @@
|
||||
;; 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)
|
@ -7,41 +7,30 @@
|
||||
:init
|
||||
(leader-def-key "o" '(nil :which-key "org"))
|
||||
:config
|
||||
(require 'ox-md)
|
||||
(add-hook 'org-mode-hook #'olivetti-mode)
|
||||
(add-hook 'org-mode-hook (lambda () (require 'org-attach)))
|
||||
(defun org-scratch ()
|
||||
(interactive)
|
||||
(switch-to-buffer "*org-scratch*")
|
||||
(org-mode))
|
||||
(cl-defun org-find-daily-note (date)
|
||||
"Find the daily note for DATE, creating it if necessary.
|
||||
|
||||
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"))))
|
||||
(defun org-find-daily-note ()
|
||||
(interactive)
|
||||
(let* ((filename (expand-file-name
|
||||
(format "~/org/daily/%s.org" date)))
|
||||
(format "~/org/daily/%s.org"
|
||||
(format-time-string "%Y-%m-%d"))))
|
||||
(file-exists (file-exists-p filename)))
|
||||
(find-file filename)
|
||||
(goto-char (point-min))
|
||||
(unless file-exists
|
||||
(org-id-get-create)
|
||||
(goto-char (point-max))
|
||||
(insert (format "#+title: %s daily note" date))
|
||||
(insert (format "#+title: %s daily note"
|
||||
(format-time-string "%Y-%m-%d")))
|
||||
(newline))
|
||||
(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-load-languages
|
||||
'((emacs-lisp . t)
|
||||
(plantuml . t)
|
||||
(python . t)
|
||||
(shell . t)))
|
||||
'((plantuml . t)))
|
||||
:custom
|
||||
(org-modules '(ol-doi
|
||||
ol-w3m
|
||||
@ -65,7 +54,7 @@ DATE should be in the format \"YYYY-MM-DD\". If called interactively, default to
|
||||
"DONE(d)"
|
||||
"CANCELLED(c)")))
|
||||
(org-capture-templates `(("d" "Daily note" plain
|
||||
(function org-capture-daily-note)
|
||||
(function org-find-daily-note)
|
||||
"* %<%I:%M %p>")
|
||||
("n" "Org-roam note" plain (function org-roam-capture))
|
||||
("h" "Hummingbird task" entry
|
||||
@ -92,25 +81,13 @@ DATE should be in the format \"YYYY-MM-DD\". If called interactively, default to
|
||||
("\\.x?html?\\'" . default)))
|
||||
(org-plantuml-exec-mode 'jar)
|
||||
(org-plantuml-jar-path (expand-file-name "~/plantuml/plantuml.jar"))
|
||||
(org-confirm-babel-evaluate nil)
|
||||
:general
|
||||
(leader-map "oa" #'org-agenda)
|
||||
(leader-map "oc" #'org-capture)
|
||||
(leader-map "ol" #'org-store-link)
|
||||
(leader-map "od" #'org-find-daily-note)
|
||||
(normal org-mode-map "<return>" #'org-return)
|
||||
(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)
|
||||
(normal org-mode-map "T" #'org-insert-todo-heading))
|
||||
|
||||
(use-package evil-org
|
||||
:after org
|
||||
@ -200,6 +177,6 @@ DATE should be in the format \"YYYY-MM-DD\". If called interactively, default to
|
||||
:config
|
||||
(general-def 'normal org-roam-backlinks-mode-map
|
||||
"RET" #'org-open-at-point
|
||||
"q" #'quit-window))
|
||||
"q" #'bury-buffer))
|
||||
|
||||
(provide 'init-org)
|
||||
|
@ -4,7 +4,7 @@
|
||||
("\\.plantuml\\'" . plantuml-mode)
|
||||
("\\.puml\\'" . plantuml-mode))
|
||||
:custom
|
||||
(plantuml-default-exec-mode 'executable)
|
||||
(plantuml-executable-path (executable-find "plantuml")))
|
||||
(plantuml-default-exec-mode 'jar)
|
||||
(plantuml-jar-path (expand-file-name "~/plantuml/plantuml.jar")))
|
||||
|
||||
(provide 'init-plantuml)
|
||||
|
@ -12,32 +12,7 @@
|
||||
("Guardfile\\'" . ruby-mode)
|
||||
("Capfile\\'" . ruby-mode)
|
||||
("Vagrantfile\\'" . ruby-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))
|
||||
:hook (ruby-mode . highlight-indent-guides-mode))
|
||||
|
||||
(use-package inf-ruby
|
||||
:commands (inf-ruby inf-ruby-console-auto)
|
||||
|
@ -383,7 +383,7 @@
|
||||
cmds
|
||||
(run-command-rake--cache-cmds
|
||||
rake-dir
|
||||
(->> (shell-command-to-string "rake -AT")
|
||||
(->> (shell-command-to-string "rake -T")
|
||||
(s-split "\n")
|
||||
(-map (lambda (s) (s-split-up-to " " s 2)))
|
||||
(-map (lambda (l) (s-join " " (-take 2 l))))
|
||||
@ -432,9 +432,7 @@
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(re-search-forward "RSpec" nil t)))
|
||||
(let ((root-dir (or (locate-dominating-file default-directory "spec")
|
||||
(projectile-project-root)
|
||||
default-directory)))
|
||||
(let ((root-dir (or (projectile-project-root) default-directory)))
|
||||
(-concat
|
||||
(list
|
||||
(list :command-name "test this file"
|
||||
|
@ -1,10 +0,0 @@
|
||||
;; -*- 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)
|
@ -2,89 +2,6 @@
|
||||
|
||||
;; Configuration as code!
|
||||
(use-package terraform-mode
|
||||
: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:"))
|
||||
:mode "\\.tf\\'")
|
||||
|
||||
(provide 'init-terraform)
|
||||
|
@ -20,9 +20,7 @@
|
||||
(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")
|
||||
(yaml "https://github.com/ikatyang/tree-sitter-yaml")
|
||||
(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")))
|
||||
(astro "https://github.com/virchau13/tree-sitter-astro")))
|
||||
|
||||
(defun treesit-install-all-grammars ()
|
||||
(interactive)
|
||||
@ -111,13 +109,4 @@
|
||||
(-map #'cdr)
|
||||
(-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)
|
||||
|
@ -1,11 +1,10 @@
|
||||
;; -*- lexical-binding: t; -*-
|
||||
|
||||
;; For some reason, the tool-bar-mode disabling in early-init.el was leaving the frame title too tall, so
|
||||
;; toggle it on and off
|
||||
(add-hook 'after-init-hook
|
||||
(lambda ()
|
||||
(tool-bar-mode)
|
||||
(tool-bar-mode -1)))
|
||||
;; Don't show ugly graphical UI elements
|
||||
(when (window-system)
|
||||
(tool-bar-mode -1)
|
||||
(scroll-bar-mode -1)
|
||||
(tooltip-mode -1))
|
||||
|
||||
(when (fboundp 'mac-auto-operator-composition-mode)
|
||||
(setq mac-auto-operator-composition-characters
|
||||
@ -15,8 +14,8 @@
|
||||
;; Doom themes are the best themes!
|
||||
(use-package doom-themes)
|
||||
(use-package doom-modeline
|
||||
:hook
|
||||
(after-init . doom-modeline-mode)
|
||||
:config
|
||||
(doom-modeline-mode 1)
|
||||
:custom
|
||||
(doom-modeline-env-version nil)
|
||||
(doom-modeline-env-enable-python nil)
|
||||
@ -26,6 +25,9 @@
|
||||
(doom-modeline-env-enable-elixir 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
|
||||
(use-package hide-mode-line
|
||||
:commands hide-mode-line-mode)
|
||||
|
@ -8,9 +8,4 @@
|
||||
(message formatted)
|
||||
formatted)))
|
||||
|
||||
(defun display-ansi-colors ()
|
||||
(interactive)
|
||||
(require 'ansi-color)
|
||||
(ansi-color-apply-on-region (point-min) (point-max)))
|
||||
|
||||
(provide 'init-utils)
|
||||
|
@ -47,8 +47,6 @@
|
||||
(leader-map "v" #'vterm)
|
||||
(vterm-mode-map "C-x C-e" #'vterm-edit-zsh-command-line
|
||||
"C-c C-g" #'vterm-send-C-g)
|
||||
((normal motion insert) vterm-mode-map
|
||||
"C-q" #'vterm-send-next-key)
|
||||
:custom
|
||||
(vterm-max-scrollback 10000)
|
||||
(vterm-environment '("TYPEWRITTEN_CURSOR=terminal"))
|
||||
|
@ -28,8 +28,6 @@
|
||||
|
||||
(general-def evil-window-map
|
||||
"m" #'delete-other-windows
|
||||
"S" #'split-root-window-below
|
||||
"V" #'split-root-window-right
|
||||
"u" #'winner-undo
|
||||
"C-r" #'winner-redo)
|
||||
|
||||
@ -39,39 +37,5 @@
|
||||
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)
|
||||
|
@ -1,32 +1,9 @@
|
||||
;; -*- lexical-binding: t; -*-
|
||||
|
||||
;; YAML - literally the worst but still holds an important place in my life
|
||||
(use-package yaml-ts-mode
|
||||
(use-package yaml-mode
|
||||
:mode ("\\.yaml\\'" "\\.yml\\'")
|
||||
:config
|
||||
(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))
|
||||
(add-hook 'yaml-mode-hook #'highlight-indent-guides-mode))
|
||||
|
||||
(provide 'init-yaml)
|
||||
|
@ -1,222 +1,7 @@
|
||||
;; -*- no-byte-compile: t; lexical-binding: 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)
|
||||
|
||||
;; Increase how much is read from processes in a single chunk (default is 4kb).
|
||||
(setq read-process-output-max (* 256 1024)) ; 256kb
|
||||
|
||||
;; 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
|
||||
;; Some startup time optimizations stolen from Doom emacs
|
||||
(setq gc-cons-threshold most-positive-fixnum ; 2^61 bytes
|
||||
gc-cons-percentage 0.6)
|
||||
|
@ -1,5 +1,14 @@
|
||||
;; -*- 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
|
||||
(autoload 'server-running-p "server")
|
||||
(defun server-start-if-not-running ()
|
||||
@ -28,12 +37,6 @@
|
||||
(straight-use-package 'use-package)
|
||||
(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"
|
||||
(use-package gcmh
|
||||
:demand t
|
||||
@ -53,6 +56,7 @@
|
||||
(exec-path-from-shell-check-startup-files nil)
|
||||
(exec-path-from-shell-arguments '("-l")))
|
||||
|
||||
|
||||
;; Don't use this file as the custom-file
|
||||
(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
|
||||
|
||||
@ -93,14 +97,11 @@
|
||||
(require 'init-js)
|
||||
(require 'init-ruby)
|
||||
(require 'init-rust)
|
||||
(require 'init-swift)
|
||||
(require 'init-web)
|
||||
(require 'init-markdown)
|
||||
(require 'init-astro)
|
||||
(require 'init-xml)
|
||||
(require 'init-yaml)
|
||||
(require 'init-groovy)
|
||||
(require 'init-devdocs)
|
||||
(require 'init-terraform)
|
||||
(require 'init-docker)
|
||||
(require 'init-nix)
|
||||
@ -113,18 +114,13 @@
|
||||
(require 'init-kubernetes)
|
||||
(require 'init-epub)
|
||||
(require 'init-pdf)
|
||||
(require 'init-eww)
|
||||
(require 'init-navigation)
|
||||
(require 'init-homebrew)
|
||||
(require 'init-devdocs)
|
||||
(require 'init-elfeed)
|
||||
(require 'init-1pass)
|
||||
(require 'init-wallabag)
|
||||
(require 'init-plantuml)
|
||||
(require 'init-mermaid)
|
||||
(require 'init-games)
|
||||
(require 'handwriting)
|
||||
(require 'init-navi)
|
||||
(require 'init-ai)
|
||||
(when (string-equal system-type "darwin")
|
||||
(require 'init-mac))
|
||||
|
||||
|
@ -1,246 +0,0 @@
|
||||
;;; 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
|
@ -1,435 +0,0 @@
|
||||
;;; 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 1929–1931 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
|
@ -1,254 +0,0 @@
|
||||
;;; 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
|
@ -1,316 +0,0 @@
|
||||
;;; 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
|
@ -1,223 +0,0 @@
|
||||
;;; 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
|
@ -8,7 +8,6 @@ tap "railwaycat/emacsmacport"
|
||||
tap "ryleelyman/seamstress"
|
||||
brew "clojure/tools/clojure"
|
||||
brew "cmake"
|
||||
brew "coreutils"
|
||||
brew "direnv"
|
||||
brew "ffmpeg"
|
||||
brew "git"
|
||||
@ -49,7 +48,6 @@ brew "seamstress"
|
||||
brew "starship"
|
||||
brew "stow"
|
||||
brew "sqlite"
|
||||
brew "terraform"
|
||||
brew "terraform-ls"
|
||||
brew "texinfo"
|
||||
brew "typescript-language-server"
|
||||
@ -68,10 +66,8 @@ cask "google-chrome"
|
||||
cask "google-cloud-sdk"
|
||||
cask "intellij-idea"
|
||||
cask "iterm2"
|
||||
cask "neovide"
|
||||
cask "notion"
|
||||
cask "notion-calendar"
|
||||
cask "postman"
|
||||
cask "protonvpn"
|
||||
cask "railwaycat/emacsmacport/emacs-mac"
|
||||
cask "rectangle"
|
||||
@ -79,4 +75,3 @@ cask "slack"
|
||||
cask "spotify"
|
||||
cask "syncthing"
|
||||
cask "the-unarchiver"
|
||||
cask "vcv-rack"
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<!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>
|
||||
|
@ -1,16 +0,0 @@
|
||||
<?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>
|
41
old-emacs/.emacs.tiny.el
Normal file
41
old-emacs/.emacs.tiny.el
Normal file
@ -0,0 +1,41 @@
|
||||
;; 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)
|
8
old-emacs/eshell/alias
Normal file
8
old-emacs/eshell/alias
Normal file
@ -0,0 +1,8 @@
|
||||
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 $*
|
35
old-emacs/init.el
Normal file
35
old-emacs/init.el
Normal file
@ -0,0 +1,35 @@
|
||||
;;; -*- 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)
|
35
old-emacs/init.el~
Normal file
35
old-emacs/init.el~
Normal file
@ -0,0 +1,35 @@
|
||||
;;; -*- 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)
|
7119
old-emacs/init.org
Executable file
7119
old-emacs/init.org
Executable file
File diff suppressed because it is too large
Load Diff
@ -1 +0,0 @@
|
||||
console.log("Witchcraft loaded!");
|
@ -267,7 +267,7 @@ setopt PROMPT_SUBST
|
||||
PROMPT=$PROMPT'%{$(vterm_prompt_end)%}'
|
||||
[[ -z "$tw_prompt" ]] || tw_prompt=$tw_prompt'%{$(vterm_prompt_end)%}'
|
||||
|
||||
[[ -f "$HOME/.zshrc.local" ]] && source "$HOME/.zshrc.local"
|
||||
[[ -f "$HOME/.zshrc-spotify" ]] && source "$HOME/.zshrc-spotify"
|
||||
|
||||
type navi > /dev/null && eval "$(navi widget zsh)"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user