;;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"))))