1 ;;; kotlin-mode.el --- Major mode for kotlin -*- lexical-binding: t; -*-
3 ;; Copyright © 2015 Shodai Yokoyama
5 ;; Author: Shodai Yokoyama (quantumcars@gmail.com)
7 ;; Package-Version: 20190116.2055
8 ;; Package-Requires: ((emacs "24.3"))
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.
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.
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/>.
34 "A Kotlin major mode."
37 (defcustom kotlin-tab-width tab-width
38 "The tab width to use for indentation."
43 (defcustom kotlin-command "kotlinc"
44 "The Kotlin command used for evaluating code."
48 (defcustom kotlin-args-repl '()
49 "The arguments to pass to `kotlin-command' to start a REPL."
53 (defcustom kotlin-repl-buffer "*KotlinREPL*"
54 "The name of the KotlinREPL buffer."
58 (defun kotlin-do-and-repl-focus (f &rest args)
60 (pop-to-buffer kotlin-repl-buffer))
62 (defun kotlin-send-region (start end)
63 "Send current region to Kotlin interpreter."
65 (comint-send-region kotlin-repl-buffer start end)
66 (comint-send-string kotlin-repl-buffer "\n"))
68 (defun kotlin-send-region-and-focus (start end)
69 "Send current region to Kotlin interpreter and switch to it."
71 (kotlin-do-and-repl-focus 'kotlin-send-region start end))
73 (defun kotlin-send-buffer ()
74 "Send whole buffer to Kotlin interpreter."
76 (kotlin-send-region (point-min) (point-max)))
78 (defun kotlin-send-buffer-and-focus ()
79 "Send whole buffer to Kotlin interpreter and switch to it."
81 (kotlin-do-and-repl-focus 'kotlin-send-buffer))
83 (defun kotlin-send-block ()
87 (kotlin-send-region (region-beginning) (region-end))
90 (defun kotlin-send-block-and-focus ()
91 "Send block to Kotlin interpreter and switch to it."
93 (kotlin-do-and-repl-focus 'kotlin-send-block))
95 (defun kotlin-send-line ()
98 (line-beginning-position)
101 (defun kotlin-send-line-and-focus ()
102 "Send current line to Kotlin interpreter and switch to it."
104 (kotlin-do-and-repl-focus 'kotlin-send-line))
106 (defun kotlin-repl ()
107 "Launch a Kotlin REPL using `kotlin-command' as an inferior mode."
110 (unless (comint-check-proc kotlin-repl-buffer)
112 (apply 'make-comint "KotlinREPL"
119 (set (make-local-variable 'comint-preoutput-filter-functions)
120 (cons (lambda (string)
121 (replace-regexp-in-string "\x1b\\[.[GJK]" "" string)) nil)))
123 (pop-to-buffer kotlin-repl-buffer))
125 (defvar kotlin-mode-map
126 (let ((map (make-keymap)))
127 (define-key map (kbd "C-c C-z") 'kotlin-repl)
128 (define-key map (kbd "C-c C-n") 'kotlin-send-line)
129 (define-key map (kbd "C-c C-r") 'kotlin-send-region)
130 (define-key map (kbd "C-c C-c") 'kotlin-send-block)
131 (define-key map (kbd "C-c C-b") 'kotlin-send-buffer)
132 (define-key map (kbd "<tab>") 'c-indent-line-or-region)
134 "Keymap for kotlin-mode")
136 (defvar kotlin-mode-syntax-table
137 (let ((st (make-syntax-table)))
140 (modify-syntax-entry ?\" "\"" st)
142 ;; `_' as being a valid part of a word
143 (modify-syntax-entry ?_ "w" st)
146 (modify-syntax-entry ?/ ". 124b" st)
147 (modify-syntax-entry ?* ". 23" st)
148 (modify-syntax-entry ?\n "> b" st)
154 (defconst kotlin-mode--misc-keywords
155 '("package" "import"))
157 (defconst kotlin-mode--type-decl-keywords
158 '("nested" "inner" "data" "class" "interface" "trait" "typealias" "enum" "object"))
160 (defconst kotlin-mode--fun-decl-keywords
163 (defconst kotlin-mode--val-decl-keywords
166 (defconst kotlin-mode--statement-keywords
170 "try" "catch" "finally" "throw"
172 "while" "for" "do" "continue" "break"
174 "when" "is" "in" "as" "return"))
176 (defconst kotlin-mode--context-variables-keywords
179 (defvar kotlin-mode--keywords
180 (append kotlin-mode--misc-keywords
181 kotlin-mode--type-decl-keywords
182 kotlin-mode--fun-decl-keywords
183 kotlin-mode--val-decl-keywords
184 kotlin-mode--statement-keywords
185 kotlin-mode--context-variables-keywords)
186 "Keywords used in Kotlin language.")
188 (defconst kotlin-mode--constants-keywords
189 '("null" "true" "false"))
191 (defconst kotlin-mode--modifier-keywords
192 '("open" "private" "protected" "public" "lateinit"
193 "override" "abstract" "final" "companion"
194 "annotation" "internal" "const" "in" "out")) ;; "in" "out"
196 (defconst kotlin-mode--property-keywords
197 '("by")) ;; "by" "get" "set"
199 (defconst kotlin-mode--initializer-keywords
200 '("init" "constructor"))
202 (defvar kotlin-mode--font-lock-keywords
205 `(and bow (group (or ,@kotlin-mode--keywords)) eow)
207 1 font-lock-keyword-face)
211 `(and (or ,@kotlin-mode--misc-keywords) (+ space)
212 (group (+ (any word ?.))))
214 1 font-lock-string-face)
218 `(and bow upper (group (* (or word "<" ">" "." "?" "!" "*"))))
220 0 font-lock-type-face)
224 `(and bow (or ,@kotlin-mode--type-decl-keywords) eow (+ space)
225 (group (+ word)) eow)
227 1 font-lock-type-face)
231 `(and bow (group (or ,@kotlin-mode--constants-keywords)) eow)
233 0 font-lock-constant-face)
237 `(and bow (or ,@kotlin-mode--val-decl-keywords) eow
239 (group (+ word)) (* space) (\? ":"))
241 1 font-lock-variable-name-face t)
245 `(and (or ,@kotlin-mode--fun-decl-keywords)
246 (+ space) bow (group (+ (any alnum word))) eow)
248 1 font-lock-function-name-face)
251 ;; Access modifier is valid identifier being used as variable
252 ;; TODO: Highlight only modifiers in the front of class/fun
254 `(and bow (group (or ,@kotlin-mode--modifier-keywords))
257 1 font-lock-keyword-face)
260 ;; by/get/set are valid identifiers being used as variable
261 ;; TODO: Highlight keywords in the property declaration statement
263 ;; `(and bow (group (or ,@kotlin-mode--property-keywords)) eow)
265 ;; 1 font-lock-keyword-face)
267 ;; Constructor/Initializer blocks
269 `(and bow (group (or ,@kotlin-mode--initializer-keywords)) eow)
271 1 font-lock-keyword-face)
273 ;; String interpolation
274 (kotlin-mode--match-interpolation 0 font-lock-variable-name-face t))
275 "Default highlighting expression for `kotlin-mode'")
277 (defun kotlin-mode--new-font-lock-keywords ()
279 ("package\\|import" . font-lock-keyword-face)
282 (defun kotlin-mode--syntax-propertize-interpolation ()
283 (let* ((pos (match-beginning 0))
284 (context (save-excursion
285 (save-match-data (syntax-ppss pos)))))
286 (when (nth 3 context)
287 (put-text-property pos
289 'kotlin-property--interpolation
292 (defun kotlin-mode--syntax-propertize-function (start end)
293 (let ((case-fold-search))
295 (remove-text-properties start end '(kotlin-property--interpolation))
297 (syntax-propertize-rules
298 ((let ((identifier '(or
299 (and alpha (* alnum))
300 (and "`" (+ (not (any "`\n"))) "`"))))
302 `(or (group "${" ,identifier "}")
303 (group "$" ,identifier))))
304 (0 (ignore (kotlin-mode--syntax-propertize-interpolation)))))
307 (defun kotlin-mode--match-interpolation (limit)
308 (let ((pos (next-single-char-property-change (point)
309 'kotlin-property--interpolation
312 (when (and pos (> pos (point)))
314 (let ((value (get-text-property pos 'kotlin-property--interpolation)))
316 (progn (set-match-data value)
318 (kotlin-mode--match-interpolation limit))))))
320 (defun kotlin-mode--prev-line ()
321 "Moves up to the nearest non-empty line"
325 (while (and (looking-at "^[ \t]*$") (not (bobp)))
326 (forward-line -1)))))
328 (defun kotlin-mode--indent-line ()
329 "Indent current line as kotlin code"
334 (kotlin-mode--beginning-of-buffer-indent))
335 (let ((not-indented t) cur-indent)
336 (cond ((looking-at "^[ \t]*\\.") ; line starts with .
338 (kotlin-mode--prev-line)
339 (cond ((looking-at "^[ \t]*\\.")
340 (setq cur-indent (current-indentation)))
343 (setq cur-indent (+ (current-indentation) (* 2 kotlin-tab-width)))))
345 (setq cur-indent 0))))
347 ((looking-at "^[ \t]*}") ; line starts with }
349 (kotlin-mode--prev-line)
350 (while (and (or (looking-at "^[ \t]*$") (looking-at "^[ \t]*\\.")) (not (bobp)))
351 (kotlin-mode--prev-line))
352 (cond ((or (looking-at ".*{[ \t]*$") (looking-at ".*{.*->[ \t]*$"))
353 (setq cur-indent (current-indentation)))
355 (setq cur-indent (- (current-indentation) kotlin-tab-width)))))
357 (setq cur-indent 0)))
359 ((looking-at "^[ \t]*)") ; line starts with )
361 (kotlin-mode--prev-line)
362 (setq cur-indent (- (current-indentation) kotlin-tab-width)))
364 (setq cur-indent 0)))
369 (kotlin-mode--prev-line)
370 (cond ((looking-at ".*{[ \t]*$") ; line ends with {
371 (setq cur-indent (+ (current-indentation) kotlin-tab-width))
372 (setq not-indented nil))
374 ((looking-at "^[ \t]*}") ; line starts with }
375 (setq cur-indent (current-indentation))
376 (setq not-indented nil))
378 ((looking-at ".*{.*->[ \t]*$") ; line ends with ->
379 (setq cur-indent (+ (current-indentation) kotlin-tab-width))
380 (setq not-indented nil))
382 ((looking-at ".*([ \t]*$") ; line ends with (
383 (setq cur-indent (+ (current-indentation) kotlin-tab-width))
384 (setq not-indented nil))
386 ((looking-at "^[ \t]*).*$") ; line starts with )
387 (setq cur-indent (current-indentation))
388 (setq not-indented nil))
391 (setq not-indented nil)))))))
393 (indent-line-to cur-indent)
394 (indent-line-to 0)))))
397 (defun kotlin-mode--beginning-of-buffer-indent ()
401 (define-derived-mode kotlin-mode prog-mode "Kotlin"
402 "Major mode for editing Kotlin."
404 (setq font-lock-defaults '((kotlin-mode--font-lock-keywords) nil nil))
405 (setq-local syntax-propertize-function #'kotlin-mode--syntax-propertize-function)
406 (set (make-local-variable 'comment-start) "//")
407 (set (make-local-variable 'comment-padding) 1)
408 (set (make-local-variable 'comment-start-skip) "\\(//+\\|/\\*+\\)\\s *")
409 (set (make-local-variable 'comment-end) "")
410 (set (make-local-variable 'indent-line-function) 'kotlin-mode--indent-line)
411 (setq-local adaptive-fill-regexp comment-start-skip)
414 :syntax-table kotlin-mode-syntax-table)
417 (add-to-list 'auto-mode-alist '("\\.kts?\\'" . kotlin-mode) t)
419 (provide 'kotlin-mode)
420 ;;; kotlin-mode.el ends here