From 2ffc66e4ad5c8150dbd8160a665a7b19e339a25b Mon Sep 17 00:00:00 2001 From: Grant Horner Date: Fri, 9 Jan 2026 10:04:55 -0500 Subject: [PATCH] setup odin mode --- .gitignore | 7 +- custom.el | 17 ++- init.el | 208 ++++++++++++++++++++++++++-------- odin-mode.el | 310 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 490 insertions(+), 52 deletions(-) create mode 100644 odin-mode.el diff --git a/.gitignore b/.gitignore index 74956ba..e43cec1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,9 @@ eln-cache/ elpa/ transient/ tramp -auto-save-list \ No newline at end of file +auto-save-list +history +projects +eshell +parinfer-rust +.mc-lists.el \ No newline at end of file diff --git a/custom.el b/custom.el index e618bfb..4d48980 100644 --- a/custom.el +++ b/custom.el @@ -5,11 +5,18 @@ ;; Your init file should contain only one such instance. ;; If there is more than one, they won't work right. '(package-selected-packages - '(aider browse-kill-ring cider company corfu exec-path-from-shell - haskell-mode haskell-ts-mode helm marginalia - markdown-ts-mode multiple-cursors orderless org-mode sly - terraform-mode tree-sitter-langs vertico visual-fill-column - yasnippet-snippets))) + '(aider browse-kill-ring caddyfile-mode cape cider company corfu + dockerfile-mode dumb-jump dumber-jump emacs-lldb + exec-path-from-shell flycheck fsharp-mode haskell-mode + haskell-ts-mode helm htmlize idris-mode marginalia + markdown-ts-mode multiple-cursors orderless org-mode + parinfer-rust-mode realgud-lldb rg sly terraform-mode + tree-sitter-langs vertico visual-fill-column + yasnippet-snippets zig-mode)) + '(safe-local-variable-directories '("/Users/grant/programming/project/edit/")) + '(safe-local-variable-values + '((Package . CL-USER) (Syntax . ANSI-Common-Lisp) (Base . 10) + (flycheck-clang-includes . "SDL3-3.2.26/include")))) (custom-set-faces ;; custom-set-faces was added by Custom. ;; If you edit it by hand, you could mess it up, so be careful. diff --git a/init.el b/init.el index b97711b..986d9f3 100644 --- a/init.el +++ b/init.el @@ -1,3 +1,5 @@ +;; -*- lexical-binding: t; -*- + (require 'package) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) @@ -6,6 +8,9 @@ (require 'use-package-ensure) (require 'bind-key) +; If startup times are slow +; (setq use-package-verbose t) + (use-package emacs) (setq tab-bar-show nil) (setq inhibit-startup-message t) @@ -27,12 +32,24 @@ (setq reb-re-syntax 'string) (setq default-tab-width 4) (setq-default tab-width 4) -(set-face-attribute 'default nil :height 160 :family "Monaco") +(set-face-attribute 'default nil :height 160 :family "Berkeley Mono") +(global-hl-line-mode 1) (setq use-package-always-ensure t) (setq mac-command-modifier 'control) (setq is-mac (string= system-type "darwin")) +(setq-default cursor-type 'bar) + +(defun hgh/disable-bar-cursor () + (blink-cursor-mode -1)) + +(defun hgh/enable-bar-cursor () + (blink-cursor-mode 1)) + +(add-hook 'activate-mark-hook 'hgh/disable-bar-cursor) +(add-hook 'deactivate-mark-hook 'hgh/enable-bar-cursor) + (when is-mac (setq dired-use-ls-dired t insert-directory-program "gls" @@ -61,59 +78,93 @@ (defun hgh/project-ripgrep (regexp) (interactive (list (read-from-minibuffer "Search (regexp): " (thing-at-point 'symbol)))) - (ripgrep-regexp regexp (project-root (project-current)))) + (rg regexp "*" (project-root (project-current)))) + +(defun hgh/current-date-time () + (interactive) + (insert (format-time-string "%Y-%m-%d %H:%M:%S"))) (add-hook 'compilation-filter-hook 'ansi-color-compilation-filter) +(defun hgh/next-error () + (interactive) + (if (eglot-managed-p) + (flymake-goto-next-error) + (next-error))) + ;; key bindings (keymap-global-set "M-o" #'other-window) (keymap-global-set "M-i" #'imenu) -(keymap-global-set "C-M-." #'end-of-buffer) -(keymap-global-set "C-M-," #'beginning-of-buffer) -(keymap-global-set "C-." #'xref-find-definitions) -(keymap-global-set "C-," #'xref-go-back) (keymap-global-set "" #'compile) (keymap-global-set "C-c r r" #'revert-buffer) (keymap-global-set "C-c C-c" #'comment-or-uncomment-region) (keymap-global-set "M-]" #'forward-paragraph) (keymap-global-set "M-[" #'backward-paragraph) (keymap-global-set "C-h h" #'eldoc) -(keymap-global-set "C-]" #'flymake-goto-next-error) +(keymap-global-set "C-]" #'hgh/next-error) (keymap-global-set "C-c e i" #'hgh/visit-init-file) (keymap-global-set "C-c c" #'compile) +(keymap-global-set "C-x C-b" #'ibuffer) +(keymap-global-set "M-0" #'delete-window) +(keymap-global-set "M-1" #'delete-other-windows) +(keymap-global-set "M-2" #'split-window-below) +(keymap-global-set "M-3" #'split-window-right) (require 'dired) +(setq dired-dwim-target t) (keymap-set dired-mode-map "C-c C-c" #'wdired-change-to-wdired-mode) -(load-theme 'modus-vivendi-tinted t) +(load-theme 'modus-vivendi t) (use-package exec-path-from-shell :when is-mac :config (exec-path-from-shell-initialize)) +(use-package man + :when is-mac + :custom + (manual-program "gman")) + (use-package magit :defer t) (use-package tree-sitter - :mode (("\\.tsx\\'" . tsx-ts-mode) - ("\\.js\\'" . typescript-ts-mode) - ("\\.mjs\\'" . typescript-ts-mode) - ("\\.mts\\'" . typescript-ts-mode) - ("\\.cjs\\'" . typescript-ts-mode) - ("\\.ts\\'" . typescript-ts-mode) - ("\\.jsx\\'" . tsx-ts-mode) - ("\\.json\\'" . json-ts-mode)) + :mode ((" \\.tsx\\'" . tsx-ts-mode) + (" \\.js\\'" . typescript-ts-mode) + (" \\.mjs\\'" . typescript-ts-mode) + (" \\.mts\\'" . typescript-ts-mode) + (" \\.cjs\\'" . typescript-ts-mode) + (" \\.ts\\'" . typescript-ts-mode) + (" \\.jsx\\'" . tsx-ts-mode) + (" \\.json\\'" . json-ts-mode)) :custom (treesit-extra-load-path '("~/repos/tree-sitter-module/dist"))) -(use-package tree-sitter-langs) +(use-package tree-sitter-langs + :defer t) -(use-package haskell-mode) +(use-package haskell-mode + :defer t) (use-package company) +(defun enable-parinfer () + (let ((buf (or (buffer-file-name) (buffer-name) ""))) + (when (and + (not (s-contains? "sbcl" buf)) + (not (s-contains? "ocicl" buf)) + (not (string-prefix-p "*sly" buf))) + (parinfer-rust-mode 1) + (electric-quote-mode 1)))) + +(use-package parinfer-rust-mode + :hook ((lisp-mode emacs-lisp-mode) . enable-parinfer) + :config + (setq parinfer-rust-disable-troublesome-modes t)) + (use-package sly + :defer t :custom (inferior-lisp-program "/opt/homebrew/bin/sbcl")) @@ -147,13 +198,13 @@ :config (global-corfu-mode 1)) +(use-package cape + :init + (add-to-list 'completion-at-point-functions #'cape-dabbrev)) + (use-package browse-kill-ring :bind (("C-c y" . browse-kill-ring))) -(defun hgh/org-mode-setup () - (org-indent-mode) - (visual-line-mode 1)) - (defun hgh/org-mode-visual-fill () (setq visual-fill-column-width 120 visual-fill-column-center-text t) @@ -173,7 +224,8 @@ (java-ts-mode . eglot-ensure) (svelte-mode . eglot-ensure) (haskell-mode . eglot-ensure) - (terraform-mode . eglot-ensure)) + (terraform-mode . eglot-ensure) + (odin-mode . eglot-ensure)) :bind (:map eglot-mode-map ("C-c r" . eglot-rename) @@ -188,14 +240,18 @@ (cl-remove-if (lambda (c) (equal (car c) 'rust-mode)) eglot-server-programs)) - (setf eglot-server-programs (cons (list '(rust-ts-mode rust-mode) "rust-analyzer" :initializationOptions '(:checkOnSave (:command "clippy"))) - eglot-server-programs)) + (setf eglot-server-programs + (append (list + (list '(rust-ts-mode rust-mode) "rust-analyzer" :initializationOptions '(:checkOnSave (:command "clippy"))) + '(svelte-mode "svelteserver" "--stdio") + '(haskell-mode "haskell-language-server-wrapper" "--lsp")) + eglot-server-programs))) - (setf eglot-server-programs (cons '(svelte-mode "svelteserver" "--stdio") - eglot-server-programs)) - - (setf eglot-server-programs (cons '(haskell-mode "haskell-language-server-wrapper" "--lsp") - eglot-server-programs))) +(setenv "DOTNET_ROOT" "~/.local/share/mise/installs/dotnet/9") +(push '("\\.csproj$" . xml-mode) auto-mode-alist) +(push '("\\.vs$" . c-mode) auto-mode-alist) +(push '("\\.fs$" . c-mode) auto-mode-alist) +(push '("\\.sbclrc" . lisp-mode) auto-mode-alist) ;; paredit @@ -209,18 +265,35 @@ (define-key paredit-mode-map (kbd "M-:") #'paredit-backward-barf-sexp) (enable-paredit-mode)) -(setq lisp-mode-hooks '((emacs-lisp-mode-hook emacs-lisp-mode) +(defvar lisp-mode-hooks nil) +(setf lisp-mode-hooks '((emacs-lisp-mode-hook emacs-lisp-mode) (clojure-mode-hook clojure-mode) (lisp-mode-hook lisp-mode) (sly-mode-hook sly-mode))) -(add-hook 'lisp-mode-hook - (lambda () - (set (make-local-variable 'lisp-indent-function) - 'common-lisp-indent-function) - (setup-paredit))) +(defun hgh/rg (search) + (interactive "sSearch: ") + (compilation-start + (concat "rg --no-heading '" search "'") + 'compilation-mode + (lambda (s) + (concat + "*" + (file-name-nondirectory (directory-file-name default-directory)) + " rg*")))) (use-package multiple-cursors + :init + (add-hook 'multiple-cursors-mode-enabled-hook + (lambda () + (remove-hook 'activate-mark-hook 'hgh/disable-bar-cursor) + (remove-hook 'deactivate-mark-hook 'hgh/disable-bar-cursor) + (blink-cursor-mode 1))) + (add-hook 'multiple-cursors-mode-disabled-hook + (lambda () + (add-hook 'activate-mark-hook 'hgh/disable-bar-cursor) + (add-hook 'deactivate-mark-hook 'hgh/disable-bar-cursor) + (blink-cursor-mode 1))) :config (global-set-key (kbd "C->") 'mc/mark-next-like-this) (global-set-key (kbd "C-<") 'mc/mark-previous-like-this)) @@ -237,14 +310,6 @@ (use-package markdown-mode :ensure t) -(use-package aider - :custom - (aider-args '("--model" "o4-mini" "--no-auto-accept-architect")) - :bind - ("C-c a" . #'aider-transient-menu) - :config - (setenv "OPENAI_API_KEY" "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")) - ;; Run Prettier only in certain major-modes before saving: (defvar hgh/prettier-modes '(tsx-ts-mode typescript-ts-mode typescript-ts-base-mode json-ts-mode javascript-mode js2-mode typescript-mode web-mode) @@ -264,8 +329,59 @@ (use-package terraform-mode) -(use-package cider) +(use-package cider + :defer t) (use-package org + :defer t :custom - (org-todo-keywords '((sequence "TODO" "INPROGRESS" "DONE")))) + (org-todo-keywords '((sequence "TODO" "INPROGRESS" "DONE"))) + (org-support-shift-select t) + (org-html-validation-link nil) + (org-html-head-include-default-style nil) + (org-html-head-include-scripts nil) + (org-html-head "") + + :config + (org-indent-mode) + (require 'ox-publish) + + (org-babel-do-load-languages + 'org-babel-load-languages + '((shell . t))) + + (setq org-publish-use-timestamps-flag nil) + + (setq org-publish-project-alist + (list + (list "writings" + :base-directory "~/Documents/writings/content" + :publishing-directory "~/Documents/writings/public" + :exclude "~/Documents/writings/notes" + :recursive t + :time-stamp-file nil + :section-numbers nil + :with-creator t + :with-author nil)))) + +(use-package dumber-jump + :init + (add-hook 'xref-backend-functions #'dumber-jump-xref-activate)) + +(use-package flycheck) + ; :init (global-flycheck-mode) + + +(use-package idris-mode) + +(use-package htmlize) + +(use-package zig-mode) + +(use-package fsharp-mode) + +(use-package caddyfile-mode) + +(use-package dockerfile-mode) + +(load-file (concat (file-name-parent-directory user-init-file) "odin-mode.el")) diff --git a/odin-mode.el b/odin-mode.el new file mode 100644 index 0000000..f3c818a --- /dev/null +++ b/odin-mode.el @@ -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 *\\_")) + +(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