(require 'dojo-common-containers) (defun company-dojo-backend (command &optional arg &rest ignored) (cl-case command (interactive (company-begin-backend 'company-dojo-backend)) ; (prefix (and (bound-and-true-p dojo-minor-mode) ; This line would test for dojo-minor-mode (prefix (and (eq major-mode 'dojo-major-mode) (dojo-get-current-completion-path))) (candidates (dojo-get-completion-candidates arg)) (annotation (dojo-get-completion-annotation arg)) (meta (dojo-get-completion-doc arg)) (post-completion (dojo-js-completion-post-completion arg)) )) (defun dojo-js-completion-post-completion (candidate) (log-completion 0 (format "Called post-completion for candidate [%s]" candidate)) (log-completion 0 (format "... symbol-id [%s], class-id [%s], is-api [%s]" (get-text-property 0 :symbol-id candidate) (get-text-property 0 :class-id candidate) (get-text-property 0 :is-api candidate))) (let* ((symbol-id (get-text-property 0 :symbol-id candidate)) (class-id (get-text-property 0 :class-id candidate)) (is-api (get-text-property 0 :is-api candidate)) (point-after-insertion nil) (orig-point (point))) (if (and (not (null symbol-id)) (not (null class-id))) (let* ((id-to-class (if is-api (dojo-workspace-id-to-api-class dojo-current-workspace) (dojo-workspace-id-to-class dojo-current-workspace))) (class (gethash class-id id-to-class)) (id-to-symbol (if class (dojo-class-id-to-symbol class) nil)) (symbol (if id-to-symbol (gethash symbol-id id-to-symbol) nil)) (parent-symbol (if symbol (dojo-symbol-get-parent dojo-current-workspace symbol) nil)) (region-start (save-excursion (beginning-of-line) (point)))) (log-completion 0 (format "... symbol is [%s]" (dojo-core-util-symbol-to-short-string symbol))) (if symbol (progn (dojo-js-symbols-populate-buffer-with-symbol 0 symbol) (cond ((dojo-core-util-is-async-function symbol) (dojo-js-completion-insert-async-callback symbol) (setq point-after-insertion (point)) (goto-char (1+ orig-point))) ((dojo-core-util-is-function-symbol symbol) (insert "()") (setq point-after-insertion (point)) (goto-char (1+ orig-point))) (t (setq point-after-insertion (point)))))) (let* ((region-end (save-excursion (goto-char point-after-insertion) (end-of-line) (point)))) (dojo-js-indent-region region-start region-end)))))) (defun dojo-js-completion-insert-async-callback (symbol) (let* ((symbol-name-raw (dojo-symbol-name symbol)) (symbol-name (if (and symbol-name-raw (> (length symbol-name-raw) 0)) symbol-name-raw "unknownName")) (return-type (dojo-symbol-get-function-return-type symbol)) (return-type-name-raw (if return-type (dojo-symbol-name return-type) "result")) (return-type-name (if (and return-type-name-raw (> (length return-type-name-raw) 0)) return-type-name-raw "result")) (current-class-name-raw (dojo-core-util-get-class-name (dojo-workspace-get-current-class))) (current-class-name (if current-class-name-raw current-class-name-raw "UnknownClass")) (i18n-not-found-var (if (and current-class-name symbol-name) (concat (dojo-strings-get-with-lower-first-char current-class-name) (dojo-strings-get-with-upper-first-char symbol-name) "Failed") "todo"))) (insert "().then(") (newline) (insert (concat "lang.hitch(this, function(" return-type-name ") {")) (newline) (insert (concat "this.registerAsyncOperationFinished(" current-class-name ".AsyncOperation.TODO);")) (newline) (newline) (insert "}), lang.hitch(this, function(err) {") (newline) (insert "ErrorHelper.processAsyncError({") (newline) (insert "err : err,") (newline) (insert "widget : this,") (newline) (insert (concat "asyncOperation : " current-class-name ".AsyncOperation.TODO,")) (newline) (insert (concat "opName : \"" symbol-name "\",")) (newline) (insert (concat "message : i18n." i18n-not-found-var)) (newline) (insert "});") (newline) (insert "})).otherwise(") (newline) (insert "lang.hitch(this, function(err) {") (newline) (insert (concat "log.error(\"Error while calling function [" symbol-name "]\", err);")) (newline) (insert "}));") (newline))) ; Use this to additionally activate company-dojo-backend, instead of activating it exclusively ;(add-to-list 'company-backends 'company-dojo-backend) (defun dojo-get-completion-candidates (prefix) (log-completion 0 (format "dojo-get-completion-candidates, called with prefix %s" prefix)) (setq dojo-completion-candidates ()) (let* ((workspace dojo-current-workspace) (resource (dojo-core-util-get-current-resource)) (current-class (dojo-core-util-get-or-load-class workspace resource)) (ancestor-path (dojo-get-ancestor-path)) (ancestor-index 0)) ; Debug (dojo-print-ancestor-path ancestor-path) (cond ((null resource) (log-completion 0 "[WARNING] resource is nil, will do nothing.")) ((null current-class) (log-completion 0 (format "[WARNING] current-class for current-resource [%s:%s] is nil, will do nothing." (dojo-resource-project resource) (dojo-resource-path resource))) nil) (; Dive down the ancestor path by recursively calling appropriate functions. Depending on ; where in the tree we are, we try to generate appropriate completions. They are ; successively added to the dojo-completion-candidates list, which in the end is ; returned as the result of this function. (and current-class (dojo-is-ancestor-of-type ancestor-path ancestor-index "js2-ast-root")) (log-completion 0 (format "Deriving completion candidates for prefix [%s]" prefix)) (log-completion 0 (format "... class is [%s:%s:%s:%s]" (dojo-class-id current-class) (dojo-class-project current-class) (dojo-class-path current-class) (dojo-class-is-api-class current-class))) (dojo-add-root-candidates ancestor-path (1+ ancestor-index) prefix current-class) (log-completion 0 (format "==================================")) (log-completion 0 (format "Found [%s] completion candidates:" (length dojo-completion-candidates))) (dolist (candidate dojo-completion-candidates) (log-completion 0 (format "Registered candidate: %s" candidate))) (log-completion 0 (format "==================================")) dojo-completion-candidates) (t (log-completion 0 "[WARNING] No current-class found, or head of ancestor-path is no [js2-ast-root], will do nothing."))))) (defun dojo-add-root-candidates (ancestor-path ancestor-index prefix current-class) (log-completion 0 (format "[%s] Called add-root-candidates" ancestor-index)) ; Here, we test for a sequence "js2-expr-stmt-node, js2-call-node" at [ancestor-index, (1+ ancestor-index], ; representing the define call of a Dojo module. (cond ((dojo-is-call-expr-in-ancestor-path ancestor-path ancestor-index "define") (dojo-add-define-candidates ancestor-path (+ ancestor-index 2) prefix current-class)))) (defun dojo-add-define-candidates (ancestor-path ancestor-index prefix current-class) "Function adding completion candidates, if we are somewhere inside the define call. The ancestor-index is supposed to point to the js2-array-node below the js2-call-node representing the define call." (log-completion 0 (format "[%s] Called add-define-candidates" ancestor-index)) ; According to the define function specification (see https://github.com/amdjs/amdjs-api/blob/master/AMD.md), ; the first parameter is an (optional) string literal, the second a (optional) array, and the third a function. ; Thus, the node type is sufficient for deciding what to do here. (let* ((curr-node (nth ancestor-index ancestor-path))) (cond ((dojo-has-node-type curr-node "js2-array-node") (dojo-add-import-path-candidates ancestor-path ancestor-index prefix current-class)) ((dojo-has-node-type curr-node "js2-function-node") (let ((define-symbol (dojo-class-define-symbol current-class)) (scopes ()) (level 0) (defined-symbols (make-hash-table :test 'equal))) (dojo-js-completion-add-function-candidates ancestor-path ancestor-index defined-symbols scopes level prefix current-class define-symbol nil ))) ((dojo-has-node-type curr-node "js2-string-node") ()) (t (log-completion 1 (format "WARNING: Node type [%s] below define function call node is not supported." (if curr-node (js2-node-short-name curr-node) "---"))))))) (defun dojo-add-import-path-candidates (ancestor-path ancestor-index prefix current-class) "Function adding completion candidates if we are within the array representing the import paths. The ancestor-index is supposed to point to the js2-array-node representing that array." ) (defun dojo-js-completion-add-node-candidates (ancestor-path ancestor-index defined-symbols scopes level prefix class) "This function processes the node with the given ancestor-index on the given ancestor-path. Symbols being completion candidates are added in appropriate scopes to the scopes list, which is propagated downwards to subsequent calls to this and similar functions while inspecting the ancestor-path." (let* ((node (nth ancestor-index ancestor-path)) (fct (if node (gethash (js2-node-short-name node) dojo-js-completion-complete-fct-map) nil))) (cond ((null node) ; We are at the end of the ancestor-path, but did not run into a case that allowed completion. Try based on text before point. (log-completion 0 (format "Begin at the end of the ancestor-path, will call add-current-textual-symbol.")) (dojo-js-completion-add-current-textual-symbol-candidates defined-symbols scopes level prefix class)) ((null fct) (log-completion 0 (format "[WARNING] dojo-add-node-candidates found unrecognized node type [%s]" (if node (js2-node-short-name node) "---")))) (t (funcall fct ancestor-path ancestor-index defined-symbols scopes level prefix class))))) (defun dojo-js-completion-add-array-candidates (ancestor-path ancestor-index defined-symbols scopes level prefix class) (log-completion level (format "[%s] Called dojo-add-array-candidates" ancestor-index)) (dojo-js-completion-add-node-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes level prefix class)) (defun dojo-js-completion-add-assign-candidates (ancestor-path ancestor-index defined-symbols scopes level prefix class) (log-completion level (format "[%s] Called dojo-add-assign-candidates" ancestor-index)) (let* ((assign-node (nth ancestor-index ancestor-path)) (next-ancestor-node (nth (1+ ancestor-index) ancestor-path)) (left-child (js2-infix-node-left assign-node)) (right-child (js2-infix-node-right assign-node))) (cond ((eq left-child next-ancestor-node) (dojo-js-completion-add-node-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes level prefix class)) ((eq right-child next-ancestor-node) (cond ((dojo-has-node-type next-ancestor-node "js2-name-node") (dojo-js-completion-add-candidates-if-prefix level prefix (list "false" "new" "true"))) (t (dojo-js-completion-add-node-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes level prefix class)))) (t (log-completion 0 (format "[WARNING] Unsupported case in add-assign-candidates, next-ancestor-node is neither left nor right node.")))))) (defun dojo-js-completion-add-block-candidates (ancestor-path ancestor-index defined-symbols scopes level prefix current-class) "Function adding completion candidates if we are inside a js2-block-node. The ancestor-index is supposed to point to that js2-block-node." (log-completion level (format "[%s] Called dojo-add-block-candidates" ancestor-index)) (let* ((block-node (nth ancestor-index ancestor-path)) (child-nodes (js2-block-node-kids block-node)) (ref-child (nth (1+ ancestor-index) ancestor-path))) ; Step over nodes in range [, [. ; Add all variable names defined here to defined-symbols. ; This is to allow subsequent code to decide wether a particular symbol was defined before point, or ; at some point later in source code. In the latter case, the symbol might still be in the function ; scope set up while parsing the class. (dojo-js-completion-add-defined-symbols defined-symbols child-nodes ref-child level) ; Proceed to higher ancestor-index of ancestor-path (dojo-js-completion-add-node-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes (1+ level) prefix class))) ; ; (cond (; Under some circumstances, the parser doesn't generate a node for the prefix, probably ; ; because it doesn't recognize it anything that looks like valid source code. As ending ; ; up in a js2-block-node isn't exactly helpful for completion, do completion based on ; ; the prefix, and the scopes in such a case. ; (null ref-child) ; (dojo-add-candidates-based-on-prefix scopes level current-class this-symbol)) ; ((dojo-has-node-type ref-child "js2-expr-stmt-node") ; (dojo-add-expr-stmt-candidates ancestor-path (1+ ancestor-index) new-scopes new-level prefix current-class this-symbol)) ; ((dojo-has-node-type ref-child "js2-if-node") ; (dojo-add-if-candidates ancestor-path (1+ ancestor-index) new-scopes new-level prefix current-class this-symbol)) ; ((dojo-has-node-type ref-child "js2-for-node") ; (dojo-add-for-candidates ancestor-path (1+ ancestor-index) new-scopes new-level prefix current-class this-symbol)) ; ((dojo-has-node-type ref-child "js2-while-node") ; (dojo-add-while-candidates ancestor-path (1+ ancestor-index) new-scopes new-level prefix current-class this-symbol)) ; ((dojo-has-node-type ref-child "js2-return-node") ; (dojo-completion-add-return-candidates ancestor-path (1+ ancestor-index) new-scopes new-level prefix current-class this-symbol)) ; (t ; (log-completion 0 (format "[WARNING] dojo-add-block-candidates doesn't yet support child node of type [%s]" (js2-node-short-name ref-child)))) ; ) ; ) ;) (defun dojo-js-completion-add-call-candidates (ancestor-path ancestor-index defined-symbols scopes level prefix class) (log-completion level (format "[%s] Called add-call-candidates" ancestor-index)) (let* ((next-ancestor-node (nth (1+ ancestor-index) ancestor-path)) (call-node (nth ancestor-index ancestor-path)) (target-node (js2-call-node-target call-node)) (arg-nodes (js2-call-node-args call-node))) (cond ((and (dojo-has-node-type target-node "js2-name-node") (string= (dojo-get-name-from-name-node target-node) "declare")) (cond ((eq next-ancestor-node (nth 0 arg-nodes)) (dojo-js-completion-add-node-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes level prefix class)) ((eq next-ancestor-node (nth 1 arg-nodes)) (cond ((dojo-has-node-type next-ancestor-node "js2-name-node") (dojo-js-completion-add-instance-imports-if-prefix (1+ level) prefix class)) (t (log-completion 0 (format "[WARNING] Unsupported node type [%s] of second argument of declare call node." (if next-ancestor-node (js2-node-short-name next-ancestor-node) "---")))))) ((eq next-ancestor-node (nth 2 arg-nodes)) (cond ((dojo-has-node-type next-ancestor-node "js2-object-node") (let ((this-symbol (dojo-class-this-symbol class))) (log-completion level (format "... using dojo-class-this-symbol as object-symbol for third parameter of declare call.")) (dojo-js-completion-add-object-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes level prefix class this-symbol))) (t (log-completion 0 (format "[WARNING] Unsupported node type [%s] of third argument of declare call node." (if next-ancestor-node (js2-node-short-name next-ancestor-node) "---")))))) (t (log-completion 0 (format "[WARNING] Unsupported case when processing the declare node; (length arg-nodes) is [%s]" (length arg-nodes)))))) ((dojo-has-node-type target-node "js2-prop-get-node") (let* ((left-node (js2-infix-node-left target-node)) (left-name (if (and left-node (dojo-has-node-type left-node "js2-name-node")) (dojo-get-name-from-name-node left-node) nil)) (left-symbol (if left-name (dojo-get-symbol-from-scopes scopes left-name) nil)) (left-path (if (and left-symbol (dojo-core-util-is-import-symbol left-symbol)) (dojo-symbol-get-import-path left-symbol) nil)) (right-node (js2-infix-node-right target-node)) (right-name (if (and right-node (dojo-has-node-type right-node "js2-name-node")) (dojo-get-name-from-name-node right-node) nil))) (cond ((and (string= left-path "dojo/_base/lang") (string= right-name "hitch") (> (length arg-nodes) 1)) (cond ((dojo-has-node-type (nth 1 arg-nodes) "js2-function-node") (dojo-js-completion-call-function-with-lang-hitch ancestor-path (1+ ancestor-index) defined-symbols scopes (1+ level) prefix class)) (t (log-completion 0 (format "[WARNING] Unsupported case when processing second parameter of lang.hitch call: [%s]" (if (nth 1 arg-nodes) (js2-node-short-name (nth 1 arg-nodes)) "---")))))) (t (dojo-js-completion-add-node-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes level prefix class))))) (t (dojo-js-completion-add-node-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes level prefix class))))) (defun dojo-js-completion-add-cond-candidates (ancestor-path ancestor-index defined-symbols scopes level prefix class) (log-completion level (format "[%s] Called add-cond-candidates" ancestor-index)) (let* ((child-node (nth (1+ ancestor-index) ancestor-path))) (cond ((dojo-has-node-type child-node "js2-name-node") (dojo-js-completion-add-candidates-if-prefix level prefix (list "null"))))) ; We step to the next node regardless of its type, so we don't need any case distinction here. (dojo-js-completion-add-node-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes (1+ level) prefix class)) (defun dojo-js-completion-add-elem-get-candidates (ancestor-path ancestor-index defined-symbols scopes level prefix class) (log-completion level (format "[%s] Called add-elem-get-candidates" ancestor-index)) ; We step to the next node regardless of its type, so we don't need any case distinction here. (dojo-js-completion-add-node-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes (1+ level) prefix class)) (defun dojo-js-completion-add-expr-stmt-candidates (ancestor-path ancestor-index defined-symbols scopes level prefix current-class) "Function adding completion candidates if we are inside a js2-expr-stmt-node. The ancestor-index is supposed to point to that node." (log-completion level (format "[%s] Called add-expr-stmt-candidates" ancestor-index)) (let* ((child-node (nth (1+ ancestor-index) ancestor-path))) (cond ((dojo-has-node-type child-node "js2-name-node") (dojo-js-completion-add-candidates-if-prefix level prefix (list "var" "if" "for" "while")) ; "this" removed because probably duplicate (dojo-js-completion-add-defined-symbols-candidates-if-prefix defined-symbols scopes level prefix)) (t (dojo-js-completion-add-node-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes level prefix current-class))))) ; ((dojo-has-node-type child-node "js2-call-node") ; (dojo-add-call-candidates ancestor-path (1+ ancestor-index) scopes level prefix current-class this-symbol)) ; ((dojo-has-node-type child-node "js2-infix-node") ; (dojo-completion-add-infix-candidates ancestor-path (1+ ancestor-index) scopes level prefix current-class this-symbol)) (defun dojo-js-completion-add-function-candidates (ancestor-path ancestor-index defined-symbols scopes level prefix class &optional function-symbol this-symbol) (log-completion level (format "[%s] Called add-function-candidates" ancestor-index)) (let* ((function-node (nth ancestor-index ancestor-path)) (child-node (nth (1+ ancestor-index) ancestor-path)) (function-symbol (if function-symbol function-symbol (let ((function-key (dojo-js-key-get-complete-node-key function-node))) (log-completion level (format "... Initial function-symbol is nil, querying function symbol from scope under key [%s]" function-key)) (dojo-core-util-get-symbol-from-scope-by-key (nth 0 scopes) function-key)))) (function-scope (if function-symbol (dojo-symbol-get-function-scope function-symbol) nil))) (log-completion level (format "... function-symbol is [%s]" (dojo-core-util-symbol-to-short-string function-symbol))) (cond ((dojo-has-node-type child-node "js2-block-node") (if this-symbol (progn (log-completion level (format "... registering defined-symbols: [this]")) (puthash "this" t defined-symbols))) (let* ((argument-scope (if function-symbol (dojo-js-completion-construct-function-arguments-scope function-symbol this-symbol) nil)) (argument-scopes (if argument-scope (append (list argument-scope) scopes) scopes)) (body-scope (if function-symbol (dojo-js-completion-construct-function-body-scope function-symbol level) nil)) (body-scopes (if body-scope (append (list body-scope) argument-scopes) argument-scopes))) ; Register arguments derived from the function-symbol. (if argument-scope (dojo-js-completion-add-defined-scope-symbols level defined-symbols argument-scope)) ; Registering arguments from the function-symbol above only recognizes changes made ; up to the last scan of the file. If one types fluently, entering a new function ; parameter might have taken place after the last scan. To also offer such parameters, ; also consider the actual function parameters. (let ((arg-nodes (js2-function-node-params function-node))) (dolist (arg-node arg-nodes) (cond ((dojo-has-node-type arg-node "js2-name-node") (let ((arg-name (dojo-get-name-from-name-node arg-node))) (if (not (gethash arg-name defined-symbols)) (progn (log-completion 0 (format "Registering additional parameter [%s]" arg-name)) (puthash arg-name t defined-symbols)))))))) (dojo-js-completion-add-node-candidates ancestor-path (1+ ancestor-index) defined-symbols body-scopes level prefix class))) (t (log-completion level (format "[WARNING] Unsupported child node [%s] of function-node." (if child-node (js2-node-short-name child-node) "---"))))))) (defun dojo-js-completion-add-for-candidates (ancestor-path ancestor-index defined-symbols scopes level prefix class) (log-completion level (format "[%s] Called add-for-candidates" ancestor-index)) (let* ((for-node (nth ancestor-index ancestor-path)) (next-ancestor-node (nth (1+ ancestor-index) ancestor-path)) (init-node (js2-for-node-init for-node)) (condition-node (js2-for-node-condition for-node)) (update-node (js2-for-node-update for-node)) (body-node (js2-loop-node-body for-node))) (cond ((eq next-ancestor-node init-node) (dojo-js-completion-add-candidates-if-prefix level prefix (list "var"))) ((eq next-ancestor-node body-node) (dojo-js-completion-add-node-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes level prefix class)) (t (log-completion 0 (format "[WARNING] Unsupported case [%s] of for-node." (if next-ancestor-node (js2-node-short-name next-ancestor-node) "---"))))))) (defun dojo-js-completion-add-for-in-candidates (ancestor-path ancestor-index defined-symbols scopes level prefix class) (log-completion level (format "[%s] Called add-for-in-candidates" ancestor-index)) (let* ((for-in-node (nth ancestor-index ancestor-path)) (next-ancestor-node (nth (1+ ancestor-index) ancestor-path)) (iterator-node (js2-for-in-node-iterator for-in-node)) (object-node (js2-for-in-node-object for-in-node)) (body-node (js2-loop-node-body for-in-node))) (cond ((eq next-ancestor-node iterator-node) (dojo-add-candidates-if-prefix level prefix (list "var"))) ((eq next-ancestor-node object-node) (dojo-js-completion-add-node-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes level prefix class)) ((eq next-ancestor-node body-node) (dojo-js-completion-add-node-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes level prefix class)) (t (log-completion 0 (format "[WARNING] Unsupported case [%s] of for-node." (if next-ancestor-node (js2-node-short-name next-ancestor-node) "---"))))))) (defun dojo-js-completion-add-if-candidates (ancestor-path ancestor-index defined-symbols scopes level prefix class) (log-completion level (format "[%s] Called add-if-candidates" ancestor-index)) ; We step to the next node regardless of its type, so we don't need any case distinction here. (dojo-js-completion-add-node-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes (1+ level) prefix class)) (defun dojo-js-completion-add-infix-candidates (ancestor-path ancestor-index defined-symbols scopes level prefix class) (log-completion level (format "[%s] Called add-infix-candidates" ancestor-index)) ; We step to the next node regardless of its type, so we don't need any case distinction here. (dojo-js-completion-add-node-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes (1+ level) prefix class)) (defun dojo-js-completion-add-new-candidates (ancestor-path ancestor-index defined-symbols scopes level prefix class) (log-completion level (format "[%s] Called add-new-candidates" ancestor-index)) (let* ((next-ancestor-node (nth (1+ ancestor-index) ancestor-path)) (new-node (nth ancestor-index ancestor-path)) (target-node (js2-new-node-target new-node))) (cond ((or (null next-ancestor-node) (and (eq next-ancestor-node target-node) (dojo-has-node-type next-ancestor-node "js2-name-node"))) (dojo-js-completion-add-candidates-if-prefix (1+ level) prefix (list "Object" "RegExp")) (dojo-js-completion-add-instance-imports-if-prefix (1+ level) prefix class)) ; If we are not inside the target node, we are inside one of the argument-nodes ((dojo-has-node-type next-ancestor-node "js2-name-node") (dojo-js-completion-add-defined-symbols-candidates-if-prefix defined-symbols scopes level prefix)) (t (dojo-js-completion-add-node-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes level prefix class))))) (defun dojo-js-completion-add-name-candidates (ancestor-path ancestor-index defined-symbols scopes level prefix class) (log-completion level (format "[%s] Called add-name-candidates" ancestor-index)) (log-completion level (format " ... defined-symbols [%s]" defined-symbols)) (dojo-js-completion-add-defined-symbols-candidates-if-prefix defined-symbols scopes level prefix)) (defun dojo-js-completion-add-object-candidates (ancestor-path ancestor-index defined-symbols scopes level prefix class &optional object-symbol) (log-completion level (format "[%s] Called add-object-candidates" ancestor-index)) (if (and object-symbol (not (dojo-core-util-is-object-symbol object-symbol))) (progn (log-completion 0 (format "[WARNING] add-object-candidates received non-object symbol %s; will ignore it." (dojo-core-util-symbol-to-short-string object-symbol))) (setq object-symbol nil))) (let ((next-ancestor-node (nth (1+ ancestor-index) ancestor-path))) (cond ((dojo-has-node-type next-ancestor-node "js2-object-prop-node") (dojo-js-completion-add-object-prop-candidates object-symbol ancestor-path (1+ ancestor-index) defined-symbols scopes level prefix class)) (t (log-completion level (format "[WARNING] Unsupported child node [%s] of object-node." (if next-ancestor-node (js2-node-short-name next-ancestor-node) "---"))))))) (defun dojo-js-completion-add-object-prop-candidates (object-symbol ancestor-path ancestor-index defined-symbols scopes level prefix class) (log-completion level (format "[%s] Called add-object-prop-candidates" ancestor-index)) (let* ((next-ancestor-node (nth (1+ ancestor-index) ancestor-path)) (object-prop-node (nth ancestor-index ancestor-path)) (left-node (js2-infix-node-left object-prop-node)) (right-node (js2-infix-node-right object-prop-node))) (cond ((eq right-node next-ancestor-node) (cond ((dojo-has-node-type next-ancestor-node "js2-name-node") (dojo-js-completion-add-candidates-if-prefix (1+ level) prefix (list "function" "new")) (dojo-js-completion-add-defined-symbols-candidates-if-prefix defined-symbols scopes level prefix)) ((dojo-has-node-type next-ancestor-node "js2-function-node") (let ((function-symbol (if object-symbol (dojo-js-completion-get-object-prop-symbol object-symbol object-prop-node) nil))) ; Check the case that the object-prop-symbol exists, but wasn't yet detected as function-symbol. (if (and function-symbol (dojo-core-util-is-function-symbol function-symbol)) (dojo-js-completion-add-function-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes (1+ level) prefix class function-symbol object-symbol) (dojo-js-completion-add-node-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes level prefix class)))) (t (dojo-js-completion-add-node-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes level prefix class)))) (t (log-completion level (format "[WARNING] Unsupported case in add-object-prop-candidates.")))))) (defun dojo-js-completion-add-paren-candidates (ancestor-path ancestor-index defined-symbols scopes level prefix class) (log-completion level (format "[%s] Called add-paren-candidates" ancestor-index)) ; We step to the next node regardless of its type, so we don't need any case distinction here. (dojo-js-completion-add-node-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes (1+ level) prefix class)) (defun dojo-js-completion-add-prop-get-candidates (ancestor-path ancestor-index defined-symbols scopes level prefix class) (let* ((next-ancestor-node (nth (1+ ancestor-index) ancestor-path)) (prop-get-node (nth ancestor-index ancestor-path)) (left-node (js2-infix-node-left prop-get-node)) (right-node (js2-infix-node-right prop-get-node))) (log-completion level (format "[%s] Called add-prop-get-candidates, for node [%s]" ancestor-index (dojo-js2-node-to-string prop-get-node))) (cond ((eq left-node next-ancestor-node) (dojo-js-completion-add-node-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes level prefix class)) ; If we are in the right node of a js2-prop-get-node, then we try to complete here based on the base symbol formed by the left node. ; A bit tricky is the following case: Suppose we complete in source code like this: ; this. ; foo.bar(); ; ... with (point) being located after the '.' in the first line. Then, the js2 parser might turn this into a js2-prop-get-node that spans ; from the 'this' to the 'foo', with 'this' being the left node, 'foo' being the right node, and (point) in between, outside both of them. ; Thus we also complete based on the base symbol formed by the left node, if we are not in the left node (case above), and the right node ; starts *after* (point). ((or (eq right-node next-ancestor-node) (> (js2-node-abs-pos right-node) (point))) (let* ((base-symbol (dojo-js-completion-get-node-symbol defined-symbols scopes level prop-get-node (point))) (members (dojo-js-completion-get-child-symbol-names base-symbol level))) (dojo-js-completion-add-candidates-if-prefix level prefix members))) (t (log-completion level (format (concat "[WARNING] next-ancestor-node is neither the left, nor the right child of the prop-get-node. ")))) ))) ; (dojo-add-all-scope-candidates level prefix scopes))))) (defun dojo-js-completion-add-return-candidates (ancestor-path ancestor-index defined-symbols scopes level prefix class) (log-completion level (format "[%s] Called add-return-candidates" ancestor-index)) ; We step to the next node regardless of its type, so we don't need any case distinction here. (dojo-js-completion-add-node-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes (1+ level) prefix class)) (defun dojo-js-completion-add-var-decl-candidates (ancestor-path ancestor-index defined-symbols scopes level prefix class) "Function adding completion candidates if we are inside a js2-var-decl-node. The ancestor-index is supposed to point to that js2-var-decl-node." (log-completion level (format "[%s] Called add-var-decl-candidates" ancestor-index)) (let ((var-init-node (nth (1+ ancestor-index) ancestor-path))) (cond ((dojo-has-node-type var-init-node "js2-var-init-node") (dojo-js-completion-add-var-init-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes level prefix class)) (t (log-completion level (format "[WARNING] Child of js2-var-decl-node doesn't have type js2-var-init-node as expected; will ignore it. Found type: [%s]" (if var-init-node (js2-node-short-name var-init-node) "---"))))))) (defun dojo-js-completion-add-throw-candidates (ancestor-path ancestor-index defined-symbols scopes level prefix class) (dojo-js-completion-add-node-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes level prefix class)) (defun dojo-js-completion-add-var-init-candidates (ancestor-path ancestor-index defined-symbols scopes level prefix class) "Function adding completion candidates if we are inside a js2-var-init-node. The ancestor-index is supposed to point to that js2-var-init-node." (log-completion level (format "[%s] Called add-var-init-candidates" ancestor-index)) (let* ((var-init-node (nth ancestor-index ancestor-path)) (next-ancestor-node (nth (1+ ancestor-index) ancestor-path)) (target-node (js2-var-init-node-target var-init-node)) (initializer-node (js2-var-init-node-initializer var-init-node)) ) (cond ((eq next-ancestor-node target-node) (log-completion level "[WARNING] ancestor-path points to the target-node of a var-init-node; this is not supported yet.")) ((eq next-ancestor-node initializer-node) (cond ((dojo-has-node-type initializer-node "js2-name-node") (dojo-js-completion-add-candidates-if-prefix level prefix (list "new")) (dojo-js-completion-add-defined-symbols-candidates-if-prefix defined-symbols scopes level prefix)) (t (dojo-js-completion-add-node-candidates ancestor-path (1+ ancestor-index) defined-symbols scopes level prefix class) ))) ; ((dojo-has-node-type initializer-node "js2-call-node") ; (dojo-add-call-candidates ancestor-path (1+ ancestor-index) scopes level prefix current-class this-symbol)) ; ((dojo-has-node-type initializer-node "js2-object-node") ; (let* ((var-name (dojo-get-name-from-var-init-node var-init-node)) ; (object-scope (dojo-construct-object-scope initializer-node (list "this" var-name) level current-class)) ; (new-scopes (append (list object-scope) scopes)) ; ) ; (dojo-add-object-candidates ancestor-path (1+ ancestor-index) new-scopes (1+ level) prefix current-class this-symbol) ; )) (t (log-completion level (format "[WARNING] ancestor-path points to an unknown child node of a js2-var-init-node; something is strange. Node type is [%s]" (if next-ancestor-node (js2-node-short-name next-ancestor-node) "---"))))))) (defun dojo-js-completion-add-current-textual-symbol-candidates (defined-symbols scopes level prefix class) "In some cases, the ancestor-path doesn't end in a usable node. E.g., if we complete 'log.' at the very end of an if statement, the last node of the ancestor-path will be the js2-scope node. Obviously, that one doesn't help at all in identifying which symbol we want to complete. Thus, in case we end up with the ancestor-path without any senseful completion node, we instead try to do completion based on (dojo-get-current-symbol-path), which just collects what looks like a symbol path before (point)." (let* ((symbol-path (dojo-get-current-symbol-path)) (tokens (split-string symbol-path "\\.")) (curr-symbol nil) (curr-index 0)) (log-completion level (format "Called add-current-textual-symbol-candidates based on symbol-path [%s]" symbol-path)) (dolist (token tokens) (log-completion level (format "... Inspecting token [%s]" token)) (cond ((= curr-index 0) (if (= (length tokens) 0) (progn (log-completion (1+ level) (format "We have just one token, thus we complete based on defined-symbols here.")) (dojo-js-completion-add-defined-symbols-candidates-if-prefix defined-symbols scopes level token)) (if (gethash token defined-symbols) (progn (setq curr-symbol (dojo-get-symbol-from-scopes scopes token)) (log-completion (1+ level) (format "Resolved first symbol %s" (dojo-core-util-symbol-to-short-string curr-symbol)))) (log-completion (1+ level) (format "Did not resolve the first token in defined-symbols, no success.")) (return nil)))) ((= curr-index (1- (length tokens))) (let ((members (dojo-js-completion-get-child-symbol-names curr-symbol level))) (dojo-js-completion-add-candidates-if-prefix level (concat "." token) members))) (t (let* ((child-symbol (dojo-js-completion-get-symbol-by-token curr-symbol token))) (if child-symbol (progn (log-completion (1+ level) (format "Stepping to child-symbol %s" (dojo-core-util-symbol-to-short-string child-symbol))) (setq curr-symbol child-symbol)) (log-completion (1+ level) (format "Could not resolve token [%s] at index [%s], no success." token curr-index)) (return nil))))) (incf curr-index)))) (defun dojo-js-completion-get-symbol-by-token (base-symbol token) (cond ((dojo-core-util-is-instance-symbol base-symbol) (let* ((import-resource-id (dojo-symbol-get-import-resource-id base-symbol)) (instance-class (dojo-core-util-get-or-load-class workspace import-resource-id)) (instance-class-this-symbol (if instance-class (dojo-class-this-symbol instance-class) nil)) (name-to-symbol (if instance-class-this-symbol (dojo-symbol-get-object-members instance-class-this-symbol) nil)) (symbol (if name-to-symbol (gethash token name-to-symbol) nil))) symbol)) ((dojo-core-util-is-object-symbol base-symbol) (dojo-symbol-get-object-member token base-symbol)) (t (log-completion 0 (format "[WARNING] Unsupported case of get-symbol-by-token, for base-symbol %s" (dojo-core-util-symbol-to-short-string base-symbol)))))) (defun dojo-js-completion-get-child-symbol-names (base-symbol level) (cond ((null base-symbol) (log-completion 0 (format "[WARNING] Found no base-symbol."))) ((and base-symbol (dojo-core-util-is-array-symbol base-symbol)) ; Completing array members (list "length" "concat" "pop" "push" "slice" "sort" "toString")) ; Completing members of type='object' base symbols ((and base-symbol (dojo-core-util-is-object-symbol base-symbol)) (cond ((eq (dojo-symbol-get-object-object-type base-symbol) 'DOJO-OBJECTTYPE-SERVICE) (let* ((service-resource-id (dojo-symbol-get-object-object-info base-symbol))) (if (null service-resource-id) (log-completion 0 (format "[WARNING] Found object-type SERVICE, but no service-resource-id.")) (let* ((service-class (dojo-java-get-or-create-service-interface-class-by-resource service-resource-id)) (service-this-symbol (dojo-class-this-symbol service-class))) (dojo-js-completion-get-object-member-completion-lists service-this-symbol))))) (t (dojo-js-completion-get-object-member-completion-lists base-symbol)))) ; Completing members of type='import' base symbols ; If base-symbol is an import symbol, we need to fetch the corresponding imported class from workspace. ; Given that we complete some base symbol path, we know that we need to search within the static symbols ; of that class. We reach them via static-symbol of that class, which is assumed to be a symbol of type ; object. We return all keys of the map corresponding to that object. ((and base-symbol (dojo-core-util-is-import-symbol base-symbol)) (let* ((import-class (dojo-js-api-get-class-by-import-symbol base-symbol)) (import-class-static-symbol (if import-class (dojo-class-static-symbol import-class) nil))) (log-completion level (format "... case right/import determined import-class %s" (if import-class (dojo-class-path import-class) "nil"))) (log-completion (1+ level) (format ".. static-symbol %s" (dojo-core-util-symbol-to-short-string import-class-static-symbol))) (if (and import-class-static-symbol (dojo-core-util-is-object-symbol import-class-static-symbol)) (dojo-js-completion-get-object-member-completion-lists import-class-static-symbol) (log-completion 0 (format "[WARNING] import-class-static-symbol has unexpected type, expecting OBJECT: %s" (dojo-core-util-symbol-to-short-string import-class-static-symbol))) ()))) ; Completing members of type='instance' base symbols ((and base-symbol (dojo-core-util-is-instance-symbol base-symbol)) (let* ((instance-class (dojo-js-api-get-class-by-import-symbol base-symbol)) (instance-class-this-symbol (if instance-class (dojo-class-this-symbol instance-class) nil))) (log-completion level (format "... case right/instance determined instance-class [%s]" (if instance-class (dojo-class-path instance-class) "nil"))) (log-completion (1+ level) (format "... this-symbol %s" (dojo-core-util-symbol-to-short-string instance-class-this-symbol))) (if instance-class-this-symbol (dojo-js-completion-get-object-member-completion-lists instance-class-this-symbol)))) (t (log-completion 0 (format ".. case right: [WARNING] Unknown subcase for base-symbol [%s] of type [%s]" (dojo-symbol-name base-symbol) (dojo-core-util-symbol-type-to-string base-symbol)))))) (defun dojo-add-expr-stmt-candidates (ancestor-path ancestor-index scopes level prefix current-class this-symbol) "Function adding completion candidates if we are inside a js2-expr-stmt-node. The ancestor-index is supposed to point to that node." (log-completion level (format "[%s] Called dojo-add-expr-stmt-candidates, prefix = [%s], level = [%s]" ancestor-index prefix level)) (let* ((child-node (nth (1+ ancestor-index) ancestor-path)) ) (cond ((dojo-has-node-type child-node "js2-name-node") (dojo-add-candidates-if-prefix level prefix (list "var" "if" "for" "while")) ; "this" removed because probably duplicate (dojo-add-all-scope-candidates level prefix scopes)) ((dojo-has-node-type child-node "js2-call-node") (dojo-add-call-candidates ancestor-path (1+ ancestor-index) scopes level prefix current-class this-symbol)) ((dojo-has-node-type child-node "js2-prop-get-node") (dojo-add-prop-get-candidates ancestor-path (1+ ancestor-index) scopes (1+ level) prefix current-class this-symbol)) ((dojo-has-node-type child-node "js2-var-decl-node") (dojo-add-var-decl-candidates ancestor-path (1+ ancestor-index) scopes level prefix current-class this-symbol)) ((dojo-has-node-type child-node "js2-infix-node") (dojo-completion-add-infix-candidates ancestor-path (1+ ancestor-index) scopes level prefix current-class this-symbol)) (t (log-completion 0 (format "[WARNING] dojo-add-expr-stmt-candidates doesn't yet support child node of type [%s]" (js2-node-short-name child-node)))) ) ) ) (defun dojo-completion-add-return-candidates (ancestor-path ancestor-index scopes level prefix current-class this-symbol) "Function adding completion candidates if we are inside a js2-return-node. The ancestor-index is supposed to point to that js2-return-node." (log-completion level (format "Called dojo-completion-add-return-candidates, prefix = [%s], level = [%s]" prefix level)) (let* ((child-node (nth (1+ ancestor-index) ancestor-path)) ) (cond ((dojo-has-node-type child-node "js2-call-node") (dojo-add-call-candidates ancestor-path (1+ ancestor-index) scopes level prefix current-class this-symbol)) (t (log-completion 0 (format "[WARNING] dojo-completion-add-return-candidates doesn't yet support child node of type [%s]" (js2-node-short-name ref-child)))) ) ) ) (defun dojo-add-if-candidates (ancestor-path ancestor-index scopes level prefix current-class this-symbol) "Function adding completion candidates if we are inside a js2-if-node. The ancestor-index is supposed to point to that js2-if-node." (let* ((if-node (nth ancestor-index ancestor-path)) (next-ancestor-node (nth (1+ ancestor-index) ancestor-path)) (condition-node (js2-if-node-condition if-node)) (then-node (js2-if-node-then-part if-node)) (else-node (js2-if-node-else-part if-node)) ) (cond ((or (eq next-ancestor-node condition-node) (eq next-ancestor-node then-node) (eq next-ancestor-node else-node)) (cond ((dojo-has-node-type next-ancestor-node "js2-scope") ; js2-scope extends js2-block-node; thus using dojo-add-block-candidates is appropriate here (dojo-add-block-candidates ancestor-path (1+ ancestor-index) scopes level prefix current-class this-symbol)) ((dojo-has-node-type next-ancestor-node "js2-if-node") (dojo-add-if-candidates ancestor-path (1+ ancestor-index) scopes level prefix current-class this-symbol)) (t (log-completion level (format "[WARNING] Node type [%s] inside js2-if-node not yet supported with respect to completion." (js2-node-short-name next-ancestor-node)))) )) (t (log-completion level (format "Unrecognized next-ancestor-node of type [%s] in dojo-add-if-candidates." (js2-node-short-name next-ancestor-node)))) ) ) ) (defun dojo-add-for-candidates (ancestor-path ancestor-index scopes level prefix current-class this-symbol) "Function adding completion candidates if we are inside a js2-for-node. The ancestor-index is supposed to point to that js2-for-node." (let* ((for-node (nth ancestor-index ancestor-path)) (next-ancestor-node (nth (1+ ancestor-index) ancestor-path)) (init-node (js2-for-node-init for-node)) (condition-node (js2-for-node-condition for-node)) (update-node (js2-for-node-update for-node)) (body-node (js2-loop-node-body for-node)) ) (cond ((eq body-node next-ancestor-node) (dojo-add-block-candidates ancestor-path (1+ ancestor-index) scopes level prefix current-class this-symbol)) (t (log-completion level (format "[WARNING] Unrecognized next-ancestor-node of type [%s] in dojo-add-for-candidates." (js2-node-short-name next-ancestor-node)))) ) ) ) (defun dojo-add-while-candidates (ancestor-path ancestor-index scopes level prefix current-class this-symbol) "Function adding completion candidates if we are inside a js2-while-node. The ancestor-index is supposed to point to that js2-while-node." (let* ((while-node (nth ancestor-index ancestor-path)) (next-ancestor-node (nth (1+ ancestor-index) ancestor-path)) (condition-node (js2-while-node-condition while-node)) (body-node (js2-loop-node-body while-node)) ) (cond ((eq body-node next-ancestor-node) (dojo-add-block-candidates ancestor-path (1+ ancestor-index) scopes level prefix current-class this-symbol)) (t (log-completion level (format "[WARNING] Unrecognized next-ancestor-node of type [%s] in dojo-add-while-candidates." (js2-node-short-name next-ancestor-node)))) ) ) ) (defun dojo-add-candidates-based-on-prefix (scopes level current-class this-symbol) "This function adds completion candidates based on the prefix at point. The prefix is considered the string before point until the first non-alphanumeric, and non-dot character. E.g. something like 'foo.bar.cde'. The approach of this function is first searching a symbol for the first path token ('foo' in the example) in the given list of scopes. If found, and if the symbol is an import or object symbol, a symbol with the name of the second token is searched within that symbol. This way, a path of symbols is traversed, until the token before the last token. If successful, we end up with a non-nil current-symbol at this point. Then, finally, usual completion based on the last token is performed, with all symbols found in the current-symbol as candidates. In the example above, the last current-symbol corresponds to 'bar', and the final completion is done with prefix '.cde'" (let* ((prefix-tokens (dojo-get-current-symbol-path-tokens)) (first-prefix-token (nth 0 prefix-tokens)) (last-prefix-token (nth (1- (length prefix-tokens)) prefix-tokens)) (completion-prefix (if (= (length prefix-tokens) 1) last-prefix-token (concat "." last-prefix-token))) (current-symbol (cond ((string= first-prefix-token "this") (log-completion level "-- setting current-symbol to this-symbol.") this-symbol) (t (log-completion level "-- setting current-symbol to get-symbol-from-scopes") (dojo-get-symbol-from-scopes scopes first-prefix-token)) )) (prefix-index 1) ) ; For import-symbols, use the static-symbol of the corresponding class (if (dojo-core-util-is-import-symbol current-symbol) (let* ((import-class (dojo-core-util-get-class-by-import-symbol dojo-current-workspace current-symbol))) (setq current-symbol (if import-class (dojo-class-static-symbol import-class) nil)))) ; Traverse the path of tokens (while (and current-symbol (< prefix-index (1- (length prefix-tokens)))) (let ((curr-token (nth prefix-index prefix-tokens))) (cond ((dojo-core-util-is-object-symbol current-symbol) (let ((name-to-symbol (dojo-symbol-get-object-members current-symbol)) ) (setq current-symbol (if (null name-to-symbol) nil (dojo-symbol-get-object-member curr-token current-symbol))) )) (t (log-completion level (format "[WARNING] Unsupported type [%s] of current symbol in dojo-add-candidates-based-on-prefix" (dojo-core-util-symbol-type-to-string current-symbol))) ) (setq prefix-index (1+ prefix-index)) ) ) ; If we ended up in some symbol, consider all symbols it provides as candidates (if current-symbol (cond ((dojo-core-util-is-object-symbol current-symbol) (let ((name-to-symbol (dojo-symbol-get-object-members current-symbol)) ) (if (not (null name-to-symbol)) (dojo-add-candidates-if-prefix level completion-prefix (hash-table-get-all-keys name-to-symbol)) ) )) (t (log-completion level (format "[WARNING] dojo-add-candidates-based-on-prefix does not support type [%s] of last base symbol" (dojo-core-util-symbol-type-to-string current-symbol))))))))) ; (if (listp candidate) ; ; Shortcut for being able to pass a list. ; (dolist (c candidate) ; (dojo-add-candidates-if-prefix prefix c) ; ) ; (if (string-prefix-p prefix candidate) ; (progn ; (push candidate dojo-completion-candidates) ; ) ; ) ; ) (defun dojo-completion-add-infix-candidates (ancestor-path ancestor-index scopes level prefix current-class this-symbol) "Function adding completion candidates if we are inside a js2-infix-node. The ancestor-index is supposed to point to that js2-infix-node." (log-completion level (format "Called dojo-completion-add-infix-candidates, for node [%s]" (dojo-js2-node-to-string (nth ancestor-index ancestor-path)))) (let* ((next-ancestor-node (nth (1+ ancestor-index) ancestor-path)) ) (cond ((null next-ancestor-node) (dojo-add-candidates-based-on-prefix scopes level current-class this-symbol)) (t (log-completion 0 (format "[WARNING] dojo-completion-add-infix-candidates doesn't yet support child node of type [%s]" (js2-node-short-name next-ancestor-node)))) ) ) ) (defun dojo-add-prop-get-candidates (ancestor-path ancestor-index scopes level prefix current-class this-symbol) ;(defun dojo-add-prop-get-candidates (prefix level scopes current-class prop-get-node this-symbol) (let* ((next-ancestor-node (nth (1+ ancestor-index) ancestor-path)) (prop-get-node (nth ancestor-index ancestor-path)) (left-node (js2-infix-node-left prop-get-node)) (right-node (js2-infix-node-right prop-get-node))) (log-completion level (format "[%s] Called dojo-add-prop-get-candidates, for node [%s]" ancestor-index (dojo-js2-node-to-string prop-get-node))) (cond ((eq left-node next-ancestor-node) (cond ((dojo-has-node-type left-node "js2-call-node") (dojo-add-call-candidates ancestor-path (1+ ancestor-index) scopes level prefix current-class this-symbol)) ((dojo-has-node-type left-node "js2-prop-get-node") (dojo-add-prop-get-candidates ancestor-path (1+ ancestor-index) scopes level prefix current-class this-symbol)) (t (log-completion level (format "[WARNING] Unsupported case of left node [%s] of js2-prop-get-node" (js2-node-short-name left-node)))))) ; If we are in the right node of a js2-prop-get-node, then we try to complete here based on the base symbol formed by the left node. ; A bit tricky is the following case: Suppose we complete in source code like this: ; this. ; foo.bar(); ; ... with (point) being located after the '.' in the first line. Then, the js2 parser might turn this into a js2-prop-get-node that spans ; from the 'this' to the 'foo', with 'this' being the left node, 'foo' being the right node, and (point) in between, outside both of them. ; Thus we also complete based on the base symbol formed by the left node, if we are not in the left node (case above), and the right node ; starts *after* (point). ((or (eq right-node next-ancestor-node) (> (js2-node-abs-pos right-node) (point))) (let* ((base-symbol (dojo-scopes-get-node-symbol scopes level prop-get-node nil nil (point))) (members (cond ((and base-symbol (dojo-core-util-is-array-symbol base-symbol)) ; Completing array members (list "length" "concat" "pop" "push" "slice" "sort" "toString")) ; Completing members of type='object' base symbols ((and base-symbol (dojo-core-util-is-object-symbol base-symbol)) (hash-table-get-all-keys (dojo-symbol-get-object-members base-symbol))) ; Completing members of type='import' base symbols ; If base-symbol is an import symbol, we need to fetch the corresponding imported class from workspace. ; Given that we complete some base symbol path, we know that we need to search within the static symbols ; of that class. We reach them via static-symbol of that class, which is assumed to be a symbol of type ; object. We return all keys of the map corresponding to that object. ((and base-symbol (dojo-core-util-is-import-symbol base-symbol)) (let* ((import-class (dojo-js-api-get-class-by-import-symbol base-symbol)) (static-symbol (if import-class (dojo-class-static-symbol import-class) nil)) (static-symbol-members (if static-symbol (hash-table-get-all-keys (dojo-symbol-get-object-members static-symbol)) ()))) (log-completion level (format ".. case right/import determined base-symbol [%s], import-class [%s], static-symbol [%s]" base-symbol import-class static-symbol)) (log-completion level (format ".. static-symbol-members [%s]" static-symbol-members)) static-symbol-members)) ; Completing members of type='instance' base symbols ((and base-symbol (dojo-core-util-is-instance-symbol base-symbol)) (let* ((instance-class (dojo-js-api-get-class-by-import-symbol base-symbol)) (instance-class-this-symbol (if instance-class (dojo-class-this-symbol instance-class) nil)) (name-to-symbol (if instance-class-this-symbol (dojo-symbol-get-object-members instance-class-this-symbol) nil))) (log-completion level (format ".. case right/instance determined instance-class [%s]" (if instance-class (dojo-class-path instance-class) "nil"))) (log-completion (1+ level) (format ".. content: [%s]" (if instance-class instance-class nil))) (log-completion (1+ level) (format ".. this-symbol [%s]" (if instance-class-this-symbol instance-class-this-symbol nil))) (if name-to-symbol (hash-table-get-all-keys name-to-symbol) ()))) (t (log-completion (format ".. case right: [WARNING] Unknown subcase for base-symbol [%s] of type [%s]" (dojo-symbol-name base-symbol) (dojo-core-util-symbol-type-to-string base-symbol))))))) (log-completion level (format ".. Determined base-symbol [%s] and members [%s]" base-symbol members)) (dojo-add-candidates-if-prefix level prefix members))) (t (log-completion level (format (concat ".. next-ancestor-node is neither the left, nor the right child of the prop-get-node. " "Will complete using prefix [%s] and the current scopes.") prefix)) (dojo-add-all-scope-candidates level prefix scopes))))) (defun dojo-add-var-decl-candidates (ancestor-path ancestor-index scopes level prefix current-class this-symbol) "Function adding completion candidates if we are inside a js2-var-decl-node. The ancestor-index is supposed to point to that js2-var-decl-node." (log-completion level (format "[%s] Called dojo-add-var-decl-candidates" ancestor-index)) (let ((var-init-node (nth (1+ ancestor-index) ancestor-path)) ) (if (dojo-has-node-type var-init-node "js2-var-init-node") (dojo-add-var-init-candidates ancestor-path (1+ ancestor-index) scopes level prefix current-class this-symbol) (log-completion level (format "[WARNING] Child of js2-var-decl-node doesn't have type js2-var-init-node as expected; will ignore it. Found type: [%s]" (js2-node-short-name var-init-node))) ) ) ) (defun dojo-add-var-init-candidates (ancestor-path ancestor-index scopes level prefix current-class this-symbol) "Function adding completion candidates if we are inside a js2-var-init-node. The ancestor-index is supposed to point to that js2-var-init-node." (log-completion level (format "[%s] Called dojo-add-var-init-candidates" ancestor-index)) (let* ((var-init-node (nth ancestor-index ancestor-path)) (next-ancestor-node (nth (1+ ancestor-index) ancestor-path)) (target-node (js2-var-init-node-target var-init-node)) (initializer-node (js2-var-init-node-initializer var-init-node)) ) (cond ((eq next-ancestor-node target-node) (log-completion level "[WARNING] ancestor-path points inside the target-node of a var-init-node; this is not supported yet.")) ((eq next-ancestor-node initializer-node) (cond ((dojo-has-node-type initializer-node "js2-call-node") (dojo-add-call-candidates ancestor-path (1+ ancestor-index) scopes level prefix current-class this-symbol)) ((dojo-has-node-type initializer-node "js2-object-node") (let* ((var-name (dojo-get-name-from-var-init-node var-init-node)) (object-scope (dojo-construct-object-scope initializer-node (list "this" var-name) level current-class)) (new-scopes (append (list object-scope) scopes)) ) ; (log-completion level (format "Adding object-scope [%s]" object-scope)) (dojo-add-object-candidates ancestor-path (1+ ancestor-index) new-scopes (1+ level) prefix current-class this-symbol) )) (t (log-completion level (format "[WARNING] initializer-node of type [%s] of a js2-var-init-node is not yet supported for completion. Tree: [%s]" (js2-node-short-name initializer-node) (dojo-node-to-string var-init-node)))) )) (t (log-completion level (format "[WARNING] ancestor-path points inside a unknown child node of a js2-var-init-node; something is strange. Node type is [%s]") (js2-node-short-name next-ancestor-node))) ) ) ) (defun dojo-add-call-candidates (ancestor-path ancestor-index scopes level prefix current-class this-symbol) "Function adding completion candidates if we are inside a js2-call-node. The ancestor-index is supposed to point to that js2-call-node." (let* ((call-node (nth ancestor-index ancestor-path)) (next-ancestor-node (nth (1+ ancestor-index) ancestor-path)) (target-node (js2-call-node-target call-node)) (arg-nodes (js2-call-node-args call-node)) (current-class-this-symbol (dojo-class-this-symbol current-class))) (log-completion level (format "[%s] Called dojo-add-call-candidates for node [%s]" ancestor-index (dojo-js2-node-to-string call-node))) (if (eq next-ancestor-node target-node) ; next-ancestor-node is the node for calling the function, e.g. 'foo' in 'foo(bar)' (progn (log-completion level (format "Inspecting target-node of type [%s]" (js2-node-short-name target-node))) (cond ((dojo-has-node-type target-node "js2-prop-get-node") (dojo-add-prop-get-candidates ancestor-path (1+ ancestor-index) scopes (1+ level) prefix current-class this-symbol)) (t (log-completion level (format "[WARNING] Node type [%s] as target node of a js2-call-node is not yet implemented, will ignore it." (js2-node-short-name target-node)))) ) ) ; next-ancestor-node is one of the parameter nodes (let* ((new-scope nil) (new-scopes scopes) ) ; Check if we are within a second argument node of type js2-function-node (if (and (= (length arg-nodes) 2) (eq next-ancestor-node (nth 1 arg-nodes)) (dojo-has-node-type next-ancestor-node "js2-function-node")) (progn ; If yes, this might be a lang.hitch call. (setq new-scope (dojo-scopes-construct-lang-hitch-scope call-node (1+ level) scopes current-class)) ; If it is one, a non-nil value is returned as new-scope, and we need to add it to the list of scopes (if new-scope (progn (setq new-scopes (append (list new-scope) scopes)) ) ) ) ) (cond ; declare function parameters ((dojo-has-node-type next-ancestor-node "js2-call-node") (dojo-add-call-candidates ancestor-path (1+ ancestor-index) new-scopes (1+ level) prefix current-class this-symbol)) ((dojo-has-node-type next-ancestor-node "js2-function-node") (dojo-add-function-candidates ancestor-path (1+ ancestor-index) new-scopes (1+ level) prefix current-class this-symbol)) ((and (dojo-has-node-type target-node "js2-name-node") (string= (dojo-get-name-from-name-node target-node) "declare")) (cond ; declare parameter, case calling declare with three parameters class name, superclass and object ; In this case, we especially are *not* in a lang.hitch call, and can thus ignore the new-scopes variable from above ((or (and (= (length arg-nodes) 3) (eq next-ancestor-node (nth 2 arg-nodes))) (and (= (length arg-nodes) 2) (eq next-ancestor-node (nth 1 arg-nodes)))) (if (not (dojo-has-node-type next-ancestor-node "js2-object-node")) (log-completion level (format "[WARNING] The implementation parameter of the declare node is not a js2-object-node, but a [%s]. Will ignore it." (js2-node-short-name next-ancestor-node))) (let* ((new-scopes (append (list (dojo-scopes-construct-this-scope level current-class)) scopes)) ) (dojo-add-object-candidates ancestor-path (1+ ancestor-index) new-scopes level prefix current-class current-class-this-symbol) ) )) ; declare parameter, case calling declare with two parameters superclass and object ((or (and (= (length arg-nodes) 3) (eq next-ancestor-node (nth 1 arg-nodes))) (and (= (length arg-nodes) 2) (eq next-ancestor-node (nth 0 arg-nodes)))) (cond ((dojo-has-node-type next-ancestor-node "js2-name-node") (dojo-add-superclass-candidates level next-ancestor-node current-class)) ((and (dojo-has-node-type next-ancestor-node "js2-array-node") (dojo-has-node-type (nth (+ ancestor-index 2) ancestor-path) "js2-name-node")) (dojo-add-superclass-candidates level (nth (+ ancestor-index 2) ancestor-path) current-class)) (t (log-completion level (format "[WARNING] Unrecognized case for completing superclass-parameter, type of next-ancestor-node is [%s]" (js2-node-short-name next-ancestor-node)))) )) (t (log-completion level (format "[WARNING] Unimplemented case of completion inside the declare node."))))) ((dojo-has-node-type next-ancestor-node "js2-name-node") (log-completion level (format "Adding all scope candidates for js2-name-node with content [%s]" (dojo-get-name-from-name-node next-ancestor-node))) (dojo-add-all-scope-candidates level prefix scopes)) ((dojo-has-node-type next-ancestor-node "js2-prop-get-node") (dojo-add-prop-get-candidates ancestor-path (1+ ancestor-index) scopes (1+ level) prefix current-class this-symbol)) (t (log-completion level (format "[WARNING] Unimplemented case of completion inside js2-call-node."))) ) ) ) ) ) (defun dojo-add-superclass-candidates (level name-node current-class) (let* ((name (dojo-get-name-from-name-node name-node)) (import-names (hash-table-get-all-keys (dojo-class-import-to-symbol current-class))) ) (dojo-add-candidates-if-prefix level name import-names) ) ) (defun dojo-add-object-candidates (ancestor-path ancestor-index scopes level prefix current-class this-symbol) "Function adding completion candidates if we are inside a js2-object-node. The ancestor-index is supposed to point to that js2-object-node." (log-completion level (format "[%s] Called dojo-add-object-candidates" ancestor-index)) (let* ((object-node (nth ancestor-index ancestor-path)) (next-ancestor-node (nth (1+ ancestor-index) ancestor-path)) (prop-nodes (js2-object-node-elems object-node)) ) (if (not (dojo-has-node-type next-ancestor-node "js2-object-prop-node")) (log-completion level (format "[WARNING] Expecting js2-object-prop-node below js2-object-node, but ancestor-path contains [%s]. Something is wrong, will ignore it." (js2-node-short-name next-ancestor-node))) (dolist (prop-node prop-nodes) (if (eq prop-node next-ancestor-node) (dojo-add-object-prop-candidates ancestor-path (1+ ancestor-index) scopes level prefix current-class this-symbol) ) ) ) ) ) (defun dojo-add-object-prop-candidates (ancestor-path ancestor-index scopes level prefix current-class this-symbol) "Function adding completion candidates if we are inside a js2-object-prop-node. The ancestor-index is supposed to point to that js2-object-prop-node." (let* ((object-prop-node (nth ancestor-index ancestor-path)) (next-ancestor-node (nth (1+ ancestor-index) ancestor-path)) (name-node (js2-infix-node-left object-prop-node)) (value-node (js2-infix-node-right object-prop-node)) ) (log-completion level (format "[%s] Called dojo-add-object-prop-candidates for object attribute [%s]" ancestor-index (dojo-get-name-from-name-node name-node))) (cond ((eq next-ancestor-node name-node) ; Do nothing, the name of an object property needs to be defined here, and cannot be completed based on information provided elsewhere ()) ((eq next-ancestor-node value-node) (cond ((dojo-has-node-type next-ancestor-node "js2-name-node") (dojo-add-candidates-if-prefix level prefix (list "true" "false" "new" 'DOJO-JSTYPE-FUNCTION)) (dojo-add-all-scope-candidates level prefix scopes)) ((dojo-has-node-type next-ancestor-node "js2-function-node") (dojo-add-function-candidates ancestor-path (1+ ancestor-index) scopes level prefix current-class this-symbol)) (t (log-completion level (format "[WARNING] Unrecognized value node type [%s] of js2-object-prop-node during completion, will ignore it." (js2-node-short-name next-ancestor-node)))) ) ) (t (log-completion level (format "[WARNING] ancestor path node of type [%s] is neither the name, nor the value of the js2-object-prop-node before; will ignore it." (js2-node-short-name next-ancestor-node)))) ) ) ) (defun dojo-add-function-candidates (ancestor-path ancestor-index scopes level prefix current-class this-symbol) "Function adding function candidates if we are inside a js2-function-node. The ancestor-index is supposed to point to that js2-function-node." (log-completion level (format "[%s] Called dojo-add-function-candidates" ancestor-index)) (let* ((function-node (nth ancestor-index ancestor-path)) (next-ancestor-node (nth (1+ ancestor-index) ancestor-path)) (body-node (js2-function-node-body function-node)) ) (cond ((eq next-ancestor-node body-node) (let* ((new-scopes (append (list (dojo-construct-function-param-scope function-node new-level)) scopes)) ) (cond ((dojo-has-node-type body-node "js2-block-node") (dojo-add-block-candidates ancestor-path (1+ ancestor-index) new-scopes (1+ level) prefix current-class this-symbol)) (t (log-completion level (format "[WARNING] body-node of js2-function-node has unsupported node type [%s], will ignore it." (js2-node-short-name body-node)))) ))) (t (log-completion level (format "[WARNING] next-ancestor-node of type [%s] is unsupported child node of js2-function-node." (js2-node-short-name next-ancestor-node)))) ) ) ) (defun dojo-get-completion-annotation (arg) ; (format "Beschreibung: %s" arg) "" ) (defun dojo-get-completion-doc (arg) (format "Documentation for %s" arg) ) (defun inspect-node (node end-p) (log-completion 0 (format "Node: %s; end-p: %s, begin %i, absbegin %i, absend %i" (js2-node-short-name node) end-p (js2-node-pos node) (js2-node-abs-pos node) (js2-node-abs-end node))) t ) (defun dojo-do-run-parser () (let* ((ast nil) ) (log-completion 0 (format "Time before parsing: %f" (float-time))) (setq ast (js2-parse)) (log-completion 0 (format "Time after parsing: %f" (float-time))) ast ) ) (defun dojo-run-parser () (interactive) (let* ((ast (dojo-do-run-parser)) ) (js2-visit-ast ast 'inspect-node) ; (message "Parse result: %s" ast) (log-completion 0 (format "Time after visiting: %f" (float-time))) ) ) ; (cond ((dojo-is-at-end-of-ancestor-path ancestor-path ancestor-index) ; (dojo-add-candidates-if-prefix prefix (list "var" (dojo-class-return-var-name current-class)))) ; ) ; (let* ((block-node (nth ancestor-index ancestor-path)) ; (children (js2-block-node-kids block-node)) ; ) ; (dolist (child children) ; (log-completion (format "Child of name [%s] starts at [%s] and ends at [%s]" ; (js2-node-short-name child) (js2-node-abs-pos child) (js2-node-abs-end child))) ; ) ; ) (defun dojo-js-completion-construct-function-arguments-scope (function-symbol this-symbol) "Returns a dojo-scope containing all arguments of the given function-symbol. Note that the constructed scope will not receive an id, and will not be placed into the workspace-global id-to-scope map, as it is not meant to live beyond the duration of one completion calculation." (let* ((argument-symbols (dojo-symbol-get-function-arguments function-symbol)) (scope (dojo-scope--create)) (name-to-symbol (make-hash-table :test 'equal))) (if this-symbol (puthash "this" this-symbol name-to-symbol)) (dolist (argument-symbol argument-symbols) (puthash (dojo-symbol-name argument-symbol) argument-symbol name-to-symbol)) (setf (dojo-scope-defined-symbols-check-enabled scope) nil) (setf (dojo-scope-name-to-symbol scope) name-to-symbol) scope)) (defun dojo-js-completion-construct-function-body-scope (function-symbol level) "Returns a dojo-scope based on the dojo-function-scope of the function-symbol. For now it contains all symbols of that scope, but reducing this to just the symbols defined before point would be desirable. Note that the constructed scope will not receive an id, and will not be placed into the workspace-global id-to-scope map, as it is not meant to live beyond the duration of one completion calculation." (let* ((function-scope (dojo-symbol-get-function-scope function-symbol)) (function-scope-name-to-symbol (if function-scope (dojo-scope-name-to-symbol function-scope) nil)) (function-scope-key-to-symbol (if function-scope (dojo-scope-key-to-symbol function-scope) nil)) (scope (if function-scope-name-to-symbol (dojo-scope--create) nil)) (name-to-symbol (if function-scope-name-to-symbol (make-hash-table :test 'equal) nil)) (key-to-symbol (if function-scope-key-to-symbol (make-hash-table :test 'equal) nil))) (if function-scope (progn (if function-scope-name-to-symbol (maphash (lambda (name symbol) (puthash name symbol name-to-symbol)) function-scope-name-to-symbol)) (if function-scope-key-to-symbol (maphash (lambda (key symbol) (puthash key symbol key-to-symbol)) function-scope-key-to-symbol)) (setf (dojo-scope-defined-symbols-check-enabled scope) t) (setf (dojo-scope-key-to-symbol scope) key-to-symbol) (setf (dojo-scope-name-to-symbol scope) name-to-symbol) scope) nil))) (defun dojo-js-completion-call-function-with-lang-hitch (ancestor-path ancestor-index defined-symbols scopes level prefix class) (log-completion level (format "[%s] Called call-function-with-lang-hitch" ancestor-index)) (let* ((hitch-symbol (dojo-js-completion-get-node-symbol defined-symbols scopes level (nth 0 arg-nodes) nil)) (hitch-this-symbol (copy-tree hitch-symbol)) (hitch-scope (dojo-scope--create)) (hitch-name-to-symbol (make-hash-table :test 'equal)) (hitch-scopes (append (list hitch-scope) scopes)) (function-node (nth ancestor-index ancestor-path)) (function-key (dojo-js-key-get-complete-node-key function-node)) (function-symbol (if function-key (dojo-core-util-get-symbol-from-scope-by-key (nth 0 scopes) function-key) nil))) (log-completion level (format "..... Querying function scope under key [%s]" function-key)) (puthash "this" t defined-symbols) (puthash "this" hitch-this-symbol hitch-name-to-symbol) (setf (dojo-scope-defined-symbols-check-enabled hitch-scope) nil) (setf (dojo-scope-name-to-symbol hitch-scope) hitch-name-to-symbol) (dojo-js-completion-add-function-candidates ancestor-path ancestor-index defined-symbols hitch-scopes level prefix class function-symbol hitch-this-symbol))) (defun dojo-js-completion-add-defined-symbols (defined-symbols child-nodes ref-child level) "Adds the names of all symbols defined by the subset of child-symbols before ref-child to the given defined-symbols map (mapped to t). Does not step down into sub-blocks of code, as they are not relevant for the question which symbols are visible at ref-child." (dolist (child-node child-nodes) ; Stop at ref-child. If no ref-child exists, since we are at the end of the ancestor-path, ; instead use (point) as limit, and compare against node position. (if (or (and (null ref-child) (>= (js2-node-abs-pos child-node) (point))) (and (not (null ref-child)) (eq child-node ref-child))) (return nil)) (cond ((dojo-has-node-type child-node "js2-expr-stmt-node") (dojo-js-completion-add-defined-expr-stmt-symbol defined-symbols (js2-expr-stmt-node-expr child-node) level)) ((or (dojo-has-node-type child-node "js2-if-node")) ; Silently ignore nodes opening deeper levels of code, whose declarations are not relevant for the code afterwards. ()) (t (log-completion level (format "[WARNING] dojo-js-completion-add-defined-symbols ignores not yet supported child node of type [%s]" (js2-node-short-name child-node))))))) (defun dojo-js-completion-add-defined-expr-stmt-symbol (defined-symbol node level) "Adds all symbols declared in the given child node of an js2-expr-stmt-node to the scope. I.e. the symbols declared in js2-expr-stmt-node-expr." (cond ((dojo-has-node-type node "js2-var-decl-node") (dojo-js-completion-add-defined-var-decl-symbols defined-symbols node level)) (t (log-extract (format "[WARNING] dojo-js-completion-add-defined-expr-stmt-symbol ignores not yet supported node of type [%s]" (js2-node-short-name node)))))) (defun dojo-js-completion-add-defined-var-decl-symbols (defined-symbols var-decl-node level) (let* ((var-decl-kids (js2-var-decl-node-kids var-decl-node))) (dolist (var-init-node var-decl-kids) (let* ((name-node (js2-var-init-node-target var-init-node)) (var-name (dojo-get-name-from-name-node name-node))) (log-completion level (format "... register-defined-symbol: [%s]" var-name)) (puthash var-name t defined-symbols))))) (defun dojo-js-completion-add-defined-scope-symbols (level defined-symbols scope) "Adds all symbols of the given scope to the given defined-symbols set." (log-completion level (format "... Will register the following symbols in defined-symbols:")) (let* ((name-to-symbol (dojo-scope-name-to-symbol scope))) (maphash (lambda (name symbol) (log-completion level (format "..... %s" (dojo-core-util-symbol-to-short-string symbol))) (puthash name t defined-symbols)) name-to-symbol))) (defun dojo-add-define-function-candidates (ancestor-path ancestor-index prefix current-class) "Function adding completion candidates if we are inside the function parameter to the define call, i.e. the function whose parameters are the imported symbols. The ancestor-index is supposed to point to the js2-function-node representing that function." (log-completion 0 (format "[%s] Called dojo-add-define-function-candidates, prefix = [%s]" ancestor-index prefix)) (let* ((level 0) (scopes (list (dojo-construct-import-scope 0 current-class)))) (dojo-add-node-candidates ancestor-path (1+ ancestor-index) scopes level prefix class))) (defun dojo-js-completion-get-node-symbol (defined-symbols scopes level node &optional max-abs-pos) "This function returns the dojo-symbol matching the given js2-node from the given list of scopes. If no symbol can be found, nil is returned. If the optional parameter max-abs-pos is set to a buffer position, child nodes whose absolute position (as returned by js2-node-abs-pos) is greater or equal than this value are ignored. This is for situations like this. this.comboBox.validate(); with (point) being located after the first 'this.', which without this parameter would be interpreted like 'this.this.comboBox.validate();' in terms of the abstract syntax tree. The list of scopes is interpreted such that former scopes override later scopes." (cond ((dojo-has-node-type node "js2-name-node") (let* ((result-symbol (dojo-js-completion-get-name-symbol defined-symbols scopes (1+ level) node))) result-symbol)) ((dojo-has-node-type node "js2-prop-get-node") (let* ((result-symbol (dojo-js-completion-get-prop-get-symbol defined-symbols scopes (1+ level) node max-abs-pos))) result-symbol)) ((dojo-has-node-type node "js2-keyword-node") (let* ((result-symbol (dojo-js-completion-get-keyword-symbol defined-symbols scopes (1+ level) node))) result-symbol)) ; ((dojo-has-node-type node "js2-elem-get-node") ; (log-extract (format ".. dojo-scopes-get-node-symbol processes case js2-elem-get-node")) ; (let* ((result-symbol (dojo-scopes-get-elem-get-symbol scopes (1+ level) node create-if-missing create-assignments-class max-abs-pos))) ; (log-extract (format "dojo-scopes-get-elem-get-symbol returned symbol [%s]" ; (if (dojo-symbol-p result-symbol) (dojo-core-util-symbol-to-short-string result-symbol) result-symbol))) ; result-symbol)) ; ((dojo-has-node-type node "js2-call-node") ; (let ((fct-symbol (dojo-js-assign-process-call-node scopes class (1+ level) node))) ; fct-symbol)) ; ((dojo-has-node-type node "js2-cond-node") ; (let ((cond-symbol (dojo-scopes-get-cond-symbol scopes level node create-if-missing create-assignments-class max-abs-pos))) ; cond-symbol)) (t (log-assign (1+ level) (format "[WARNING] dojo-js-completion-get-node-symbol: Ignoring not yet supported node of type [%s]" (js2-node-short-name node))) nil))) (defun dojo-js-completion-get-name-symbol (defined-symbols scopes level node &optional skip-defined-symbols-check) (let* ((name (dojo-get-name-from-name-node node)) (ret-symbol nil)) (log-completion (1+ level) (format "Called get-name-symbol for name [%s]" name)) (dolist (scope scopes) (let* ((name-to-symbol (dojo-scope-name-to-symbol scope)) (defined-symbols-check-enabled (dojo-scope-defined-symbols-check-enabled scope)) (symbol (if (or skip-defined-symbols-check (null defined-symbols-check-enabled) (gethash name defined-symbols)) (progn (gethash name name-to-symbol)) nil))) (log-completion (1+ level) (format "Checking scope with content %s" (hash-table-get-all-keys name-to-symbol))) (log-completion (1+ level) (format "... found symbol [%s]" (if symbol (dojo-core-util-symbol-to-short-string symbol) "---"))) (if (not (null symbol)) (setq ret-symbol symbol)) (if (not (null ret-symbol)) (return nil)))) ret-symbol)) (defun dojo-js-completion-get-prop-get-symbol (defined-symbols scopes level node &optional max-abs-pos) (let* ((left-node (js2-infix-node-left node)) (left-symbol (dojo-js-completion-get-node-symbol defined-symbols scopes (1+ level) left-node max-abs-pos)) (right-node (js2-infix-node-right node))) (if (null left-symbol) (log-completion 0 (format "[WARNING] dojo-js-completion-get-prop-get-symbol found no symbol")) (progn (log-completion level (format "dojo-js-completion-get-prop-get-symbol found left-symbol [%s]" (dojo-core-util-symbol-to-short-string left-symbol))) (if (or (null max-abs-pos) (< (js2-node-abs-pos right-node) max-abs-pos)) (let ((found-right-symbol (dojo-js-completion-get-child-symbol left-symbol (1+ level) right-node))) (if (null found-right-symbol) (progn (log-completion level (format "Found no right-symbol although its node is within search range.")) (log-completion level (format "... Returning left-symbol %s" (dojo-core-util-symbol-to-short-string left-symbol))) left-symbol) (log-completion level (format "Found right-symbol %s" (dojo-core-util-symbol-to-short-string found-right-symbol))) found-right-symbol)) (log-completion level (format "Right-node is after max-abs-pos, returning left-symbol %s" (dojo-core-util-symbol-to-short-string left-symbol))) left-symbol))))) (defun dojo-js-completion-get-keyword-symbol (defined-symbols scopes level node) (let* ((node-type (js2-node-type node))) (cond ((eq node-type js2-THIS) (if (gethash "this" defined-symbols) (dojo-scopes-get-symbol-by-name scopes (1+ level) "this"))) (t (log-completion 0 (format "[WARNING] Unsupported type [%s] of keyword-node." (dojo-js2-node-type-to-string node-type))))))) (defun dojo-js-completion-get-child-symbol (parent-symbol level child-node) ; If the parent-symbol is an import, then we must follow the project/path reference in that symbol towards the ; corresponding dojo-class, and then take the this-symbol of that dojo-class. (log-completion level (format "Called dojo-js-completion-get-child-symbol for parent-symbol %s and child-node [%s]" (dojo-core-util-symbol-to-short-string parent-symbol) (js2-node-short-name child-node))) (let ((object-symbol parent-symbol)) (cond ((dojo-core-util-is-import-symbol parent-symbol) (let* ((import-resource-id (dojo-symbol-get-import-resource-id parent-symbol)) (import-path (dojo-symbol-get-import-path parent-symbol)) (import-class (dojo-js-api-get-class-by-import-symbol parent-symbol)) (import-class-static-symbol (if import-class (dojo-class-static-symbol import-class) nil))) (if import-class-static-symbol (progn (log-completion level (format "... get-child-symbol steps to static-symbol %s of import [%s %s]" (dojo-core-util-symbol-to-short-string import-class-static-symbol) import-resource-id import-path)) (setq object-symbol import-class-static-symbol)) (log-completion level (format "... get-child-symbol found import-resource-id/path [%s %s]" import-resource-id import-path)) (log-completion level (format "... get-child-symbol found import-class [%s:%s]" (if import-class (dojo-class-project import-class) "---") (if import-class (dojo-class-path import-class) "---"))) (log-completion level (format "... get-child-symbol found symbol [%s %s %s] with resource/path [%s %s], but no static-symbol." (dojo-symbol-id parent-symbol) (dojo-core-util-symbol-type-to-string parent-symbol) (dojo-symbol-name parent-symbol) import-resource-id import-path))))) ((dojo-core-util-is-instance-symbol parent-symbol) (let* ((import-resource-id (dojo-symbol-get-import-resource-id parent-symbol)) (import-path (dojo-symbol-get-import-path parent-symbol)) (instance-class (dojo-js-api-get-class-by-import-symbol parent-symbol)) (instance-class-this-symbol (if instance-class (dojo-class-this-symbol instance-class) nil))) (if instance-class-this-symbol (progn (log-completion level (format "... get-child-symbol steps to this-symbol of instance [%s %s]" import-resource-id import-path)) (setq object-symbol instance-class-this-symbol)) (log-completion level (format "... get-child-symbol found symbol [%s %s %s] with resource/path [%s %s], but no this-symbol." (dojo-symbol-id parent-symbol) (dojo-core-util-symbol-type-to-string parent-symbol) (dojo-symbol-name parent-symbol) import-resource-id import-path)))))) (log-completion level (format "Will continue with object-symbol %s" (dojo-core-util-symbol-to-short-string object-symbol))) (cond ((null object-symbol) (log-completion level (format "[WARNING] object-symbol is nil")) nil) ((null (dojo-symbol-type object-symbol)) (log-completion level (format "[WARNING] object-symbol %s has no type" (dojo-core-util-symbol-to-short-string object-symbol))) nil) ((dojo-core-util-is-object-symbol object-symbol) (cond ((dojo-has-node-type child-node "js2-name-node") (dojo-js-completion-get-name-child-symbol object-symbol level child-node)) (t (log-assign (1+ level) (format "[WARNING] ... get-child-symbol: Ignoring not yet supported node type [%s]" (js2-node-short-name child-node))) nil))) ; ((dojo-core-util-is-array-symbol object-symbol) ; (cond ((eq type 'DOJO-JSTYPE-FUNCTION) ; (cond ((dojo-has-node-type child-node "js2-name-node") ; (let* ((fct-name (dojo-get-name-from-name-node child-node)) ; (fct-symbol (dojo-core-util-get-or -create-builtin-fct object-symbol fct-name level))) ; fct-symbol)) ; (t ; (log-assign (1+ level) (format "[WARNING] dojo-scopes-get-child-symbol: Ignoring not yet supported node type [%s] of array symbol." ; (js2-node-short-name child-node)))))) ; (t ; (log-assign (1+ level) (format (concat "[WARNING] dojo-scopes-get-child-symbol: Cannot determine symbol of array, " ; "since symbol type [%s] is not yet supported at this point.") type))))) ; ((and (dojo-core-util-is-import-symbol object-symbol) (eq (dojo-symbol-get-import-type object-symbol) 'DOJO-IMPORT-I18N)) ; ; TODO: The vast majority of the symbols in an i18n file are strings. But there are a few exceptions, e.g. format objects for date formats. ; ; As we don't really parse the i18n files up to now, ignore those exceptions and always assume that such a member is a string. ; ; But the completely accurate way would be parsing the i18n file, and deciding about the type based on that parse result. ; 'DOJO-JSTYPE-STRING) ; ((dojo-core-util-is-instance-symbol object-symbol) ; ()) ; Only symbols of type object are considered here so far. (t (log-completion level (format "[WARNING] get-child-symbol does not support object-symbol %s" (dojo-core-util-symbol-to-short-string object-symbol))) nil)))) (defun dojo-js-completion-get-name-child-symbol (parent-symbol level child-node) "Returns the child symbol corresponding to the given child-node, relative to the given parent-symbol. Assumptions: parent-symbol is of type object; child-node is of type js2-name-node." (let* ((name (dojo-get-name-from-name-node child-node)) (child-symbol (gethash name (dojo-symbol-get-object-members parent-symbol)))) (log-completion level (format "get-name-child-symbol called for name [%s] returns child-symbol %s" name (dojo-core-util-symbol-to-short-string child-symbol))) child-symbol)) (defun dojo-js-completion-get-object-prop-symbol (object-symbol object-prop-node) (let ((left-node (js2-infix-node-left object-prop-node))) (cond ((dojo-has-node-type left-node "js2-name-node") (let ((name (dojo-get-name-from-name-node left-node))) (dojo-symbol-get-object-member name object-symbol))) (t (log-completion 0 (format "[WARNING] Unsupported node type [%s] of object-prop left node." (js2-node-short-name left-node))))))) (defun dojo-js-completion-add-all-scope-candidates (defined-symbols scopes level prefix) (log-completion level (format "add-all-scope-candidates called with prefix [%s] and [%s] scopes" prefix (length scopes))) (dolist (scope scopes) (let* ((name-to-symbol (dojo-scope-name-to-symbol scope)) (names (hash-table-get-all-keys name-to-symbol)) ) (dojo-add-candidates-if-prefix level prefix names) ) ) ) (defun dojo-js-completion-add-defined-symbols-candidates-if-prefix (defined-symbols scopes level prefix) ; (let* ((prefix-starts-with-dot (starts-with prefix ".")) ; (prefix-without-dot (if prefix-starts-with-dot (substring prefix 1) prefix))) (maphash (lambda (symbol-name unused-value) (if (string-prefix-p prefix symbol-name) (progn (log-completion level (format "... [CANDIDATE] %s" symbol-name)) (push symbol-name dojo-completion-candidates)))) defined-symbols)) (defun dojo-js-completion-add-instance-imports-if-prefix (level prefix class) (let* ((define-symbol (dojo-class-define-symbol class)) (argument-symbols (if (and define-symbol (dojo-core-util-is-function-symbol define-symbol)) (dojo-symbol-get-function-arguments define-symbol) nil))) (dolist (argument-symbol argument-symbols) (if (dojo-core-util-is-import-symbol argument-symbol) (let* ((import-resource-id (dojo-symbol-get-import-resource-id argument-symbol)) (import-class (dojo-core-util-get-or-load-class dojo-current-workspace import-resource-id)) (import-class-this-symbol (if import-class (dojo-class-this-symbol import-class) nil)) (import-this-members (if import-class-this-symbol (dojo-symbol-get-object-members import-class-this-symbol) nil))) (if import-this-members (catch 'stop-maphash (maphash (lambda (curr-name curr-symbol) (dojo-js-completion-add-candidates-if-prefix (1+ level) prefix (dojo-symbol-name argument-symbol)) (throw 'stop-maphash nil)) import-this-members)))))))) (defun dojo-js-completion-add-candidates-if-prefix (level prefix candidates &optional defined-symbols) "This function adds one or several candidate strings to the dojo-completion-candidates list, if they start with the given prefix. For the candidates parameter, the following cases are supported: - candidates is a string: A single candidate has been passed - candidates is a list of strings: Multiple candidates have been passed - candidates is a list of lists: The latter lists contain two elements, a string and a dojo-symbol. The string is the completion candidate, the symbol a dojo-symbol corresponding to the completion candidate. If the prefix starts with a dot '.', that dot is ignored when comparing strings, i.e. prefix '.foo' is considered a prefix of candidate 'foobar'; however the registered candidate will be '.foobar' and not 'foobar'. The motivation for this behaviour is, that the user expects completion when hitting the dot key, but we need some non-empty prefix when completing. Thus, when the user types 'foo.bar', the prefix ending up here will be '.bar'." ; If a single string is passed, put it into a list. (if (not (listp candidates)) (setq candidates (list candidates))) (let* ((prefix-starts-with-dot (starts-with prefix ".")) (prefix-without-dot (if prefix-starts-with-dot (substring prefix 1) prefix))) (log-completion level (format "... add-candidates-if-prefix: prefix = [%s], prefix-starts-with-dot = [%s], prefix-without-dot = [%s]." prefix prefix-starts-with-dot prefix-without-dot)) (log-completion level (format "... [CANDIDATES] %s" candidates)) (dolist (candidate-list candidates) (let* ((candidate (if (listp candidate-list) (nth 0 candidate-list) candidate-list)) (symbol (if (listp candidate-list) (nth 1 candidate-list) nil))) (if (and (or (null defined-symbols) (gethash candidate defined-symbols)) (string-prefix-p prefix-without-dot candidate)) (if prefix-starts-with-dot (push (dojo-js-completion-get-candidate-with-text-properties (concat "." candidate) symbol) dojo-completion-candidates) (push (dojo-js-completion-get-candidate-with-text-properties candidate symbol) dojo-completion-candidates))))))) (defun dojo-js-completion-get-object-member-completion-lists (object-symbol) (let* ((completion-lists ()) (name-to-symbol (dojo-symbol-get-object-members object-symbol))) (maphash (lambda (name symbol) (push (list name symbol) completion-lists)) name-to-symbol) completion-lists)) (defun dojo-js-completion-get-candidate-with-text-properties (candidate symbol) (if (null symbol) candidate (let* ((symbol-id (dojo-symbol-id symbol)) (class-id (dojo-symbol-class-id symbol)) (is-api-symbol (dojo-symbol-is-api-symbol symbol)) (ret-candidate candidate)) (add-text-properties 0 (length ret-candidate) (list :symbol-id symbol-id :class-id class-id :is-api is-api-symbol) ret-candidate) ret-candidate))) (defun dojo-js-completion-construct-complete-fct-map () (let* ((node-to-fct (make-hash-table :test 'equal))) ; The following functions all need to have the following parameters in this order: ; ancestor-path ancestor-index defined-symbols scopes level prefix class ; Additional function-specific optional parameters are allowed at the ; end of the parameter lists; they will be ignored in the general code ; calling these functions. (puthash "js2-array-node" 'dojo-js-completion-add-array-candidates node-to-fct) (puthash "js2-assign-node" 'dojo-js-completion-add-assign-candidates node-to-fct) (puthash "js2-block-node" 'dojo-js-completion-add-block-candidates node-to-fct) (puthash "js2-scope" 'dojo-js-completion-add-block-candidates node-to-fct) (puthash "js2-call-node" 'dojo-js-completion-add-call-candidates node-to-fct) (puthash "js2-cond-node" 'dojo-js-completion-add-cond-candidates node-to-fct) (puthash "js2-elem-get-node" 'dojo-js-completion-add-elem-get-candidates node-to-fct) (puthash "js2-expr-stmt-node" 'dojo-js-completion-add-expr-stmt-candidates node-to-fct) (puthash "js2-for-node" 'dojo-js-completion-add-for-candidates node-to-fct) (puthash "js2-for-in-node" 'dojo-js-completion-add-for-in-candidates node-to-fct) (puthash "js2-function-node" 'dojo-js-completion-add-function-candidates node-to-fct) (puthash "js2-if-node" 'dojo-js-completion-add-if-candidates node-to-fct) (puthash "js2-infix-node" 'dojo-js-completion-add-infix-candidates node-to-fct) (puthash "js2-name-node" 'dojo-js-completion-add-name-candidates node-to-fct) (puthash "js2-new-node" 'dojo-js-completion-add-new-candidates node-to-fct) (puthash "js2-object-node" 'dojo-js-completion-add-object-candidates node-to-fct) (puthash "js2-paren-node" 'dojo-js-completion-add-paren-candidates node-to-fct) (puthash "js2-prop-get-node" 'dojo-js-completion-add-prop-get-candidates node-to-fct) (puthash "js2-return-node" 'dojo-js-completion-add-return-candidates node-to-fct) (puthash "js2-var-decl-node" 'dojo-js-completion-add-var-decl-candidates node-to-fct) (puthash "js2-throw-node" 'dojo-js-completion-add-throw-candidates node-to-fct) node-to-fct)) (defvar dojo-js-completion-complete-fct-map (dojo-js-completion-construct-complete-fct-map) "Map from js2-node names (like js2-call-node) to the corresponding functions to be called in dojo-js-completion-add-node-candidates.") (provide 'dojo-js-completion)