├── README.md └── io-mode.el /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superbobry/io-mode/fd65ae769093defcf554d6d637eba6e6dfc29f56/README.md -------------------------------------------------------------------------------- /io-mode.el: -------------------------------------------------------------------------------- 1 | ;;; io-mode.el --- Major mode to edit Io language files in Emacs 2 | 3 | ;; Copyright (C) 2010 Sergei Lebedev 4 | 5 | ;; Version: 20100405 6 | ;; Keywords: languages, io 7 | ;; Author: Sergei Lebedev 8 | ;; URL: https://github.com/superbobry/io-mode 9 | 10 | ;; This file is not part of GNU Emacs. 11 | 12 | ;; This program is free software; you can redistribute it and/or modify 13 | ;; it under the terms of the GNU General Public License as published by 14 | ;; the Free Software Foundation; either version 2, or (at your option) 15 | ;; any later version. 16 | 17 | ;; This program is distributed in the hope that it will be useful, 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | ;; GNU General Public License for more details. 21 | 22 | ;; You should have received a copy of the GNU General Public License 23 | ;; along with this program; if not, write to the Free Software 24 | ;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 25 | 26 | ;;; Commentary: 27 | 28 | ;; No documentation is availible at the moment, but a nice and clean 29 | ;; README is soon to come. 30 | 31 | ;;; Installation 32 | 33 | ;; In your shell: 34 | 35 | ;; $ cd ~/.emacs.d/packges 36 | ;; $ git clone git://github.com/superbobry/io-mode.git 37 | 38 | ;; In your Emacs config: 39 | 40 | ;; (add-to-list 'load-path "~/.emacs.d/packages/io-mode") 41 | ;; (require 'io-mode) 42 | 43 | ;;; Thanks 44 | 45 | ;; Major thanks to defunkt's coffee-mode, which helped me to get over most 46 | ;; of the tought parts of writing a major mode. 47 | ;; 48 | ;; Sources: 49 | ;; http://renormalist.net/Renormalist/EmacsLanguageModeCreationTutorial 50 | ;; 51 | 52 | 53 | ;;; Code: 54 | 55 | (require 'comint) 56 | (require 'font-lock) 57 | (require 'hideshow) 58 | (require 'newcomment) 59 | 60 | ;; 61 | ;; Customizable Variables 62 | ;; 63 | 64 | (defconst io-mode-version "20100405" 65 | "The version of this `io-mode'.") 66 | 67 | (defgroup io nil 68 | "A major mode for editing Io language." 69 | :group 'languages) 70 | 71 | (defcustom io-debug-mode nil 72 | "Whether to run in debug mode or not. Logs to `*Messages*'." 73 | :type 'boolean 74 | :group 'io) 75 | 76 | (defcustom io-cleanup-whitespace t 77 | "Should we `delete-trailing-whitespace' on save? Probably." 78 | :type 'boolean 79 | :group 'io) 80 | 81 | (defcustom io-tab-width tab-width 82 | "The tab width to use when indenting." 83 | :type 'integer 84 | :group 'io) 85 | 86 | (defcustom io-command "io" 87 | "The Io command used for evaluating code. Must be in your path." 88 | :type 'string 89 | :group 'io) 90 | 91 | (defvar io-mode-hook nil 92 | "A hook for you to run your own code when the mode is loaded.") 93 | 94 | (defvar io-mode-map (make-keymap) 95 | "Keymap for Io major mode.") 96 | 97 | 98 | ;; 99 | ;; Macros 100 | ;; 101 | 102 | (defun io-debug (string &rest args) 103 | "Print a message when in debug mode." 104 | (when io-debug-mode 105 | (apply 'message (append (list string) args)))) 106 | 107 | (defmacro io-line-as-string () 108 | "Return the current line as a string." 109 | `(buffer-substring (point-at-bol) (point-at-eol))) 110 | 111 | ;; 112 | ;; Define Language Syntax 113 | ;; 114 | 115 | ;; Special? Self and call objects are :) 116 | (defvar io-special-re (regexp-opt '("self" "thisContext" "call") 'symbols)) 117 | 118 | ;; Operators (not all of them are present in the Io core, 119 | ;; but you can still define them, if you need it) 120 | ;; a) normal 121 | (defvar io-operators-re 122 | (regexp-opt 123 | '("*" "/" "%" "^" "+" "-" ">>" "++" "--" 124 | "<<" ">" "<" "<=" ">=" "==" "!=" "&" 125 | "^" ".." "|" "&&" "||" "!=" "+=" "-=" 126 | "*=" "/=" "<<=" ">>=" "&=" "|=" "%=" 127 | "=" ":=" "<-" "<->" "->"))) 128 | 129 | ;; b) special 130 | (defvar io-operators-special-re "@\\{1,2\\}\\|?") 131 | 132 | ;; Booleans 133 | (defvar io-boolean-re "\\b\\(true\\|false\\|nil\\)\\b") 134 | 135 | ;; Prototypes 136 | (defvar io-prototypes-re "\\b[A-Z]+\\w*\\b") 137 | 138 | ;; Messages 139 | (defvar io-messages-re 140 | (regexp-opt 141 | '("activate" "activeCoroCount" "and" "asString" 142 | "block" "break" "catch" "clone" "collectGarbage" 143 | "compileString" "continue" "do" "doFile" "doMessage" 144 | "doString" "else" "elseif" "exit" "for" "foreach" 145 | "foreachReversed" "forward" "getSlot" "getEnvironmentVariable" 146 | "hasSlot" "if" "ifFalse" "ifNil" "ifTrue" 147 | "isActive" "isNil" "isResumable" "list" 148 | "message" "method" "or" "parent" "pass" "pause" 149 | "perform" "performOn" "performWithArgList" "print" 150 | "println" "proto" "raise" "raiseResumable" "removeSlot" 151 | "resend" "resume" "return" "schedulerSleepSeconds" 152 | "sender" "setSchedulerSleepSeconds" "setSlot" 153 | "shallowCopy" "slotNames" "super" "system" 154 | "then" "thisBlock" "thisMessage" "try" "type" 155 | "uniqueId" "updateSlot" "wait" "while" "write" 156 | "writeln" "yield") 157 | 'symbols)) 158 | 159 | ;; Comments 160 | (defvar io-comments-re "\\(\\(#\\|//\\).*$\\|/\\*\\(.\\|[\r\n]\\)*?\\*/\\)") 161 | 162 | ;; Methods 163 | (defvar io-method-declaration-name-re "\\(\\sw+\\)\s*:=\s*\\(method\\)") 164 | 165 | ;; Variables 166 | (defvar io-variable-declaration-name-re "\\(\\sw+\\)\s*:=\s*\\(\\sw+\\)") 167 | 168 | ;; Create the list for font-lock. Each class of keyword is given a 169 | ;; particular face. 170 | (defvar io-font-lock-keywords 171 | ;; Note: order here matters! 172 | `((,io-special-re . font-lock-variable-name-face) 173 | (,io-method-declaration-name-re (1 font-lock-function-name-face)) 174 | (,io-variable-declaration-name-re (1 font-lock-variable-name-face)) 175 | (,io-operators-re . font-lock-builtin-face) 176 | (,io-operators-special-re . font-lock-warning-face) 177 | (,io-boolean-re . font-lock-constant-face) 178 | (,io-prototypes-re . font-lock-type-face) 179 | (,io-messages-re . font-lock-keyword-face) 180 | (,io-comments-re . font-lock-comment-face))) 181 | 182 | (eval-and-compile 183 | (defvar io-string-delimiter-re 184 | (rx (group (or "\"" "\"\"\""))))) 185 | 186 | (defun io-syntax-count-quotes (quote-char point limit) 187 | (let ((i 0)) 188 | (while (and (< i 3) 189 | (or (not limit) (< (+ point i) limit)) 190 | (eq (char-after (+ point i)) quote-char)) 191 | (setq i (1+ i))) 192 | i)) 193 | 194 | (defun io-syntax-stringify () 195 | "Put `syntax-table' property correctly on single/triple quotes." 196 | (let* ((num-quotes (length (match-string-no-properties 1))) 197 | (ppss (prog2 198 | (backward-char num-quotes) 199 | (syntax-ppss) 200 | (forward-char num-quotes))) 201 | (string-start (and (not (nth 4 ppss)) (nth 8 ppss))) 202 | (quote-starting-pos (- (point) num-quotes)) 203 | (quote-ending-pos (point)) 204 | (num-closing-quotes 205 | (and string-start 206 | (io-syntax-count-quotes 207 | (char-before) string-start quote-starting-pos)))) 208 | (cond ((and string-start (= num-closing-quotes 0)) 209 | nil) 210 | ((not string-start) 211 | (put-text-property quote-starting-pos (1+ quote-starting-pos) 212 | 'syntax-table (string-to-syntax "|"))) 213 | ((= num-quotes num-closing-quotes) 214 | (put-text-property (1- quote-ending-pos) quote-ending-pos 215 | 'syntax-table (string-to-syntax "|"))) 216 | ((> num-quotes num-closing-quotes) 217 | (put-text-property quote-starting-pos quote-ending-pos 218 | 'syntax-table (string-to-syntax "|")))))) 219 | 220 | ;; 221 | ;; REPL 222 | ;; 223 | 224 | (defun io-normalize-sexp (str) 225 | "Normalize a given Io code string, removing all newline characters." 226 | ;; Oddly enough, Io interpreter doesn't allow newlines anywhere, 227 | ;; including multiline strings and method calls, we need to make 228 | ;; a flat string from a code block, before it's passed to the 229 | ;; interpreter. Obviously, this isn't a good solution, since 230 | ;; a := """Cool multiline 231 | ;; string!""" 232 | ;; would become 233 | ;; a := """Cool multiline string!""" 234 | ;; ... 235 | (replace-regexp-in-string 236 | ;; ... and finally strip the remaining newlines. 237 | "[\r\n]+" "; " (replace-regexp-in-string 238 | ;; ... then strip multiple whitespaces ... 239 | "\s+" " " 240 | (replace-regexp-in-string 241 | ;; ... then remove all newline characters near brackets 242 | ;; and comas ... 243 | "\\([(,]\\)[\n\r\s]+\\|[\n\r\s]+\\()\\)" "\\1\\2" 244 | ;; This should really be read bottom-up, start by removing 245 | ;; all comments ... 246 | (replace-regexp-in-string io-comments-re "" str))))) 247 | 248 | (defun io-repl () 249 | "Launch an Io REPL using `io-command' as an inferior mode." 250 | (interactive) 251 | (let ((io-repl-buffer (get-buffer "*Io*"))) 252 | (unless (comint-check-proc io-repl-buffer) 253 | (setq io-repl-buffer 254 | (apply 'make-comint "Io" io-command nil))) 255 | (pop-to-buffer io-repl-buffer))) 256 | 257 | (defun io-repl-sexp (str) 258 | "Send the expression to an Io REPL." 259 | (interactive "sExpression: ") 260 | (let ((io-repl-buffer (io-repl))) 261 | (save-current-buffer 262 | (set-buffer io-repl-buffer) 263 | (comint-goto-process-mark) 264 | (insert (io-normalize-sexp str)) 265 | ;; Probably ARTIFICIAL value should be made an option, 266 | ;; like `io-repl-display-sent'. 267 | (comint-send-input)))) 268 | 269 | (defun io-repl-sregion (beg end) 270 | "Send the region to an Io REPL." 271 | (interactive "r") 272 | (io-repl-sexp (buffer-substring beg end))) 273 | 274 | (defun io-repl-sbuffer () 275 | "Send the content of the buffer to an Io REPL." 276 | (interactive) 277 | (io-repl-sregion (point-min) (point-max))) 278 | 279 | ;; 280 | ;; Helper Functions 281 | ;; 282 | 283 | (defun io-before-save () 284 | "Hook run before file is saved. Deletes whitespace if `io-cleanup-whitespace' is non-nil." 285 | (when io-cleanup-whitespace 286 | (delete-trailing-whitespace))) 287 | 288 | 289 | ;; 290 | ;; Indentation 291 | ;; 292 | 293 | (defun io-indent-line () 294 | "Indent current line as Io source." 295 | (interactive) 296 | 297 | (if (= (point) (point-at-bol)) 298 | (insert-tab) 299 | (save-excursion 300 | (let ((prev-indent 0) (cur-indent 0)) 301 | ;; Figure out the indentation of the previous 302 | ;; and current lines. 303 | (setq prev-indent (io-previous-indent) 304 | cur-indent (current-indentation)) 305 | 306 | ;; Shift one column to the left. 307 | (beginning-of-line) 308 | (insert-tab) 309 | 310 | (when (= (point-at-bol) (point)) 311 | (forward-char io-tab-width)) 312 | 313 | ;; We're too far, remove all indentation. 314 | (when (> (- (current-indentation) prev-indent) io-tab-width) 315 | (backward-to-indentation 0) 316 | (delete-region (point-at-bol) (point))))))) 317 | 318 | (defun io-previous-indent () 319 | "Returns the indentation level of the previous non-blank line." 320 | (save-excursion 321 | (forward-line -1) 322 | (if (bobp) 323 | 0 324 | (progn 325 | (while (io-line-empty-p) (forward-line -1)) 326 | (current-indentation))))) 327 | 328 | (defun io-line-empty-p () 329 | "Is this line empty? Returns non-nil if so, nil if not." 330 | (or (bobp) 331 | (string-match "^\\s-*$" (io-line-as-string)))) 332 | 333 | (defun io-newline-and-indent () 334 | "Inserts a newline and indents it to the same level as the previous line." 335 | (interactive) 336 | 337 | ;; Remember the current line indentation level, 338 | ;; insert a newline, and indent the newline to the same 339 | ;; level as the previous line. 340 | (let ((prev-indent (current-indentation)) (indent-next nil)) 341 | (newline) 342 | (insert-tab (/ prev-indent io-tab-width))) 343 | 344 | ;; Last line was a comment so this one should probably be, 345 | ;; too. Makes it easy to write multi-line comments (like the 346 | ;; one I'm writing right now). 347 | (when (io-previous-line-is-comment) 348 | ;; Using `match-string' is probably not obvious, but current 349 | ;; implementation of `io-previous-is-comment' is using `looking-at', 350 | ;; which modifies match-data variables. 351 | (insert (match-string 0)))) 352 | 353 | 354 | ;; 355 | ;; Comments 356 | ;; 357 | 358 | (defun io-previous-line-is-comment () 359 | "Returns `t' if previous line is a comment." 360 | (save-excursion 361 | (forward-line -1) 362 | (io-line-is-comment))) 363 | 364 | (defun io-line-is-comment () 365 | "Returns `t' if current line is a comment." 366 | (save-excursion 367 | (backward-to-indentation 0) 368 | ;; No support for multi line comments yet. 369 | (looking-at "\\(#\\|//\\)+\s*"))) 370 | 371 | 372 | ;; 373 | ;; Define Major Mode 374 | ;; 375 | 376 | ;;;###autoload 377 | (define-derived-mode io-mode fundamental-mode 378 | "Io" 379 | "Major mode for editing Io language..." 380 | 381 | (define-key io-mode-map (kbd "C-m") 'io-newline-and-indent) 382 | (define-key io-mode-map (kbd "C-c ") 'io-repl) 383 | (define-key io-mode-map (kbd "C-c C-c") 'io-repl-sbuffer) 384 | (define-key io-mode-map (kbd "C-c C-r") 'io-repl-sregion) 385 | (define-key io-mode-map (kbd "C-c C-e") 'io-repl-sexp) 386 | (define-key io-mode-map (kbd "C-c ;") 'comment-dwim) 387 | 388 | ;; code for syntax highlighting 389 | (set (make-local-variable 'font-lock-defaults) '((io-font-lock-keywords))) 390 | 391 | ;; comments 392 | ;; a) python style 393 | (modify-syntax-entry ?# "< b" io-mode-syntax-table) 394 | (modify-syntax-entry ?\n "> b" io-mode-syntax-table) 395 | ;; b) c style 396 | (modify-syntax-entry ?/ ". 124b" io-mode-syntax-table) 397 | (modify-syntax-entry ?* ". 23" io-mode-syntax-table) 398 | (modify-syntax-entry ?\n "> b" io-mode-syntax-table) 399 | 400 | (set (make-local-variable 'syntax-propertize-function) 401 | (syntax-propertize-rules 402 | (io-string-delimiter-re 403 | (0 (ignore (io-syntax-stringify)))))) 404 | 405 | (setq comment-start "# " 406 | comment-start-skip "# *" 407 | comment-end "" 408 | comment-column 40 409 | comment-style 'indent) 410 | 411 | ;; strings 412 | (modify-syntax-entry ?\' "\"" io-mode-syntax-table) 413 | (modify-syntax-entry ?\" "\"" io-mode-syntax-table) 414 | 415 | ;; indentation 416 | (make-local-variable 'indent-line-function) 417 | (setq indent-line-function 'io-indent-line 418 | io-tab-width tab-width ;; just in case... 419 | indent-tabs-mode nil) ;; tabs are evil.. 420 | 421 | ;; hideshow 422 | (unless (assq 'io-mode hs-special-modes-alist) 423 | (add-to-list 'hs-special-modes-alist 424 | '(io-mode "(" ")" "\\(?:#\\|/[*/]\\)"))) 425 | 426 | ;; hooks 427 | (set (make-local-variable 'before-save-hook) 'io-before-save)) 428 | 429 | ;; 430 | ;; On Load 431 | ;; 432 | 433 | ;; Run io-mode for files ending in .io. 434 | ;;;###autoload 435 | (add-to-list 'auto-mode-alist '("\\.io$" . io-mode)) 436 | 437 | 438 | (provide 'io-mode) 439 | 440 | ;;; io-mode.el ends here 441 | --------------------------------------------------------------------------------