branch: externals/gtags-mode
commit 55b0cbadb6c39d56372db4c8af0b34e94bdb454d
Author: Jimmy Aguilar Mena <kratsbinov...@gmail.com>
Commit: Jimmy Aguilar Mena <kratsbinov...@gmail.com>

    First commit.
    
    All basic functionalities are working.
---
 global-xref.el | 233 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 233 insertions(+)

diff --git a/global-xref.el b/global-xref.el
new file mode 100644
index 0000000000..12e4f0b958
--- /dev/null
+++ b/global-xref.el
@@ -0,0 +1,233 @@
+;;; global-xref.el --- GNU Global integration with xref and imenu. -*- 
lexical-binding: t; -*-
+
+;; Copyright (C) 2022 Jimmy Aguilar Mena
+
+;; Author: Jimmy Aguilar Mena
+;; URL: https://github.com/Ergus/global-xref
+;; Keywords: xref, imenu, gtags, global
+;; Version: 1.0 alpha
+;; Package-Requires: ((emacs "28"))
+
+;; Copyright (C) 2022  Jimmy Aguilar Mena
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; GNU Global integration with xref and imenu.
+
+;;; Code:
+
+(require 'xref)
+(require 'cl-generic)
+
+(defgroup global-xref nil
+  "GNU Global grup for xref."
+  :group 'xref)
+
+(defcustom global-xref-global "global"
+  "GNU Global executable."
+  :type 'string)
+
+(defcustom global-xref-gtags "gtags"
+  "Gtags executable."
+  :type 'string)
+
+(defcustom global-xref-lighter "Global-Xref"
+  "Gtags executable."
+  :type 'string)
+
+(defun global-xref--buffer-to-list ()
+  "Return lines in current buffer in a list."
+  (let ((lines))
+    (goto-char (point-min))
+    (while (not (eobp))
+      (push (buffer-substring-no-properties
+            (line-beginning-position) (line-end-position))
+           lines)
+      (forward-line 1))
+    (nreverse lines)))
+
+(defvar-local global-xref--global (executable-find global-xref-global))
+(defvar-local global-xref--gtags (executable-find global-xref-gtags))
+
+(defvar-local global-xref--project-root nil
+  "Project Global root for this buffer.")
+
+(defun global-xref--process-file (args &optional postfunction)
+  "Run GNU Global with `process-file' and ARGS.
+Return the output as a string or passes it to POSTFUNCTION.
+Return nil if an error occurred."
+  (with-connection-local-variables
+   (when global-xref-global
+     (with-temp-buffer
+       (let ((status (apply #'process-file global-xref--global nil 
(current-buffer) nil args)))
+        (if (eq status 0)
+            (if (functionp postfunction)
+                (funcall postfunction)
+              (string-trim (buffer-substring-no-properties (point-min) 
(point-max))))
+          (error "%s exited with status %s" global-xref--global status)
+          (let ((inhibit-message t))
+            (message "global error output:\n%s" (buffer-string)))
+          nil))))))
+
+(defsubst global-xref--to-list (args)
+  "Run GNU Global with `process-file' and ARGS return a list."
+  (global-xref--process-file args #'global-xref--buffer-to-list))
+
+(defun global-xref--set-connection-locals ()
+  "Set GLOBAL connection local variables when possible."
+  (when-let* ((host (file-remote-p default-directory 'host))
+             (symvars (intern (concat "global-xref-" host "-vars")))
+             (criteria `(:machine ,host))
+             (connection-local-variables-alist t))
+    (hack-connection-local-variables criteria)
+    (unless (alist-get 'global-xref--global connection-local-variables-alist)
+      (connection-local-set-profile-variables
+       symvars
+       `((global-xref--global . ,(executable-find (file-name-base 
global-xref-global) t))
+        (global-xref--gtags . ,(executable-find (file-name-base 
global-xref-gtags) t))))
+      (connection-local-set-profiles criteria symvars))))
+
+(defun global-xref--find-root ()
+  "Return the GLOBAL project root.  Return nil if none."
+  (global-xref--process-file '("--print-dbpath")))
+
+(defun global-xref--filter-find-symbol (creator args symbol)
+  "Run GNU Global and apply CREATOR to global-xref--to-list output.
+Return the results as a list."
+  (remove nil
+         (mapcar (lambda (gtags-x-line)
+                   (when (string-match
+                          "^\\([^ \t]+\\)[ \t]+\\([0-9]+\\)[ \t]+\\([^ 
\t\]+\\)[ \t]+\\(.*\\)"
+                          gtags-x-line)
+                     (funcall creator
+                              (match-string 1 gtags-x-line)   ;; name
+                              (match-string 4 gtags-x-line)   ;; code
+                              (match-string 3 gtags-x-line)   ;; file
+                              (string-to-number (match-string 2 gtags-x-line)) 
;; line
+                              )))
+                 (global-xref--to-list
+                  (append args (list "--result=ctags-x" "--path-style=absolute"
+                                     (shell-quote-argument symbol)))))))
+
+;; Interactive commands.
+(defun global-xref-create-db (root-dir)
+  "Create a GLOBAL database in ROOT-DIR."
+  (interactive "DCreate db in directory: ")
+  (let ((default-directory root-dir)
+       (buffer (get-buffer-create " *Global-Xref create*")))
+    (with-connection-local-variables
+     (set-process-sentinel
+      (start-file-process "global-xref-create-db" buffer global-xref--gtags)
+      (lambda (process event)
+       (message "Global created %s: %s" root-dir (string-trim event))
+       ;; If not GLOBAL root, maybe we can set it after this.
+       (when (and (eq (process-status process) 'exit)
+                  (not global-xref--project-root))
+         (setq global-xref--project-root (global-xref--find-root))
+         (message "Set GLOBAL project-root: %s" 
global-xref--project-root)))))))
+
+(defun global-xref-update ()
+  "Update GLOBAL project database."
+  (interactive)
+  (if global-xref--project-root
+      (global-xref--process-file '("--update"))
+    (error "Not under a GLOBAL project")))
+
+(defun global-xref-update-file (file)
+  "Update GLOBAL project database for single FILE."
+  (interactive "fFile: ")
+  (if (and global-xref--project-root
+          (file-exists-p file)
+          (string-prefix-p global-xref--project-root (expand-file-name file)))
+      (global-xref--process-file (list "--single-update" file))
+    (error "Root: %s Buffer file: %s" global-xref--project-root file)))
+
+(defun global-xref--after-save-hook ()
+  "After save hook to update GLOBAL database with changed data."
+  (when buffer-file-name
+    (global-xref-update-file buffer-file-name)))
+
+;; xref integration
+(defun global-xref--find-symbol (args symbol)
+  "Run GNU Global to create xref input list with ARGS on SYMBOL.
+Return the results as a list of xref location objects.  ARGS are
+any additional command line arguments to pass to GNU Global."
+  (global-xref--filter-find-symbol
+   (lambda (_name code file line)
+     (xref-make code (xref-make-file-location
+                     (concat (file-remote-p default-directory) file)
+                     line 0)))
+   args symbol))
+
+(defun global-xref-xref-backend ()
+  "Global-Xref backend for Xref."
+  (and global-xref--project-root 'global-xref))
+
+(cl-defmethod xref-backend-identifier-completion-table ((_backend (eql 
global-xref)))
+  "List all symbols."
+  (global-xref--to-list '("--completion")))
+
+(cl-defmethod xref-backend-definitions ((_backend (eql global-xref)) symbol)
+  "List all definitions for SYMBOL."
+  (global-xref--find-symbol '("--definition") symbol))
+
+(cl-defmethod xref-backend-references ((_backend (eql global-xref)) symbol)
+  "List all referenced for SYMBOL."
+  (global-xref--find-symbol '("--reference") symbol))
+
+(cl-defmethod xref-backend-apropos ((_backend (eql global-xref)) symbol)
+  "List grepped list of candidates SYMBOL."
+  (global-xref--find-symbol '("--grep") symbol))
+
+;; imenu integration
+(defvar-local global-xref--imenu-default-function nil)
+
+(defun global-xref--imenu-goto-function (_name line)
+  "Function to goto with imenu when LINE info."
+  (funcall-interactively #'goto-line line))
+
+(defun global-xref-imenu-create-index-function ()
+  "Make imenu use gtags."
+  (when-let ((commandfile (or (file-remote-p buffer-file-name 'localname)
+                             buffer-file-name)))
+    (global-xref--filter-find-symbol
+     (lambda (name _code _file line)
+       (list name line #'global-xref--imenu-goto-function))
+     '("--file")
+     commandfile)))
+
+;;;###autoload
+(define-minor-mode global-xref-mode
+  "Use GNU Global as backend for several Emacs features in this buffer."
+  :global nil
+  :lighter global-xref-lighter
+  (cond
+   (global-xref-mode
+    (global-xref--set-connection-locals)
+    (setq global-xref--project-root (global-xref--find-root))
+    (add-hook 'xref-backend-functions #'global-xref-xref-backend nil t)
+    (add-hook 'after-save-hook #'global-xref--after-save-hook nil t)
+    (setq global-xref--imenu-default-function imenu-create-index-function)
+    (setq imenu-create-index-function 
#'global-xref-imenu-create-index-function)
+    )
+   (t
+    (setq global-xref--project-root nil)
+    (remove-hook 'xref-backend-functions #'global-xref-xref-backend t)
+    (remove-hook 'after-save-hook #'global-xref--after-save-hook t)
+    (setq imenu-create-index-function global-xref--imenu-default-function))))
+
+(provide 'global-xref)
+;;; global-xref.el ends here

Reply via email to