emacs-matlab/semanticdb-matlab.el

386 lines
13 KiB
EmacsLisp

;;; semanticdb-matlab.el --- Semantic database extensions for MATLAB
;;; Copyright (C) 2008, 2012, 2013, 2019 David Engster
;; Author: David Engster <dengste@eml.cc>
;; based heavily on semanticdb-skel.el (C) Eric Ludlam
;; This file is not part of GNU Emacs.
;; This 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, or (at your option)
;; any later version.
;; This software 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 GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary:
;;
;; Support for Semantic Databases for MATLAB buffers.
;; For generic function searching.
(require 'eieio)
(require 'eieio-opt)
;; eval-and-compile needed because of the condition-case.
;; Normally a require is eval'd during compile, so this is the same.
(eval-and-compile
(condition-case nil
(require 'semanticdb)
(error (require 'semantic/db))))
(eval-and-compile
(require 'matlab)
(require 'matlab-shell))
;;; Code:
;; Put all directories which should be recursively scanned for your
;; personal MATLAB files here.
(defvar semanticdb-matlab-include-paths
(if (file-exists-p (expand-file-name "~/matlab"))
(list (expand-file-name "~/matlab") ;; Default location for extra code.
)
;; Else, no default path.
nil)
"Directories which should be scanned for m-files.")
;;; Classes:
(defclass semanticdb-table-matlab (semanticdb-search-results-table)
((major-mode :initform matlab-mode)
)
"A table for returning search results from MATLAB path.")
(defclass semanticdb-project-database-matlab
(semanticdb-project-database
;; Use SINGLETON if there should be only one copy of this database.
;; Do not use this if you need a different copy for different projects.
;; eieio-singleton
)
((new-table-class :initform semanticdb-table-matlab
:type class
:documentation
"New tables created for this database are of this class.")
)
"Database representing MATLAB path.")
;; Create the database, and add it to searchable databases for matlab mode.
(defvar-mode-local matlab-mode semanticdb-project-system-databases
(list
(make-instance 'semanticdb-project-database-matlab))
"Search MATLAB path for symbols.")
;; NOTE: Be sure to modify this to the best advantage of your
;; language.
(defvar-mode-local matlab-mode semanticdb-find-default-throttle
'(project omniscience)
"Search project files, then search this omniscience database.
It is not necessary to to system or recursive searching because of
the omniscience database.")
;;; Filename based methods
;;
(cl-defmethod semanticdb-get-database-tables ((obj semanticdb-project-database-matlab))
"For a MATLAB database OBJ, there are no explicit tables.
Create one of our special tables that can act as an intermediary."
;; NOTE: This method overrides an accessor for the `tables' slot in
;; a database. You can either construct your own (like tmp here
;; or you can manage any number of tables.
;; We need to return something since there is always the "master table"
;; The table can then answer file name type questions.
(when (not (slot-boundp obj 'tables))
(let ((newtable (make-instance 'semanticdb-table-matlab)))
(oset obj tables (list newtable))
(oset newtable parent-db obj)
(oset newtable tags nil)
))
(cl-call-next-method))
(cl-defmethod semanticdb-file-table ((obj semanticdb-project-database-matlab) filename)
"From OBJ, return FILENAME's associated table object."
;; NOTE: See not for `semanticdb-get-database-tables'.
(car (semanticdb-get-database-tables obj))
)
(cl-defmethod semanticdb-get-tags ((table semanticdb-table-matlab ))
"Return the list of tags belonging to TABLE."
;; NOTE: Omniscient databases probably don't want to keep large tabes
;; lolly-gagging about. Keep internal Emacs tables empty and
;; refer to alternate databases when you need something.
nil)
(cl-defmethod semanticdb-equivalent-mode ((table semanticdb-table-matlab) &optional buffer)
"Return non-nil if TABLE's mode is equivalent to BUFFER.
Equivalent modes are specified by by `semantic-equivalent-major-modes'
local variable."
(with-current-buffer buffer
(eq (or mode-local-active-mode major-mode) 'matlab-mode)))
(cl-defmethod semanticdb-full-filename ((obj semanticdb-table-matlab))
"Fetch the full filename that OBJ refers to.
This function is currently a stub."
;; FIXME
;; return filename for object - what should we do with builtin functions?
nil)
;;; Usage
;;
;; Unlike other tables, an omniscent database does not need to
;; be associated with a path. Use this routine to always add ourselves
;; to a search list.
(define-mode-local-override semanticdb-find-translate-path matlab-mode
(path brutish)
"Return a list of semanticdb tables asociated with PATH.
If brutish, do the default action.
If not brutish, do the default action, and append the system
database (if available.)"
(let ((default
;; When we recurse, disable searching of system databases
;; so that our MATLAB database only shows up once when
;; we append it in this iteration.
(let ((semanticdb-search-system-databases nil)
)
(if (fboundp 'semanticdb-find-translate-path-default)
(semanticdb-find-translate-path-default path brutish)
(error "Variable semanticdb-find-translate-path-default doesn't exist")
))))
;; Don't add anything if BRUTISH is on (it will be added in that fcn)
;; or if we aren't supposed to search the system.
(if (or brutish (not semanticdb-search-system-databases))
default
(let ((tables (apply #'append
(mapcar
(lambda (db) (semanticdb-get-database-tables db))
semanticdb-project-system-databases))))
(append default tables)))))
;;; Search Overrides
;;
;; NOTE WHEN IMPLEMENTING: Be sure to add doc-string updates explaining
;; how your new search routines are implemented.
;;
(defvar semanticdb-matlab-system-files-cache '(nil)
"Internal cache for system M files.
This variable caches all M files in the directories listed in
`semantic-matlab-system-paths-include' under MATLAB root
directory. Users can reset this cache using
`semanticdb-matlab-reset-files-cache'")
(defvar semanticdb-matlab-user-files-cache '(nil)
"Internal cache for user M files.
This variable caches all M files in the directories listed in
`semanticdb-matlab-include-paths'. Users can reset this cache
using `semanticdb-matlab-reset-files-cache'.")
(defvar semanticdb-matlab-user-class-cache nil
"Internal cache for user classes.")
(defun semanticdb-matlab-reset-files-cache ()
"Reset semanticdb-matlab file cache."
(interactive)
(setq semanticdb-matlab-user-files-cache '(nil))
(setq semanticdb-matlab-system-files-cache '(nil)))
(defun semanticdb-matlab-possibly-add-buffer-to-cache ()
"Add current buffer file name to cache.
This function will add the current buffer file name to
`semanticdb-matlab-user-files-cache' if not already there. Meant
to be called in local `after-save-hook'."
(unless (and semanticdb-matlab-user-files-cache
(member (buffer-file-name)
(cdr semanticdb-matlab-user-files-cache)))
(setcdr semanticdb-matlab-user-files-cache
(append (cdr semanticdb-matlab-user-files-cache)
(list (buffer-file-name))))))
;; Make sure newly created MATLAB files get in the user-files-cache
(add-hook 'matlab-mode-hook
(lambda ()
;; add buffer-local after-save-hook
(add-hook
'after-save-hook
'semanticdb-matlab-possibly-add-buffer-to-cache t t)))
;; Helper functions
(defun semanticdb-matlab-scan-directories
(dirs &optional recursive exclude-classes exclude-private)
"Get list of all m-files in DIRS.
DIRS is a list of directories. If RECURSIVE, every subdirectory
will be included in the search. If EXCLUDE-CLASSES, class
directories (beginning with '@') will be skipped. If
EXCLUDE-PRIVATE, 'private' directories will be skipped."
(if dirs
(let (files)
(dolist (dir dirs)
(let (subdirs)
(dolist (cur (directory-files dir t "[^.]" t))
(if (file-directory-p cur)
(when (and recursive
(not (and exclude-classes
(string-match ".*/@" cur)))
(not (and exclude-private
(string-match ".*/private$" cur))))
(push cur subdirs))
(when (string-match "\\.m$" cur)
(push cur files))))
(when subdirs
(setq files
(append files
(semanticdb-matlab-scan-directories
subdirs recursive exclude-classes exclude-private))))))
files)
nil))
(defvar semantic-matlab-dependency-system-include-path) ;; quiet compiler warning
(defun semanticdb-matlab-cache-files ()
"Cache user and system MATLAB files if necessary."
;; car of *-file-cache variables is used as flag
(unless (car semanticdb-matlab-system-files-cache)
(setq semanticdb-matlab-system-files-cache
(cons t
(semanticdb-matlab-scan-directories
semantic-matlab-dependency-system-include-path t t t))))
(unless (car semanticdb-matlab-user-files-cache)
(setq semanticdb-matlab-user-files-cache
(cons t
(semanticdb-matlab-scan-directories
semanticdb-matlab-include-paths t nil nil)))
))
(defun semanticdb-matlab-find-name (name &optional type)
"Find NAME in matlab file names.
If TYPE is 'regex, NAME is a regular expression.
If TYPE is 'prefix, NAME is a prefix."
(semanticdb-matlab-cache-files)
(let ((files (append (cdr semanticdb-matlab-system-files-cache)
(cdr semanticdb-matlab-user-files-cache)))
regexp results)
(cond
((eq type 'prefix)
(setq regexp (format "^%s.*\\.m$" name)))
((eq type 'regex)
(setq regexp (format "%s\\.m$" name)))
(t
(setq regexp (format "^%s\\.m" name))))
(dolist (cur files)
(when (string-match regexp (file-name-nondirectory cur))
(push cur results)))
results))
(define-mode-local-override semantic-ctxt-current-class-list
matlab-mode (point)
"Return a list of tag classes that are allowed at point.
If point is nil, the current buffer location is used."
(cond
((looking-at ".+=")
'(variable type))
((looking-back "\\(get\\|set\\)([a-zA-Z_0-9]*" nil)
'(variable type))
((looking-back "\\(get\\|set\\)([a-zA-Z_0-9]+,'[a-zA-Z_0-9]*" nil)
'(variable))
((looking-back "\\.[a-zA-Z_0-9]*" nil)
'(variable))
((looking-at "\\s-*([a-zA-Z_0-9]+,")
'(function))
(t
'(function variable type))))
;; Search functions
(cl-defmethod semanticdb-find-tags-by-name-method
((table semanticdb-table-matlab) name &optional tags)
"Find all tags named NAME in TABLE.
Return a list of tags."
;; If we have tags, go up.
(if tags (cl-call-next-method)
(let (where)
;; If MATLAB shell is active, use it.
(when (and (matlab-shell-active-p)
(setq where (matlab-shell-which-fcn name)))
(when (and (not (file-exists-p (car where)))
;; Sometimes MATLAB builtin functions lie.
(string-match "@" (car where)))
(setq where
(list
(concat
(substring (car where) 0 (match-beginning 0))
name ".m")))))
(unless (car where)
;; Fall back to home-made database.
(setq where
(list (car (semanticdb-matlab-find-name name)))))
(if (car where)
(list (car (semanticdb-file-stream (car where))))
nil))))
(cl-defmethod semanticdb-find-tags-by-name-regexp-method
((table semanticdb-table-matlab) regex &optional tags)
"Find all tags with name matching REGEX in TABLE.
Optional argument TAGS is a list of tags to search.
Return a list of tags."
(if tags (cl-call-next-method)
(let ((files (semanticdb-matlab-find-name regex 'regex)))
(delq nil
(mapcar #'(lambda (x)
(car (semanticdb-file-stream x)))
files)))))
(cl-defmethod semanticdb-find-tags-for-completion-method
((table semanticdb-table-matlab) prefix &optional tags)
"In TABLE, find all occurances of tags matching PREFIX.
Optional argument TAGS is a list of tags to search.
Returns a table of all matching tags."
;; If we have tags, go up.
(if tags (cl-call-next-method)
;; first, get completions from home-made database...
(let ((compdb (semanticdb-matlab-find-name prefix 'prefix))
compshell)
;; ...and from MATLAB shell, if available
(when (matlab-shell-active-p)
(setq compshell
(mapcar
(lambda (x)
(when (stringp x)
(let ((where (matlab-shell-which-fcn (car x))))
;; correct name for builtin functions
(when (and (cdr where)
(string-match
"\\(.*\\)/@.*\\(/[A-Za-z_0-9]+\\.m\\)"
(car where)))
(setq where
(list
(concat (match-string 1 (car where))
(match-string 2 (car where))))))
(list (car where)))))
(matlab-shell-completion-list prefix)))
;; combine results
(mapc
(lambda (x)
(unless (member x compdb)
(setq compdb (append compdb x))))
compshell))
;; generate tags
(delq nil
(mapcar #'(lambda (x)
(car (semanticdb-file-stream x)))
compdb)))))
(provide 'semanticdb-matlab)
;;; semanticdb-matlab.el ends here