branch: externals/gtags-mode commit 5525174de97fc4c0d16fca6c9e3997924a52ee9c Author: Jimmy Aguilar Mena <kratsbinov...@gmail.com> Commit: Jimmy Aguilar Mena <kratsbinov...@gmail.com>
Many changes: (gtags-mode--roots-list) : Renamed to gtags-mode--alist. It is now a list of plists. (gtags-mode--has-open-root) : Renamed to gtags-mode--get-plist. It now returns the plist associated with root after appliying the action. (gtags-mode--exec-async-sentinel) : Now executes an action to clear the completions cache in the associated plist. (gtags-mode--exec-async) : Use process-put to remember the caller buffer. (gtags-mode--list-completions) : New function to return the completion list associated with a string, or return-generate the cache conveniently. (gtags-mode--find-file-hook) : Update to new functions (xref-backend-identifier-completion-table) : Use the new function gtags-mode--list-completions. (project-files) : Delete duplicates when multiple outputs. (gtags-mode-completion-function) : New function to provide a capf backend. --- gtags-mode.el | 118 ++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 78 insertions(+), 40 deletions(-) diff --git a/gtags-mode.el b/gtags-mode.el index 15b4c1f23f..6db74cec32 100644 --- a/gtags-mode.el +++ b/gtags-mode.el @@ -50,16 +50,26 @@ :type 'string :risky t) -(defvar gtags-mode--roots-list nil +(defvar gtags-mode--alist nil "Full list of Global roots. The address is absolute for remote hosts.") -(put 'gtags-mode--roots-list 'risky-local-variable t) +(put 'gtags-mode--alist 'risky-local-variable t) + +(defun gtags-mode--get-plist (file action) + "Apply ACTION on a plist with known prefix FILE from `gtags-mode--alist'." + (let ((truename (file-truename file))) + (catch 'found + (mapc (lambda (plist) + (when (string-prefix-p (plist-get plist :root) truename) + (throw 'found (funcall action plist)))) + gtags-mode--alist) + nil))) (defvar-local gtags-mode--global (executable-find gtags-mode-global-executable)) (defvar-local gtags-mode--gtags (executable-find gtags-mode-gtags-executable)) (defvar-local gtags-mode--root nil "Project Global root for this buffer. -the address is relative on remote hosts.") +The address is relative on remote hosts and includes the remote prefix.") (defconst gtags-mode--output-format-regex "^\\([^[:blank:]]+\\)[[:blank:]]+\\([[:digit:]]+\\)[[:blank:]]+\\([^[:blank:]]+\\)[[:blank:]]+\\(.*\\)" @@ -97,15 +107,20 @@ the address is relative on remote hosts.") (defun gtags-mode--exec-async-sentinel (process event) "Sentinel to run when PROCESS emits EVENT. This is the sentinel set in `gtags-mode--exec-async'." - (let ((temp-buffer (process-buffer process))) - (if (and (eq (process-status process) 'exit) + (let ((temp-buffer (process-buffer process)) + (parent-buffer (process-get process :buffer))) + (if (and (eq (process-status process) 'exit) ;; if success (eq (process-exit-status process) 0)) - (and (buffer-name temp-buffer) + (and (buffer-name temp-buffer) ;; kill temp buffer (kill-buffer temp-buffer)) - (with-current-buffer temp-buffer + (with-current-buffer temp-buffer ;; else print error (while (accept-process-output process)) - (message "Global error output:\n%s" (buffer-string))))) - (message "Async %s: %s" (process-command process) event)) + (message "Global error output:\n%s" (buffer-string)))) + (when (buffer-live-p parent-buffer) ;; Always clear the cache + (with-current-buffer parent-buffer + (gtags-mode--get-plist gtags-mode--root + (lambda (p) (plist-put p :cache nil)))))) + (message "Async %s: %s" (process-command process) event)) ;; Notify (defun gtags-mode--exec-async (cmd args) "Run CMD with ARGS asynchronously and set SENTINEL to process. @@ -113,11 +128,13 @@ Starts an asynchronous process and sets `gtags-mode--exec-async-sentinel' as the process sentinel if SENTINEL is nil or not specified. Returns the process object." (when cmd - (make-process :name (format "%s-async" cmd) - :buffer (generate-new-buffer " *temp*" t) - :command (append (list cmd) args) - :sentinel #'gtags-mode--exec-async-sentinel - :file-handler t))) + (let ((pr (make-process :name (format "%s-async" cmd) + :buffer (generate-new-buffer " *temp*" t) + :command (append (list cmd) args) + :sentinel #'gtags-mode--exec-async-sentinel + :file-handler t))) + (process-put pr :buffer (current-buffer)) + pr))) (defun gtags-mode--exec-sync (cmd args) "Run CMD with ARGS synchronously, on success call SENTINEL. @@ -137,12 +154,31 @@ Returns the output of SENTINEL or nil if any error occurred." (defun gtags-mode--find-root () "Return the GLOBAL project root. Return nil if none." (when-let ((root (car (gtags-mode--exec-sync gtags-mode--global - '("--print-dbpath"))))) + '("--print-dbpath"))))) (setq root (concat (file-remote-p default-directory) (file-truename root))) - (add-to-list 'gtags-mode--roots-list root) + (add-to-list 'gtags-mode--alist `(:root ,root :cache nil) + nil (lambda (o1 o2) + (string= (plist-get o1 :root) (plist-get o2 :root)))) root)) +(defun gtags-mode--list-completions (prefix) + "Get the list of completions for PREFIX. +When PREFIX is nil or empty; return the entire list of +completions usually from the cache." + (cond + ((and (stringp prefix) (not (string-blank-p prefix))) + (gtags-mode--exec-sync gtags-mode--global + (append '("--ignore-case" "--completion") + `(,(shell-quote-argument prefix))))) + ((plist-get (gtags-mode--get-plist gtags-mode--root #'identity) :cache)) + (t (gtags-mode--get-plist + gtags-mode--root + (lambda (plist) + (plist-put plist + :cache (gtags-mode--exec-sync gtags-mode--global '("--completion"))) + plist))))) + (defun gtags-mode--filter-find-symbol (args symbol creator) "Run `gtags-mode--exec-sync' with ARGS on SYMBOL and filter output with CREATOR. Returns the results as a list of CREATORS outputs similar to @@ -185,21 +221,11 @@ name, code, file, line." (list "--single-update" (file-name-nondirectory buffer-file-name))))) -(defun gtags-mode--has-open-root (file) - "Check for a known prefix for FILE in `gtags-mode--roots-list'." - (let ((truename (file-truename file))) - (catch 'found - (mapc (lambda (root) - (when (string-prefix-p root truename) - (throw 'found root))) - gtags-mode--roots-list) - nil))) - (defun gtags-mode--find-file-hook () "Try to enable `gtags' when opening a file. Check the roots and enable `gtags' if the found-file is in one of them." - (when (gtags-mode--has-open-root buffer-file-name) + (when (gtags-mode--get-plist buffer-file-name #'identity) (gtags-mode 1))) (defun gtags-mode--buffers-in-root (root) @@ -229,7 +255,7 @@ any additional command line arguments to pass to GNU Global." (cl-defmethod xref-backend-identifier-completion-table ((_backend (eql gtags))) "List all symbols." - (gtags-mode--exec-sync gtags-mode--global '("--completion"))) + (gtags-mode--list-completions nil)) (cl-defmethod xref-backend-definitions ((_backend (eql gtags)) symbol) "List all definitions for SYMBOL." @@ -261,30 +287,40 @@ any additional command line arguments to pass to GNU Global." ;; project integration =============================================== (defun gtags-mode-project-backend (dir) "Return the project for DIR as an array." - (when-let ((root (gtags-mode--has-open-root dir))) - (list 'gtags root))) + (when-let ((plist (gtags-mode--get-plist dir #'identity))) + (list 'gtags (plist-get plist :root)))) (cl-defmethod project-root ((project (head gtags))) "Root for PROJECT." (cadr project)) (cl-defmethod project-files ((project (head gtags)) &optional dirs) - "Root for PROJECT." + "List files inside all the PROJECT or in if specified DIRS ." (let* ((root (project-root project)) - (remote (file-remote-p root))) - (mapcan (lambda (dir) - (when-let* ((tdir (file-truename dir)) - ((string-prefix-p root tdir))) - (gtags-mode--filter-find-symbol - '("--path") (string-remove-prefix root tdir) - (lambda (_name _code file _line) - (concat remote file))))) - (or dirs (list root))))) + (remote (file-remote-p root)) + (results (mapcan + (lambda (dir) + (when-let* ((tdir (file-truename dir)) + ((string-prefix-p root tdir))) + (gtags-mode--filter-find-symbol + '("--path") (string-remove-prefix root tdir) + (lambda (_name _code file _line) + (concat remote file))))) + (or dirs (list root))))) + (if (> (length dirs) 1) (delete-dups results) results))) (cl-defmethod project-buffers ((project (head gtags))) "Return the list of all live buffers that belong to PROJECT." (gtags-mode--buffers-in-root (project-root project))) +;; Completion at-point +(defun gtags-mode-completion-function () + "Generate completion list." + (when-let (bounds (bounds-of-thing-at-point 'symbol)) + (list (car bounds) (cdr bounds) + (completion-table-dynamic #'gtags-mode--list-completions) + :exclusive 'no))) + ;;;###autoload (define-minor-mode gtags-mode "Use GNU Global as backend for several Emacs features in this buffer." @@ -298,6 +334,7 @@ any additional command line arguments to pass to GNU Global." (add-hook 'project-find-functions #'gtags-mode-project-backend) (add-hook 'xref-backend-functions #'gtags-xref-backend nil t) (add-hook 'after-save-hook #'gtags-mode--after-save-hook nil t) + (add-hook 'completion-at-point-functions #'gtags-mode-completion-function nil t) (setq gtags-mode--imenu-default-function imenu-create-index-function) (setq imenu-create-index-function #'gtags-mode-imenu-create-index-function) ;; Enable the mode in all the files inside `gtags-mode--root' @@ -313,6 +350,7 @@ any additional command line arguments to pass to GNU Global." (remove-hook 'project-find-functions #'gtags-mode-project-backend) (remove-hook 'xref-backend-functions #'gtags-xref-backend t) (remove-hook 'after-save-hook #'gtags-mode--after-save-hook t) + (remove-hook 'completion-at-point-functions #'gtags-mode-completion-function t) (setq imenu-create-index-function gtags-mode--imenu-default-function)))) (provide 'gtags-mode)