Files
memex/0_inbox/Scrivener_Emacs_Vim/elpa/jsx-mode-20130908.1024/jsx-mode.el

878 lines
29 KiB
EmacsLisp

;;; jsx-mode.el --- major mode for JSX
;; Copyright (c) 2012 DeNA, Co., Ltd (http://dena.jp/intl/)
;; Author: Takeshi Arabiki (abicky)
;; Version: 0.1.10
;; Package-Version: 20130908.1024
;; URL: https://github.com/jsx/jsx-mode.el
;; Permission is hereby granted, free of charge, to any person obtaining a copy
;; of this software and associated documentation files (the "Software"), to deal
;; in the Software without restriction, including without limitation the rights
;; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
;; copies of the Software, and to permit persons to whom the Software is
;; furnished to do so, subject to the following conditions:
;; The above copyright notice and this permission notice shall be included in
;; all copies or substantial portions of the Software.
;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
;; THE SOFTWARE.
;;; Commentary:
;; =============
;; Get Started
;; =============
;; Put this file in your Emacs lisp path (e.g. ~/.emacs.d/site-lisp)
;; and add to the following lines to your ~/.emacs.d/init.el.
;; (add-to-list 'auto-mode-alist '("\\.jsx\\'" . jsx-mode))
;; (autoload 'jsx-mode "jsx-mode" "JSX mode" t)
;; See also init.el.example.
;; ==============
;; Key Bindings
;; ==============
;; In `jsx-mode', the following keys are bound by default.
;; C-c C-c comment-region (Comment or uncomment each line in the region)
;; C-c c jsx-compile-file (Compile the current buffer)
;; C-c C jsx-compile-file-async (Compile the current buffer asynchronously)
;; C-c C-r jsx-run-buffer (Run the current buffer)
;; TODO:
;; * support imenu
;; * fix a bug that any token after implements is colored
;; e.g. 'J' will be colored in the code like 'class C implements I { J'
;; * support indentations for lambda statment
;; * support auto-complete
;;; Code:
(eval-when-compile
(require 'thingatpt)
(require 'flymake)
(require 'cl)
(require 'json)
(require 'auto-complete nil t)
(require 'popup nil t))
(defconst jsx-version "0.1.10"
"Version of `jsx-mode'")
(defgroup jsx nil
"JSX mode."
:group 'languages)
(defcustom jsx-indent-level 4
"indent level in `jsx-mode'"
:type 'integer
:group 'jsx-mode)
(defcustom jsx-cmd "jsx"
"jsx command for `jsx-mode'"
:type 'string
:group 'jsx-mode)
(defcustom jsx-cmd-options '()
"jsx command options for `jsx-mode'.
For example, if this value is '(\"--add-search-path\" \"/path/to/lib\"),
then execute command like \"jsx --add-search-path /path/to/lib --run sample.jsx\".
"
:type '(repeat string)
:group 'jsx-mode)
(defcustom jsx-node-cmd "node"
"node command for `jsx-mode'"
:type 'string
:group 'jsx-mode)
(defcustom jsx-syntax-check-mode "parse"
"Jsx compilation mode for the syntax check in `jsx-mode'.
The value should be \"parse\" or \"compile\". (Default: \"parse\")"
:type '(choice (const "parse")
(const "compile"))
:group 'jsx-mode)
(defcustom jsx-use-flymake nil
"Whether or not to use `flymake-mode' in `jsx-mode'."
:type 'boolean
:group 'jsx-mode)
(defcustom jsx-use-auto-complete nil
"Whether or not to use `auto-complete-mode' in `jsx-mode'."
:type 'boolean
:group 'jsx-mode)
(defvar jsx-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-c C-c") 'comment-region)
(define-key map (kbd "C-c c") 'jsx-compile-file)
(define-key map (kbd "C-c C") 'jsx-compile-file-async)
(define-key map (kbd "C-c C-r") 'jsx-run-buffer)
map))
(defvar jsx-mode-syntax-table
(let ((st (make-syntax-table)))
(modify-syntax-entry ?_ "w" st)
(modify-syntax-entry ?% "." st)
;; C-style comments
;; cf. Syntax Tables > Syntax Descriptors > Syntax Flags
(modify-syntax-entry ?/ ". 124b" st)
(modify-syntax-entry ?* ". 23" st)
(modify-syntax-entry ?\n "> b" st)
;; string
(modify-syntax-entry ?\' "\"" st)
st))
;; Constant variables
(defconst jsx--constant-variables
'("__FILE__" "__LINE__"
"Infinity"
"NaN"
"false"
"null"
"this" "true"
"undefined"))
(defconst jsx--keywords
'(;; literals shared with ECMA 262literals shared with ECMA 262
"null" "true" "false"
"NaN" "Infinity"
;; keywords shared with ECMA 262
"break" "do" "instanceof" "typeof"
"case" "else" "new" "var"
"catch" "finally" "return" "void"
"continue" "for" "switch" "while"
"function" "this"
"default" "if" "throw"
"delete" "in" "try"
;; keywords of JSX
"class" "extends" "super"
"import" "implements"
"interface" "static"
"__FILE__" "__LINE__"
"undefined")
"keywords defined in parser.js of JSX")
(defconst jsx--reserved-words
'(;; literals of ECMA 262 but not used by JSX
"debugger" "with"
;; future reserved words of ECMA 262
"const" "export"
;; future reserved words within strict mode of ECMA 262
"let" "private" "public" "yield"
"protected"
;; JSX specific reserved words
"extern" "native"
"trait" "using"
"as" "is"
"operator" "package")
"reserved words defined in parser.js of JSX")
(defconst jsx--contextual-keywords
'("__noconvert__" "__readonly__" "abstract" "final" "mixin" "override"))
(defconst jsx--builtin-functions
'("assert" "log"))
(defconst jsx--primary-types
'("boolean" "int" "number" "string"))
(defconst jsx--extra-types
'("MayBeUndefined" "Nullable" "void" "variant"))
(defconst jsx--reserved-classes
'("Array"
"Boolean"
"Date"
"Error" "EvalError"
"Function"
"Map"
"Number"
"Object"
"RangeError" "ReferenceError" "RegExp"
"String" "SyntaxError"
"TypeError"))
(defconst jsx--template-owners
'("Array" "Map" "MayBeUndefined"))
(defconst jsx--modifiers
'("static" "abstract" "override" "final" "const" "native" "__readonly__"))
(defconst jsx--class-definitions
'("class" "extends" "implements" "interface" "mixin"))
;; Regural expressions
(defconst jsx--identifier-start-re
"[a-zA-Z_]")
(defconst jsx--identifier-re
(concat jsx--identifier-start-re "[a-zA-Z0-9_]*"))
(defconst jsx--function-definition-re
(concat
"^\\s-*\\(?:\\(?:" (mapconcat 'identity jsx--modifiers "\\|") "\\)\\s-+\\)*"
"function\\s-+\\(" jsx--identifier-re "\\)"))
(defconst jsx--function-definition-in-map-re
(concat
"\\(?:^\\|,\\)\\s-*\\(" jsx--identifier-re "\\)\\s-*:\\s-*function\\s-*("))
(defconst jsx--keywords-re
(regexp-opt
(append jsx--keywords jsx--reserved-words jsx--contextual-keywords)
'words))
(defconst jsx--constant-variable-re
(regexp-opt jsx--constant-variables 'words))
(defconst jsx--primitive-type-re
(regexp-opt
(append jsx--primary-types jsx--extra-types jsx--template-owners)
'words))
(defconst jsx--reserved-class-re
(concat
"\\<\\("
(regexp-opt jsx--reserved-classes)
"\\)\\s-*[,;>(]"))
(defconst jsx--regex-literal-re
(concat
"\\(?:^\\|[(,;:=]\\)\\s-*"
;; beginning of a regex literal
"\\(/\\)"
;; first character
;; "/*" means beginning of a comment, so exclude "*"
"\\(?:\\[\\(?:\\\\.\\|[^]]\\)*\\]\\|\\\\.\\|[^/\\*\n]\\)"
"\\(?:\\[\\(?:\\\\.\\|[^]]\\)*\\]\\|\\\\.\\|\\|[^/\\\n]\\)*"
;; end of a regex literal
"\\(/\\)\\([gim]*\\)"))
(defconst jsx--builtin-function-re
(concat
"\\(?:^\\|[;{]\\)\\s-*\\("
(regexp-opt jsx--builtin-functions 'words)
"\\)"))
(defconst jsx--class-definition-re
(concat
(regexp-opt jsx--class-definitions 'words)
"\\s-+\\(" jsx--identifier-re "\\)"))
(defconst jsx--create-instance-re
(concat
"\\<new\\s-+"
;; call 'new foo.Foo' to create a Foo class instance defined in foo.jsx
;; if import "foo.jsx" into foo
"\\(?:" jsx--identifier-re "\\.\\)?"
"\\(" jsx--identifier-re "\\)"))
;; class name of the return value like function createFoo() : Foo {
(defconst jsx--return-class-re
(concat
")\\s-*:\\s-*"
"\\(?:" jsx--identifier-re "\\.\\)?"
"\\(" jsx--identifier-re "\\)\\s-*\\(?:[[,{]\\|$\\)"))
(defconst jsx--template-class-re
(concat "<\\s-*\\(" jsx--identifier-re "\\)\\s-*>"))
(defconst jsx--variable-definition-with-class-re
(concat
"\\<\\var\\s-+\\(" jsx--identifier-re "\\)"
"\\s-*:\\s-*"
"\\(?:" jsx--identifier-re "\\.\\)?"
"\\(" jsx--identifier-re "\\)""\\>"))
;; currently not support definitions like 'var a:int, b:int;'
(defconst jsx--variable-definition-re
(concat
"\\<\\var\\s-+\\(" jsx--identifier-re "\\)\\>"))
(defconst jsx--variable-and-type-re
(concat
"\\(" jsx--identifier-re "\\)\\s-*:\\s-*\\(" jsx--identifier-re "\\)"))
(defconst jsx--import-from-re
(concat
"import\\s-+\\(" jsx--identifier-re "\\)\\s-*\\(from\\|$\\)?"))
(defconst jsx--quoted-re
(concat
"'[^'\\]*\\(?:\\\\.[^'\\]*\\)*'" ; single quote
"\\|"
"\"[^\"\\]*\\(?:\\\\.[^\"\\]*\\)*\"")) ; double quote
(defconst jsx--import-into-re
(concat
"import\\s-+" jsx--quoted-re "\\s-+\\(into\\)"))
(defun jsx--in-arg-definition-p ()
(when (list-at-point)
(save-excursion
(search-backward "(" nil t)
(forward-symbol -1)
(or (equal (word-at-point) "function")
(progn (forward-symbol -1)
(equal (word-at-point) "function"))))))
(defvar jsx-font-lock-keywords
`(
(,jsx--constant-variable-re 0 font-lock-constant-face)
(,jsx--builtin-function-re 1 font-lock-builtin-face)
(,jsx--regex-literal-re 3 font-lock-string-face)
(,jsx--variable-definition-re 1 font-lock-variable-name-face)
(,jsx--primitive-type-re 0 font-lock-type-face)
(,jsx--reserved-class-re 1 font-lock-type-face)
(,jsx--keywords-re 0 font-lock-keyword-face)
(,jsx--class-definition-re 2 font-lock-type-face)
(,jsx--create-instance-re 1 font-lock-type-face)
(,jsx--template-class-re 1 font-lock-type-face)
(,jsx--return-class-re 1 font-lock-type-face)
(,jsx--import-from-re
(1 font-lock-type-face)
(2 font-lock-keyword-face))
(,jsx--import-into-re 1 font-lock-keyword-face)
(,jsx--variable-definition-with-class-re
(1 font-lock-variable-name-face)
(2 font-lock-type-face))
;; color names of interface or mixin like implements A, B, C
,(list
"\\(?:^\\|\\s-\\)implements\\s-+"
(list (concat "\\(" jsx--identifier-re "\\)\\s-*\\(?:[,{]\\|$\\)")
'(forward-symbol -1)
nil
'(1 font-lock-type-face)))
;; color class names like below (color 'B', 'I', and 'J')
;; class A
;; extends B
;; implements I, J {
;;
;; currently not color names like below (not color 'J')
;; class A
;; extends B
;; implements I,
;; J {
,(list
(concat
"^\\s-*\\(" jsx--identifier-re "\\)\\(?:\\s-\\|$\\)")
(list (concat "\\<" jsx--identifier-re "\\>")
'(if (save-excursion
(jsx--backward-non-comment-word 2)
(looking-at (concat
(regexp-opt jsx--class-definitions)
"\\s-*$")))
(backward-word)
(end-of-line))
nil
'(0 font-lock-type-face)))
;; color function arguments like function(a: int, b:int)
,(list
(concat
"\\<function\\>\\(?:\\s-+" jsx--identifier-re "\\)?\\s-*(\\s-*")
(list jsx--variable-and-type-re
'(unless (jsx--in-arg-definition-p) (end-of-line))
nil
'(1 font-lock-variable-name-face)
'(2 font-lock-type-face)))
(,(concat "<\\s-*\\(" jsx--identifier-re "\\)\\s-*>") 1 font-lock-type-face)
;; color classes of function arugments like function(:int, :int)
,(list
(concat
"\\<function\\>\\s-*(\\s-*")
(list (concat "\\s-*:\\s-*\\(" jsx--identifier-re "\\)")
'(unless (jsx--in-arg-definition-p) (end-of-line))
nil
'(1 font-lock-type-face)))
;; color function arguments
;; function(a: int,
;; b:int)
;;
;; currently not color arguments like below
;; function(a:
;; int,
;; b
;; :int)
,(list
(concat
"^\\s-*,?\\s-*" jsx--variable-and-type-re)
(list jsx--variable-and-type-re
'(if (save-excursion (backward-char)
(jsx--in-arg-definition-p))
(forward-symbol -2)
(end-of-line))
nil
'(1 font-lock-variable-name-face)
'(2 font-lock-type-face)))
;; color classes of function arguments like below
;; function(:int,
;; :int)
;;
;; currently not color classes like below
;; function(:
;; int,
;; :int)
,(list
(concat
"^\\s-*,?\\s-*:\\s-*\\(" jsx--identifier-re "\\)")
(list (concat "\\s-*:\\s-*\\(" jsx--identifier-re "\\)")
'(if (save-excursion (backward-char)
(jsx--in-arg-definition-p))
(search-backward ":")
(end-of-line))
nil
'(1 font-lock-type-face)))
;; function names should be colored after coloring arguments,
;; otherwise arguments will be colored
;; e.g. function(b : string, a : function() : void)
(,jsx--function-definition-re 1 font-lock-function-name-face)
(,jsx--function-definition-in-map-re 1 font-lock-function-name-face)
))
(defvar jsx-font-lock-syntactic-keywords
`((,jsx--regex-literal-re (1 "|") (2 "|"))))
(defun jsx--in-string-or-comment-p (&optional pos)
(nth 8 (syntax-ppss pos)))
(defun jsx--in-comment-p (&optional pos)
(nth 4 (syntax-ppss pos)))
(defun jsx--non-block-statement-p ()
(save-excursion
(jsx--go-to-previous-non-comment-char)
(or (string-match-p "\\(?:do\\|else\\)\\_>" (or (word-at-point) ""))
(and (= (char-after) ?\))
(progn
(forward-char)
(backward-list)
(backward-word)
(looking-at "\\(?:for\\|if\\|while\\)\\_>"))))))
(defun jsx--in-condition-p ()
(when (list-at-point)
(save-excursion
(search-backward "(" nil t)
(forward-symbol -1)
(let ((word (word-at-point)))
(or (equal word "while") (equal word "if"))))))
(defun jsx--go-to-previous-non-comment-char ()
(search-backward-regexp "[[:graph:]]" nil t)
(while (jsx--in-comment-p)
;; move to the beginning of the comment
(search-backward-regexp "/\\*\\|//" nil t)
;; move to the previous visible character
(search-backward-regexp "[[:graph:]]" nil t)))
(defun jsx--go-to-next-non-comment-char ()
(if (looking-at "[[:graph:]]")
(forward-char))
(search-forward-regexp "[[:graph:]]" nil t)
(while (save-excursion (forward-char) (jsx--in-comment-p))
;; move to the end of the comment
(search-forward-regexp "\\*/\\|$" nil t)
;; move to the next visible character
(search-forward-regexp "[[:graph:]]" nil t)))
(defun jsx--backward-non-comment-word (&optional arg)
(let ((cnt (or arg 1)))
(while (> cnt 0)
(backward-word)
(while (jsx--in-comment-p)
(backward-word))
(setq cnt (1- cnt)))))
(defun jsx--backward-up-list (&optional level ppss)
"Move back outside of parentheses LEVEL times
and return the position if suceeded.
If LEVEL is larger than the current depth, the ourermost leve is used."
(let* ((pos-list (nth 9 (or ppss (syntax-ppss))))
(pos (nth (- (length pos-list) (or level 1)) pos-list)))
(and pos (goto-char pos))))
(defun jsx-indent-line ()
(interactive)
(let ((indent-length (jsx--calculate-indentation))
(offset (- (current-column) (current-indentation))))
(when indent-length
(indent-line-to indent-length)
(if (> offset 0) (forward-char offset)))))
(defun jsx--calculate-indentation ()
;; TODO: refactoring
(save-excursion
(back-to-indentation)
(let* ((cw (current-word t))
(ca (char-after))
(ppss (syntax-ppss)))
(cond
((eq (syntax-ppss-context ppss) 'string)
nil)
((eq (syntax-ppss-context ppss) 'comment)
(save-excursion
(let ((end-of-comment-p (looking-at "\\*/")))
(forward-line -1)
(back-to-indentation)
(if (eq (point) (nth 8 ppss))
;; the previous line is the beginning of the comment
(+ (current-column) (if (eq ca ?*) 1 2))
;; the indentation level of the end of the comment
;; should be the same level as its beginning
;; if the previous line doesn't begin with '*'
(when (and end-of-comment-p
(not (eq (char-after) ?*)))
(goto-char (nth 8 ppss)))
(current-column)))))
((eq ca ?{)
(progn
(jsx--go-to-previous-non-comment-char)
(if (eq (char-after) ?\[)
(+ (current-indentation) jsx-indent-level)
(current-indentation))))
((and
(or (eq ca ?})
(eq ca ?\))
(eq ca ?\])
(equal cw "case")
(equal cw "default"))
(jsx--backward-up-list 1 ppss))
(back-to-indentation)
(while (or (jsx--in-arg-definition-p) (jsx--in-condition-p))
(jsx--backward-up-list)
(back-to-indentation))
(current-indentation))
((jsx--non-block-statement-p)
(+ (progn
(jsx--go-to-previous-non-comment-char)
(current-indentation))
jsx-indent-level))
((or (jsx--in-arg-definition-p) (jsx--in-condition-p))
(progn
(jsx--go-to-previous-non-comment-char)
(if (= (char-after) ?\()
(+ (current-indentation) jsx-indent-level)
(jsx--backward-up-list)
(jsx--go-to-next-non-comment-char)
(backward-char)
(current-column))))
((jsx--backward-up-list 1 ppss)
(back-to-indentation)
(while (or (jsx--in-arg-definition-p) (jsx--in-condition-p))
(jsx--backward-up-list)
(back-to-indentation))
(+ (current-column) jsx-indent-level))
(t 0)
))))
;; compile or run the buffer
(defun jsx--some-buffers-modified-p ()
(let ((bufs (buffer-list))
buf modified-p)
(while (and (not modified-p) bufs)
(setq buf (car bufs))
(when (string-match-p "\\.jsx\\'" (buffer-name buf))
(with-current-buffer buf
(setq modified-p (buffer-modified-p))))
(setq bufs (cdr bufs)))
modified-p))
(defun jsx--generate-cmd (&optional options)
(setq options (append jsx-cmd-options options))
(format "%s %s" jsx-cmd (mapconcat 'identity options " ")))
(defun jsx-compile-file (&optional options dst async)
"Compile the JSX script of the current buffer
and make a JS script in the same directory."
(interactive)
(if (jsx--some-buffers-modified-p)
(save-some-buffers nil t))
;; FIXME: file-name-nondirectory needs temporarily
(let* ((jsx-file (file-name-nondirectory (buffer-file-name)))
(js-file (or dst (substring jsx-file 0 -1)))
cmd)
(setq options (append jsx-cmd-options options))
(setq cmd (jsx--generate-cmd
(append options (list "--output" js-file jsx-file))))
(if async
(setq cmd (concat cmd " &")))
(message "Compiling...: %s" cmd)
(if (eq (shell-command cmd) 0) js-file nil)))
(defun jsx-compile-file-async (&optional options dst)
"Compile the JSX scirpt of the current buffer asynchronously
and make a JS script in the same directory."
(interactive)
(jsx-compile-file options dst t))
(defun jsx-compile-file-and-run ()
"Compile the JSX script of the current buffer,
make a JS script in the same directory, and run it."
(interactive)
(let* ((js-file (jsx-compile-file '("--executable")))
(cmd (format "%s %s" jsx-node-cmd js-file)))
(if js-file
(shell-command cmd))))
(defun jsx-run-buffer (&optional options)
"Run the JSX script of the current buffer."
(interactive)
(if (jsx--some-buffers-modified-p)
(save-some-buffers nil t))
(let ((jsx-file (file-name-nondirectory (buffer-file-name)))
(cmd jsx-cmd))
(setq options (append jsx-cmd-options options))
(setq cmd (jsx--generate-cmd
(append options (list "--run" jsx-file))))
(setq cmd (format "%s --run %s" cmd jsx-file))
(shell-command cmd)))
;; flymake
(defvar jsx-err-line-patterns
'(("\\[\\([^:]+\\):\\([0-9]+\\)\\(?::[0-9]+\\)?\\] \\(.*\\)" 1 2 nil 3)))
(defun jsx-flymake-on ()
"Turn on `flymake-mode' in `jsx-mode'"
(interactive)
(set (make-local-variable 'flymake-err-line-patterns) jsx-err-line-patterns)
(add-to-list 'flymake-allowed-file-name-masks '("\\.jsx\\'" jsx--flymake-init))
(flymake-mode t))
(defun jsx-flymake-off ()
"Turn off `flymake-mode' in `jsx-mode'"
(interactive)
(flymake-mode 0))
(defun jsx--flymake-init ()
(let* ((temp-file (flymake-init-create-temp-buffer-copy
;; if use import "*.jsx", _flymake.jsx is very annoying,
;; so not use 'flymake-create-temp-inplace
(lambda (file-name prefix)
(concat
(flymake-create-temp-inplace file-name prefix) ".tmp"))))
(local-file (file-relative-name
temp-file
(file-name-directory buffer-file-name))))
(list jsx-cmd (append jsx-cmd-options
(list
"--mode" jsx-syntax-check-mode
"--output" "/dev/null"
local-file)))))
(defun jsx--get-errs-for-current-line ()
"Return the list of errors/warnings for the current line"
(let* ((line-no (flymake-current-line-no))
(line-err-info-list (nth 0 (flymake-find-err-info flymake-err-info line-no)))
(msgs '()))
(dolist (err-info line-err-info-list)
(let* ((text (flymake-ler-text err-info))
(line (flymake-ler-line err-info)))
(setq msgs (append msgs (list (format "[%s] %s" line text))))))
msgs))
(defun jsx-display-err-for-current-line ()
"Display the errors/warnings for the current line in the echo area
if there are any errors or warnings in `jsx-mode'."
(interactive)
(let ((msgs (jsx--get-errs-for-current-line)))
(message (mapconcat 'identity msgs "\n"))))
(defun jsx-display-popup-err-for-current-line ()
"Display a popup window with errors/warnings for the current line
if there are any errors or warnings in `jsx-mode'."
(interactive)
(let ((msgs (jsx--get-errs-for-current-line)))
(if (require 'popup nil t)
(popup-tip (mapconcat 'identity msgs "\n"))
(message "`popup' is not instelled."))))
;; auto-complete
(defvar jsx--try-to-show-document-p nil)
(defvar jsx--hard-line-feed
(propertize "\n" 'hard t)
"Line feed for `fill-region'")
(defvar jsx-ac-source
'((candidates . jsx--get-candidates)
(document . jsx--get-document)
(prefix . jsx--ac-prefix)
(cache)))
(defun jsx--ac-prefix ()
"Enable completion even if after invisible characters."
(or (ac-prefix-default) (point)))
(defadvice auto-complete (before jsx--add-requires-to-ac-source activate)
"Invoke completion whenever auto-complete is executed."
(if (string= major-mode "jsx-mode")
(add-to-list 'jsx-ac-source '(requires . 0))))
(defadvice auto-complete (after jsx--remove-requires-from-ac-source activate)
(if (string= major-mode "jsx-mode")
(setq jsx-ac-source (delete '(requires . 0) jsx-ac-source))))
(defun jsx--copy-buffer-to-tmp-file ()
(let ((tmpfile (make-temp-name (concat (buffer-file-name) "."))))
(write-region nil nil tmpfile nil 'silent)
tmpfile))
(defun jsx--sort-candidates (a b)
(string< (assoc-default 'word a) (assoc-default 'word b)))
(defun jsx--make-method-string (method args return-type)
(format "%s(%s) : %s"
method
(mapconcat
(lambda (arg)
(format "%s : %s"
(assoc-default 'name arg)
(assoc-default 'type arg)))
args ", ")
return-type))
(defun jsx--parse-candidates (str)
;; JSON example (cf. src/completion.jsx of JSX)
;; {
;; "word" : "stringify",
;; "partialWord" : "ringify",
;; "doc" : "serialize a value or object as a JSON string",
;; "type" : "function (value : variant) : string",
;; "returnType" : "string",
;; "args" : [ { "name" : "value", "type" : "variant" } ],
;; "definedClass" : "JSON",
;; "definedFilename" : "lib/built-in/jsx",
;; "definedLineNumber" : 903
;; }
(let* ((json-array-type 'list)
(candidates-info (json-read-from-string str)))
(when candidates-info
(setq candidates-info (sort candidates-info 'jsx--sort-candidates))
(loop with (candidates docs symbol)
for info in candidates-info
;; (assoc-default 'args info) is nil if the method has no arguments
for args = (assq 'args info)
for desc = (or (assoc-default 'doc info) "not documented")
for prev-word = nil then word
for word = (assoc-default 'word info)
for name = word
when (and prev-word (not (equal word prev-word)))
collect (propertize prev-word 'docs docs 'symbol symbol)
into candidates
and do (setq docs)
and do (setq symbol)
do (when args
(setq symbol "f")
(setq name (jsx--make-method-string
word (cdr args) (assoc-default 'returnType info))))
collect `((name . ,name) (desc . ,desc)) into docs
finally return
(nconc candidates
(list (propertize word 'docs docs 'symbol symbol)))))))
(defun jsx--get-candidates ()
(let* ((tmpfile (jsx--copy-buffer-to-tmp-file))
(line (line-number-at-pos))
;; don't use (current-column) for tab indents
(col (1+ (- (point) (line-beginning-position))))
(cmd (jsx--generate-cmd
(list "--complete" (format "%d:%d" line col) tmpfile))))
(with-temp-buffer
(unwind-protect
(call-process-shell-command cmd nil t)
(delete-file tmpfile))
(jsx--parse-candidates (buffer-string)))))
(defadvice fill-region (before jsx--fill-region activate)
"Preserve the line feeds in documents
cf. https://github.com/auto-complete/popup-el/issues/43"
(when jsx--try-to-show-document-p
(beginning-of-buffer)
(replace-string "\n" jsx--hard-line-feed)
(setq use-hard-newlines t)))
(defun jsx--sort-docs (a b)
(or
(string< (assoc-default 'desc a) (assoc-default 'desc b))
(string< (assoc-default 'name a) (assoc-default 'name b))))
(defun jsx--get-document (candidate)
(setq jsx--try-to-show-document-p t)
(run-at-time 0 nil (lambda() (setq jsx--try-to-show-document-p)))
(let ((docs (get-text-property 0 'docs candidate)))
(when docs
(setq docs (sort (copy-alist docs) 'jsx--sort-docs))
(loop with document and names
for doc in docs
for prev-desc = nil then desc
for desc = (assoc-default 'desc doc)
do (when (and prev-desc (not (equal desc prev-desc)))
(if (and document (not (equal document "")))
(setq document (concat "\n\n" document)))
(setq document (format "%s\n\n%s" names prev-desc))
(setq names nil))
do (if names
(setq names (format "%s\n%s" names (assoc-default 'name doc)))
(setq names (assoc-default 'name doc)))
finally return
(if document
(format "%s\n\n%s\n\n%s" document names desc)
(format "%s\n\n%s" names desc))))))
;;;###autoload
(define-derived-mode jsx-mode fundamental-mode "Jsx"
:syntax-table jsx-mode-syntax-table
(set (make-local-variable 'font-lock-defaults)
'(jsx-font-lock-keywords nil nil))
(set (make-local-variable 'font-lock-syntactic-keywords)
jsx-font-lock-syntactic-keywords)
(set (make-local-variable 'indent-line-function) 'jsx-indent-line)
(set (make-local-variable 'comment-start) "// ")
(set (make-local-variable 'comment-end) "")
(when (and jsx-use-auto-complete (require 'auto-complete nil t))
(require 'json)
(add-to-list 'ac-modes 'jsx-mode)
(setq ac-sources '(jsx-ac-source ac-source-filename)))
(if jsx-use-flymake
(jsx-flymake-on)))
(provide 'jsx-mode)
;;; jsx-mode.el ends here