Files
infra/dotfiles/desktop/.emacs.d/lisp/misc/custom-functions.el
2026-04-26 19:39:14 +02:00

251 lines
9.3 KiB
EmacsLisp

;;functions to support syncing .elfeed between machines
;;makes sure elfeed reads index from disk before launching
(defvar fscotto/elfeed-initial-update-done nil
"Non-nil once Elfeed has triggered its first automatic update this session.")
(defvar fscotto/elfeed-update-timer nil
"Timer used for periodic Elfeed updates.")
(defun fscotto/elfeed-auto-update ()
"Refresh Elfeed feeds in the background."
(when (require 'elfeed nil 'noerror)
(require 'elfeed-db)
(elfeed-db-load)
(elfeed-update)))
(defun fscotto/elfeed-start-auto-update ()
"Start the periodic Elfeed update timer if needed."
(unless (timerp fscotto/elfeed-update-timer)
(setq fscotto/elfeed-update-timer
(run-at-time "15 min" (* 15 60) #'fscotto/elfeed-auto-update))))
(defun fscotto/elfeed-load-db-and-open ()
"Open Elfeed, updating once per session and then every 15 minutes.
Based on https://pragmaticemacs.wordpress.com/2016/08/17/read-your-rss-feeds-in-emacs-with-elfeed/
Created: 2016-08-17
Updated: 2025-06-13"
(interactive)
(require 'elfeed)
(require 'elfeed-db)
(elfeed-db-load)
(elfeed)
(fscotto/elfeed-start-auto-update)
(unless fscotto/elfeed-initial-update-done
(setq fscotto/elfeed-initial-update-done t)
(message "Updating Elfeed feeds...")
(elfeed-update)))
(defun fscotto/project-root ()
"Return projectile project root or fallback to default-directory."
(if (featurep 'projectile)
(or (projectile-project-root) default-directory)
default-directory))
(defun fscotto/project-vterm ()
"Open vterm in project root."
(interactive)
(let ((default-directory (fscotto/project-root)))
(vterm)))
(defun fscotto/open-multi-vterm-in (directory)
"Open a new multi-vterm buffer in DIRECTORY."
(let ((default-directory (file-name-as-directory directory)))
(multi-vterm)))
(defun fscotto/home-multi-vterm ()
"Open a new multi-vterm buffer in HOME."
(interactive)
(fscotto/open-multi-vterm-in (expand-file-name "~/")))
(defun fscotto/project-multi-vterm ()
"Open a new multi-vterm buffer in project root."
(interactive)
(fscotto/open-multi-vterm-in (fscotto/project-root)))
(defconst fscotto/opencode-db-path
(expand-file-name "~/.local/share/opencode/opencode.db")
"Path to the OpenCode SQLite database.")
(defun fscotto/launch-external-terminal (&optional command directory)
"Launch external terminal in DIRECTORY, optionally running COMMAND."
(let* ((default-directory (file-name-as-directory (or directory (fscotto/project-root))))
(terminal-program (or (executable-find fscotto/external-terminal-program)
fscotto/external-terminal-program))
(args (append
(list fscotto/external-terminal-working-directory-option
default-directory)
(when command
(append
(list fscotto/external-terminal-execute-option)
command)))))
(unless (file-executable-p terminal-program)
(user-error "External terminal not found: %s" fscotto/external-terminal-program))
(apply #'start-process
"fscotto-external-terminal"
nil
terminal-program
args)))
(defun fscotto/opencode-session-candidates (directory)
"Return OpenCode session candidates for DIRECTORY.
Each entry is a cons cell of display string and session id."
(let ((sqlite3-program (executable-find "sqlite3"))
(session-directory (directory-file-name (expand-file-name directory))))
(unless sqlite3-program
(user-error "sqlite3 not found"))
(unless (file-exists-p fscotto/opencode-db-path)
(user-error "OpenCode database not found: %s" fscotto/opencode-db-path))
(mapcar
(lambda (line)
(pcase-let* ((fields (split-string line "\t"))
(`(,session-id ,title ,updated-at) fields)
(title (if (and title (not (string= title "")))
title
"Untitled session"))
(updated-seconds (/ (string-to-number updated-at) 1000))
(updated-label (format-time-string "%Y-%m-%d %H:%M"
(seconds-to-time updated-seconds)))
(short-id (if (> (length session-id) 12)
(substring session-id 0 12)
session-id)))
(cons (format "%s | %s | %s" title updated-label short-id)
session-id)))
(process-lines
sqlite3-program
"-tabs"
"-noheader"
fscotto/opencode-db-path
(format (concat
"select id, title, time_updated "
"from session "
"where directory = '%s' and time_archived is null "
"order by time_updated desc;")
(replace-regexp-in-string "'" "''" session-directory))))))
(defun fscotto/project-opencode-latest-session-id ()
"Return the latest saved OpenCode session id for the current project."
(cdr (car (fscotto/opencode-session-candidates (fscotto/project-root)))))
(defun fscotto/project-agent-dwim ()
"Choose an agent for the current project and launch it externally."
(interactive)
(let ((agent (completing-read "Agent: " '("Codex" "OpenCode") nil t)))
(pcase agent
("OpenCode"
(let ((session-id (fscotto/project-opencode-latest-session-id)))
(if session-id
(fscotto/launch-external-terminal (list "opencode" "--session" session-id)
(fscotto/project-root))
(fscotto/project-opencode))))
("Codex"
(fscotto/launch-external-terminal '("codex" "resume" "--last"))))))
(defun fscotto/project-opencode-session ()
"Resume a saved OpenCode session for the current project."
(interactive)
(let* ((project-directory (fscotto/project-root))
(candidates (fscotto/opencode-session-candidates project-directory)))
(unless candidates
(user-error "No OpenCode sessions found for %s" project-directory))
(let* ((selection (completing-read "OpenCode session: " candidates nil t))
(session-id (cdr (assoc selection candidates))))
(fscotto/launch-external-terminal (list "opencode" "--session" session-id)
project-directory))))
(defun fscotto/project-agent-session ()
"Choose an agent and resume a saved session for the current project."
(interactive)
(let ((agent (completing-read "Agent session: " '("Codex" "OpenCode") nil t)))
(pcase agent
("OpenCode"
(fscotto/project-opencode-session))
("Codex"
(fscotto/launch-external-terminal '("codex" "resume"))))))
(defun fscotto/project-external-terminal ()
"Open the external terminal in project root."
(interactive)
(fscotto/launch-external-terminal))
(defun fscotto/project-opencode ()
"Open the external terminal in project root and run opencode."
(interactive)
(fscotto/launch-external-terminal '("opencode")))
(defun fscotto/project-magit-status ()
"Open magit-status in project root."
(interactive)
(let ((default-directory (fscotto/project-root)))
(magit-status)))
(defun fscotto/magit-dispatch ()
"Load Magit if necessary and open magit-dispatch."
(interactive)
(require 'magit)
(call-interactively #'magit-dispatch))
(defun fscotto/disable-c-formatting ()
(setq-local lsp-enable-on-type-formatting nil))
;; Golang development support functions
(defun fscotto/go-format-on-save ()
"Format Go buffers on save using gofmt."
(add-hook 'before-save-hook #'lsp-format-buffer nil t))
(defun fscotto/go-mod-tidy ()
"Esegue go mod tidy nella root del progetto."
(interactive)
(let ((default-directory (project-root (project-current t))))
(compile "go mod tidy")))
(defun fscotto/go-mod-download ()
"Scarica i moduli Go."
(interactive)
(let ((default-directory (project-root (project-current t))))
(compile "go mod download")))
(defun fscotto/go-mod-after-save ()
(when (and (eq major-mode 'go-mod-ts-mode)
(lsp-workspaces))
(lsp-restart-workspace)))
(defun fscotto/go-test-package ()
"Run `go test` in the current package."
(interactive)
(let ((default-directory (project-root (project-current t))))
(compile "go test")))
(defun fscotto/go-test-module ()
"Run `go test ./...` in the current Go module."
(interactive)
(let ((default-directory (project-root (project-current t))))
(compile "go test ./...")))
(defun fscotto/go-test-current-test ()
"Run `go test -run` for the test at point."
(interactive)
(let* ((test-name (thing-at-point 'symbol t))
(default-directory (project-root (project-current t))))
(unless test-name
(user-error "No test name at point"))
(compile (format "go test -run '^%s$'" test-name))))
(defun fscotto/go-test-at-point ()
"Return Go test name at point."
(let ((sym (thing-at-point 'symbol t)))
(unless (and sym (string-prefix-p "Test" sym))
(user-error "No Go test at point"))
sym))
(defun fscotto/sudo-edit (&optional arg)
"Reopen current file as root via TRAMP.
If ARG is provided (C-u), prompts for file path."
(interactive "P")
(if arg
(find-file (concat "/sudo:root@localhost:" (read-file-name "File: ")))
(if buffer-file-name
(find-alternate-file (concat "/sudo:root@localhost:" buffer-file-name))
(user-error "This buffer is not associated with a file"))))