Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Emacs' xref framework to jump to classes/defines/types #127

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ Features
1. Syntax highlighting
2. Indentation and alignment of expressions and statements
3. Tag navigation (aka `imenu`)
4. Manual validation and linting of manifests (see [Flycheck][] for on-the-fly
4. Cross-reference navigation (aka `xref`) to classes, defined types, data
types or functions defined in other modules
5. Manual validation and linting of manifests (see [Flycheck][] for on-the-fly
validation and linting)
5. Integration with [Puppet Debugger][]
6. Integration with [Puppet Debugger][]

Installation
------------
Expand Down Expand Up @@ -62,6 +64,8 @@ Key | Command
<kbd>C-c C-z</kbd> | Launch a puppet-debugger REPL
<kbd>C-c C-r</kbd> | Send the currently marked region to the REPL
<kbd>C-c C-b</kbd> | Send the current buffer to the REPL
<kbd>M-.</kbd> | Jump to the resource definition at point
<kbd>M-,</kbd> | Jump back after visiting a resource definition


For the integration with puppet-debugger to work, the puppet-debugger gem needs
Expand Down
109 changes: 109 additions & 0 deletions puppet-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,14 @@ buffer-local wherever it is set."
:group 'puppet
:package-version '(puppet-mode . "0.4"))

(defcustom puppet-module-path
'("/etc/puppetlabs/code/environments/production/modules")
"Paths to search for modules when resolving cross references.
Remote directories as defined by TRAMP are possible but very slow
when accessed."
:group 'puppet
:type '(repeat directory))


;;; Version information
(defun puppet-version (&optional show-version)
Expand Down Expand Up @@ -1122,6 +1130,105 @@ With a prefix argument SUPPRESS it simply inserts $."
(delete-region (+ min 1) (- max 1)))))



;;; Xref

(defun puppet-module-root (file)
"Return the Puppet module root directory for FILE.
Walk up the directory tree until a directory is found, that
either contains a \"manifests\", \"lib\" or \"types\" subdir.
Return the directory name or nil if no directory is found."
(locate-dominating-file
file
(lambda (path)
(and (file-accessible-directory-p path)
(or (file-readable-p (expand-file-name "manifests" path))
(file-readable-p (expand-file-name "lib" path))
(file-readable-p (expand-file-name "types" path)))))))

(defun puppet-autoload-path (identifier &optional directory extension)
"Resolve IDENTIFIER into Puppet module and relative autoload path.
Use DIRECTORY as module subdirectory (defaults to \"manifests\"
and EXTENSION as file extension (defaults to \".pp\") when
building the path. Return a cons cell where the first part is
the module name and the second part is a relative path name below
that module where the identifier should be defined according to
the Puppet autoload rules."
(let* ((components (split-string identifier "::"))
(module (car components))
(path (cons (or directory "manifests")
(butlast (cdr components))))
(file (if (cdr components)
(car (last components))
"init")))
(cons module
(concat (mapconcat #'file-name-as-directory path "")
file
(or extension ".pp")))))

(defun puppet--xref-backend ()
"The Xref backend for `puppet-mode'."
'puppet)

(cl-defmethod xref-backend-identifier-at-point ((_backend (eql puppet)))
"Return the Puppet identifier at point."
(let ((thing (thing-at-point 'symbol)))
(and thing (substring-no-properties thing))))

(cl-defmethod xref-backend-definitions ((_backend (eql puppet)) identifier)
"Find the definitions of a Puppet resource IDENTIFIER.
First the location of the visited file is checked. Then all
directories from `puppet-module-path' are searched for the module
and the file according to Puppet's autoloading rules."
(let* ((resource (downcase (if (string-prefix-p "::" identifier)
(substring identifier 2)
identifier)))
(pupfiles (puppet-autoload-path resource))
(typfiles (puppet-autoload-path resource "types"))
(funfiles (puppet-autoload-path resource "functions"))
(xrefs '()))
(if pupfiles
(let* ((module (car pupfiles))
;; merged list of relative path names to classes/defines/types
(pathlist (mapcar #'cdr (list pupfiles typfiles funfiles)))
;; list of directories where this module might be
(moddirs (mapcar (lambda (dir) (expand-file-name module dir))
puppet-module-path))
;; the regexp to find the resource definition in the file
(resdef (concat "^\\(class\\|define\\|type\\|function\\)\\s-+"
resource
"\\((\\|{\\|\\s-\\|$\\)"))
;; files to visit when searching for the resource
(files '()))
;; Check the current module directory (if the buffer actually visits
;; a file) and all module subdirectories from `puppet-module-path'.
(dolist (dir (if buffer-file-name
(cons (puppet-module-root buffer-file-name) moddirs)
moddirs))
;; Try all relative path names below the module directory that
;; might contain the resource; save the file name if the file
;; exists and we haven't seen it (we might try to check a file
;; twice if the current module is also below one of the dirs in
;; `puppet-module-path').
(dolist (path pathlist)
(let ((file (expand-file-name path dir)))
(if (and (not (member file files))
(file-readable-p file))
(setq files (cons file files))))))
;; Visit all found files to finally locate the resource definition
(dolist (file files)
(with-temp-buffer
(insert-file-contents-literally file)
(save-match-data
(when (re-search-forward resdef nil t)
(push (xref-make
(match-string-no-properties 0)
(xref-make-file-location
file (line-number-at-pos (match-beginning 1)) 0))
xrefs)))))))
xrefs))



;;; Imenu

Expand Down Expand Up @@ -1273,6 +1380,8 @@ for each entry."
;; Alignment
(setq align-mode-rules-list puppet-mode-align-rules)
(setq align-mode-exclude-rules-list puppet-mode-align-exclude-rules)
;; Xref
(add-hook 'xref-backend-functions #'puppet--xref-backend)
;; IMenu
(setq imenu-create-index-function #'puppet-imenu-create-index))

Expand Down