diff options
author | Thomas Voss <mail@thomasvoss.com> | 2024-09-08 12:06:50 +0200 |
---|---|---|
committer | Thomas Voss <mail@thomasvoss.com> | 2024-09-08 12:07:32 +0200 |
commit | 98cbdb2271e93cf7b45bf5a89106f4353ca9bdfc (patch) | |
tree | 339b83b31351e515cedcd834c29b0ba8d5e2d049 /.config/emacs/init.el | |
parent | a793342e47d2e282fe3ae4f554f7c6cefadfcffe (diff) |
emacs: Add the new configuration
Diffstat (limited to '.config/emacs/init.el')
-rw-r--r-- | .config/emacs/init.el | 691 |
1 files changed, 691 insertions, 0 deletions
diff --git a/.config/emacs/init.el b/.config/emacs/init.el new file mode 100644 index 0000000..42c100c --- /dev/null +++ b/.config/emacs/init.el @@ -0,0 +1,691 @@ +;; -*- lexical-binding: t; -*- + +;;; Preamble +(setq user-full-name "Thomas Voss") +(setq user-mail-address "mail@thomasvoss.com") + +(when (< emacs-major-version 29) + (error "Emacs 29 or newer is required")) + +(when (featurep 'native-compile) + (setq + native-comp-async-report-warnings-errors nil + native-comp-verbose 0 + native-comp-debug 0 + native-comp-jit-compilation t)) + +(require 'package) +(custom-set-variables + '(package-archives '(("gnu" . "http://elpa.gnu.org/packages/") + ("nongnu" . "https://elpa.nongnu.org/nongnu/") + ("melpa" . "http://melpa.org/packages/"))) + '(package-user-dir (expand-file-name "pkg" x-data-directory))) +(package-initialize) + +(unless (package-installed-p 'use-package) + (package-refresh-contents) + (package-install 'use-package)) + +(eval-and-compile + (custom-set-variables + '(use-package-always-defer t) + '(use-package-always-ensure t) + '(use-package-expand-minimally t))) + +(eval-when-compile + (require 'use-package)) + +;;; Convenience Macros and -Functions +(defmacro λ (&rest body) + "Convenience macro to create lambda functions that take no arguments +with much shorter and concise syntax. Calling ‘λ’ with BODY is +equivalent to calling ‘lambda’ with an empty argument list and BODY." + (declare (pure t) (side-effect-free t)) + `(lambda () ,@body)) + +(defmacro λi (&rest body) + "Convenience macro to create interactive lambda functions that take +no arguments with much shorter and concise syntax. Calling ‘λi’ with +BODY is equivalent to calling ‘lambda’ with an empty argument list and +BODY directly after ‘(interactive)’." + (declare (pure t) (side-effect-free t)) + `(lambda () (interactive) ,@body)) + +(defun x-mode-to-hook (mode) + "Get the hook corresponding to MODE." + (declare (pure t) (side-effect-free t)) + (intern (concat (symbol-name mode) "-hook"))) + +(defun x-mode-to-ts-mode (mode) + "Get the tree-sitter mode corresponding to MODE." + (declare (pure t) (side-effect-free t)) + (intern (concat + (string-remove-suffix "-mode" (symbol-name mode)) + "-ts-mode"))) + +(defun x-do-and-center (function &rest arguments) + "Call FUNCTION with ARGUMENTS and then center the screen." + (apply function arguments) + (recenter)) + +;;; Rational Defaults +(prefer-coding-system 'utf-8) +(customize-set-variable + 'save-interprogram-paste-before-kill t) + +(savehist-mode) +(global-hl-line-mode) + +(fset #'yes-or-no-p #'y-or-n-p) +(dolist (mode #'(blink-cursor-mode + menu-bar-mode + scroll-bar-mode + show-paren-mode + tool-bar-mode + tooltip-mode)) + (apply mode '(-1))) + +(custom-set-variables + '(large-file-warning-threshold nil) + '(vc-follow-symlinks t) + '(ad-redefinition-action 'accept)) + +(custom-set-variables + '(mouse-wheel-scroll-amount '(1 ((shift) . 1))) + '(mouse-wheel-progressive-speed nil) + '(mouse-wheel-follow-mouse t) + '(scroll-step 1)) +(pixel-scroll-precision-mode) + +(customize-set-value 'show-paren-delay 0) +(dolist (hook '(conf-mode-hook prog-mode-hook)) + (add-hook hook #'show-paren-local-mode)) + +(customize-set-value + 'read-extended-command-predicate + #'command-completion-default-include-p) + +(setq-default display-line-numbers 'relative) +(line-number-mode) +(column-number-mode) + +;; Backup settings +(custom-set-variables + '(delete-old-versions t) + '(version-control t) + '(kept-new-versions 2) + '(kept-old-versions 2)) + +(setq-default fill-column 73) +(add-hook 'text-mode-hook #'auto-fill-mode) + +(require 'autorevert) +(customize-set-variable + 'global-auto-revert-non-file-buffers t) +(global-auto-revert-mode) + +(customize-set-variable + 'custom-file (expand-file-name + (format "emacs-custom-%s.el" (user-uid)) + temporary-file-directory)) +(load custom-file 'noerror) + +(define-key global-map [remap backward-delete-char-untabify] + #'backward-delete-char) + +;;; Documentation Improvements +(use-package helpful + :bind + (("C-c h f" . #'helpful-callable) + ("C-c h v" . #'helpful-variable) + ("C-c h k" . #'helpful-key) + ("C-c h o" . #'helpful-symbol) + ("C-c h p" . #'helpful-at-point))) + +;;; Vim Emulation +(use-package evil + :bind + (:map evil-normal-state-map + ("g a" . #'x-evil-align-regexp) + ("g c" . #'x-evil-comment-or-uncomment-region) + ("g s" . #'x-evil-sort-lines) + ("C-h" . #'evil-window-left) + ("C-j" . #'evil-window-down) + ("C-k" . #'evil-window-up) + ("C-l" . #'evil-window-right)) + :init + (setq ;; All of the following must be set before loading ‘evil-mode’ + evil-want-Y-yank-to-eol t + evil-v$-excludes-newline t + evil-respect-visual-line-mode t + evil-split-window-below t + evil-vsplit-window-right t + evil-want-fine-undo t + evil-undo-system #'undo-redo + evil-flash-delay 1 + evil-want-keybinding nil) + (evil-mode) + (global-visual-line-mode) + :config + (evil-set-leader nil (kbd "SPC")) + (evil-set-leader nil (kbd ",") 'localleader) + (dolist (mode '(normal visual)) + (evil-global-set-key mode (kbd "C-u") (λi (x-do-and-center #'evil-scroll-up 0))) + (evil-global-set-key mode (kbd "C-d") (λi (x-do-and-center #'evil-scroll-down 0))) + (evil-global-set-key mode (kbd "C-v") #'evil-visual-line) + (evil-global-set-key mode (kbd "M-v") #'evil-visual-block)) + + (evil-define-operator x-evil-align-regexp (start end regexp repeat) + "Evil operator for ‘align-regexp’." + :move-point nil + :restore-point t + (interactive (let ((range (evil-operator-range))) + (list (car range) + (cadr range) + (concat "\\(\\s-*\\)" + (read-string "Align regexp: ")) + (y-or-n-p "Repeat? ")))) + (align-regexp start end regexp 1 1 repeat)) + + (evil-define-operator x-evil-comment-or-uncomment-region (start end) + "Evil operator for ‘comment-or-uncomment-region’." + :move-point nil + :restore-point t + (comment-or-uncomment-region start end)) + + (evil-define-operator x-evil-sort-lines (start end) + "Evil operator for ‘sort-lines’." + :move-point nil + :restore-point t + (sort-lines nil start end))) + +(defun x-evil-surround-mode-if-evil-mode () + (global-evil-surround-mode (unless (evil-mode) -1))) + +(use-package evil-surround + :after evil + :hook (evil-mode . x-evil-surround-mode-if-evil-mode) + :init + (x-evil-surround-mode-if-evil-mode) + ;; (global-evil-surround-mode) + :config + (defmacro x-evil-define-and-bind-quoted-text-object (name key start-regexp end-regexp) + (let ((inner-name (make-symbol (concat "evil-inner-" name))) + (outer-name (make-symbol (concat "evil-a-" name)))) + `(progn + (evil-define-text-object ,inner-name (count &optional beg end type) + (evil-select-paren ,start-regexp ,end-regexp beg end type count nil)) + (evil-define-text-object ,outer-name (count &optional beg end type) + (evil-select-paren ,start-regexp ,end-regexp beg end type count 'inclusive)) + (define-key evil-inner-text-objects-map ,key #',inner-name) + (define-key evil-outer-text-objects-map ,key #',outer-name)))) + + (x-evil-define-and-bind-quoted-text-object "single-quote-open" "‘" "‘" "’") + (x-evil-define-and-bind-quoted-text-object "single-quote-close" "’" "‘" "’") + (x-evil-define-and-bind-quoted-text-object "double-quote-open" "“" "“" "”") + (x-evil-define-and-bind-quoted-text-object "double-quote-open" "“" "“" "”") + + (defun x-evil-surround-function () + "Read a function name from the minibuffer and index the list with +the selection. This is nearly identical to ‘evil-surround-function’ +except it provides a useful prompt, and is language-aware." + (let ((list-name (or (evil-surround-read-from-minibuffer "Function name: ") + ""))) + (if (derived-mode-p 'lisp-mode 'common-lisp-mode 'emacs-lisp-mode) + (cons (format "(%s " list-name) ")") + (cons (format "%s(" (or list-name "")) ")")))) + + (defun x-evil-surround-list () + "Read a list name from the minibuffer and index the list with the +selection." + (let ((list-name (evil-surround-read-from-minibuffer "List name: "))) + (cons (format "%s[" (or list-name "")) "]"))) + + (setq-default + evil-surround-pairs-alist + (append + '((?‘ . ("‘ " . " ’")) + (?’ . ("‘" . "’")) + (?“ . ("“ " . " ”")) + (?“ . ("“" . "”")) + (?f . x-evil-surround-function) + (?l . x-evil-surround-list)) + evil-surround-pairs-alist))) + +(use-package evil-collection + :after evil + :init + (evil-collection-init)) + +;;; Force Spaces For Alighment +(defun x-align-with-spaces (function &rest arguments) + "Advice to force a given function to align using spaces instead of +tabs, regardless of the value of ‘indent-tabs-mode’." + (let (indent-tabs-mode) + (apply function arguments))) + +(dolist (f #'(align-regexp c-backslash-region)) + (advice-add f :around #'x-align-with-spaces)) + +;;; Minibuffer Improvements +(use-package vertico + :bind + (:map vertico-map + ("C-j" . vertico-next) + ("C-k" . vertico-previous) + ("C-l" . vertico-insert) + :map minibuffer-local-map + ("C-h" . x-minibuffer-backward-kill)) + :custom + (vertico-cycle t) + :init + (vertico-mode) + :config + (defun x-minibuffer-backward-kill (arg) + "When minibuffer completing a filename, delete up to the parent +folder, otherwise delete a word." + (interactive "p") + (if minibuffer-completing-file-name + (if (string-match-p "/." (minibuffer-contents)) + (zap-up-to-char (- arg) ?/) + (delete-minibuffer-contents)) + (backward-kill-word arg)))) + +(use-package marginalia + :after vertico + :init + (marginalia-mode)) + +(use-package orderless + :custom + (completion-styles '(orderless basic)) + (orderless-matching-styles '(orderless-prefixes)) + (completion-category-overrides '((file (styles basic partial-completion))))) + +;;; Completions +;; Disable corfu for now (it’s causing Emacs to crash) +;; (use-package corfu +;; :hook ((prog-mode . corfu-mode)) +;; :custom +;; (corfu-auto t) +;; (corfu-cycle t) +;; (corfu-auto-prefix 1) +;; (corfu-auto-delay 0)) +(defun x-company-require-prefix (candidates) + "Transformer for ‘company-mode’ that requires that all candidates begin with +‘company-prefix’." + (seq-filter (lambda (s) (string-prefix-p company-prefix s)) candidates)) + +(defun x-company-select-candidate (pred) + "Select either the next or previous candidate in the candidate list based on +the comparison of the ‘company-pseudo-tooltip-overlay’ height and 0 using PRED." + (let ((ov company-pseudo-tooltip-overlay)) + (if (and ov (apply pred (list (overlay-get ov 'company-height) 0))) + (company-select-previous) + (company-select-next)))) + +(defun x-company-next-candidate () + "Select the next available candidate, taking into account if the candidate +list is flipped or not." + (interactive) + (x-company-select-candidate #'<)) + +(defun x-company-previous-candidate () + "Select the previous available candidate, taking into account if the candidate +list is flipped or not." + (interactive) + (x-company-select-candidate #'>)) + +(use-package company + :bind (:map company-active-map + ("C-j" . #'x-company-next-candidate) + ("C-k" . #'x-company-previous-candidate)) + :hook ((conf-mode prog-mode) . company-mode) + :custom + (company-minimum-prefix-length 1) + (company-idle-delay (lambda () (unless (company-in-string-or-comment) 0))) + (company-selection-wrap-around t) + (company-tooltip-align-annotations t) + (company-tooltip-flip-when-above t) + (company-frontends '(company-pseudo-tooltip-unless-just-one-frontend + company-preview-frontend + company-echo-metadata-frontend)) + (company-transformers '(x-company-require-prefix + company-sort-by-backend-importance))) + +;;; Increment- and Decrement Numbers +(defun x-increment-number-at-point (&optional arg) + "Increment the number at point by ARG or 1 if ARG is nil. If called +interactively, the universal argument can be used to specify ARG. If +the number at point has leading zeros then the width of the number is +preserved." + (interactive "*p") + (save-excursion + (save-match-data + (skip-chars-backward "0123456789") + (when (eq (char-before (point)) ?-) + (goto-char (1- (point)))) + (when (re-search-forward "-?\\([0-9]+\\)" nil 'noerror) + (let ((answer (+ (string-to-number (match-string 0) 10) + (or arg 1))) + (width (length (match-string 1)))) + (replace-match + (format + (concat "%0" (int-to-string (if (< answer 0) (1+ width) width)) "d") + answer))))))) + +(defun x-decrement-number-at-point (&optional arg) + "The same as ‘x-increment-number-at-point’, but ARG is negated." + (interactive "*p") + (x-increment-number-at-point (- (or arg 1)))) + +(keymap-global-set "C-c a" #'x-increment-number-at-point) +(keymap-global-set "C-c x" #'x-decrement-number-at-point) + +;;; Indentation Settings +(setq-default + tab-width 4 + indent-tabs-mode t) +(customize-set-variable 'evil-shift-width + (default-value 'tab-width)) + +(defvar x-indentation-settings + '((c-mode :extra-vars (c-basic-offset)) + (css-mode :extra-vars (css-indent-offset)) + (emacs-lisp-mode :spaces t) + (graphviz-dot-mode :extra-vars (graphviz-dot-indent-width)) + (lisp-mode :spaces t) + (org-mode :spaces t) + (python-mode :extra-vars (python-indent-offset)) + (sgml-mode :width 2 :extra-vars (sgml-basic-offset)) + (sh-mode :extra-vars (sh-basic-offset))) + "A list of per-mode indentation settings. Each list contains a +major-mode and the 3 optional keyword arguments of :spaces, :width, and +:extra-vars. When setting the settings for a given major-mode, the +settings will also be applied for that mode’s tree-sitter variant. + +If :spaces is non-nil, then indentation will be performed with spaces +instead of tabs characters. + +If :width is non-nil, then it will override the modes given tab-width. + +If :extra-vars is non-nill, then it shall be a list of additional +mode-specific variables that need to be assigned the desired +indentation-width.") + +(defun x-set-indentation-settings () + "Apply the indentation settings specified by ‘x-indentation-settings’." + (interactive) + (dolist (plist x-indentation-settings) + (let* ((mode (car plist)) + (args (cdr plist)) + (width (or (plist-get args :width) + (default-value 'tab-width))) + (spaces (or (plist-get args :spaces) + (not (default-value 'indent-tabs-mode)))) + (extra (plist-get args :extra-vars)) + (callback + (λ (indent-tabs-mode (when spaces -1)) + (setq-local tab-width width + evil-shift-width width) + (dolist (var extra) + (set var width)))) + (mode-hook (intern (concat (symbol-name mode) "-hook"))) + (mode-ts (intern (concat + (string-remove-suffix + "-mode" (symbol-name mode)) + "-ts-mode")))) + (dolist (hook (list (x-mode-to-hook mode) + (x-mode-to-hook (x-mode-to-ts-mode mode)))) + (add-hook hook callback 95))))) + +(x-set-indentation-settings) + +;;; Git Integration +(use-package magit + :bind ("C-c g" . magit-status) + :custom + (magit-display-buffer-function + #'magit-display-buffer-same-window-except-diff-v1)) + +(use-package magit-todos + :after magit + :init (magit-todos-mode)) + +(defun x-magit-status () + (interactive) + (thread-last + (project-current t) + (project-root) + (magit-status))) + +(require 'project) +(add-to-list 'project-switch-commands '(x-magit-status "Git status" ?g)) + +;;; Tree-Sitter +(when (treesit-available-p) + (setq treesit-language-source-alist + '((cpp "https://github.com/tree-sitter/tree-sitter-cpp") + (elisp "https://github.com/Wilfred/tree-sitter-elisp") + (gomod "https://github.com/camdencheek/tree-sitter-go-mod") + (html "https://github.com/tree-sitter/tree-sitter-html"))) + + (defun x-treesit-source-sync () + "Install all the tree-sitter grammars in +‘treesit-language-source-alist’. This function does not assert whether +or not the grammar is already installed, making it useful for updating +existing grammars." + (interactive) + (dolist (spec treesit-language-source-alist) + (treesit-install-language-grammar (car spec)))) + + (thread-last + (mapcar #'car treesit-language-source-alist) + (seq-remove #'treesit-language-available-p) + (mapc #'treesit-install-language-grammar))) + +;;; Language Server Protocol +(use-package eglot + :hook ((c-mode . eglot-ensure) + (c++-mode . eglot-ensure) + (c-ts-mode . eglot-ensure) + (c++-ts-mode . eglot-ensure)) + :bind + (:map eglot-mode-map + ("C-c l a" . #'eglot-code-actions) + ("C-c l r" . #'eglot-rename)) + :init + (fset #'jsonrpc--log-event #'ignore) + :custom + (eglot-events-buffer 0) + (eglot-extend-to-xref t) + :config + (setq eglot-managed-mode-hook + (list (λ (eglot-inlay-hints-mode -1)))) + (dolist (feature '(eldoc flymake)) + (add-to-list 'eglot-stay-out-of feature)) + (with-eval-after-load 'eglot + (add-to-list 'eglot-server-programs + '((c-mode c-ts-mode c++-mode c++-ts-mode) + . ("clangd" "--header-insertion=never"))))) + +(use-package eglot-booster + :after eglot + :config (eglot-booster-mode)) + +;;; Automatically Create Directories +(defun x-auto-create-directories (original-function filename &rest arguments) + "Automatically create and delete parent directories of files. This +is an ‘:override’ advice for ‘find-file’ and friends. It +automatically creates the parent directory (or directories) of the +file being visited, if necessary. It also sets a buffer-local +variable so that the user will be prompted to delete the newly created +directories if they kill the buffer without saving it." + (let (dirs-to-delete) + (let* ((dir-to-create (file-name-directory filename)) + (current-dir dir-to-create)) + ;; We want to go up each directory component and add them to + ;; ‘dirs-to-delete’ individually. + (while (not (file-exists-p current-dir)) + (push current-dir dirs-to-delete) + (setq current-dir (file-name-directory + (directory-file-name current-dir)))) + + (unless (file-exists-p dir-to-create) + (make-directory dir-to-create 'parents))) + + ;; Use ‘prog1’ so that we maintain the original return value + (prog1 (apply original-function filename arguments) + (when dirs-to-delete + (setq-local x-dirs-to-delete (reverse dirs-to-delete)) + + ;; When we kill the buffer we want to ask if we should delete parent + ;; directories *unless* the buffer was saved, in which case we don’t + ;; want to do anything. + (add-hook 'kill-buffer-hook #'x-delete-directories-if-appropriate + 'depth 'local) + (add-hook 'after-save-hook #'x-remove-auto-directory-hooks + 'depth 'local))))) + +(dolist (command #'(find-file + find-alternate-file + write-file)) + (advice-add command :around #'x-auto-create-directories)) + +(defun x-delete-directories-if-appropriate () + "Delete parent directories if appropriate. This is a function for +‘kill-buffer-hook’. If ‘x-auto-create-directories’ created the +directory containing the file for the current buffer automatically, +then offer to delete it. Otherwise, do nothing. Also clean up +related hooks." + (unless (file-exists-p buffer-file-name) + (dolist (dir-to-delete x-dirs-to-delete) + (when (and (stringp dir-to-delete) + (file-exists-p dir-to-delete) + (y-or-n-p (format "Also delete directory ‘%s’?" + (directory-file-name dir-to-delete)))) + (delete-directory dir-to-delete))))) + +(defun x-remove-auto-directory-hooks () + "Clean up directory-deletion hooks, if necessary." + (remove-hook 'kill-buffer-hook #'x-delete-directories-if-appropriate 'local) + (remove-hook 'after-save-hook #'x-remove-auto-directory-hooks 'local)) + +;;; Colorize Compilation Buffer +(require 'ansi-color) +(defun x-colorize-buffer () + "Parse ANSI escape sequences in the current buffer." + (let ((inhibit-read-only t)) + (ansi-color-apply-on-region (point-min) (point-max)))) +(add-hook 'compilation-filter-hook #'x-colorize-buffer) + +;;; User Interface Themeing +(load-theme 'mango 'no-confirm) +(set-fringe-style (cons 32 32)) + +(defvar x-alpha-background 90 + "The opacity of a graphical Emacs frame, ranging from 0–100. A value +of 0 is fully transparent while a value of 100 is fully opaque.") + +(defun x-set-alpha-background (value) + "Set the current frames’ background opacity to VALUE." + (interactive "NOpacity [0–100]: ") + (set-frame-parameter nil 'alpha-background value)) +(add-to-list + 'default-frame-alist (cons 'alpha-background x-alpha-background)) + +(defvar x-monospace-font '("Iosevka Smooth" :weight regular :height 160) + "The default monospace font to use. This is a list containing a +font name, font weight, and font height in that order.") + +(defvar x-proportional-font '("Ysabeau" :weight light :height 180) + "The default proportional font to use. This is a list containing a +font name, font weight, and font height in that order.") + +(let* ((mono-family (car x-monospace-font)) + (mono-props (cdr x-monospace-font)) + (prop-family (car x-proportional-font)) + (prop-props (cdr x-proportional-font)) + (mono-weight (plist-get mono-props :weight)) + (mono-height (plist-get mono-props :height)) + (prop-weight (plist-get prop-props :weight)) + (prop-height (plist-get prop-props :height))) + (set-face-attribute 'default nil + :font mono-family + :weight mono-weight + :height mono-height) + (set-face-attribute 'fixed-pitch nil + :font mono-family + :weight mono-weight + :height mono-height) + (set-face-attribute 'variable-pitch nil + :font prop-family + :weight prop-weight + :height prop-height)) + +;;; C-Style +(defun x-c-defun-open-safe (_syntax _position) + (if (c-cpp-define-name) + '(after) + '(before after))) + +(defun x-c (_syn _pos) + (message "OMG") + '(before)) + +(c-add-style + "mango" + '((indent-tabs-mode . t) + (c-backslash-column . 80) + (c-backslash-max-column . 80) + (c-basic-offset . 4) + (c-block-comment-prefix . "") + (c-comment-only-line-offset . 0) + (c-label-minimum-indentation . 0) + (c-cleanup-list . (brace-else-brace + brace-elseif-brace + brace-catch-brace + comment-close-slash + scope-operator)) + (c-offsets-alist . ((label . [0]) + (arglist-intro . +) + (arglist-cont . 0) + (arglist-cont-nonempty . +) + (arglist-close . 0))) + (c-hanging-braces-alist . ((defun-open . x-c-defun-open-safe) + (defun-close before) + (class-open after) + (class-close before) + (block-open after) + (block-close . c-snug-do-while) + (brace-list-open after) + (brace-list-close before) + (extern-lang-open after) + (extern-lang-close before) + (substatement-open after) + (statement-case-open after))) + (c-hanging-colons-alist . ((case-label after) + (label after))))) +(customize-set-variable 'c-default-style "mango") + +;;; Emacs Calculator +(setq calc-display-trail nil) + +;;; Emacs Tetris +(defun x-tetris-rotate-mirror () + (interactive) + (tetris-rotate-next) + (tetris-rotate-next)) + +(use-package tetris + :hook (tetris-mode . (lambda () (evil-local-mode -1))) + :bind + (:map tetris-mode-map + ("a" . #'tetris-move-left) + ("d" . #'tetris-move-right) + ("k" . #'tetris-rotate-next) + (";" . #'tetris-rotate-prev) + ("l" . #'tetris-move-down) + ("o" . #'x-tetris-rotate-mirror) + ("SPC" . #'tetris-move-bottom))) +(put 'narrow-to-page 'disabled nil) |