;;; helm-systemd.el --- helm's systemd interface -*- lexical-binding: t; -*- ;; Copyright (C) 2016 ;; Author: ;; Package-Version: 20160517.2333 ;; Package-X-Original-Version: 0.0.1 ;; Package-Requires: ((emacs "24.4") (helm "1.9.2") (with-editor "2.5.0")) ;; Keywords: convenience ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;;; Code: (require 'cl-lib) (require 'helm) (require 'with-editor) (require 'subr-x) (defvar helm-systemd-command-types '("service" "timer" "mount" "target" "socket" "scope" "device")) (defvar helm-systemd-list-all nil) (defvar helm-systemd-list-not-loaded nil) (defvar helm-systemd-buffer-name "*Helm systemd log*") (defvar helm-systemd-status-mode-hook nil ) (defconst helm-systemd-actions-list '(("print". "Printed") ("restart". "Restarted") ("stop" ."Stopped") ("start". "Started"))) (defvar helm-systemd-status-font-lock-keywords `(("\\(Loaded\\|Active\\|Status\\|Docs\\|Process\\|Main PID\\|Tasks\\|CGroup\\):" (1 'helm-bookmark-gnus) ) ("active (running)" 0 'hi-green) ("inactive (dead)" 0 'helm-bookmark-info) ("active (exited)" 0 'helm-bookmark-info) ("[fF]ailed" 0 'diredp-executable-tag) ("ā”€\\([0-9]+\\)" (1 'helm-bookmark-info)) ; PIDs ("[ā—šŸ”œ] .*" 0 'helm-buffer-file) ; command lines ā—šŸ”œ "Default expressions to highlight in `helm systemd log'.")) (define-derived-mode helm-systemd-status-mode fundamental-mode "Systemd-log" "Major mode for viewing systemd status logs. \\{helm-systemd-status-mode-map}" (setq-local font-lock-defaults '(helm-systemd-status-font-lock-keywords)) (font-lock-mode t)) (add-to-list 'auto-mode-alist `(, (concat (regexp-quote helm-systemd-buffer-name) "\\'") . helm-systemd-status-mode)) (defun helm-systemd-command-line-option () (concat "--no-pager --no-legend -t " (car helm-systemd-command-types) (if helm-systemd-list-all " --all"))) (defvar helm-systemd-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) (define-key map (kbd "") 'helm-cr-empty-string) (define-key map (kbd "") 'helm-cr-empty-string) (define-key map (kbd "C-]") 'helm-systemd-next-type) (define-key map (kbd "C-[") 'helm-systemd-prev-type) (delq nil map)) "Keymap for `helm-systemd'.") (defun helm-systemd-concatspace (word-list) "Concatenate list of string with spaces as separator" (mapconcat 'identity (delq nil word-list) " ")) (defun helm-systemd-systemctl-command (&rest args) "Construct string with: 'systemctl default-args' ARGS" (helm-systemd-concatspace (push (concat "systemctl " (helm-systemd-command-line-option)) args) )) (defun helm-systemd-get-canditates (sysd-options) "Return a list of systemd service unit" (let* ((result ()) (leftcolumnwidth (number-to-string 25)) (hash (make-hash-table :test 'equal)) (sysd-lu (shell-command-to-string (helm-systemd-systemctl-command " list-units " sysd-options))) (sysd-lu (delete "" (split-string sysd-lu "\n")))) (mapc (lambda (line) (puthash (car (split-string line)) line hash)) sysd-lu) (if helm-systemd-list-not-loaded (let* ((sysd-luf (shell-command-to-string (helm-systemd-systemctl-command " list-unit-files " sysd-options))) (sysd-luf (delete "" (split-string sysd-luf "\n")))) (mapc (lambda (line-luf) (let ((unit (car (split-string line-luf)))) (unless (gethash unit hash nil) (puthash unit line-luf hash)))) sysd-luf))) (let ((maxunitlength (string-to-number leftcolumnwidth))) (maphash (lambda (unit descr) (setq maxunitlength (max maxunitlength (length unit)))) hash) (setq leftcolumnwidth (number-to-string maxunitlength))) (maphash (lambda (unit descr) (let* ((unit_misc (string-trim-left (substring descr (length unit) (length descr)))) (formatted_output (format (concat "%-" leftcolumnwidth "s %s") unit unit_misc))) (push formatted_output result)) ) hash) result )) (defun helm-systemd-display (unit-command unit &optional isuser nodisplay) (with-current-buffer (get-buffer-create helm-systemd-buffer-name) (helm-systemd-status-mode) (let ((command (helm-systemd-systemctl-command (if isuser "--user") unit-command unit))) (insert "\nšŸ”œ " command "\n") (if (or isuser (string= unit-command "status")) (insert (shell-command-to-string command)) (with-temp-buffer (cd "/sudo::/") (setq command (shell-command-to-string (concat "sudo " command)))) (insert command) ) (insert "\n")) ;; (propertise-sysd-buffer ) (unless nodisplay (display-buffer (current-buffer))))) (defun helm-systemd-next-type () (interactive) (setq helm-systemd-command-types (append (cdr helm-systemd-command-types) (list (car helm-systemd-command-types)))) (with-helm-alive-p (helm-force-update ))) (defun helm-systemd-prev-type () (interactive) (setq helm-systemd-command-types (append (last helm-systemd-command-types) (remove (car (last helm-systemd-command-types)) helm-systemd-command-types))) (with-helm-alive-p (helm-force-update ))) (defun helm-system-persis-action (_line &optional isuser) "Show unit status" (let ((units (helm-marked-candidates))) (mapc (lambda (line) (let ((unit (car (split-string line)))) (helm-systemd-display "status" unit isuser ))) units))) (defun helm-systemd-transformer (candidates source) (let ((res candidates)) (unless (string= (car helm-systemd-command-types) "device") (setq res (cl-loop for i in candidates for split = (split-string i) for unit = (car split) for loaded = (nth 1 split) for active = (nth 2 split) for running = (nth 3 split) for description = (if running (helm-systemd-concatspace (cl-subseq split 4))) collect (let ((line i)) (unless (and unit loaded active running description) line) (if (and loaded (not (string= (car helm-systemd-command-types) "mount"))) (let* ((isenabled (car (split-string (shell-command-to-string (helm-systemd-concatspace `("systemctl" "is-enabled " ,(if (string-match "User" (cdr (assoc 'name source))) "--user") ,unit)))))) (propena (cond ((string= isenabled "enabled") 'helm-bookmark-info) ((string= isenabled "static") 'helm-bookmark-gnus) (t 'helm-bookmark-gnus))) (isenabled (format "%8s" isenabled) )) (setq line (if active (replace-regexp-in-string loaded (concat (propertize isenabled 'face propena) " " loaded " ") line ) (replace-regexp-in-string loaded (concat (propertize isenabled 'face propena) " ") line ))))) ;; list-units case (if (string= running "running") (setq line (replace-regexp-in-string running (propertize running 'face 'helm-ff-directory) line ))) (if (string= running "exited") (setq line (replace-regexp-in-string running (propertize running 'face 'helm-bookmark-info) line ))) (if (string= running "failed") (setq line (replace-regexp-in-string running (propertize running 'face 'diredp-executable-tag) line ))) (if description (setq line (replace-regexp-in-string (regexp-quote description) (propertize description 'face 'helm-buffer-process) line t))) line )))) res)) (defmacro helm-systemd-make-actions (sysd-verb isuser) `(lambda (_ignore) (mapc (lambda (candidate) (helm-systemd-display ,sysd-verb (car (split-string candidate)) ,isuser t) (message (concat (cdr (assoc ,sysd-verb helm-systemd-actions-list)) " " (car (split-string candidate))))) (helm-marked-candidates)))) (defun helm-systemd-build-source () (helm-build-sync-source "systemd" :candidates (lambda () (reverse (helm-systemd-get-canditates "") )) :action (helm-make-actions "Print" (helm-systemd-make-actions "status" nil) "Restart" (helm-systemd-make-actions "restart" nil) "Stop" (helm-systemd-make-actions "stop" nil) "Start" (helm-systemd-make-actions "start" nil)) :persistent-action #'helm-system-persis-action :persistent-help "Show unit status" :keymap helm-systemd-map :filtered-candidate-transformer #'helm-systemd-transformer)) (defun helm-systemd-build-source-user () (helm-build-sync-source "Systemd User" :candidates (lambda () (reverse (helm-systemd-get-canditates "--user"))) :action (helm-make-actions "Print" (helm-systemd-make-actions "status" t) "Restart" (helm-systemd-make-actions "restart" t) "Stop" (helm-systemd-make-actions "stop" t) "Start" (helm-systemd-make-actions "start" nil) "Edit with Emacs" (lambda (candidate) (add-to-list 'with-editor-envvars "SYSTEMD_EDITOR" t) (with-editor-async-shell-command (concat "systemctl --user --full edit " (car (split-string candidate))) ))) :persistent-action (lambda (line) (funcall #'helm-system-persis-action line t)) :persistent-help "Show unit status" :keymap helm-systemd-map :filtered-candidate-transformer #'helm-systemd-transformer)) ;;;###autoload (defun helm-systemd () (interactive) (helm :sources (mapcar (lambda (func) (funcall func)) '(helm-systemd-build-source helm-systemd-build-source-user)) :truncate-lines t :buffer (concat "*helm systemd*")) ) (provide 'helm-systemd) ;;; helm-systemd.el ends here