;;; racket-smart-open.el -*- lexical-binding: t; -*- ;; Copyright (c) 2013-2019 by Greg Hendershott. ;; Portions Copyright (C) 1985-1986, 1999-2013 Free Software Foundation, Inc. ;; Author: Greg Hendershott ;; URL: https://github.com/greghendershott/racket-mode ;; License: ;; This 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, or (at your option) ;; any later version. This 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. See ;; http://www.gnu.org/licenses/ for details. ;;; racket-smart-open-bracket-mode (require 'racket-custom) (require 'racket-parens) (require 'racket-ppss) (require 'racket-util) ;;;###autoload (define-minor-mode racket-smart-open-bracket-mode "Minor mode to let you always type `[`' to insert `(` or `[` automatically. Behaves like the \"Automatically adjust opening square brackets\" feature in Dr. Racket. By default, inserts a `(`. Inserts a `[` in the following cases: - `let`-like bindings -- forms with `let` in the name as well as things like `parameterize`, `with-handlers`, and `with-syntax`. - `case`, `cond`, `match`, `syntax-case`, `syntax-parse`, and `syntax-rules` clauses. - `for`-like bindings and `for/fold` accumulators. - `class` declaration syntax, such as `init` and `inherit`. When the previous s-expression in a sequence is a compound expression, uses the same kind of delimiter. To force insert `[`, use `quoted-insert'. Combined with `racket-insert-closing' this means that you can press the unshifted `[` and `]` keys to get whatever delimiters follow the Racket conventions for these forms. When something like `electric-pair-mode' or `paredit-mode' is active, you need not even press `]`. Tip: When also using `paredit-mode', enable that first so that the binding for the `[`' key in the map for `racket-smart-open-bracket-mode' has higher priority. See also the variable `minor-mode-map-alist'." :lighter " RacketSmartOpen" :keymap (racket--easy-keymap-define '(("[" racket-smart-open-bracket))) (unless (memq major-mode '(racket-mode racket-repl-mode)) (setq racket-smart-open-bracket-mode nil) (user-error "racket-smart-open-bracket-mode only works with with Racket Mode buffers"))) (defconst racket--smart-open-bracket-data (eval-when-compile `(;; cond-like (0 0 ,(rx (seq "(" (or "augment" "augment-final" "augride" "cond" "field" "inherit" "inherit-field" "inherit/super" "inherit/inner" "init" "init-field" "match-lambda" "match-lambda*" "match-lambda**" "overment" "override" "override-final" "public" "pubment" "public-final" "rename-inner" "rename-super" "super-new") (or space line-end)))) ;; case-like (2 0 ,(rx (seq "(" (or "case" "new" "match" "match*" "syntax-parse" "syntax-rules") (or space line-end)))) ;; syntax-case (3 0 ,(rx (seq "(" (or "syntax-case") (or space line-end)))) ;; syntax-case* (4 0 ,(rx (seq "(" (or "syntax-case*") (or space line-end)))) ;; let-like ;; ;; In addition to the obvious suspects with 'let' in the name, ;; handles forms like 'parameterize', 'with-handlers', 'for', ;; and 'for/fold' accumulator bindings. (0 1 ,(rx (seq (or "for" "for/list" "for/vector" "for/hash" "for/hasheq" "for/hasheqv" "for/and" "for/or" "for/lists" "for/first" "for/last" "for/fold" "for/flvector" "for/extflvector" "for/set" "for/sum" "for/product" "for*" "for*/list" "for*/vector" "for*/hash" "for*/hasheq" "for*/hasheqv" "for*/and" "for*/or" "for*/lists" "for*/first" "for*/last" "for*/fold" "for*/flvector" "for*/extflvector" "for*/set" "for*/sum" "for*/product" "fluid-let" "let" "let*" "let*-values" "let-struct" "let-syntax" "let-syntaxes" "let-values" "let/cc" "let/ec" "letrec" "letrec-syntax" "letrec-syntaxes" "letrec-syntaxes+values" "letrec-values" "match-let" "match-let*" "match-let-values" "match-let*-values" "match-letrec" "parameterize" "parameterize*" "with-handlers" "with-handlers*" "with-syntax" "with-syntax*") (or space line-end)))) ;; for/fold bindings ;; ;; Note: Previous item handles the first, accumulators subform. (0 2 ,(rx (seq (or "for/fold" "for*/fold") (or space line-end)))) ;; named-let bindings ;; (0 2 ,(rx (seq "let" (1+ whitespace) (1+ (not (in "()[]{}\",'`;#|\" ")))))))) "A list of lists. Each sub list is arguments to supply to `racket--smart-open-bracket-helper'.") (defun racket--smart-open-bracket-helper (pre-backward-sexps post-backward-sexps regexp) "Is point at a subform of a known form REGEXP that should open with '['. Returns '[' or nil." (and (save-excursion (ignore-errors (backward-sexp pre-backward-sexps) t)) (save-excursion (ignore-errors (backward-up-list) (backward-sexp post-backward-sexps) (when (looking-at-p regexp) ?\[))))) (defun racket-smart-open-bracket (&optional prefix) "Automatically insert a `(` or a `[` as appropriate. See `racket-smart-open-bracket-mode'." (interactive "P") (let ((ch (or (and (save-excursion (let ((pt (point))) (beginning-of-defun) (let ((state (parse-partial-sexp (point) pt))) (or (racket--ppss-string-p state) (racket--ppss-comment-p state))))) ?\[) (cl-some (lambda (xs) (apply #'racket--smart-open-bracket-helper xs)) racket--smart-open-bracket-data) (racket--open-paren #'backward-sexp) ?\())) (if (fboundp 'racket--paredit-aware-open) (racket--paredit-aware-open prefix ch) (racket--self-insert ch)))) (put 'racket-smart-open-bracket 'delete-selection #'racket--electric-pair-mode-not-active) (eval-after-load 'paredit '(progn (declare-function paredit-open-round 'paredit) (declare-function paredit-open-square 'paredit) (declare-function paredit-open-curly 'paredit) (defun racket--paredit-aware-open (prefix ch) "A paredit-aware helper for `racket-smart-open-bracket'. When `paredit-mode' is active, use its functions, such as `paredit-open-round'. Note: This function isn't defined unless paredit is loaded, so check for this function's existence using `fboundp'." (let ((paredit-active (and (boundp 'paredit-mode) paredit-mode))) (cond ((not paredit-active) (racket--self-insert ch)) ((eq ch ?\() (paredit-open-round prefix)) ((eq ch ?\[) (paredit-open-square prefix)) ((eq ch ?\{) (paredit-open-curly prefix)) (t (racket--self-insert ch))))))) (provide 'racket-smart-open) ;; racket-smart-open.el ends her