Files
autoscratch/autoscratch.el

325 lines
11 KiB
EmacsLisp
Raw Normal View History

2023-06-01 19:23:53 +02:00
;;; autoscratch.el --- Automatically switch scratch buffer mode -*- lexical-binding: t; -*-
2017-07-18 19:38:25 +02:00
;; Copyright (C) 2017-2023, T.v.Dein <tlinden@cpan.org>
2017-07-18 19:38:25 +02:00
;; This file is NOT part of Emacs.
;; 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 2 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, write to the Free Software
;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
;; USA
;; Version: 0.0.4
;; Package-Version: 20230505.800
2017-07-18 19:38:25 +02:00
;; Author: T.v.Dein <tlinden@cpan.org>
;; Keywords: convenience, buffer, scrach
2017-07-18 19:38:25 +02:00
;; URL: https://github.com/tlinden/autoscratch
;; License: GNU General Public License >= 2
;; Package-Requires: ((emacs "24.1"))
;; SPDX-License-Identifier: GPL-3.0
2017-07-18 19:38:25 +02:00
;;; Commentary:
;;;; Introduction:
;; This simple major mode can be used as the initial major mode for
2023-06-01 19:44:39 +02:00
;; the scratch buffer. It automatically switches to another major mode
2017-07-18 19:38:25 +02:00
;; based on regexes triggered by text input.
;;
;; In the default configuration it responds to the first
;; non-whitespace character entered into the scratch buffer and
;; switches major mode based on this. See the variable
;; `autoscratch-triggers-alist' for the defaults. For example, if you
;; enter a paren, it will switch to `emacs-lisp-mode'.
;;;; Configuration:
;;
;; The minimum configuration looks like this:
;;
2023-06-01 19:34:34 +02:00
;; (require 'autoscratch)
2017-07-18 19:38:25 +02:00
;; (setq initial-major-mode 'autoscratch-mode)
;; (setq initial-scratch-message "")
;; (setq inhibit-startup-screen t)
;;
;; You may, however, configure the trigger list
;; `autoscratch-triggers-alist' according to your preferences. This
;; list consists of cons cells, where the `car' is a regexp and the
2023-06-01 19:44:39 +02:00
;; `cdr' an Emacs Lisp form (e.g. a lambda or defun). If you want to
2017-07-18 19:38:25 +02:00
;; use regexps which match more than one character, then you need to
;; set `autoscratch-trigger-on-first-char' to `nil' and possibly tune
;; `autoscratch-trigger-after' accordingly.
;; If no regexp matches and/or `autoscratch-trigger-after' is
;; exceeded, then the `autoscratch-default-trigger' form will be
;; executed, which by default is `fundamental-mode', but you can of
;; course change this.
;; Autoscratch can also be configured to rename the current buffer
;; after it switched mode based on a trigger and create a new
2023-06-01 19:44:39 +02:00
;; `autoscratch-buffer' in the background. In order to enable this
2017-07-18 19:38:25 +02:00
;; feature, set `autoscratch-fork-after-trigger' to t.
;; To further tune the trigger bahavior you can tune
;; `autoscratch-trigger-hook' which will be called after executing the
;; form of the matching trigger.
;;; Code:
;;;; Customizables
(defgroup autoscratch nil
"Autoscratch Mode."
:prefix "autoscratch-"
:group 'emacs)
(defcustom autoscratch-triggers-alist
'(("[(;]" . (emacs-lisp-mode))
("#" . (autoscratch-select
'(("perl" . (cperl-mode))
("ruby" . (ruby-mode))
("python" . (python-mode))
("conf" . (conf-unix-mode))
("shell" . (shell-script-mode)))))
("[-a-zA-Z0-9]" . (text-mode))
("/" . (c-mode))
("*" . (progn (insert " ") (org-mode)))
("." . (fundamental-mode)))
"Default trigger alist for autoscratch mode.
This list triggers after the first character entered.
You can customize this variable directly or use `add-to-list'
to add more triggers.
In case there are more possibilities for a character, you can use
`autoscratch-select' to make autoscratch ask interactively for a
major mode to switch to, which see. A good example for such a
character is # which is the comment-char in many modes."
2017-07-18 19:38:25 +02:00
:group 'autoscratch
:type 'list)
(defcustom autoscratch-trigger-on-first-char t
"Trigger after the first character has been entered, no matter what."
:group 'autoscratch
:type 'boolean)
(defcustom autoscratch-default-trigger '(fundamental-mode)
"Default form to execute when nothing else matches."
:group 'autoscratch
:type 'code)
(defcustom autoscratch-fork-after-trigger t
2023-06-01 19:10:47 +02:00
"Create a new autoscratch buffer after the trigger fired."
:group 'autoscratch
:type :boolean)
2017-07-18 19:38:25 +02:00
(defcustom autoscratch-trigger-after 5
2023-06-01 19:10:47 +02:00
"Max chars to be entered to force trigger the default form."
:group 'autoscratch
:type :variable)
2017-07-18 19:38:25 +02:00
(defcustom autoscratch-reset-default-directory nil
"Reset default directory when a new scratch buffer is created.
When autoscratch creates a new scratch buffer, it can reset the
default directory to `~/' (change to home directory). By default
it doesn't do this."
2023-06-01 19:10:47 +02:00
:group 'autoscratch
:type :boolean)
2017-07-18 19:38:25 +02:00
;;;; Public Vars
(defvar autoscratch-trigger-hook ()
"Hooks called after executing a matching trigger form.")
2017-07-18 19:38:25 +02:00
(defvar autoscratch-rename-hook ()
"Hooks called after renaming the current buffer.")
;;;; Public Functions
(defun autoscratch-buffer-rename ()
"Rename current autoscratch buffer.
New name is '*autoscratch-<new-major-mode><N>*
Executes `autoscratch-rename-hook' afterwards."
(interactive)
(rename-buffer
(generate-new-buffer-name
(format "*%s-scratch*"
(replace-regexp-in-string
"-mode" ""
(format "%s" major-mode))))
(run-hooks 'autoscratch-rename-hook)))
2017-07-18 19:38:25 +02:00
(defun autoscratch-buffer ()
"Create and switch to a new autoscratch buffer."
(interactive)
(let ((buf (get-buffer-create "*scratch*")))
(switch-to-buffer buf)
2023-06-01 19:10:47 +02:00
(autoscratch-mode)))
2017-07-18 19:38:25 +02:00
(defun autoscratch-select(modelist)
"Interactively ask for major mode to switch to.
Argument MODELIST must be an alist where the car of each
pair must be a name and the cdr some arbitrary Emacs Lisp
2017-07-18 19:38:25 +02:00
form.
Example for MODELIST:
'((\"perl\" . (cperl-mode))
(\"ruby\" . (ruby-mode))
(\"python\" . (python-mode))
(\"shell\" . (shell-script-mode))
This function shall only be called from `autoscratch-triggers-alist'."
(interactive)
(let ((keys ()))
(dolist (e modelist)
(push (car e) keys))
(eval (cdr (assoc (completing-read "switch mode to: " keys) modelist)))))
;;;; Internal Functions
(defun autoscratch--function-p (form)
"Check if FORM is a function."
(if (symbolp form)
(fboundp form)
(functionp form)))
(defun autoscratch--fork-and-rename-current ()
"Rename buffer and create new autoscratch.
If `autoscratch-fork-after-trigger' is t, create a
new autoscratch buffer and rename the current one
to $mode-scratch."
(interactive)
(let ((cur (current-buffer)))
(when (eq t autoscratch-fork-after-trigger)
(autoscratch-buffer-rename)
(autoscratch-buffer)
(switch-to-buffer cur))))
(defun autoscratch--eval-trigger (form)
"If FORM is a function execute interactively, otherwise evaluate it.
2017-07-25 13:31:54 +02:00
Executes `autoscratch-trigger-hook' after evaluation.
Supported values for FORM include:
'emacs-lisp-mode
'(lambda() (emacs-lisp-mode)
'(emacs-lisp-mmode)"
(if (autoscratch--function-p form)
(funcall form)
(eval form))
2017-07-18 19:38:25 +02:00
(run-hooks 'autoscratch-post-trigger-hook)
(message "autoscratch switched to %s" major-mode))
2017-07-18 19:38:25 +02:00
(defun autoscratch--look-for-triggers (forward)
"Check if one of the configured trigger chars has been entered.
If FORWARD matchs one of the trigger chars, evaluated the the
associated Lisp form."
2017-07-18 19:38:25 +02:00
(let ((matchform nil))
(when (or (catch 'done
(dolist (trigger autoscratch-triggers-alist)
(when (if forward
(looking-at (car trigger))
2023-06-01 19:10:47 +02:00
(looking-back (car trigger) 1))
2017-07-18 19:38:25 +02:00
(setq matchform (cdr trigger))
(throw 'done t))))
(eq t autoscratch-trigger-on-first-char))
(autoscratch--eval-trigger (or matchform autoscratch-default-trigger))
(autoscratch--fork-and-rename-current)
;; else: multichar allowed, continue until max
(when (> (point) autoscratch-trigger-after)
(autoscratch--eval-trigger autoscratch-default-trigger)))))
2023-06-12 09:19:37 +02:00
(defun autoscratch--self-insert-command (n)
"Look for autoscratch trigger, execute if found and call `self-insert-command'.
N is the char the user just entered into the (new) scratch buffer."
2017-07-18 19:38:25 +02:00
(interactive "p")
2023-06-12 09:19:37 +02:00
(self-insert-command n)
2017-07-18 19:38:25 +02:00
(autoscratch--look-for-triggers nil))
(defun autoscratch--yank (&optional arg)
"Look for autoscratch trigger, execute if found and call `yank'.
ARG is the content of the clipboard being yanked."
2017-07-18 19:38:25 +02:00
(interactive "*P")
(let ((start (point))
(end))
(yank arg)
(setq end (point))
(goto-char start)
(autoscratch--look-for-triggers t)
(goto-char end)))
;;;; Keymap
;;;###autoload
(defvar autoscratch-mode-map
(let ((map (make-sparse-keymap)))
(define-key map [remap self-insert-command] 'autoscratch--self-insert-command)
(define-key map [remap yank] 'autoscratch--yank)
map)
"Keymap used by autoscratch mode.")
2017-07-18 19:38:25 +02:00
;;;; Major Mode
;;;###autoload
(define-derived-mode autoscratch-mode fundamental-mode "autoscratch"
"Autoscratch major mode automatically switches major mode.
This simple major mode can be used as the initial major mode for
the scratch buffer. It automatically switches to another major
2017-07-18 19:38:25 +02:00
mode based on regexes triggered by text input.
In the default configuration it responds to the first
non-whitespace character entered into the scratch buffer and
switches major mode based on this. See the variable
`autoscratch-triggers-alist' for the defaults. For example, if
you enter a paren, it will switch to `emacs-lisp-mode'.
Configuration:
The minimum configuration looks like this:
(require 'autoscratch-mode)
(setq initial-major-mode 'autoscratch-mode)
You may, however, configure the trigger list
`autoscratch-triggers-alist' according to your preferences. This
list consists of cons cells, where the `car' is a regexp and the
`cdr' an Emacs Lisp form (e.g. a lambda or defun). If you want
2017-07-18 19:38:25 +02:00
to use regexps which match more than one character, then you need
to set `autoscratch-trigger-on-first-char' to true and possibly
tune `autoscratch-trigger-after' accordingly.
If no regexp matches and/or `autoscratch-trigger-after' is
exceeded, then the `autoscratch-default-trigger' form will be
executed, which by default is `fundamental-mode', but you can of
course change this.
Autoscratch can also be configured to rename the current buffer
after it switched mode based on a trigger and create a new
`autoscratch-buffer' in the background. In order to enable this
2017-07-18 19:38:25 +02:00
feature, set `autoscratch-fork-after-trigger' to t.
\\{autoscratch-mode-map}"
;; fixes github#1
(if autoscratch-reset-default-directory
(setq default-directory "~/")))
2017-07-18 19:38:25 +02:00
2023-06-01 19:23:53 +02:00
(provide 'autoscratch)
2017-07-18 19:38:25 +02:00
2023-06-01 19:23:53 +02:00
;;; autoscratch.el ends here