;;; oni.el --- Functions for emacs ;; Copyright (C) 2012 Tom Willemsen ;; Author: Tom Willemsen ;; Keywords: local ;; 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: (autoload 'notifications-notify "notifications") (autoload 'jabber-send-message "jabber-chat") (defmacro oni:define-mailbox (name email &optional signature longname) "Define a mailbox function for mailbox NAME with address EMAIL. Optionally set signature to SIGNATURE and use LONGNAME as the actual account name." `(defun ,(make-symbol (concat "oni:" name "-mailbox")) () ,(concat "Settings for " name " mailbox") (setq mu4e-mu-home ,(expand-file-name (concat "~/.mu/" name)) mu4e-maildir ,(expand-file-name (concat "~/documents/mail/" (or longname name))) mu4e-get-mail-command ,(concat "offlineimap -oa " (or longname name)) mu4e~main-buffer-name ,(concat "*mu4e-" name "*") user-mail-address ,email message-sendmail-extra-arguments '("-a" ,name) message-signature-file ,signature))) (defmacro oni:email (user at host dot com) "Turn arguments into an email address. The resulting email address will look like: USER@HOST.COM, AT and DOT are intentionally being skipped." (concat (symbol-name user) "@" (symbol-name host) "." (symbol-name com))) (defvar oni:mailbox-map '("top" ("menu" ("ryulash.org" . "ryuslash") ("ninthfloor" . "ninthfloor") ("gmail" . "gmail") ("aethon" . "aethon"))) "A mailbox map for use with `tmm-prompt'.") (defvar oni:required-packages '(graphviz-dot-mode htmlize magit rainbow-delimiters rainbow-mode yasnippet markdown-mode flymake flymake-cursor sauron expand-region fill-column-indicator git-auto-commit-mode idomenu magit smex) "List of all the packages I have (want) installed.") (defun oni:after-save-func () "Function for `after-save-hook'." (oni:compile-el) (executable-make-buffer-file-executable-if-script-p) (let* ((dom-dir (locate-dominating-file (buffer-file-name) "Makefile")) (TAGSp (not (string= "" (shell-command-to-string (concat "grep \"^TAGS:\" " dom-dir "Makefile")))))) (when (and dom-dir TAGSp) (shell-command (concat "make -C " dom-dir " TAGS >/dev/null 2>&1"))))) (defun oni:appt-display-window-and-jabber (min-to-app new-time appt-msg) "Send a message to my phone jabber account." (jabber-send-message (car jabber-connections) "phone@ryuslash.org" nil (format "%s%s (in %s minutes)" new-time appt-msg min-to-app) nil) (appt-disp-window min-to-app new-time appt-msg)) (defun oni:before-save-func () "Function for `before-save-hook'." (if (eq major-mode 'html-mode) (oni:replace-html-special-chars)) (if (not (eq major-mode 'markdown-mode)) (delete-trailing-whitespace))) (defun oni:c-mode-func () "Function for `c-mode-hook'." (local-set-key [f9] 'compile) (local-set-key "\C-j" 'oni:newline-and-indent)) (defun oni:close-client-window () "Close a client's frames." (interactive) (server-save-buffers-kill-terminal nil)) (defun oni:compile-el () "Compile the current buffer file if it is an .el file." (let* ((full-file-name (buffer-file-name)) (file-name (file-name-nondirectory full-file-name)) (suffix (file-name-extension file-name))) (if (and (not (string-equal file-name ".dir-locals.el")) (string-equal suffix "el")) (byte-compile-file full-file-name)))) (defun oni:css-mode-func () "Function for `css-mode-hook'." (local-set-key "\C-j" 'oni:newline-and-indent) (rainbow-mode)) (defun oni:current-jabber-status () "Return a string representing the current jabber status." (or (and (not *jabber-connected*) "Offline") (and (not (string= *jabber-current-status* "")) *jabber-current-status*) "Online")) (defun oni:diary-display-func () "Function for `diary-display-hook'." (diary-fancy-display)) (defun oni:emacs-lisp-mode-func () "Function for `emacs-lisp-mode-hook'." (eldoc-mode)) (defun oni:emms-toggle-playing () "Toggle between playing/paused states." (interactive) (if (eq emms-player-playing-p nil) (emms-start) (emms-pause))) (defun oni:erc-mode-func () "Function for `erc-mode-hook'." (erc-fill-mode -1) (visual-line-mode) (setq truncate-lines nil)) (defun oni:eshell-mode-func () "Function for `eshell-mode-hook'." (setq truncate-lines nil)) (defun oni:eshell-prompt-function () "Show a pretty shell prompt." (let ((status (if (zerop eshell-last-command-status) ?+ ?-)) (hostname (shell-command-to-string "hostname")) (dir (abbreviate-file-name (eshell/pwd))) (branch (shell-command-to-string "git branch --contains HEAD 2>/dev/null | sed -e '/^[^*]/d'")) (userstatus (if (zerop (user-uid)) ?# ?$))) (concat (propertize (char-to-string status) 'face `(:foreground ,(if (= status ?+) "green" "red"))) " " (propertize (substring hostname 0 -1) 'face 'mode-line-buffer-id) " " (propertize (oni:shorten-dir dir) 'face 'font-lock-string-face) " " (when (not (string= branch "")) (propertize ;; Cut off "* " and "\n" (substring branch 2 -1) 'face 'font-lock-function-name-face)) " \n" (propertize (char-to-string userstatus) 'face `(:foreground "blue")) "> "))) (defun oni:flymake-mode-func () "Function for `flymake-mode-hook'." (local-set-key [M-P] 'flymake-goto-prev-error) (local-set-key [M-N] 'flymake-goto-next-error)) (defun oni:go-mode-func () "Function for `go-mode-hook'." (setq indent-tabs-mode nil) (local-set-key "\C-j" 'oni:newline-and-indent)) (defun oni:gtags-mode-func () "Function for `gtags-mode-hook'." (local-set-key "\M-," 'gtags-find-tag) (local-set-key "\M-." 'gtags-find-rtag)) (defun oni:html-mode-func () "Function for `html-mode-hook'." (yas-minor-mode) (fci-mode)) (defun oni:indent-shift-left (start end &optional count) "Rigidly indent region. Region is from START to END. Move COUNT number of spaces if it is non-nil otherwise use `tab-width'." (interactive (if mark-active (list (region-beginning) (region-end) current-prefix-arg) (list (line-beginning-position) (line-end-position) current-prefix-arg))) (if count (setq count (prefix-numeric-value count)) (setq count tab-width)) (when (> count 0) (let ((deactivate-mark nil)) (save-excursion (goto-char start) (while (< (point) end) (if (and (< (current-indentation) count) (not (looking-at "[ \t]*$"))) (error "Can't shift all lines enough")) (forward-line)) (indent-rigidly start end (- count)))))) (defun oni:indent-shift-right (start end &optional count) "Indent region between START and END rigidly to the right. If COUNT has been specified indent by that much, otherwise look at `tab-width'." (interactive (if mark-active (list (region-beginning) (region-end) current-prefix-arg) (list (line-beginning-position) (line-end-position) current-prefix-arg))) (let ((deactivate-mark nil)) (if count (setq count (prefix-numeric-value count)) (setq count tab-width)) (indent-rigidly start end count))) (defun oni:jabber-alert-message-func (from buffer text title) (notifications-notify :title title :body text)) (defun oni:jabber-chat-mode-func () "Function for `jabber-chat-mode-hook'." (visual-line-mode) (setq mode-line-format (append (cddr jabber-chat-header-line-format) '(global-mode-string)) header-line-format nil)) (defun oni:jabber-roster-mode-func () "Function for `jabber-roster-mode-hook'." (setq mode-line-format (list (propertize " %m" 'face 'mode-line-buffer-id)))) (defun oni:java-mode-func () "Function for `java-mode-hook'." (local-set-key "\C-j" 'oni:newline-and-indent)) (defun oni:js-mode-func () "Function for `js-mode-hook'." (rainbow-delimiters-mode) (local-set-key "\C-j" 'oni:newline-and-indent) (pretty-symbols-mode -1)) (defun oni:js2-mode-func () "Function for `js2-mode-hook'." (oni:prog-mode-func) (oni:js-mode-func) (local-set-key (kbd "") #'slime-js-reload) (slime-js-minor-mode)) (defun oni:kill-region-or-backward-char () "Either `kill-region' or `backward-delete-char-untabify'." (interactive) (if (region-active-p) (kill-region (region-beginning) (region-end)) (backward-delete-char-untabify 1))) (defun oni:kill-region-or-forward-char () "Either `kill-region' or `delete-forward-char'." (interactive) (if (region-active-p) (kill-region (region-beginning) (region-end)) (delete-forward-char 1))) (defun oni:kill-region-or-line () "Either `kill-region' or `kill-line'." (interactive) (if (region-active-p) (kill-region (region-beginning) (region-end)) (kill-line))) (defun oni:lua-mode-func() "Function for `lua-mode-hook'." (local-unset-key (kbd ")")) (local-unset-key (kbd "]")) (local-unset-key (kbd "}"))) (defun oni:magit-log-edit-mode-func () "Function for `magit-log-edit-mode-hook'." (auto-fill-mode) (font-lock-add-keywords nil '(("\\`\\(.\\{,50\\}\\)\\(.*\\)\n?\\(.*\\)$" (1 'git-commit-summary-face) (2 'git-commit-overlong-summary-face) (3 'git-commit-nonempty-second-line-face)) ("`\\([^']+\\)'" 1 font-lock-constant-face)) t)) (defun oni:markdown-mode-func () "Function for `markdown-mode-hook'." (setq-local comment-auto-fill-only-comments nil) (setq-local whitespace-style '(face trailing)) (auto-fill-mode) (whitespace-mode)) (defun oni:message-mode-func () "Function for `message-mode-hook'." (setq-local comment-auto-fill-only-comments nil) (auto-fill-mode) (flyspell-mode)) (defun oni:mini-fix-timestamp-string (date-string) "A minimal version of Xah Lee's `fix-timestamp-string'. Turn DATE-STRING into something else that can be worked with in code. Found at http://xahlee.org/emacs/elisp_parse_time.html" (setq date-string (replace-regexp-in-string "Jan" "01" date-string) date-string (replace-regexp-in-string "Feb" "02" date-string) date-string (replace-regexp-in-string "Mar" "03" date-string) date-string (replace-regexp-in-string "Apr" "04" date-string) date-string (replace-regexp-in-string "May" "05" date-string) date-string (replace-regexp-in-string "Jun" "06" date-string) date-string (replace-regexp-in-string "Jul" "07" date-string) date-string (replace-regexp-in-string "Aug" "08" date-string) date-string (replace-regexp-in-string "Sep" "09" date-string) date-string (replace-regexp-in-string "Oct" "10" date-string) date-string (replace-regexp-in-string "Nov" "11" date-string) date-string (replace-regexp-in-string "Dec" "12" date-string)) (string-match "^\\([0-9]\\{2\\}\\)-\\([0-9]\\{2\\}\\)-\\([0-9]\\{4\\}\\)$" date-string) (format "%s-%s-%s" (match-string 3 date-string) (match-string 2 date-string) (match-string 1 date-string))) (defun oni:move-beginning-of-dwim () "Move to beginning of line either after indentation or before." (interactive) (let ((start (point))) (back-to-indentation) (if (= start (point)) (beginning-of-line)))) (defun oni:move-end-of-dwim () "Move to end of line, either before any comments or after." (interactive) (let ((start (point)) (eolpos (line-end-position))) (beginning-of-line) (if (and comment-start (comment-search-forward eolpos t)) (progn (search-backward-regexp (concat "[^ \t" comment-start "]")) (forward-char) (when (or (bolp) (= start (point))) (end-of-line))) (end-of-line)))) (defun oni:myepisodes-formatter (plist) "Format RSS items from MyEpisodes as org tasks. PLIST contains all the pertinent information." (let ((str (plist-get plist :title))) (string-match "^\\[ \\([^\]]+\\) \\]\\[ \\([^\]]+\\) \\]\\[ \\([^\]]+\\) \\]\\[ \\([^\]]+\\) \\]$" str) (let* ((title (match-string 1 str)) (episode (match-string 2 str)) (name (match-string 3 str)) (date (oni:mini-fix-timestamp-string (match-string 4 str)))) (format "* ACQUIRE %s %s - %s \n SCHEDULED: <%s>" title episode name date)))) (defun oni:newline-and-indent () "`newline-and-indent', but with a twist. When dealing with braces, add another line and indent that too." (interactive) (if (and (not (or (= (point) (point-max)) (= (point) (point-min)))) (or (and (char-equal (char-before) ?{) (char-equal (char-after) ?})) (and (char-equal (char-before) ?\() (char-equal (char-after) ?\))))) (save-excursion (newline-and-indent))) (newline-and-indent)) (defun oni:org-mode-func () "Function for `org-mode-hook'." (auto-fill-mode) (yas-minor-mode) (setq-local comment-auto-fill-only-comments nil)) (defun oni:php-mode-func () "Function for `php-mode-hook'." (flymake-mode) (local-set-key "\C-j" 'oni:newline-and-indent) (c-set-offset 'arglist-intro '+) (c-set-offset 'arglist-close '0) (rainbow-delimiters-mode) (setq fci-rule-column 80)) (defun oni:pretty-control-l-function (win) "Just make a string of either `fci-rule-colum' or `fill-column' length -1. Use the `-' character. WIN is ignored." (make-string (1- (if (boundp 'fci-rule-column) fci-rule-column fill-column)) ?-)) (defun oni:prog-mode-func () "Function for `prog-mode-hook'." (rainbow-delimiters-mode) (fci-mode) (pretty-symbols-mode) (yas-minor-mode) (auto-fill-mode)) (defun oni:python-mode-func () "Function for `python-mode-hook'." (flymake-mode) (local-set-key (kbd "C->") 'python-indent-shift-right) (local-set-key (kbd "C-<") 'python-indent-shift-left) (set (make-local-variable 'electric-indent-chars) nil) (rainbow-delimiters-mode) (setq fci-rule-column 79 fill-column 72) (setq-local whitespace-style '(tab-mark)) (fci-mode) (whitespace-mode)) (defun oni:raise-ansi-term (arg) "Create or show an `ansi-term' buffer." (interactive "P") (let ((buffer (get-buffer "*ansi-term*"))) (if (and buffer (not arg)) (switch-to-buffer buffer) (call-interactively 'ansi-term)))) (defun oni:raise-eshell () "Start or switch back to `eshell'. Also change directories to current working directory." (interactive) (let ((dir (file-name-directory (or (buffer-file-name) "~/"))) (hasfile (not (eq (buffer-file-name) nil)))) (eshell) (if (and hasfile (eq eshell-process-list nil)) (progn (eshell/cd dir) (eshell-reset))))) (defun oni:raise-scratch (&optional mode) "Show the *scratch* buffer. If called with a universal argument, ask the user which mode to use. If MODE is not nil, open a new buffer with the name *MODE-scratch* and load MODE as its major mode." (interactive (list (if current-prefix-arg (read-string "Mode: ") nil))) (let* ((bname (if mode (concat "*" mode "-scratch*") "*scratch*")) (buffer (get-buffer bname)) (mode-sym (intern (concat mode "-mode")))) (unless buffer (setq buffer (generate-new-buffer bname)) (with-current-buffer buffer (when (fboundp mode-sym) (funcall mode-sym)))) (select-window (display-buffer buffer)))) (defun oni:replace-html-special-chars () "Replace special characters with HTML escaped entities." (oni:replace-occurrences "é" "é")) (defun oni:replace-occurrences (from to) "Replace all occurrences of FROM with TO in the current buffer." (save-excursion (goto-char (point-min)) (while (search-forward from nil t) (replace-match to)))) (defun oni:request-pull () "Start a mail to request pulling from a git repository." (interactive) (let* ((default-directory (expand-file-name (or (locate-dominating-file default-directory ".git") (magit-read-top-dir nil)))) (refs (magit-list-interesting-refs magit-uninteresting-refs)) (from (cdr (assoc (completing-read "From: " refs) refs))) (url (read-from-minibuffer "Pull URL: ")) (to (symbol-name (read-from-minibuffer "Up to (HEAD): " nil nil t nil "HEAD"))) (patchp (and current-prefix-arg (listp current-prefix-arg)))) (message "Requesting pull for %s from %s to %s at %s with%s patch" default-directory from to url (if patchp "" "out")) (compose-mail nil (concat "Requesting pull for " (file-name-base (directory-file-name default-directory)))) (save-excursion (goto-char (point-max)) (insert (shell-command-to-string (concat "git --git-dir='" default-directory ".git' --work-tree='" default-directory "' request-pull " (when patchp "-p ") from " " url " " to)))))) (defun oni:required-packages-installed-p () "Check if all the packages I need are installed." (let ((tmp-packages oni:required-packages) (result t)) (while (and tmp-packages result) (if (not (package-installed-p (car tmp-packages))) (setq result nil)) (setq tmp-packages (cdr tmp-packages))) result)) (defun oni:rst-mode-func () "Function for `rst-mode-hook'." (auto-fill-mode)) (defun oni:self-insert-dwim () "Execute self insert, but when the region is active call self insert at the end of the region and at the beginning." (interactive) (if (region-active-p) (let ((electric-pair-mode nil) (beginning (region-beginning)) (end (region-end))) (goto-char end) (self-insert-command 1) (save-excursion (goto-char beginning) (self-insert-command 1))) (self-insert-command 1))) (defun oni:shorten-dir (dir) "Shorten a directory, (almost) like fish does it." (while (string-match "\\(/\\.?[^./]\\)[^/]+/" dir) (setq dir (replace-match "\\1/" nil nil dir))) dir) (defun oni:show-buffer-position () "Show the position in the current buffer." (interactive) (message (format "%d:%d" (line-number-at-pos) (current-column)))) (defun oni:split-window-interactive (dir) "Split windows in direction DIR. Can also delete or switch to another window." (interactive (list (read-char "Direction (h,v,q,d,o): "))) (case dir ((?v) (split-window-vertically)) ((?h) (split-window-horizontally)) ((?q) (delete-other-windows)) ((?d) (delete-window)) ((?o) (other-window 1)))) (defun oni:split-window-interactively (window) "Ask for a direction and split WINDOW that way. If no direction is given, don't split." (let ((dir (read-char "Direction (h,v): "))) (case dir ((?v) (split-window-vertically)) ((?h) (split-window-horizontally)) (t window)))) (defun oni:start-emms () "Check to see if the function `emms' exists, if not call `emms-player-mpd-connect' and assume that will have loaded it." (interactive) (unless (fboundp 'emms) (emms-player-mpd-connect)) (emms)) (defun oni:term-mode-func () "Function for `term-mode-hook'." (setq truncate-lines nil)) (defun oni:texinfo-mode-func () "Function for `texinfo-mode-hook'." (auto-fill-mode)) (defun oni:view-mail (inbox) "Show a menu with all mailbox options from `oni:mailbox-map' for easy selection." (interactive (list (progn (require 'tmm) (let ((tmm-completion-prompt "Choose a mailbox\n")) (tmm-prompt oni:mailbox-map))))) (if inbox (progn (require 'mu4e) (funcall (intern (concat "oni:" inbox "-mailbox"))) (mu4e)))) (defun oni:write-file-func () "Function for `write-file-hooks'." (time-stamp)) (defun oni:yas-minor-mode-func () "Function for `yas-minor-mode-hook'." (define-key yas-minor-mode-map (kbd "TAB") nil) (define-key yas-minor-mode-map [(tab)] nil) (define-key yas-minor-mode-map (kbd "C-\\") 'yas-expand)) (define-skeleton html-tag "Testing creation of an html tag" "Tagname:" "<" str ("Attribute: " " " str "=\"" (skeleton-read "Value: ") "\"") ">\n" "\n") (provide 'oni) ;;; oni.el ends here