summaryrefslogtreecommitdiff
path: root/modules/system-utils.el
blob: 6e51c32cfcc0e3dc53510be8d499e77f3280e2c1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
;;; system-utils --- System-Wide Utilities -*- lexical-binding: t; coding: utf-8; -*-
;; author Craig Jennings <c@cjennings.net>
;;
;;; Commentary:
;;
;; A "system-util" is an enhancement to common a Emacs command, or the extension
;; of an existing command. Perhaps this group can be better named.
;;
;; Eval-Buffer     : This is a command I use frequently that's not mapped and
;;                   doesn't give confirmation. Fixed it.
;; Sudo-Edit       : If you're using Emacs to edit system files, you'll want
;;                   this so you don't have to run Emacs as root.
;; Open-File-With  : This set of methods will open a given file using the default application
;;                   for the OS and detached from Emacs. Leveraged in Dired/Dirvish.
;; Server Shutdown : Closes the running Emacs Server. Will close all attached Emacs clients.
;; savehist        : Persistence for recently opened files, and minibuffer history
;; ibuffer         : Better replacement for list-buffer with icons for easier recognition
;; scratch-buffer  : A little joy in the scratch buffer can go a long way.
;; dictionary      : look up words via sdcv
;; log-silently    : for debugging messages where you don't want to see activity in the echo area.
;; proced          : I was astonished to discover a process monitor application built into Emacs.
;;                   Then again, given all that's here, how surprising is it really?
;;
;;; Code:

(declare-function dired-get-file-for-visit "dired" ())
(declare-function dired-file-name-at-point "dired" ())
(declare-function env-linux-p "host-environment" ())
(declare-function env-macos-p "host-environment" ())
(declare-function env-windows-p "host-environment" ())
(declare-function w32-shell-execute "w32fns" (operation document &optional parameters show-flag))

;;; -------------------------------- Eval Buffer --------------------------------

(defun cj/eval-buffer-with-confirmation-or-error-message ()
  "Evaluate the buffer and display a message."
  (interactive)
  (condition-case err
      (progn
        (eval-buffer)
        (message "Buffer evaluated."))
    (error
     (message "Error occurred during evaluation: %s" (error-message-string err)))))
(keymap-global-set "C-c b" #'cj/eval-buffer-with-confirmation-or-error-message)

;;; ---------------------------- Edit A File With Sudo ----------------------------

(use-package sudo-edit
  :commands sudo-edit
  :bind ("C-x M-f" . sudo-edit))

;;; ------------------------------- Open File With ------------------------------
;; TASK: Favor this method over cj/open-this-file-with and add to custom buffer funcs

(defun cj/open-file-with-command (command)
  "Open the current file with COMMAND.
Works in both Dired buffers and regular file buffers. The command runs
fully detached from Emacs."
  (interactive "MOpen with command: ")
  (let* ((file (cond
                ;; In dired/dirvish mode, get file at point
                ((derived-mode-p 'dired-mode)
				 (dired-get-file-for-visit))
                ;; In a regular file buffer
                (buffer-file-name
                 buffer-file-name)
                ;; Fallback - prompt for file
                (t
                 (read-file-name "File to open: "))))
         ;; For xdg-open and similar launchers, we need special handling
         (is-launcher (member command '("xdg-open" "open" "start"))))
    ;; Validate file exists
    (unless (and file (file-exists-p file))
      (error "No valid file found or selected"))
    ;; Use different approaches for launchers vs regular commands
    (if is-launcher
        ;; For launchers, use call-process with 0 to fully detach
        (progn
          (call-process command nil 0 nil file)
          (message "Opening %s with %s..." (file-name-nondirectory file) command))
      ;; For other commands, use start-process-shell-command for potential output
      (let* ((output-buffer-name (format "*Open with %s: %s*"
                                         command
                                         (file-name-nondirectory file)))
             (output-buffer (generate-new-buffer output-buffer-name)))
        (start-process-shell-command
         command
         output-buffer
         (format "%s %s" command (shell-quote-argument file)))
        (message "Running %s on %s..." command (file-name-nondirectory file))))))


(defun cj/identify-external-open-command ()
  "Return the OS-default \"open\" command for this host.
Signals an error if the host is unsupported."
  (cond
   ((env-linux-p)   "xdg-open")
   ((env-macos-p)   "open")
   ((env-windows-p) "start")
   (t (error "External-open: unsupported host environment"))))

(defun cj/xdg-open (&optional filename)
  "Open FILENAME (or the file at point) with the OS default handler.
Logs output and exit code to buffer *external-open.log*."
  (interactive)
  (let* ((file  (expand-file-name (or filename (dired-file-name-at-point))))
         (cmd   (cj/identify-external-open-command))
         (logbuf (get-buffer-create "*external-open.log*")))
    (with-current-buffer logbuf
      (goto-char (point-max))
      (insert (format-time-string "[%Y-%m-%d %H:%M:%S] "))
      (insert (format "Opening: %s\n" file)))
    (cond
     ;; Windows: let the shell handle association; fully detached.
     ((env-windows-p)
      (w32-shell-execute "open" file))
     ;; macOS/Linux: run the opener synchronously; it returns immediately.
     (t
      (call-process cmd nil 0 nil file)
      (with-current-buffer logbuf
        (insert "  → Launched asynchronously\n"))))
    nil))

;;; ------------------------------ Server Shutdown ------------------------------

(defun cj/server-shutdown ()
  "Save buffers, kill Emacs and shutdown the server."
  (interactive)
  (save-some-buffers)
  (kill-emacs))
(keymap-global-set "C-<f10>" #'cj/server-shutdown)

;;; ---------------------------- History Persistence ----------------------------
;; Persist history over Emacs restarts

(use-package savehist
  :ensure nil  ; built-in
  :config
  (savehist-mode)
  (setq savehist-file  "~/.emacs.d/.emacs-history"))

;;; ------------------------ List Buffers With Nerd Icons -----------------------

;; Remap list-buffers to ibuffer (built-in). Keybinding is separate from
;; nerd-icons-ibuffer package, which only adds icons to ibuffer.
(keymap-global-set "<remap> <list-buffers>" #'ibuffer)

(use-package nerd-icons-ibuffer
  :after nerd-icons
  :hook (ibuffer-mode . nerd-icons-ibuffer-mode)
  :config
  (setq nerd-icons-ibuffer-icon            t
        nerd-icons-ibuffer-color-icon      t
        nerd-icons-ibuffer-human-readable-size t))

;;; -------------------------- Scratch Buffer Happiness -------------------------

(defvar scratch-emacs-version-and-system
  (concat ";; Emacs " emacs-version
          " on " system-configuration ".\n"))
(defvar scratch-greet
  (concat ";; Emacs ♥ you, " user-login-name ". Happy Hacking!\n\n"))
(setopt initial-scratch-message
        (concat scratch-emacs-version-and-system scratch-greet))

;;; --------------------------------- Dictionary --------------------------------

(use-package quick-sdcv
  :bind
  ("C-h d" . quick-sdcv-search-input)
  :custom
  (quick-sdcv-dictionary-prefix-symbol "►")
  (quick-sdcv-ellipsis " ▼"))

;;; -------------------------------- Log Silently -------------------------------

(defun cj/log-silently (format-string &rest args)
  "Append formatted message (FORMAT-STRING with ARGS) to *Messages* buffer.
This does so without echoing in the minibuffer."
  (let ((inhibit-read-only t))
    (with-current-buffer (get-buffer-create "*Messages*")
      (goto-char (point-max))
      (unless (bolp) (insert "\n"))
      (insert (apply #'format format-string args))
      (unless (bolp) (insert "\n")))))

;;; ------------------------------ Process Monitor ------------------------------

(use-package proced
  :ensure nil ;; built-in
  :commands proced
  :bind ("C-M-p" . proced)
  :custom
  (proced-auto-update-flag t)
  (proced-show-remote-processes t)
  (proced-enable-color-flag t)
  (proced-format 'custom)
  :config
  (add-to-list 'proced-format-alist
               '(custom user pid ppid sess tree pcpu pmem rss start time
                        state (args comm))))

(provide 'system-utils)
;;; system-utils.el ends here