moved local packages to separate folder

Allows me to update the load-path and rely on ‘use-package‘ to
manage/configure these packages
This commit is contained in:
2026-01-09 17:37:55 -05:00
parent caaa3becef
commit 43c52516a6
4 changed files with 47 additions and 19 deletions

342
local-lisp/codex.el Normal file
View File

@@ -0,0 +1,342 @@
;;; codex.el --- Simple Codex chat buffer -*- lexical-binding: t; -*-
;; Copyright (C) 2025
;; Author: Grant Horner
;; Version: 0.1
;; Package-Requires: ((emacs "27.1"))
;; Keywords: tools, convenience
;;; Commentary:
;; Basic chat buffer for Codex using `codex exec --json`.
;; {"type":"thread.started","thread_id":"019ba390-af9c-7872-8617-c8038b61d559"}
;; {"type":"turn.started"}
;; {"type":"item.completed","item":{"id":"item_0","type":"reasoning","text":"**Responding with greeting**"}}
;; {"type":"item.completed","item":{"id":"item_1","type":"agent_message","text":"Hi! How can I help?"}}
;; {"type":"turn.completed","usage":{"input_tokens":4050,"cached_input_tokens":3712,"output_tokens":13}}
;;; Code:
(require 'json)
(require 'cl-lib)
(require 'subr-x)
(defgroup codex nil
"Chat with Codex."
:group 'tools)
(defface codex-prompt-delimiter-face
'((t :inherit shadow))
"Face for prompt delimiters."
:group 'codex)
(defface codex-user-prompt-face
'((t :inherit font-lock-keyword-face))
"Face for the user prompt label."
:group 'codex)
(defface codex-assistant-prompt-face
'((t :inherit font-lock-function-name-face))
"Face for the Codex prompt label."
:group 'codex)
(defcustom codex-command "codex"
"Codex executable to invoke."
:type 'string
:group 'codex)
(defcustom codex-provider nil
"Custom codex provider to use.
Note on Copilot:
In order for copilot integration to work, you need a section in your
~/.codex/config.toml like this:
[model_providers.github-copilot]
name = \"Github Copilot\"
base_url = \"https://api.githubcopilot.com\"
env_key = \"GITHUB_COPILOT_TOKEN\"
wire_api = \"chat\"
http_headers = {
Copilot-Integration-Id = \"vscode-chat\"
}"
:type 'string
:group 'codex)
(defcustom codex-model nil
"Model for Codex to use."
:type 'string
:group 'codex)
(defcustom codex-skip-git-repo-check t
"Whether to pass --skip-git-repo-check to Codex."
:type 'boolean
:group 'codex)
(defcustom codex-buffer-name "*codex*"
"What the default name should be fore the codex chat buffer."
:type 'string
:group 'codex)
(defcustom codex-wrap-lines t
"Whether to truncate long lines in `codex-mode`."
:type 'boolean
:group 'codex)
(defcustom codex-sessions-file (concat (file-name-parent-directory user-init-file) "codex-sessions")
"What the default name should be fore the codex chat buffer."
:type 'string
:group 'codex)
(defcustom codex-sessions-history-dir
(concat (file-name-parent-directory user-init-file) "codex-sessions-history/")
"Directory where Codex session histories are stored."
:type 'directory
:group 'codex)
(defvar codex--session-id nil)
(defvar codex-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-c C-c") #'codex-send)
(define-key map (kbd "C-c C-s") #'codex-switch-sessions)
map)
"Keymap for `codex-mode'.")
(defconst codex--font-lock-keywords
'(("^---$" . 'codex-prompt-delimiter-face)
("^User:$" . 'codex-user-prompt-face)
("^Codex:$" . 'codex-assistant-prompt-face)))
;;;###autoload
(define-derived-mode codex-mode fundamental-mode "Codex"
"Major mode for chatting with Codex."
(setq-local buffer-read-only nil)
(setq-local truncate-lines (not codex-wrap-lines))
(setq-local font-lock-defaults '(codex--font-lock-keywords))
(setq-local codex--busy nil))
(defvar-local codex--prompt-start nil)
(defun codex--ensure-history-dir ()
(make-directory codex-sessions-history-dir t))
(defun codex--history-file ()
(when (and codex--session-id (not (string-empty-p codex--session-id)))
(expand-file-name codex--session-id codex-sessions-history-dir)))
(defun codex--set-prompt-start-from-buffer ()
(let* ((haystack (buffer-string))
(needle "---\nUser:\n")
(index (cl-search needle haystack :from-end t)))
(when index
(setq codex--prompt-start (copy-marker (+ (length needle) index)))
t)))
(defun codex--save-session-history ()
(with-codex-buffer
(let ((history-file (codex--history-file)))
(when history-file
(codex--ensure-history-dir)
(write-region (point-min) (point-max) history-file nil 'silent)))))
(defun codex--load-session-history ()
(with-codex-buffer
(let ((history-file (codex--history-file)))
(setq codex--prompt-start nil)
(erase-buffer)
(if (and history-file (file-exists-p history-file))
(progn
(insert-file-contents history-file)
(goto-char (point-max))
(unless (codex--set-prompt-start-from-buffer)
(user-prompt)))
(user-prompt)))))
(defun list->hash-set (list &optional test)
"Return a hash table whose keys are the elements of LIST."
(let ((ht (make-hash-table :test (or test #'equal))))
(dolist (x list ht)
(puthash x t ht))))
(defmacro with-codex-buffer (&rest body)
`(with-current-buffer (get-buffer-create codex-buffer-name)
,@body))
(defun prompt-delimiter ()
(newline)
(insert "---")
(newline))
(defun user-prompt ()
(prompt-delimiter)
(insert "User:")
(newline)
(setq codex--prompt-start (point-marker)))
(defun codex--write-to-chat (msg)
(with-codex-buffer
(newline)
(insert "Codex:")
(newline)
(insert msg)
(user-prompt)
(codex--save-session-history)))
(defun codex--parse-session-id (jsons)
(let ((thread-started-message
(or (cl-find-if
(lambda (hm)
(and (string-equal (gethash "type" hm) "thread.started")
(gethash "thread_id" hm)))
jsons)
(make-hash-table))))
(gethash "thread_id" thread-started-message)))
(defun codex--parse-msg-from-response (response-string)
(let* ((output (string-trim response-string))
(lines (string-lines output))
(jsons (delq nil
(mapcar (lambda (line)
(condition-case nil
(json-parse-string line)
(json-parse-error nil)))
lines)))
(msg (cl-find-if (lambda (hm) (let ((type (gethash "type" hm))
(item (gethash "item" hm)))
(and type
item
(string-equal type "item.completed")
(string-equal (gethash "type" item) "agent_message"))))
jsons))
(item (and msg (gethash "item" msg)))
(text (and item (gethash "text" item))))
;; We should make sure we get one and only one message here, otherwise bail out
(setf codex--session-id (codex--parse-session-id jsons))
(or text output)))
(defun codex--ensure-session-in-sessions-file (prompt)
(let ((sessions (codex--read-sessions-file)))
(unless (member codex--session-id (mapcar #'cdr sessions))
(codex--write-sessions-file (cons (cons prompt codex--session-id) sessions)))))
(defun codex--format-response (msg)
(with-temp-buffer
(insert msg)
(fill-region (point-min) (point-max))
(buffer-string)))
(defun codex--send (prompt)
"Sends a prompt to codex."
(let* ((buf (generate-new-buffer "*codex--send*"))
(proc (make-process
:name "codex"
:buffer buf
:command
(let* ((resume (when (and codex--session-id (not (string-empty-p codex--session-id)))
(progn
(message "Using session %s" codex--session-id)
(list "resume" codex--session-id))))
(skip (and codex-skip-git-repo-check (list "--skip-git-repo-check")))
(provider (and codex-provider (list "-c" (concat "model_provider=" codex-provider))))
(model (and codex-model (list "-m" codex-model)))
(command (append (list codex-command "exec") resume skip provider model (list "--json" prompt))))
command))))
(set-process-sentinel
proc
(lambda (p event)
(message event)
(when (string= event "finished\n")
(with-current-buffer (process-buffer p)
(thread-first
(buffer-string)
codex--parse-msg-from-response
codex--format-response
codex--write-to-chat)
(codex--ensure-session-in-sessions-file prompt)
(kill-buffer)))
(when (string-prefix-p "exited abnormally" event)
(with-current-buffer (process-buffer p)
(error (buffer-string))))))))
(defun codex--get-current-user-prompt ()
(with-codex-buffer
(let ((prompt-start (and codex--prompt-start
(marker-buffer codex--prompt-start)
(marker-position codex--prompt-start))))
;; fallback in case we somehow lost where our user prompt starts
(unless prompt-start
(when (codex--set-prompt-start-from-buffer)
(setq prompt-start (marker-position codex--prompt-start))))
(unless prompt-start
(error "No current prompt start"))
(buffer-substring-no-properties prompt-start (point-max)))))
(defun codex-send ()
(interactive)
(with-current-buffer codex-buffer-name
(let ((prompt (codex--get-current-user-prompt)))
(if (string-equal prompt "")
(error "Prompt is empty!")
(newline)
(insert "---")
(codex--save-session-history)
(codex--send prompt)))))
;;;###autoload
(defun codex (session)
"Open a Codex chat buffer."
(interactive
(list (completing-read "Session: " (cons '("New") (codex--read-sessions-file)))))
(let ((session-id (cdr (assoc session (codex--read-sessions-file)))))
(setf codex--session-id session-id)
(let ((buf (get-buffer-create codex-buffer-name)))
(pop-to-buffer buf)
(unless (derived-mode-p 'codex-mode)
(codex-mode))
(codex--load-session-history)
(goto-char (point-max)))))
(defun codex--read-sessions-file ()
(if (file-exists-p codex-sessions-file)
(with-temp-buffer
(insert-file-contents codex-sessions-file)
(goto-char (point-min))
(condition-case nil
(read (current-buffer))
(end-of-file nil)))
(codex--write-sessions-file '())
nil))
(defun codex--write-sessions-file (list-of-session-ids)
(let ((filename codex-sessions-file))
(with-temp-buffer
(insert ";;; -*- lisp-data -*-\n")
(let ((print-length nil)
(print-level nil))
(pp list-of-session-ids (current-buffer)))
(write-region nil nil filename nil 'silent))))
(defun codex-switch-sessions (new-session)
(interactive
(list (completing-read "Session: " (cons '("New") (codex--read-sessions-file)))))
(let ((session-id (cdr (assoc new-session (codex--read-sessions-file)))))
(codex--save-session-history)
(setf codex--session-id session-id)
(codex--load-session-history)))
(defun codex-rename-current-session (name)
(interactive "sNew name: ")
(let* ((sessions (codex--read-sessions-file))
(new-sessions
(mapcar
(lambda (tup)
(if (string-equal (cdr tup) codex--session-id)
(cons name (cdr tup))
tup))
sessions)))
(codex--write-sessions-file new-sessions)))
(provide 'codex)
;;; codex.el ends here

310
local-lisp/odin-mode.el Normal file
View File

@@ -0,0 +1,310 @@
;;; odin-mode.el --- A minor mode for odin
;; Author: Ethan Morgan
;; Keywords: odin, language, languages, mode
;; Package-Requires: ((emacs "24.1"))
;; Homepage: https://github.com/glassofethanol/odin-mode
;; This file is NOT part of GNU Emacs.
;;; Code:
(require 'cl-lib)
(require 'rx)
(require 'js)
(defgroup odin nil
"Odin mode"
:group 'languages)
;; `compilation-mode' configuration
(eval-after-load 'compile
'(add-to-list 'compilation-error-regexp-alist '("^\\(.*?\\)(\\([0-9]+\\):\\([0-9]+\\).*" 1 2 3)))
(defconst odin-mode-syntax-table
(let ((table (make-syntax-table)))
(modify-syntax-entry ?\" "\"" table)
(modify-syntax-entry ?\\ "\\" table)
;; additional symbols
(modify-syntax-entry ?' "\"" table)
(modify-syntax-entry ?` "\"" table)
(modify-syntax-entry ?: "." table)
(modify-syntax-entry ?+ "." table)
(modify-syntax-entry ?- "." table)
(modify-syntax-entry ?% "." table)
(modify-syntax-entry ?& "." table)
(modify-syntax-entry ?| "." table)
(modify-syntax-entry ?^ "." table)
(modify-syntax-entry ?! "." table)
(modify-syntax-entry ?$ "." table)
(modify-syntax-entry ?= "." table)
(modify-syntax-entry ?< "." table)
(modify-syntax-entry ?> "." table)
(modify-syntax-entry ?? "." table)
;; Need this for #directive regexes to work correctly
(modify-syntax-entry ?# "_" table)
;; Modify some syntax entries to allow nested block comments
(modify-syntax-entry ?/ ". 124b" table)
(modify-syntax-entry ?* ". 23n" table)
(modify-syntax-entry ?\n "> b" table)
(modify-syntax-entry ?\^m "> b" table)
table))
(defconst odin-builtins
'("len" "cap"
"typeid_of" "type_info_of"
"swizzle" "complex" "real" "imag" "quaternion" "conj"
"jmag" "kmag"
"min" "max" "abs" "clamp"
"expand_to_tuple"
"init_global_temporary_allocator"
"copy" "pop" "unordered_remove" "ordered_remove" "clear" "reserve"
"resize" "new" "new_clone" "free" "free_all" "delete" "make"
"clear_map" "reserve_map" "delete_key" "append_elem" "append_elems"
"append" "append_string" "clear_dynamic_array" "reserve_dynamic_array"
"resize_dynamic_array" "incl_elem" "incl_elems" "incl_bit_set"
"excl_elem" "excl_elems" "excl_bit_set" "incl" "excl" "card"
"assert" "panic" "unimplemented" "unreachable"))
(defconst odin-keywords
'("import" "foreign" "package"
"where" "when" "if" "else" "for" "switch" "in" "notin" "do" "case"
"break" "continue" "fallthrough" "defer" "return" "proc"
"struct" "union" "enum" "bit_field" "bit_set" "map" "dynamic"
"auto_cast" "cast" "transmute" "distinct" "opaque"
"using" "inline" "no_inline"
"size_of" "align_of" "offset_of" "type_of"
"context"
;; "_"
;; Reserved
"macro" "const"))
(defconst odin-constants
'("nil" "true" "false"
"ODIN_OS" "ODIN_ARCH" "ODIN_ENDIAN" "ODIN_VENDOR"
"ODIN_VERSION" "ODIN_ROOT" "ODIN_DEBUG"))
(defconst odin-typenames
'("bool" "b8" "b16" "b32" "b64"
"int" "i8" "i16" "i32" "i64"
"i16le" "i32le" "i64le"
"i16be" "i32be" "i64be"
"i128" "u128"
"i128le" "u128le"
"i128be" "u128be"
"uint" "u8" "u16" "u32" "u64"
"u16le" "u32le" "u64le"
"u16be" "u32be" "u64be"
"f32" "f64"
"complex64" "complex128"
"quaternion128" "quaternion256"
"rune"
"string" "cstring"
"uintptr" "rawptr"
"typeid" "any"
"byte"))
(defconst odin-attributes
'("builtin"
"export"
"static"
"deferred_in" "deferred_none" "deferred_out"
"require_results"
"default_calling_convention" "link_name" "link_prefix"
"deprecated" "private" "thread_local"))
(defconst odin-proc-directives
'("#force_inline"
"#force_no_inline"
"#type")
"Directives that can appear before a proc declaration")
(defconst odin-directives
(append '("#align" "#packed"
"#any_int"
"#raw_union"
"#no_nil"
"#complete"
"#no_alias"
"#c_vararg"
"#assert"
"#file" "#line" "#location" "#procedure" "#caller_location"
"#load"
"#defined"
"#bounds_check" "#no_bounds_check"
"#partial") odin-proc-directives))
(defun odin-wrap-word-rx (s)
(concat "\\<" s "\\>"))
(defun odin-wrap-keyword-rx (s)
(concat "\\(?:\\S.\\_<\\|\\`\\)" s "\\_>"))
(defun odin-wrap-directive-rx (s)
(concat "\\_<" s "\\>"))
(defun odin-wrap-attribute-rx (s)
(concat "[[:space:]\n]*@[[:space:]\n]*(?[[:space:]\n]*" s "\\>"))
(defun odin-keywords-rx (keywords)
"build keyword regexp"
(odin-wrap-keyword-rx (regexp-opt keywords t)))
(defun odin-directives-rx (directives)
(odin-wrap-directive-rx (regexp-opt directives t)))
(defun odin-attributes-rx (attributes)
(odin-wrap-attribute-rx (regexp-opt attributes t)))
(defconst odin-identifier-rx "[[:word:][:multibyte:]_]+")
(defconst odin-hat-type-rx (rx (group (and "^" (1+ (any word "." "_"))))))
(defconst odin-dollar-type-rx (rx (group "$" (or (1+ (any word "_")) (opt "$")))))
(defconst odin-number-rx
(rx (and
symbol-start
(or (and (+ digit) (opt (and (any "eE") (opt (any "-+")) (+ digit))))
(and "0" (any "xX") (+ hex-digit)))
(opt (and (any "_" "A-Z" "a-z") (* (any "_" "A-Z" "a-z" "0-9"))))
symbol-end)))
(defconst odin-proc-rx (concat "\\(\\_<" odin-identifier-rx "\\_>\\)\\s *::\\s *\\(" (odin-directives-rx odin-proc-directives) "\\)?\\s *\\_<proc\\_>"))
(defconst odin-type-rx (concat "\\_<\\(" odin-identifier-rx "\\)\\s *::\\s *\\(?:struct\\|enum\\|union\\|distinct\\)\\s *\\_>"))
(defconst odin-font-lock-defaults
`(
;; Types
(,odin-hat-type-rx 1 font-lock-type-face)
(,odin-dollar-type-rx 1 font-lock-type-face)
(,(odin-keywords-rx odin-typenames) 1 font-lock-type-face)
(,odin-type-rx 1 font-lock-type-face)
;; Hash directives
(,(odin-directives-rx odin-directives) 1 font-lock-preprocessor-face)
;; At directives
(,(odin-attributes-rx odin-attributes) 1 font-lock-preprocessor-face)
;; Keywords
(,(odin-keywords-rx odin-keywords) 1 font-lock-keyword-face)
;; single quote characters
("'\\(\\\\.\\|[^']\\)'" . font-lock-constant-face)
;; Variables
(,(odin-keywords-rx odin-builtins) 1 font-lock-builtin-face)
;; Constants
(,(odin-keywords-rx odin-constants) 1 font-lock-constant-face)
;; Strings
;; ("\\\".*\\\"" . font-lock-string-face)
;; Numbers
(,(odin-wrap-word-rx odin-number-rx) . font-lock-constant-face)
;; Procedures
(,odin-proc-rx 1 font-lock-function-name-face)
("---" . font-lock-constant-face)
("\\.\\.<" . font-lock-constant-face)
("\\.\\." . font-lock-constant-face)
))
;; add setq-local for older emacs versions
(unless (fboundp 'setq-local)
(defmacro setq-local (var val)
`(set (make-local-variable ',var) ,val)))
(defconst odin--defun-rx "\(.*\).*\{")
(defmacro odin-paren-level ()
`(car (syntax-ppss)))
(defun odin-line-is-defun ()
"return t if current line begins a procedure"
(interactive)
(save-excursion
(beginning-of-line)
(let (found)
(while (and (not (eolp)) (not found))
(if (looking-at odin--defun-rx)
(setq found t)
(forward-char 1)))
found)))
(defun odin-beginning-of-defun (&optional count)
"Go to line on which current function starts."
(interactive)
(let ((orig-level (odin-paren-level)))
(while (and
(not (odin-line-is-defun))
(not (bobp))
(> orig-level 0))
(setq orig-level (odin-paren-level))
(while (>= (odin-paren-level) orig-level)
(skip-chars-backward "^{")
(backward-char))))
(if (odin-line-is-defun)
(beginning-of-line)))
(defun odin-end-of-defun ()
"Go to line on which current function ends."
(interactive)
(let ((orig-level (odin-paren-level)))
(when (> orig-level 0)
(odin-beginning-of-defun)
(end-of-line)
(setq orig-level (odin-paren-level))
(skip-chars-forward "^}")
(while (>= (odin-paren-level) orig-level)
(skip-chars-forward "^}")
(forward-char)))))
(defalias 'odin-parent-mode
(if (fboundp 'prog-mode) 'prog-mode 'fundamental-mode))
;;;###autoload
(define-derived-mode odin-mode odin-parent-mode "Odin"
:syntax-table odin-mode-syntax-table
:group 'odin
(setq bidi-paragraph-direction 'left-to-right)
(setq-local require-final-newline mode-require-final-newline)
(setq-local parse-sexp-ignore-comments t)
(setq-local comment-start-skip "\\(//+\\|/\\*+\\)\\s *")
(setq-local comment-start "//")
(setq-local comment-end "")
(setq-local indent-line-function 'js-indent-line)
(setq-local font-lock-defaults '(odin-font-lock-defaults))
(setq-local beginning-of-defun-function 'odin-beginning-of-defun)
(setq-local end-of-defun-function 'odin-end-of-defun)
(setq-local electric-indent-chars
(append "{}():;," electric-indent-chars))
(setq imenu-generic-expression
`(("type" ,(concat "^" odin-type-rx) 1)
("proc" ,(concat "^" odin-proc-rx) 1)))
(font-lock-ensure))
;;;###autoload
(add-to-list 'auto-mode-alist '("\\.odin\\'" . odin-mode))
(provide 'odin-mode)
;;; odin-mode.el ends here