├── README.md └── ttl-mode.el /README.md: -------------------------------------------------------------------------------- 1 | This is an Emacs mode for editing Turtle (RDF) files. 2 | 3 | I've changed the indenting for ttl-mode somewhat to support graphs 4 | (`{}`) better. 5 | 6 | Original readme: 7 | 8 | It is based on an excellent start made by Hugo Haas. 9 | I've extended it to support indentation, some electric punctuation, 10 | and hungry delete. 11 | 12 | To use, download the file `ttl-mode.el` from [Bitbucket](https://bitbucket.org/nxg/ttl-mode) 13 | (or clone the project), put the file in the emacs load path (look at the variable 14 | load-path to find where emacs currently searches), and add something 15 | like the following to your `.emacs` file. 16 | 17 | (autoload 'ttl-mode "ttl-mode" "Major mode for OWL or Turtle files" t) 18 | (add-hook 'ttl-mode-hook ; Turn on font lock when in ttl mode 19 | 'turn-on-font-lock) 20 | (setq auto-mode-alist 21 | (append 22 | (list 23 | '("\\.n3" . ttl-mode) 24 | '("\\.ttl" . ttl-mode)) 25 | auto-mode-alist)) 26 | 27 | Comments and contributions most welcome. 28 | 29 | * Copyright 2003-2007, [Hugo Haas](http://www.hugoh.net) 30 | * Copyright 2011-2012, [Norman Gray](https://nxg.me.uk) 31 | * Copyright 2013, [Daniel Gerber](https://danielgerber.net) 32 | * Copyright 2016, [Peter Vasil](http://petervasil.net) 33 | 34 | `ttl-mode.el` is released under the terms of the 35 | [two-clause BSD licence](https://opensource.org/licenses/bsd-license.php) 36 | (see the `ttl-model.el` header for the licence text). 37 | 38 | Norman Gray 39 | https://nxg.me.uk 40 | -------------------------------------------------------------------------------- /ttl-mode.el: -------------------------------------------------------------------------------- 1 | ;;; ttl-mode.el --- mode for Turtle (and Notation 3) 2 | ;; ttl-mode.el is released under the terms of the two-clause BSD licence: 3 | ;; 4 | ;; Copyright 2003-2007, Hugo Haas 5 | ;; Copyright 2011-2012, Norman Gray 6 | ;; Copyright 2013, Daniel Gerber 7 | ;; Copyright 2016, Peter Vasil 8 | ;; All rights reserved. 9 | ;; 10 | ;; Redistribution and use in source and binary forms, 11 | ;; with or without modification, 12 | ;; are permitted provided that the following conditions are met: 13 | ;; 14 | ;; 1. Redistributions of source code must retain the above copyright 15 | ;; notice, this list of conditions and the following disclaimer. 16 | ;; 17 | ;; 2. Redistributions in binary form must reproduce the above copyright 18 | ;; notice, this list of conditions and the following disclaimer in the 19 | ;; documentation and/or other materials provided with the distribution. 20 | ;; 21 | ;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | ;; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | ;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | ;; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | ;; HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | ;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | ;; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | ;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | ;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | ;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | ;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | ;; 33 | ;; 34 | ;;; Commentary: 35 | ;; 36 | ;; See Hugo's commentary for original goals and further discussion, 37 | ;; at http://larve.net/people/hugo/2003/scratchpad/NotationThreeEmacsMode.html 38 | ;; Also draws on http://dishevelled.net/elisp/turtle-mode.el (which is for the _other_ turtle!) 39 | 40 | ;; Project hosted at . See there for updates. 41 | 42 | ;; For documentation on Notation 3, see: 43 | ;; http://www.w3.org/DesignIssues/Notation3.html 44 | 45 | ;; Current features: 46 | ;; - *Turtle* grammar subset 47 | ;; - approximate syntax highlighting 48 | ;; - comment/uncomment block with M-; 49 | ;; - indentation 50 | 51 | ;; To use: 52 | ;; 53 | ;; (autoload 'ttl-mode "ttl-mode") 54 | ;; (add-hook 'ttl-mode-hook 'turn-on-font-lock) 55 | ;; (add-to-list 'auto-mode-alist '("\\.\\(n3\\|ttl\\|trig\\)\\'" . ttl-mode)) 56 | 57 | ;;; Code: 58 | 59 | (require 'cl-lib) 60 | 61 | (defgroup ttl nil "Customization for ttl-mode" :group 'text) 62 | 63 | (defcustom ttl-indent-level 4 64 | "Number of spaces for each indentation step in `ttl-mode'." 65 | :type 'integer) 66 | 67 | (defcustom ttl-electric-punctuation t 68 | "If non-nil, `\;' or `\.' will self insert, reindent the line, and do a newline. (To insert while t, do: \\[quoted-insert] \;)." 69 | :type 'boolean) 70 | 71 | (defcustom ttl-indent-on-idle-timer t 72 | "If non-nil, will automatically indent a line after `ttl-idle-timer-timeout'." 73 | :type 'boolean) 74 | 75 | (defcustom ttl-indent-idle-timer-period 2 76 | "If `ttl-indent-on-idle-timer' is non-nil, indent after EMACS has been idle for this many seconds." 77 | :type 'integer) 78 | 79 | 80 | (defvar ttl-indent-idle-timer nil "TTL-mode autoindent idle timer if idle auto indentation is used (`ttl-indent-on-idle-timer' is non-nil).") 81 | 82 | ;;;###autoload 83 | (define-derived-mode ttl-mode prog-mode "N3/Turtle mode" 84 | "Major mode for Turtle RDF documents." 85 | 86 | ;; Comments syntax 87 | (set (make-local-variable 'comment-start) " # ") 88 | (modify-syntax-entry ?\n "> " ttl-mode-syntax-table) 89 | (modify-syntax-entry ?\' "\"" ttl-mode-syntax-table) 90 | ;; fontification 91 | (setq font-lock-defaults 92 | `((,(regexp-opt '("@prefix" "@base" "@keywords" "PREFIX" "BASE" "@forAll" "@forSome" "true" "false" "a") 'symbols) ;keywords 93 | ("\\^\\^[^,;.]+" 0 font-lock-preprocessor-face t) ;literal types 94 | ("\\?[[:word:]_]+" 0 font-lock-variable-name-face) ;Existentially quantified variables 95 | ("@[[:word:]_]+" . font-lock-preprocessor-face) ;languages 96 | ; Does not work with iris containing multiple colons (one:two:three is apparently allowed.) 97 | ("\\(:?[-[:alnum:]]+\\|_\\)?:" . font-lock-type-face) ;prefix 98 | (":\\([[:word:]_-]+\\)\\>" 1 font-lock-constant-face nil) ;suffix 99 | ;; TODO: This incorrectly highlights resources in strings. 100 | ("<.*?>" 0 font-lock-function-name-face t) ;resources 101 | ("[,;.]" 0 font-lock-keyword-face)))) 102 | 103 | ;; indentation 104 | (set (make-local-variable 'indent-line-function) 'ttl-indent-line) 105 | (set (make-local-variable 'indent-tabs-mode) nil) 106 | (set (make-local-variable 'syntax-propertize-function) 'ttl-propertize-comments) 107 | (setq show-trailing-whitespace t) 108 | (if (and ttl-indent-on-idle-timer (not ttl-indent-idle-timer)) 109 | (setq ttl-indent-idle-timer (run-with-idle-timer ttl-indent-idle-timer-period t 'ttl-idle-indent)) 110 | (when ttl-indent-idle-timer 111 | (setq ttl-indent-idle-timer (cancel-timer ttl-indent-idle-timer))))) 112 | 113 | ;; electric punctuation 114 | (define-key ttl-mode-map (kbd "\,") 'ttl-electric-comma) 115 | (define-key ttl-mode-map (kbd "\;") 'ttl-electric-semicolon) 116 | (define-key ttl-mode-map (kbd "\,") 'ttl-electric-comma) 117 | (define-key ttl-mode-map (kbd "\.") 'ttl-electric-dot) 118 | (define-key ttl-mode-map [backspace] 'ttl-hungry-delete-backwards) 119 | 120 | 121 | ;; Could be replaced with a call to syntax-propertize-rules. See 122 | ;; https://emacs.stackexchange.com/questions/36909/how-can-i-make-syntax-propertize-skip-part-of-the-buffer 123 | (defun ttl-propertize-comments (start end) 124 | "Set the syntax class to `comment-start` for all hashes that are prepended by a space between START and END." 125 | (save-excursion 126 | (goto-char start) 127 | (save-match-data 128 | (while (search-forward "#" end t) 129 | (let ((char-before (buffer-substring-no-properties 130 | (max (point-min) (- (point) 2)) 131 | (min (point-max) (- (point) 1))))) 132 | (when (or (equal char-before " ") 133 | (equal char-before "\n")) 134 | (put-text-property (match-beginning 0) (match-end 0) 'syntax-table '(11)))))))) 135 | 136 | (defun ttl-indent-line () 137 | "Indent current line." 138 | (interactive) 139 | (save-excursion 140 | (indent-line-to 141 | (or (ttl-calculate-indentation) 0))) 142 | (move-to-column (max (current-indentation) (current-column)))) 143 | 144 | (defun ttl-idle-indent () 145 | "Indent the current line, and check you're in an ttl-mode buffer." 146 | (when (eq major-mode 'ttl-mode) 147 | (ttl-indent-line))) 148 | 149 | (defun ttl-calculate-indentation () 150 | "Calculate the indentation for the current line." 151 | (save-excursion 152 | (backward-to-indentation 0) 153 | (cl-destructuring-bind 154 | (last-indent last-character after-prefix) 155 | (save-excursion (ttl-skip-uninteresting-lines) (list (current-indentation) (char-before) (ttl-in-prefix-line))) 156 | (let* ((syntax-info (syntax-ppss)) 157 | (base-indent (* ttl-indent-level (ttl-adjusted-paren-depth (nth 9 syntax-info))))) 158 | (cond 159 | ;; in multiline string 160 | ((nth 3 syntax-info) (current-indentation)) 161 | ;; First line in buffer. 162 | ((= (line-number-at-pos (point) t) 1) 0) 163 | ;; beginning of stanza 164 | ((and (or (looking-at "@") ; @prefix, @base, @keywords. 165 | (looking-at "PREFIX") 166 | (looking-at "BASE")) 167 | (not (looking-at "\\(@forSome\\)\\|\\(@forAll\\)"))) ; @forAll and @forSome should be indented normally. 168 | 0) 169 | ;; ((looking-at "#") base-indent) 170 | ((looking-at "[])}]") ; Indent to level of matching parenthesis. 171 | (save-excursion 172 | (goto-char (nth 1 syntax-info)) 173 | (current-indentation))) 174 | ((and (not (bobp)) 175 | (or (ttl-first-line-of ?\[ last-character) 176 | (ttl-first-line-of ?\( last-character) 177 | (ttl-first-line-of ?\{ last-character))) 178 | (+ last-indent ttl-indent-level)) 179 | ((eq ?. last-character) base-indent) 180 | (after-prefix 0) 181 | ((ttl-in-blank-node) base-indent) 182 | (last-character (+ base-indent ttl-indent-level))))))) 183 | 184 | (defun ttl-adjusted-paren-depth (parenpos) 185 | "Calculate parenthesis depth from PARENPOS, ignoring parentheses on the same line." 186 | ;; Just enough common lisp to be dangerous. 187 | (length (delete-dups (cl-loop for pos in parenpos collect (line-number-at-pos pos))))) 188 | 189 | (defun ttl-in-prefix-line () 190 | (save-excursion 191 | (beginning-of-line) 192 | (looking-at (rx (or "@" "PREFIX" "BASE"))))) 193 | 194 | (defun ttl-skip-uninteresting-lines () 195 | "Skip backwards to the first non-comment content." 196 | (forward-line -1) 197 | ;; First, skip lines that consist only of comments. 198 | (while (and 199 | (not (bobp)) 200 | ;; Skip comment lines 201 | (or 202 | (string-match (rx (and string-start (* blank) ?# (*? not-newline) line-end)) (thing-at-point 'line)) 203 | ;; Skip empty lines 204 | (string-match (rx (and string-start (* blank) line-end)) (thing-at-point 'line)))) 205 | (forward-line -1)) 206 | ;; Then, go to last non-comment-character 207 | (if (search-forward " #" (point-at-eol) t) 208 | (backward-char 2) 209 | (end-of-line))) 210 | 211 | 212 | (defun ttl-insulate () 213 | "Return non-nil if this location should not be electrified." 214 | (or (not ttl-electric-punctuation) 215 | (let '(s (syntax-ppss)) 216 | (or (nth 3 s) 217 | (nth 4 s) 218 | (ttl-in-resource))))) 219 | 220 | (defun ttl-last-bracket-is (brack) 221 | "Is the last bracket equal to BRACK?" 222 | (let ((list-start (nth 1 (syntax-ppss)))) 223 | (and list-start 224 | (equal (char-after list-start) brack)))) 225 | 226 | (defun ttl-first-line-of (brack lastchar) 227 | "Whether we are in the first line of a node introduced by BRACK. 228 | 229 | LASTCHAR is the last character of the preceding line." 230 | (and (ttl-last-bracket-is brack) 231 | (eq brack lastchar))) 232 | 233 | (defun ttl-in-blank-node () 234 | "Is point within a blank node, marked by [...]?" 235 | (ttl-last-bracket-is ?\[)) 236 | 237 | (defun ttl-in-graph () 238 | "Is point within a graph?" 239 | (ttl-last-bracket-is ?\{)) 240 | 241 | (defun ttl-in-object-list () 242 | "Are we in a node list, marked by (...)?" 243 | (ttl-last-bracket-is ?\()) 244 | 245 | (defun ttl-in-resource () 246 | "Is point within a resource, marked by <...>?" 247 | (save-excursion 248 | (and (re-search-backward "[<> ]" nil t) 249 | (looking-at "<")))) 250 | 251 | (defun ttl-in-comment () 252 | "Are we in a comment, marked by a hash mark?" 253 | (nth 4 (syntax-ppss))) 254 | 255 | (defun ttl-in-string () 256 | "Are we in a string?" 257 | (nth 3 (syntax-ppss))) 258 | 259 | (defun ttl-electric-comma () 260 | "Insert spaced comma, indent, insert newline and reindent." 261 | (interactive) 262 | (if (ttl-insulate) (insert ",") 263 | (progn 264 | (if (not (looking-back " " 1)) (insert " ")) 265 | (insert ", ") 266 | (ttl-indent-line)))) 267 | 268 | (defun ttl-electric-semicolon () 269 | "Insert spaced semicolon, indent, insert newline and reindent next line." 270 | (interactive) 271 | (if (ttl-insulate) (insert ";") 272 | (if (not (looking-back " " 1)) (insert " ")) 273 | (insert ";") 274 | (reindent-then-newline-and-indent))) 275 | 276 | (defun ttl-electric-dot () 277 | "Insert spaced dot, insert newline, indent." 278 | (interactive) 279 | (if (and (ttl-in-blank-node) 280 | (not (ttl-in-comment)) 281 | (not (ttl-in-string))) 282 | (message "No period in blank nodes.") 283 | (if (ttl-insulate) (insert ".") 284 | (if (not (looking-back " " 1)) (insert " ")) 285 | (insert ".") 286 | (reindent-then-newline-and-indent)))) 287 | 288 | (defun ttl-skip-ws-backwards () ;adapted from cc-mode 289 | "Move backwards across whitespace." 290 | (while (progn 291 | (skip-chars-backward " \t\n\r\f\v") 292 | (and (eolp) 293 | (eq (char-before) ?\\))) 294 | (backward-char))) 295 | 296 | (defun ttl-hungry-delete-backwards () 297 | "Delete backwards hungrily. 298 | 299 | Deletes either all of the preceding whitespace, or a single 300 | non-whitespace character if there is no whitespace before point." 301 | (interactive) 302 | (let ((here (point))) 303 | (ttl-skip-ws-backwards) 304 | (if (/= (point) here) 305 | (delete-region (point) here) 306 | (backward-delete-char-untabify 1)))) 307 | 308 | (provide 'ttl-mode) 309 | ;;; ttl-mode.el ends here 310 | --------------------------------------------------------------------------------