;;; ... -*- lexical-binding: t -*- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Basic configuration ;;; ;;; Configures basic global variables, look 'n feel, and global Emacs ;;; services (defun drkp:setup-user (name address) "Set up user information" (setq user-full-name name user-mail-address address)) ;;; Frame look (in case X resources aren't present) (defun drkp:defluff () "Disabled frame fluff. This includes the tool bar, menu bar, and scroll bar." ;; These are predicated to avoid autoloading modules solely for the ;; purpose of disabling them (ie, the tool-bar module) (if tool-bar-mode (tool-bar-mode -1)) (set-scroll-bar-mode 'right) ;; (if menu-bar-mode (menu-bar-mode -1)) ;; (if scroll-bar-mode (scroll-bar-mode -1)) ) (defun drkp:set-frame-colors (frame) "Set the frame color scheme for `frame'." (modify-frame-parameters frame '((background-color . "Black") (foreground-color . "White") (cursor-color . "Red")))) (defun drkp:setup-colors () "Set frame colors right now and whenever a new frame is made. This should be done from the Xresources, but this will work if that isn't present, and this doesn't hurt." (add-hook 'after-make-frame-functions (function drkp:set-frame-colors)) (drkp:set-frame-colors nil)) ;;; Buffer look (defun drkp:setup-buffer-look () "Sets up miscellaneous aspects of buffer look and feel." (setq truncate-partial-width-windows nil column-number-mode t frame-title-format '("%b" " - " invocation-name)) (global-font-lock-mode t)) (defun drkp:setup-mode-line () "Move the useful, short, non-static information to earlier in the mode line to make it more visible. This also removes some of the unnecessary whitespace." ;; Different Emacs installations apparently have wildly different ;; mode-line defaults, even if they look basically the same. In ;; particular, there are many ways of getting the mode list. This ;; does its best to guess the right thing. (if (not (boundp 'mode-line-modes)) (if (fboundp 'mode-line-mode-name) (setq mode-line-modes '(" %[(" (:eval (mode-line-mode-name)) mode-line-process minor-mode-alist "%n" ")%]")) (setq mode-line-modes "Help! drkp:setup-mode-line is confused!"))) (setq-default mode-line-format '("-" mode-line-mule-info mode-line-modified mode-line-frame-identification mode-line-buffer-identification " " (line-number-mode (column-number-mode "(%2l,%2c) " "L%2l ") (column-number-mode "C%2l ")) (-3 "%p") " " global-mode-string mode-line-modes "-%-"))) (defun drkp:setup-mmm () (custom-set-faces '(mmm-default-submode-face ((((background dark)) (:background "DimGray")) (t (:background "LightGray"))))) (setq mmm-global-mode 'maybe)) ;;; Usability (defun drkp:setup-misc-usability () "Sets up lots of miscellaneous usability features" (mouse-wheel-mode t) ;; Undo for window configuration changes (winner-mode 1) ;; Save buffer positions between sessions (when (require 'saveplace nil t) (setq-default save-place t) (if (= (user-uid) 0) ;; Being root screws with permissions of saveplace's files, so ;; put it somewhere else (setq save-place-file (concat save-place-file "-root")))) ;; Uniquify buffer names by prepending path elements (toggle-uniquify-buffer-names) (setq uniquify-buffer-name-style 'forward) ;; icomplete improves minibuffer completion usability (when (require 'icomplete nil t) (icomplete-mode 1) (setq icomplete-prospects-length 50)) ;; Place backup files in ~/.emacs-backup (when (require 'ebackup nil t) ;; Only keep 5 last backup copies around (the default is 10) (setq ebackup-max-copies 5)) ;; Interpret ANSI color codes in grep output (require 'compilation-colorization nil t) ;; Highlight file completion buffers (when (require 'color-completion nil t) (global-color-completion t)) ;; Setup longlines mode for autoload (when (locate-library "longlines") (autoload 'longlines-mode "longlines" "Minor mode for automatically wrapping long lines." t) (setq longlines-show-hard-newlines t)) ;; Misc usability variables (setq load-warn-when-source-newer t inhibit-startup-message t next-line-add-newlines nil x-stretch-cursor t inhibit-startup-buffer-menu t query-replace-lazy-highlight t default-indicate-buffer-boundaries t mouse-autoselect-window t tempo-interactive t kill-whole-line t) ;; Ignore .svn directories in completion (add-to-list 'completion-ignored-extensions ".svn/") ;; Enable dangerous functions (put 'narrow-to-region 'disabled nil) ;; The Emacs terminal emulator tends to confuse programs by setting ;; TERM to "Emacs". It's basically a vt100 emulator (just like every ;; other terminal emulator out there) (setenv "TERM" "vt100") ;; We have progressed beyond the typewriter era, even if emacs ;; hasn't. (setq sentence-end-double-space nil)) ;;; General settings (defun drkp:setup-ispell () "Configures the ispell package to use aspell if it's available." (if (executable-find "aspell") (setq ispell-program-name "aspell") (setq ispell-program-name "ispell"))) (defun drkp:disable-ange-ftp () "Disables Ange FTP filename completion. Having this enabled can really slow things down." (let ((fnha file-name-handler-alist)) (while fnha (if (string-match "^ange-.*" (symbol-name (cdar fnha))) (setq file-name-handler-alist (delq (car fnha) file-name-handler-alist))) (setq fnha (cdr fnha))))) (defun drkp:is-tramp-root-buffer (name) (and name (tramp-tramp-file-p name) (let* ((trampfile (tramp-dissect-file-name name)) (method (tramp-file-name-method trampfile)) (user (tramp-file-name-user trampfile)) (host (tramp-file-name-host trampfile))) (or (string= method "sudo") (string= user "root"))))) (defun drkp:sudo-buffer-file-name () "Returns the current buffer name modified for root access via sudo, or nil if the buffer has no file, it's already opened as root, or we don't know how to modify it." (when buffer-file-name (if (and (tramp-tramp-file-p buffer-file-name) (not (drkp:is-tramp-root-buffer buffer-file-name))) (let* ((trampfile (tramp-dissect-file-name buffer-file-name)) (method (tramp-file-name-method trampfile)) (user (tramp-file-name-user trampfile)) (host (tramp-file-name-host trampfile)) (localname (tramp-file-name-localname trampfile))) (if (string= method "ssh") ;; Rewrite ssh to use sudo:root; assumes proxy set up (tramp-make-tramp-file-name "sudo" "root" host localname) (error "Don't know how to rewrite filename with method %s" method))) ;; Not a tramp buffer; use sudo method (tramp-make-tramp-file-name "sudo" "root" nil "localhost" nil buffer-file-name)))) (defun toggle-read-only-sudo () "Toggle whether the file is read-only. If read-only and no permission to write the file, offer to reopen it as root (using tramp and sudo)." (interactive) (let ((sudo-file-name (drkp:sudo-buffer-file-name))) (if (and buffer-read-only (not (buffer-modified-p)) sudo-file-name (not (file-writable-p buffer-file-name)) (y-or-n-p "File is not writable; open as root? ")) (find-alternate-file sudo-file-name) (toggle-read-only)))) (defface mode-line-root '((t :background "red" :foreground "white" :inherit mode-line )) "Mode line face for files opened as root") (defun drkp:change-modeline-if-root () "Change modeline color to red if opened as root." (if (drkp:is-tramp-root-buffer buffer-file-name) (face-remap-add-relative 'mode-line 'mode-line-root))) (defun drkp:setup-tramp () (require 'tramp) ;; Set TERM=dumb so that zsh disables fancy prompt stuff (setq tramp-terminal-type "dumb") ;; Add any missing path entries we might depend on (add-to-list 'tramp-remote-path "~/scripts" ) (add-to-list 'tramp-remote-path "~/.local/bin" ) ;; Make "/sudo:host:" connect via ssh then sudo (add-to-list 'tramp-default-proxies-alist '(nil "\\`root\\'" "/ssh:%h:")) (add-to-list 'tramp-default-proxies-alist '((regexp-quote (system-name)) nil nil)) ;; Replace toggle-read-only binding with one that prompts for sudo (global-set-key (kbd "C-x C-q") 'toggle-read-only-sudo) ;; Indicate files opened as root with red modeline (add-hook 'find-file-hook 'drkp:change-modeline-if-root) ;; Tweaks for remote comint terminals ;; XXX these aren't strictly tramp-specific, maybe they should go ;; somewhere else? (add-hook 'shell-mode-hook #'ansi-color-for-comint-mode-on) (setq comint-scroll-to-bottom-on-output t comint-move-point-for-output t comint-prompt-read-only t)) (defun drkp:setup-magit () (use-package magit :ensure t :bind (("C-x g" . magit-status) ("C-x C-g" . magit-status)) :custom (magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1) ; (magit-commit-show-diff nil) (magit-bury-buffer-function #'magit-restore-window-configuration) (magit-auto-revert-mode 1) (magit-diff-refine-hunk 'all))) (defun drkp:setup-global-bindings () "Sets useful global bindings." (global-set-key "\C-c\g" (function goto-line)) (global-set-key "\C-x\C-k" (function kill-buffer)) (if (require 'magic-buffer-list nil t) ;; XXX Use autoload voodoo instead (progn (global-set-key "\C-x\C-b" (function magic-buffer-list)) (global-set-key "\C-xB" (function magic-buffer-list-other-window)) (global-set-key "\M-r" (function magic-buffer-list-and-select-next)) (global-set-key "\M-R" (function magic-buffer-list-and-select-prev))) (global-set-key "\C-x\C-b" (function electric-buffer-list))) ) ;; Helper function to open non-Magit/with-editor emacsclient ;; invocations in their own frame (defun drkp:server-pop-to-new-frame-maybe () "Open client buffers in a new frame, except Magit/with-editor commit buffers." (let* ((buf (current-buffer)) (fname (or (buffer-file-name buf) "")) (commitish (or (derived-mode-p 'git-commit-mode) (string-match-p "/\\.git/\\(COMMIT_EDITMSG\\|TAG_EDITMSG\\|MERGE_MSG\\)$" fname))) (with-editor (bound-and-true-p with-editor-mode))) (unless (or commitish with-editor) (bury-buffer) (let ((pop-up-windows nil) (pop-up-frames t)) (pop-to-buffer buf))))) (defun drkp:setup-server () "Sets up the Emacs server. This doesn't actually start it, relying on ~/bin/ec to do that when appropriate, but it configures it to open a new frame on each connection and to not complain about closing server buffers." (when (require 'server nil t) ;; Rely on the ec script or the user to start the server in the ;; right instance. This way stray emacs instances don't fight ;; over who gets the server. ;; ;; For Aqua emacs-app, however, do start the server since ;; emacsclient isn't going to do that. (when (eq window-system 'ns) (server-start)) ;; Always open a new frame. XXX I'm not sure what the right ;; behavior is if the buffer is already open, but I know it's not ;; what server does. Either focus the existing buffer, or create ;; a new frame containing it without messing with any existing ;; frames. (add-hook 'server-switch-hook #'drkp:server-pop-to-new-frame-maybe) ;; Make kill-buffer just release the client without complaining ;; about it (remove-hook 'kill-buffer-query-functions 'server-kill-buffer-query-function))) (defun kill-server-or-frame-or-emacs (&optional arg) "If there are multiple frames open, close only the current frame. If this is the last frame, just do a `save-buffers-kill-emacs'. This has special knowledge of the Emacs server and will attempt to kill server-controlled buffers in a way that makes it feel like the frames popped up by new connections are independent Emacsen. This differs somewhat from `save-buffers-kill-terminal' in that it will always kill only the current frame, even if it was not started by emacsclient." (interactive "P") (when (featurep 'server) ;; For each window in this frame, if the buffer in that window ;; came from the Emacs server, see if this is the only window ;; containing that buffer and kill it if so ;; ;; Supposedly server-save-buffers-kill-terminal should do this in ;; emacs23, but in my experience it didn't work correctly (walk-windows (lambda (wnd) (save-excursion (let ((buffer (window-buffer wnd))) (if (and server-buffer-clients (null (cdr (get-buffer-window-list buffer)))) (progn (set-buffer buffer) (server-done)))))) nil (selected-frame))) ;; If there are multiple frames, kill just this frame (let ((frames (frame-list))) (if (and frames (cdr frames)) (delete-frame) ;; Last frame, so kill emacs (save-buffers-kill-emacs arg)))) (defun drkp:setup-kill-dwim () "Changes \C-x\C-c to use `kill-server-or-frame-or-emacs'. This goes well with `drkp:setup-server'." (global-set-key "\C-x\C-c" (function kill-server-or-frame-or-emacs))) (defun drkp:setup-ns () (when (eq window-system 'ns) (setq ns-alternate-modifier (quote alt) ns-command-modifier (quote meta) ns-function-modifier (quote hyper)))) ;;; Everything (defun drkp:basic-setup-all () "Sets up everything in drkp-basic, except `drkp:setup-user'." (drkp:defluff) (drkp:setup-colors) (drkp:setup-buffer-look) (drkp:setup-mode-line) (drkp:setup-mmm) (drkp:setup-misc-usability) (drkp:setup-ispell) (drkp:disable-ange-ftp) (drkp:setup-tramp) (drkp:setup-magit) (drkp:setup-global-bindings) (drkp:setup-server) (drkp:setup-kill-dwim) (drkp:setup-ns))