My personal emacs configuration
at main 253 lines 11 kB view raw
1;;; sql-upcase.el --- Upcase SQL keywords -*- lexical-binding: t -*- 2 3;; Author: Phil S. 4;; URL: https://www.emacswiki.org/emacs/SqlUpcase 5;; Keywords: abbrev, convenience, languages 6;; Created: 9 May 2016 7;; Package-Requires: ((emacs "24.3")) 8;; Version: 0.2 9 10;; This program is free software; you can redistribute it and/or modify 11;; it under the terms of the GNU General Public License as published by 12;; the Free Software Foundation, either version 3 of the License, or 13;; (at your option) any later version. 14 15;; This program is distributed in the hope that it will be useful, 16;; but WITHOUT ANY WARRANTY; without even the implied warranty of 17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18;; GNU General Public License for more details. 19 20;; You should have received a copy of the GNU General Public License 21;; along with this program. If not, see <http://www.gnu.org/licenses/>. 22 23 24;;; Commentary: 25;; 26;; `sql-upcase-mode' converts SQL keywords to upper-case as you type 27;; or otherwise insert text in the buffer -- for instance, killing and 28;; yanking an entire SQL query would upcase all keywords in that query. 29;; 30;; It utilises the product-specific regexps defined by sql.el, and thus 31;; will upcase only the keywords defined for the buffer's `sql-product'. 32;; (Note that `sql-mode' buffers default to the `ansi' product.) 33;; 34;; You can enable it via `sql-mode-hook' and/or `sql-interactive-mode-hook': 35;; 36;; (add-hook 'sql-mode-hook 'sql-upcase-mode) 37;; (add-hook 'sql-interactive-mode-hook 'sql-upcase-mode) 38;; 39;; Note that, by default, only lower-case keywords are processed. 40;; To handle mixed-case keywords as well, customize `sql-upcase-mixed-case'. 41 42 43;;; Change Log: 44;; 45;; 0.2 - Added `sql-upcase-region' and `sql-upcase-buffer' commands. 46;; - Rename from sql-upcase-mode.el to sql-upcase.el. 47;; - Match both boundaries of a keyword, to avoid false-positives. 48;; - Constrain 'in string' and 'in comment' tests to current query. 49;; - Fixed error with whitespace at beginning of buffer. 50;; 0.1 - Initial release to EmacsWiki. 51 52 53;;; Code: 54 55(require 'sql) 56 57(defcustom sql-upcase-mixed-case nil 58 "If nil, `sql-upcase-keywords' looks only for lower-case keywords, 59and mixed-case keywords are ignored. 60 61If non-nil, then mixed-case keywords will also be upcased." 62 :type '(choice (const :tag "Lower-case only" nil) 63 (const :tag "Both lower- and mixed-case" t)) 64 :group 'SQL) 65 66(defvar sql-upcase-boundary "[\t\n\r ();]" 67 "Regexp matching a character which can precede or follow a keyword. 68 69In addition we match \"^\" at the start of a keyword; and `sql-upcase-region' 70and `sql-upcase-buffer' both match \"\\'\" at the end of a keyword.") 71 72(defvar sql-upcase-inhibited nil 73 "Set non-nil to prevent `sql-upcase-keywords' from acting.") 74 75(defvar-local sql-upcase-comint-output nil) 76 77(defvar sql-upcase-regions) 78 79;;;###autoload 80(define-minor-mode sql-upcase-mode 81 "Automatically upcase SQL keywords as text is inserted in the buffer. 82 83Intended to be enabled via `sql-mode-hook' and/or `sql-interactive-mode-hook'. 84 85Note that this can be a little aggressive in `sql-interactive-mode'. Although 86output from the inferior process is ignored, all other text changes to the 87buffer are processed (e.g. cycling through the command history)." 88 :lighter " sql^" 89 (if sql-upcase-mode 90 (progn 91 (add-hook 'after-change-functions 'sql-upcase-keywords nil :local) 92 (when (derived-mode-p 'sql-interactive-mode) 93 (add-hook 'comint-preoutput-filter-functions 94 'sql-upcase-comint-preoutput nil :local))) 95 ;; Disable. 96 (remove-hook 'after-change-functions 'sql-upcase-keywords :local) 97 (when (derived-mode-p 'sql-interactive-mode) 98 (remove-hook 'comint-preoutput-filter-functions 99 'sql-upcase-comint-preoutput :local)))) 100 101;;;###autoload 102(defun sql-upcase-region (beginning end) 103 "Upcase SQL keywords within the marked region. 104 105Keywords overlapping BEGINNING will be upcased. 106Keywords overlapping END will not be upcased." 107 (interactive "*r") 108 (save-excursion 109 ;; Avoid upcasing a word preceding the region. 110 (goto-char beginning) 111 (and (not (eobp)) 112 (looking-at sql-upcase-boundary) 113 (setq beginning (1+ beginning))) 114 ;; Allow upcasing the final word in the region. 115 (goto-char end) 116 (and (not (eobp)) 117 (looking-at sql-upcase-boundary) 118 (setq end (1+ end)))) 119 ;; Make an exception if the last character of the buffer is the last 120 ;; character of a keyword. Normally we require a trailing boundary 121 ;; character matching `sql-upcase-boundary', but for this command we 122 ;; will also treat the end of the buffer as a boundary. 123 (let ((sql-upcase-boundary 124 (concat "\\(?:\\'\\|" sql-upcase-boundary "\\)"))) 125 ;; Call our `after-change-functions' handler. 126 (sql-upcase-keywords beginning end 0))) 127 128;;;###autoload 129(defun sql-upcase-buffer () 130 "Upcase all SQL keywords in the buffer." 131 (interactive) 132 (sql-upcase-region (point-min) (point-max))) 133 134(defun sql-upcase-comint-preoutput (output) 135 "Inhibit `sql-upcase-keywords' for comint process output. 136 137Called via `comint-preoutput-filter-functions'." 138 (setq sql-upcase-comint-output t) 139 output) 140 141(defun sql-upcase-keywords (beginning end old-len) 142 "Automatically upcase SQL keywords and builtin function names. 143 144If `sql-upcase-mixed-case' is non-nil, then only lower-case keywords 145will be processed, and mixed-case keywords will be ignored. 146 147Triggered by `after-change-functions' (see which regarding the 148function arguments), and utilising the product-specific font-lock 149keywords specified in `sql-product-alist'." 150 (when (eq old-len 0) ; The text change was an insertion. 151 (if sql-upcase-comint-output 152 ;; The current input is output from comint, so ignore it and 153 ;; just reset this flag. 154 (setq sql-upcase-comint-output nil) 155 ;; User-generated input. 156 (unless (or undo-in-progress 157 sql-upcase-inhibited) 158 (let ((sql-upcase-regions nil) 159 (case-fold-search sql-upcase-mixed-case)) 160 (save-excursion 161 ;; Any errors must be handled, otherwise we will be removed 162 ;; automatically from `after-change-functions'. 163 (with-demoted-errors "sql-upcase-keywords error: %S" 164 ;; Process all keywords affected by the inserted text. 165 ;; 166 ;; The "two steps forward one step back" approach of looking 167 ;; for the ENDs of keywords in the text and then testing the 168 ;; preceding words may seem odd, but one of the primary use-cases 169 ;; is as-you-type processing, in which case the text inserts we 170 ;; are looking at are typically single self-insert characters, 171 ;; and we are actually searching just that single character to 172 ;; see if it is a keyword-ending character, in order that we can 173 ;; upcase the previously-entered keyword before it. 174 (goto-char beginning) 175 (while (and (< (point) end) 176 (re-search-forward sql-upcase-boundary end :noerror)) 177 (save-excursion 178 (goto-char (match-beginning 0)) 179 (and (not (bobp)) 180 ;; ...if the preceding character is of word syntax... 181 (eq (char-syntax (char-before)) ?w) 182 ;; ...and we're not inside a string or a comment... 183 (let* ((sep (if sql-prompt-regexp 184 (concat 185 "\\`\\|\\(?:" sql-prompt-regexp "\\)") 186 "\\`")) 187 ;; TODO: Use (sql-beginning-of-statement 1)? 188 ;; Might error out. Should call that only once 189 ;; (and only if needed), and cache the result. 190 (from (save-excursion 191 (re-search-backward sep nil :noerror) 192 (or (match-end 0) (point-min)))) 193 (syn (parse-partial-sexp from (point)))) 194 (not (or (nth 3 syn) ; string 195 (nth 4 syn)))) ; comment 196 ;; Try to match the preceding word as a SQL keyword. 197 (sql-upcase-match-keyword)))))) 198 ;; Upcase the matched regions (if any) 199 (when sql-upcase-regions 200 (undo-boundary) ;; now that save-excursion has returned 201 (mapc (lambda (r) (upcase-region (car r) (cdr r))) 202 sql-upcase-regions))))))) 203 204(defun sql-upcase-match-keyword () 205 "Matches a keyword for `sql-upcase-keywords'. 206 207Tests whether the preceding word: 208 2091) is itself preceded by (only) whitespace or ( 2102a) matches the regexp for a keyword 2112b) matches the regexp for a builtin, followed by (" 212 (and (catch 'matched 213 (let ((inhibit-field-text-motion t)) ;; for comint 214 (forward-word -1) 215 (unless (bolp) 216 (forward-char -1))) 217 ;; Try to match a keyword using the regexps for this SQL product. 218 (let* ((before (concat "\\(?:^\\|" sql-upcase-boundary "\\)")) 219 (after sql-upcase-boundary) 220 ;; Build regexp for statement starters. 221 ;; FIXME: Generate once only, as a buffer-local var? 222 (statements ;; n.b. each of these is already a regexp 223 (delq nil 224 (list (sql-get-product-feature sql-product :statement) 225 (unless (eq sql-product 'ansi) 226 (sql-get-product-feature 'ansi :statement))))) 227 (statements-regexp 228 (concat "\\(?:" (mapconcat 'identity statements "\\|") "\\)"))) 229 ;; Check statement starters first 230 (if (looking-at (concat before statements-regexp after)) 231 (throw 'matched t) 232 ;; Otherwise process the product's font-lock keywords. 233 ;; TODO: I'm not sure that `font-lock-builtin-face' can be assumed 234 ;; to just be functions. (e.g. SET is not seen as a keyword.) 235 (dolist (keywords (sql-get-product-feature sql-product :font-lock)) 236 (when (or (and (eq (cdr keywords) 'font-lock-keyword-face) 237 (looking-at (concat before (car keywords) after))) 238 (and (eq (cdr keywords) 'font-lock-builtin-face) 239 (looking-at (concat before (car keywords) "(")))) 240 (throw 'matched t)))))) 241 ;; If `sql-upcase-mixed-case' is non-nil then check for at least one 242 ;; lower-case character in the matched region, as otherwise the upcase 243 ;; will be a no-op (but stored as a change in the buffer undo list). 244 (or (not sql-upcase-mixed-case) 245 (save-match-data 246 (let ((case-fold-search nil)) 247 (re-search-forward "[[:lower:]]" (match-end 0) :noerror)))) 248 ;; Store the matched keyword region for subsequent upcasing. 249 (push (cons (match-beginning 0) (match-end 0)) 250 sql-upcase-regions))) 251 252(provide 'sql-upcase) 253;;; sql-upcase.el ends here