diff options
| author | cashmere <cashmere@autistici.org> | 2026-08-13 16:24:59 +0200 |
|---|---|---|
| committer | cashmere <cashmere@autistici.org> | 2026-08-13 16:24:59 +0200 |
| commit | e82a2d3fb339b787440f5581a0426925b7780d6a (patch) | |
| tree | 8ad7243379678baa58b6652db84514a294708a37 /config.el | |
| parent | 894fe6ec74947627966384190f501c450789198f (diff) | |
| download | emacs-e82a2d3fb339b787440f5581a0426925b7780d6a.tar.gz | |
md: 2026-08-13T16:25:03+02:00
Diffstat (limited to 'config.el')
| -rw-r--r-- | config.el | 412 |
1 files changed, 203 insertions, 209 deletions
@@ -1,8 +1,4 @@ ;;; config.el --- Emacs-Kick --- A feature rich Emacs config for (neo)vi(m)mers -*- lexical-binding: t; -*- -(setenv "LSP_USE_PLISTS" "true") -;; (setq debug-on-error t) -;; (setenv "LSP_USE_PLISTS" "true") -;; (setq lsp-use-plists t) (setq pgtk-wait-for-event-timeout 0.001) (setq package-enable-at-startup nil) ;; (setq-default mode-line-format t) ;; disabled: boolean t is not valid for mode-line-format @@ -1115,23 +1111,6 @@ TITLE names the new denote-style file in ~/org." -(use-package kitty-graphics - :ensure (:host github :repo "cashmeredev/kitty-graphics.el") - :demand t - :custom - (kitty-graphics-enable-video t) - (kitty-graphics-shr-scale 'fit) - (kitty-graphics-shr-fit-width 0.4) - (kitty-graphics-shr-fit-height 20) - (kitty-graphics-doc-view-resolution-scale 2.0) - :hook (dired-mode . kitty-graphics-dired-auto-preview-mode) - :config - (setq kitty-graphics-enable-browser t - kitty-graphics-casty-program "~/projects/casty/bin/casty.js" - kitty-graphics-casty-chrome "helium-browser") - - (kitty-graphics-setup)) - (with-eval-after-load 'org (setq org-confirm-babel-evaluate nil) (org-babel-do-load-languages @@ -1492,10 +1471,6 @@ Skips capture tasks and projects." (defun my/org-capture-meeting-to-journal () (my/org-capture-to-journal "\n* Meeting: ")) -(with-eval-after-load 'eglot - (add-to-list 'eglot-server-programs - '(org-mode . ("harper-ls" "--stdio")))) - (defun my/org-capture-task-to-journal () (let* ((context (read-string "Task: ")) (todo-state (completing-read "TODO state: " @@ -2239,16 +2214,22 @@ Timers that expired while Emacs was closed fire immediately." (use-package company :ensure t + :demand t :hook (after-init . global-company-mode) :custom - (company-idle-delay 0) - (company-tooltip-idle-delay 0) - (company-minimum-prefix-length 2) + (company-idle-delay nil) + ;; (company-minimum-prefix-length 2) (company-require-match nil) - (company-frontends - '(company-pseudo-tooltip-unless-just-one-frontend - company-echo-metadata-frontend)) - (company-backends '(company-capf))) + (company-transformers '(my/company-limit-candidates)) + (company-backends '(company-capf)) + :config + (defun my/company-limit-candidates (candidates) + (seq-take candidates 4)) + + (define-key company-mode-map [remap indent-for-tab-command] + #'company-manual-begin) + (define-key company-mode-map [remap c-indent-line-or-region] + #'company-manual-begin)) (use-package which-key :ensure t @@ -2273,13 +2254,7 @@ Timers that expired while Emacs was closed fire immediately." ;; used by `completion-at-point'. The order of the functions matters, the ;; first function returning a result wins. Note that the list of buffer-local ;; completion functions takes precedence over the global list. - (add-hook 'completion-at-point-functions #'cape-file) - ;; (add-hook 'completion-at-point-functions #'cape-history) - ;; ... - :config - (with-eval-after-load 'eglot - (advice-add 'eglot-completion-at-point :around #'cape-wrap-noninterruptible) - (advice-add 'eglot-completion-at-point :around #'cape-wrap-buster))) + (add-hook 'completion-at-point-functions #'cape-file)) (use-package embark :ensure (:wait t) @@ -2319,7 +2294,6 @@ Timers that expired while Emacs was closed fire immediately." "\\`\\*scratch\\*\\'" "\\`\\*Completions\\*\\'" "\\`\\*Flymake" - "\\`\\*lsp-help\\*\\'" "\\`\\*eldoc" "\\`\\*elpaca-log\\*\\'" "\\`\\*Native-compile-Log\\*\\'" @@ -2336,47 +2310,191 @@ Timers that expired while Emacs was closed fire immediately." (use-package citre :ensure t - :defer t + :demand t :init (require 'citre-config) :custom (citre-default-create-tags-file-location 'in-dir) (citre-edit-ctags-options-manually nil) (citre-auto-enable-citre-mode-modes '(prog-mode)) - :bind (("C-c e" . citre-jump) - ("C-c u" . citre-update-this-tags-file))) - -(use-package eglot - :ensure nil :config - ;; nil: LSP server stays alive when last managed buffer closes. - ;; Verhindert churn bei org src edit buffers (C-c ' macht buffer auf/zu - ;; → mit t neuer pyrefly process jedes Mal). - (setq eglot-autoshutdown nil) - (setq eglot-send-changes-idle-time 0.1) - (setq eglot-sync-connect nil) - (setq eglot-connect-timeout 30) - (setq eglot-events-buffer-size 0) - (setq eglot-report-progress nil) - (setq jsonrpc-default-request-timeout 10)) - - ;; (add-hook 'eglot-managed-mode-hook - ;; (lambda () - ;; (eglot-inlay-hints-mode -1))) + (defconst my/project-ctags-nix-options + '("--langdef=nix" + "--map-nix=+.nix" + "--kinddef-nix=a,attribute,attributes" + "--regex-nix=/^[[:space:]]*([A-Za-z_][A-Za-z0-9_'-]*(\\.[A-Za-z_][A-Za-z0-9_'-]+)+)[[:space:]]*=[^=]/\\1/a/" + "--regex-nix=/^[[:space:]]*([A-Za-z_][A-Za-z0-9_'-]*)[[:space:]]*=[^=]/\\1/a/")) + + (defun my/project-ctags-root () + (or (projectile-project-root) + (user-error "Not in a Projectile project"))) + + (defun my/project-ctags-nixpkgs (root) + (when (and (file-directory-p (expand-file-name "npins" root)) + (executable-find "npins")) + (let ((default-directory root) + (output (generate-new-buffer " *npins*"))) + (unwind-protect + (when (zerop (process-file "npins" nil output nil + "get-path" "nixpkgs")) + (with-current-buffer output + (string-trim (buffer-string)))) + (kill-buffer output))))) + + (defun my/project-ctags-nixos-options (nixpkgs output) + (let ((expression + (format + "let + nixpkgs = %S; + evaluated = import (nixpkgs + \"/nixos/lib/eval-config.nix\") { + system = builtins.currentSystem; + modules = [ ]; + }; + collect = prefix: attrs: + builtins.concatLists (builtins.map + (name: + let value = attrs.${name}; in + if builtins.isAttrs value && (value._type or null) == \"option\" + then + if value.declarations == [ ] then [ ] else [ + [ + (builtins.concatStringsSep \".\" (prefix ++ [ name ])) + (builtins.toString (builtins.head value.declarations)) + ] + ] + else if builtins.isAttrs value then collect (prefix ++ [ name ]) value + else [ ]) + (builtins.attrNames attrs)); +in + builtins.concatStringsSep \"\\n\" (builtins.map builtins.toJSON (collect [ ] evaluated.options))" + nixpkgs))) + (with-current-buffer output + (erase-buffer)) + (if (zerop (process-file "nix-instantiate" nil output nil + "--eval" "--raw" "--strict" + "--expr" expression)) + (mapcar (lambda (line) + (json-parse-string line :array-type 'list)) + (split-string (with-current-buffer output + (buffer-string)) + "\n" t)) + (user-error "Could not evaluate NixOS options")))) - ;; (setq eglot-code-action-indications '(eldoc-hint)) + (defun my/project-ctags-add-nixos-options (tags-file root output) + (when-let* ((nixpkgs (my/project-ctags-nixpkgs root))) + (condition-case err + (let (headers tags) + (dolist (line (with-temp-buffer + (insert-file-contents tags-file) + (split-string (buffer-string) "\n" t))) + (if (string-prefix-p "!" line) + (push line headers) + (push line tags))) + (dolist (option (my/project-ctags-nixos-options nixpkgs output)) + (pcase-let ((`(,name ,file) option)) + (push (format "%s\t%s\t1;\"\tkind:attribute\tlanguage:nix\troles:def" + name file) + tags))) + (with-temp-file tags-file + (dolist (line (nreverse headers)) + (insert line "\n")) + (dolist (line (sort (delete-dups tags) #'string<)) + (insert line "\n")))) + (error + (message "NixOS option tags unavailable: %s" + (error-message-string err)))))) - ;; (setq eglot-ignored-server-capabilities - ;; '(:inlayHintProvider :documentHighlightProvider))) + (defun my/project-ctags-files (root) + (let ((default-directory root) + files) + (dolist (file (projectile-current-project-files)) + (unless (string= file ".tags") + (when (file-regular-p (expand-file-name file root)) + (push (expand-file-name file root) files)))) + (nreverse files))) + + + (defun my/project-ctags-enable-citre (root) + (dolist (buffer (buffer-list)) + (with-current-buffer buffer + (when (and buffer-file-name + (file-in-directory-p buffer-file-name root)) + (citre-mode 1))))) + (defun my/project-ctags-start (root) + (let* ((ctags (executable-find "ctags")) + (tags-file (expand-file-name ".tags" root)) + (list-file (make-temp-file "citre-files-")) + (temporary-tags-file (make-temp-file "citre-tags-")) + (output (get-buffer-create "*citre-ctags*")) + (files (my/project-ctags-files root))) + (unless ctags + (user-error "Universal Ctags is not available")) + (unless files + (user-error "No project files to tag")) + (with-temp-file list-file + (dolist (file files) + (insert file "\n"))) + (with-current-buffer output + (erase-buffer)) + (make-process + :name (format "ctags:%s" (file-name-nondirectory (directory-file-name root))) + :buffer output + :command (append + (list ctags + "--options=NONE" + "--output-format=e-ctags") + my/project-ctags-nix-options + (list "--languages=all" + "--kinds-all=*" + "--fields=*" + "--extras=*" + "-f" + temporary-tags-file + "-L" + list-file)) + :connection-type 'pipe + :sentinel + (lambda (process _event) + (when (memq (process-status process) '(exit signal)) + (unwind-protect + (if (and (eq (process-status process) 'exit) + (zerop (process-exit-status process))) + (progn + (my/project-ctags-add-nixos-options + temporary-tags-file root output) + (rename-file temporary-tags-file tags-file t) + (citre-clear-tags-file-cache) + (my/project-ctags-enable-citre root) + (message "Ctags ready for %s" root)) + (message "Ctags failed for %s; see %s" + root + (buffer-name output))) + (when (file-exists-p list-file) + (delete-file list-file)) + (when (file-exists-p temporary-tags-file) + (delete-file temporary-tags-file)))))) + (message "Generating Ctags for %s..." root))) + + (defun my/project-ctags-setup () + (interactive) + (let* ((root (my/project-ctags-root)) + (tags-file (expand-file-name ".tags" root))) + (if (file-exists-p tags-file) + (my/project-ctags-start root) + (when (y-or-n-p (format "Set up Ctags for %s? " root)) + (my/project-ctags-start root))))) + + (with-eval-after-load 'evil + (evil-define-key '(normal motion) 'global + (kbd "g d") #'citre-jump + (kbd "g D") #'citre-query-jump + (kbd "g p") #'citre-peek + (kbd "g r") #'citre-jump-to-reference + (kbd "g b") #'citre-jump-back))) (use-package typst-ts-mode :ensure t - :mode "\\.typ\\'" - :hook (typst-ts-mode . eglot-ensure) - :config - (with-eval-after-load 'eglot - (add-to-list 'eglot-server-programs - `(typst-ts-mode . ,(eglot-alternatives '("tinymist" "lsp")))))) + :mode "\\.typ\\'") (use-package yasnippet :ensure t @@ -2391,12 +2509,7 @@ Timers that expired while Emacs was closed fire immediately." :ensure t :mode "\\.nix\\'" :config - (modify-syntax-entry ?. "_" nix-ts-mode--syntax-table) - (with-eval-after-load 'eglot - (add-to-list 'eglot-server-programs - '(nix-ts-mode . ("nixd"))))) - -(put 'eglot-workspace-configuration 'safe-local-variable #'listp) + (modify-syntax-entry ?. "_" nix-ts-mode--syntax-table)) (with-eval-after-load 'compile (dolist (rule '((nix-def "^\\s-*- In [`']\\(/[^'\n]+\\)'" 1) @@ -2428,25 +2541,15 @@ Timers that expired while Emacs was closed fire immediately." :ensure nil :mode (("\\.c\\'" . c-ts-mode) ("\\.h\\'" . c-ts-mode)) - :hook (c-ts-mode . eglot-ensure) :custom (c-ts-mode-indent-offset 2) - (c-ts-mode-indent-style 'k&r) - :config - (with-eval-after-load 'eglot - (add-to-list 'eglot-server-programs - '((c-ts-mode c-mode) . - ("clangd" - "--background-index" - "--clang-tidy" - "--header-insertion=never"))))) + (c-ts-mode-indent-style 'k&r)) (use-package c3-ts-mode :ensure (:host github :repo "c3lang/c3-ts-mode") :mode (("\\.c3\\'" . c3-ts-mode) ("\\.c3i\\'" . c3-ts-mode) ("\\.c3t\\'" . c3-ts-mode)) - :hook (c3-ts-mode . eglot-ensure) :custom (c3-ts-mode-indent-offset 2) (setq treesit-font-lock-level 4) @@ -2455,24 +2558,7 @@ Timers that expired while Emacs was closed fire immediately." '(c3 "https://github.com/c3lang/tree-sitter-c3")) :config (unless (treesit-language-available-p 'c3) - (treesit-install-language-grammar 'c3)) - (with-eval-after-load 'eglot - (add-to-list 'eglot-server-programs - '(c3-ts-mode . ("c3-lsp"))))) - -(use-package python - :ensure nil - :mode ("\\.py\\'" . python-ts-mode) - :hook (python-ts-mode . eglot-ensure) - :custom - (python-indent-offset 4) - (python-indent-guess-indent-offset-verbose nil) - (python-shell-interpreter "python3") - (python-shell-completion-native-enable nil) - :config - (with-eval-after-load 'eglot - (add-to-list 'eglot-server-programs - '((python-ts-mode python-mode) . ("rass" "--" "pyrefly" "lsp" "--" "ruff" "server"))))) + (treesit-install-language-grammar 'c3))) (use-package pyvenv :ensure t @@ -2483,37 +2569,11 @@ Timers that expired while Emacs was closed fire immediately." (lambda () (pyvenv-mode 1)))) -(use-package rust-ts-mode - :ensure nil - :mode "\\.rs\\'" - :hook (rust-ts-mode . eglot-ensure) - :config - (with-eval-after-load 'eglot - (add-to-list 'eglot-server-programs - '(rust-ts-mode . - ("rust-analyzer" :initializationOptions - (:check (:command "clippy"))))))) - (use-package js :ensure nil :mode ("\\.js\\'" . js-ts-mode) - :hook (js-ts-mode . eglot-ensure) - :config - (setq js-indent-level 2) - (with-eval-after-load 'eglot - (add-to-list 'eglot-server-programs - '(js-ts-mode . ("rass" "--" "typescript-language-server" "--stdio" "--" "vscode-eslint-language-server" "--stdio"))))) - -(use-package typescript-ts-mode - :ensure nil - :mode (("\\.ts\\'" . typescript-ts-mode) - ("\\.tsx\\'" . tsx-ts-mode)) - :hook ((typescript-ts-mode . eglot-ensure) - (tsx-ts-mode . eglot-ensure)) :config - (with-eval-after-load 'eglot - (add-to-list 'eglot-server-programs - '((typescript-ts-mode tsx-ts-mode) . ("rass" "--" "typescript-language-server" "--stdio" "--" "vscode-eslint-language-server" "--stdio"))))) + (setq js-indent-level 2)) (use-package xonsh-mode :ensure (:host github :repo "seanfarley/xonsh-mode") @@ -2530,38 +2590,6 @@ Xonsh is a python superset; reuse python tree-sitter parser." (add-to-list 'auto-mode-alist '("\\.xsh\\'" . xonsh-ts-mode)) (add-to-list 'auto-mode-alist '("\\.xonshrc\\'" . xonsh-ts-mode)))) -(defun my/org-src-eglot-python () - "Give org src edit buffers a stable temp file name so eglot can attach. -Name derived from owning org file path → identical across re-opens of -the same src block file, so eglot reuses the existing connection -instead of churning." - (when (and (derived-mode-p 'python-ts-mode 'python-mode) - (not buffer-file-name) - (bound-and-true-p org-src-mode)) - (let* ((parent (or (buffer-file-name (org-src-source-buffer)) - (buffer-name (org-src-source-buffer)) - "scratch")) - (root (or (and (project-current) - (project-root (project-current))) - temporary-file-directory)) - (name (format "ob-src-%s.py" - (substring (md5 parent) 0 8)))) - (setq-local buffer-file-name (expand-file-name name root)) - (set-buffer-modified-p nil) - (eglot-ensure)))) - -(add-hook 'org-src-mode-hook #'my/org-src-eglot-python) - -(with-eval-after-load 'eglot - (add-to-list 'eglot-server-programs - '((xonsh-mode xonsh-ts-mode) - . ("rass" "--" "pyrefly" "lsp" "--" "ruff" "server")))) - -;; kein auto-eglot fuer xonsh — pyrefly.toml waere noetig. Manuell via -;; M-x my/pyrefly-here-xonsh (legt temp pyrefly.toml an + startet eglot). -;; (add-hook 'xonsh-mode-hook #'eglot-ensure) -;; (add-hook 'xonsh-ts-mode-hook #'eglot-ensure) - (defvar my/ob-xonsh-installer (lambda () (defun org-babel-execute:xonsh (body params) @@ -2590,38 +2618,18 @@ BODY is the xonsh script. PARAMS may include :dir and :cmdline." (use-package yaml-ts-mode :ensure nil - :mode "\\.ya?ml\\'" - :hook (yaml-ts-mode . eglot-ensure)) + :mode "\\.ya?ml\\'") (use-package bash-ts-mode :ensure nil :mode (("\\.sh\\'" . bash-ts-mode) ("\\.bash\\'" . bash-ts-mode) ("\\.bashrc\\'" . bash-ts-mode) - ("\\.bash_profile\\'" . bash-ts-mode)) - :hook (bash-ts-mode . eglot-ensure) - :config - (with-eval-after-load 'eglot - (add-to-list 'eglot-server-programs - '(bash-ts-mode . ("bash-language-server" "start")))) - - (defun sh-mode-setup () - (add-hook 'before-save-hook #'eglot-format-buffer -10 t)) - - (add-hook 'bash-ts-mode-hook #'sh-mode-setup)) + ("\\.bash_profile\\'" . bash-ts-mode))) (use-package just-mode :ensure t - :mode ("[Jj]ustfile\\'" "\\.just\\'") - :hook (just-mode . eglot-ensure) - :config - (with-eval-after-load 'eglot - (add-to-list 'eglot-server-programs - '(just-mode . ("just-lsp"))))) - -;; (use-package justl -;; :ensure t -;; :after just-mode) + :mode ("[Jj]ustfile\\'" "\\.just\\'")) (use-package olivetti :ensure t @@ -3144,14 +3152,7 @@ still require a restart since elpaca queues run at init time." "^\\*Help\\*$" "^\\*Apropos\\*$" "^\\*info\\*$" - "^\\*Async-native-compile-log\\*$" - - ;; LSP Buffers - "^\\*lsp-log\\*$" - "^\\*clojure-lsp\\*$" - "^\\*clojure-lsp::stderr\\*$" - "^\\*ts-ls\\*$" - "^\\*ts-ls::stderr\\*$")) + "^\\*Async-native-compile-log\\*$")) (use-package projectile :ensure (:wait t) @@ -3171,7 +3172,7 @@ still require a restart since elpaca queues run at init time." :after (helm projectile) :commands (helm-projectile-find-file helm-projectile-switch-project) :custom - (helm-projectile-fuzzy-match t)) + (helm-projectile-fuzzy-match nil)) (use-package wgrep :ensure t @@ -3568,12 +3569,7 @@ place. `C-c C-c' commits, `C-c C-k' aborts." (defun my/format-buffer () (interactive) - (cond - ((eq major-mode 'rust-ts-mode) (eglot-format-buffer)) - ((eq major-mode 'python-ts-mode) (eglot-format-buffer)) - ((eq major-mode 'c-mode) (eglot-format-buffer)) - ((bound-and-true-p eglot--managed-mode) (eglot-format-buffer)) - (t (message "No formatter for %s" major-mode)))) + (user-error "No formatter configured for %s" major-mode)) (defun my/find-file-or-switch-project () "Find a file in the current project; outside one, pick a project first. @@ -3640,6 +3636,7 @@ workspace (e.g. *scratch*)." "pa" '(projectile-add-known-project :wk "add project") "pi" '(projectile-invalidate-cache :wk "invalidate cache") "pt" '(projectile-run-task :wk "tasks") + "pc" '(my/project-ctags-setup :wk "setup/update ctags") "g" '(:ignore t :wk "git") "gc" '(magit-clone :wk "clone") @@ -3651,11 +3648,8 @@ workspace (e.g. *scratch*)." (let ((current-prefix-arg 4)) (call-interactively #'xref-find-definitions))) :wk "definition other window") - "gI" '((lambda () (interactive) - (let ((current-prefix-arg 4)) - (call-interactively #'eglot-find-implementation))) - :wk "implementation other window") - "gt" '(eglot-find-typeDefinition :wk "go to type definition") + "gI" '(citre-query-jump :wk "find definition") + "gt" '(citre-peek :wk "peek definition") "gr" '(xref-find-references :wk "find references") "gs" '(magit-file-stage :wk "stage file") "gu" '(my/magit-uncommit :wk "uncommit (keep & unstage)") @@ -3699,9 +3693,9 @@ workspace (e.g. *scratch*)." "c" '(:ignore t :wk "code") "cc" '(my/compile-or-recompile :wk "compile") "cC" '(ghostel-compile :wk "recompile") - "ca" '(eglot-code-actions :wk "code actions") - "cr" '(eglot-rename :wk "lsp rename") - "cf" '(eglot-format :wk "format buffer") + "ca" '(citre-query-jump :wk "find definition") + "cr" '(query-replace :wk "replace") + "cf" '(my/format-buffer :wk "format buffer") "cs" '(yas-insert-snippet :wk "snippets") "cl" '(flycheck-list-errors :wk "list errors") |
