Last active
July 2, 2018 00:11
-
-
Save anachronic/c88b695caa4ed870eedae8e37c12ff5b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(use-package js2-mode | |
:ensure t | |
:mode "\\.js\\'" | |
:config | |
(setq js2-basic-offset 2) | |
(setq-default js2-mode-show-parse-errors nil | |
js2-mode-show-strict-warnings nil | |
js2-idle-timer-delay 0.4) | |
(with-eval-after-load 'dumb-jump | |
(define-key js2-mode-map (kbd "M-.") 'dumb-jump-go) | |
(define-key js2-mode-map (kbd "M-,") 'dumb-jump-back)) | |
;; Set sensible mode lighters... | |
;; (add-hook 'js2-mode-hook (lambda () (setq-local mode-name "Javascript"))) | |
;; (add-hook 'js2-jsx-mode-hook (lambda () (setq-local mode-name "JSX"))) | |
;; I tend to write javascript in camelCase. | |
(add-hook 'js2-mode-hook 'subword-mode) | |
;; The following comes from: | |
;; https://github.com/redguardtoo/emacs.d/blob/master/lisp/init-javascript.el | |
;; Which comes from | |
;; https://emacs.stackexchange.com/a/3885/13735 | |
;; Feature is imenu for Angular and I believe others | |
(defvar javascript-common-imenu-regex-list | |
'(("Attribute" " \\([a-z][a-zA-Z0-9-_]+\\) *= *\{[a-zA-Z0-9_.(), ]+\}\\( \\|$\\)" 1) | |
("Controller" "[. \t]controller([ \t]*['\"]\\([^'\"]+\\)" 1) | |
("Controller" "[. \t]controllerAs:[ \t]*['\"]\\([^'\"]+\\)" 1) | |
("Filter" "[. \t]filter([ \t]*['\"]\\([^'\"]+\\)" 1) | |
("State" "[. \t]state[(:][ \t]*['\"]\\([^'\"]+\\)" 1) | |
("Factory" "[. \t]factory([ \t]*['\"]\\([^'\"]+\\)" 1) | |
("Service" "[. \t]service([ \t]*['\"]\\([^'\"]+\\)" 1) | |
("Module" "[. \t]module( *['\"]\\([a-zA-Z0-9_.]+\\)['\"], *\\[" 1) | |
("ngRoute" "[. \t]when(\\(['\"][a-zA-Z0-9_\/]+['\"]\\)" 1) | |
("Directive" "[. \t]directive([ \t]*['\"]\\([^'\"]+\\)" 1) | |
("Event" "[. \t]\$on([ \t]*['\"]\\([^'\"]+\\)" 1) | |
("Config" "[. \t]config([ \t]*function *( *\\([^\)]+\\)" 1) | |
("Config" "[. \t]config([ \t]*\\[ *['\"]\\([^'\"]+\\)" 1) | |
("Value" "[. \t]value([ \t]*['\"]\\([^'\"]+\\)" 1) | |
("OnChange" "[ \t]*\$(['\"]\\([^'\"]*\\)['\"]).*\.change *( *function" 1) | |
("OnClick" "[ \t]*\$([ \t]*['\"]\\([^'\"]*\\)['\"]).*\.click *( *function" 1) | |
("Watch" "[. \t]\$watch( *['\"]\\([^'\"]+\\)" 1) | |
("Function" "function[ \t]+\\([a-zA-Z0-9_$.]+\\)[ \t]*(" 1) | |
("Function" "^[ \t]*\\([a-zA-Z0-9_$.]+\\)[ \t]*=[ \t]*function[ \t]*(" 1) | |
;; ;; {{ es6 beginning | |
;; ("Function" "^[ \t]*\\([A-Za-z_$][A-Za-z0-9_$]+\\)[ \t]*([a-zA-Z0-9, ]*) *\{ *$" 1) ;; es6 fn1 () { } | |
;; ("Function" "^[ \t]*\\([A-Za-z_$][A-Za-z0-9_$]+\\)[ \t]*=[ \t]*(?[a-zA-Z0-9, ]*)?[ \t]*=>" 1) ;; es6 fn1 = (e) => | |
;; ;; }} | |
("Task" "[. \t]task([ \t]*['\"]\\([^'\"]+\\)" 1) | |
)) | |
;; {{ patching imenu in js2-mode | |
(setq js2-imenu-extra-generic-expression javascript-common-imenu-regex-list) | |
(defvar js2-imenu-original-item-lines nil | |
"List of line infomration of original imenu items.") | |
(defun js2-imenu--get-line-start-end (pos) | |
(let* (b e) | |
(save-excursion | |
(goto-char pos) | |
(setq b (line-beginning-position)) | |
(setq e (line-end-position))) | |
(list b e))) | |
(defun js2-imenu--get-pos (item) | |
(let* (val) | |
(cond | |
((integerp item) | |
(setq val item)) | |
((markerp item) | |
(setq val (marker-position item)))) | |
val)) | |
(defun js2-imenu--get-extra-item-pos (item) | |
(let* (val) | |
(cond | |
((integerp item) | |
(setq val item)) | |
((markerp item) | |
(setq val (marker-position item))) | |
;; plist | |
((and (listp item) (listp (cdr item))) | |
(setq val (js2-imenu--get-extra-item-pos (cadr item)))) | |
;; alist | |
((and (listp item) (not (listp (cdr item)))) | |
(setq val (js2-imenu--get-extra-item-pos (cdr item))))) | |
val)) | |
(defun js2-imenu--extract-line-info (item) | |
"Recursively parse the original imenu items created by js2-mode. | |
The line numbers of items will be extracted." | |
(let* (val) | |
(if item | |
(cond | |
;; Marker or line number | |
((setq val (js2-imenu--get-pos item)) | |
(push (js2-imenu--get-line-start-end val) | |
js2-imenu-original-item-lines)) | |
;; The item is Alist, example: (hello . 163) | |
((and (listp item) (not (listp (cdr item)))) | |
(setq val (js2-imenu--get-pos (cdr item))) | |
(if val (push (js2-imenu--get-line-start-end val) | |
js2-imenu-original-item-lines))) | |
;; The item is a Plist | |
((and (listp item) (listp (cdr item))) | |
(js2-imenu--extract-line-info (cadr item)) | |
(js2-imenu--extract-line-info (cdr item))) | |
;;Error handling | |
(t (message "Impossible to here! item=%s" item)))))) | |
(defun js2-imenu--item-exist (pos lines) | |
"Try to detect does POS belong to some LINE" | |
(let* (rlt) | |
(dolist (line lines) | |
(if (and (< pos (cadr line)) (>= pos (car line))) | |
(setq rlt t))) | |
rlt)) | |
(defun js2-imenu--is-item-already-created (item) | |
(unless (js2-imenu--item-exist | |
(js2-imenu--get-extra-item-pos item) | |
js2-imenu-original-item-lines) | |
item)) | |
(defun js2-imenu--check-single-item (r) | |
(cond | |
((and (listp (cdr r))) | |
(let (new-types) | |
(setq new-types | |
(delq nil (mapcar 'js2-imenu--is-item-already-created (cdr r)))) | |
(if new-types (setcdr r (delq nil new-types)) | |
(setq r nil)))) | |
(t (if (js2-imenu--item-exist (js2-imenu--get-extra-item-pos r) | |
js2-imenu-original-item-lines) | |
(setq r nil)))) | |
r) | |
(defun my-validate-json-or-js-expression (&optional not-json-p) | |
"Validate buffer or select region as JSON. | |
If NOT-JSON-P is not nil, validate as Javascript expression instead of JSON." | |
(interactive "P") | |
(let* ((json-exp (if (region-active-p) (my-selected-str) | |
(my-buffer-str))) | |
(jsbuf-offet (if not-json-p 0 (length "var a="))) | |
errs | |
first-err | |
(first-err-pos (if (region-active-p) (region-beginning) 0))) | |
(unless not-json-p | |
(setq json-exp (format "var a=%s;" json-exp))) | |
(with-temp-buffer | |
(insert json-exp) | |
(unless (featurep 'js2-mode) | |
(require 'js2-mode)) | |
(js2-parse) | |
(setq errs (js2-errors)) | |
(cond | |
((not errs) | |
(message "NO error found. Good job!")) | |
(t | |
;; yes, first error in buffer is the last element in errs | |
(setq first-err (car (last errs))) | |
(setq first-err-pos (+ first-err-pos (- (cadr first-err) jsbuf-offet))) | |
(message "%d error(s), first at buffer position %d: %s" | |
(length errs) | |
first-err-pos | |
(js2-get-msg (caar first-err)))))) | |
(if first-err (goto-char first-err-pos)))) | |
(defun my-print-json-path (&optional hardcoded-array-index) | |
"Print the path to the JSON value under point, and save it in the kill ring. | |
If HARDCODED-ARRAY-INDEX provided, array index in JSON path is replaced with it." | |
(interactive "P") | |
(cond | |
((memq major-mode '(js2-mode)) | |
(js2-print-json-path hardcoded-array-index)) | |
(t | |
(let* ((cur-pos (point)) | |
(str (my-buffer-str))) | |
(when (string= "json" (file-name-extension buffer-file-name)) | |
(setq str (format "var a=%s;" str)) | |
(setq cur-pos (+ cur-pos (length "var a=")))) | |
(unless (featurep 'js2-mode) | |
(require 'js2-mode)) | |
(with-temp-buffer | |
(insert str) | |
(js2-init-scanner) | |
(js2-do-parse) | |
(goto-char cur-pos) | |
(js2-print-json-path)))))) | |
(defun js2-imenu--remove-duplicate-items (extra-rlt) | |
(delq nil (mapcar 'js2-imenu--check-single-item extra-rlt))) | |
(defun js2-imenu--merge-imenu-items (rlt extra-rlt) | |
"RLT contains imenu items created from AST. | |
EXTRA-RLT contains items parsed with simple regex. | |
Merge RLT and EXTRA-RLT, items in RLT has *higher* priority." | |
;; Clear the lines. | |
(set (make-variable-buffer-local 'js2-imenu-original-item-lines) nil) | |
;; Analyze the original imenu items created from AST, | |
;; I only care about line number. | |
(dolist (item rlt) | |
(js2-imenu--extract-line-info item)) | |
;; @see https://gist.github.com/redguardtoo/558ea0133daa72010b73#file-hello-js | |
;; EXTRA-RLT sample: | |
;; ((function ("hello" . #<marker 63>) ("bye" . #<marker 128>)) | |
;; (controller ("MyController" . #<marker 128)) | |
;; (hellworld . #<marker 161>)) | |
(setq extra-rlt (js2-imenu--remove-duplicate-items extra-rlt)) | |
(append rlt extra-rlt)) | |
;; {{ print json path, will be removed when latest STABLE js2-mode released | |
(defun js2-get-element-index-from-array-node (elem array-node &optional hardcoded-array-index) | |
"Get index of ELEM from ARRAY-NODE or 0 and return it as string." | |
(let* ((idx 0) elems (rlt hardcoded-array-index)) | |
(setq elems (js2-array-node-elems array-node)) | |
(if (and elem (not hardcoded-array-index)) | |
(setq rlt (catch 'nth-elt | |
(dolist (x elems) | |
;; We know the ELEM does belong to ARRAY-NODE, | |
(if (eq elem x) (throw 'nth-elt idx)) | |
(setq idx (1+ idx))) | |
0))) | |
(format "[%s]" rlt))) | |
;; }} | |
(eval-after-load 'js2-mode | |
'(progn | |
(defadvice js2-mode-create-imenu-index (around my-js2-mode-create-imenu-index activate) | |
(let (rlt extra-rlt) | |
ad-do-it | |
(setq extra-rlt | |
(save-excursion | |
(imenu--generic-function js2-imenu-extra-generic-expression))) | |
(setq ad-return-value (js2-imenu--merge-imenu-items ad-return-value extra-rlt)) | |
ad-return-value)))) | |
;; }} | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment