├── README.md
├── company-matlab.el
├── doc-matlab.el
├── flycheck-matlab.el
├── image
├── file.png
├── flycheck-demo.png
└── shell.png
├── jtd-matlab.el
├── matlab-mode.el
├── matlab-server.el
└── matlab.el
/README.md:
--------------------------------------------------------------------------------
1 | # matlab-mode
2 |
3 | An emacs matlab mode based on the [classic one](http://matlab-emacs.sourceforge.net/).
4 |
5 | ## Warning
6 | 1. First use or open ```.m``` file can be slow because it needs to open a background matlab process.
7 | 2. You need to set ```matlab-server-executable``` to where your matlab exec.
8 |
9 | ## Highlights
10 |
11 | 1. A company-mode backend
12 | 
13 | 
14 |
15 | 2. A flycheck backend
16 | 
17 |
18 | 3. Easy to view document of word at cursor (default keybinding: Ctrl-c + h).
19 |
20 | 4. Jump to definition (default keybinding: Ctrl-c + s).
21 | Currently, this only helps open the souce file (can not go to the definition of the function).
22 | If it does not work, it may be becasue you have not added the file's folder into matlab workspace.
23 |
24 | ## Install
25 |
26 | 1. Install dependencies like ```s.el```, ```flycheck``` and ```company-mode```.
27 | 2. Copy the code to somewhere
28 | 3. Add the following to the initialization file
29 |
30 | ```elisp
31 | (setq matlab-server-executable "/path/to/matlab/binary")
32 | (add-to-list 'load-path "/path/to/matlab-mode/")
33 | (require 'matlab-mode)
34 | (matlab-mode-common-setup)
35 | ```
36 |
37 | ## Usage
38 | To use the functionalities, one must first run M-x ```matlab-start``` to start the background process.
39 | Since matlab is a bit slow to start, you may need to wait a few seconds.
40 |
41 | ## Recommendation
42 | Recommend to turn-off automatic complete in company-mode and do the completion when needed.
43 |
44 | ## Copyright
45 |
46 | ```matlab.el``` is mostly copied from the ["classic" matlab mode](http://matlab-emacs.sourceforge.net/).
47 |
48 | ## TODO
49 |
50 | 1. debug?
51 |
--------------------------------------------------------------------------------
/company-matlab.el:
--------------------------------------------------------------------------------
1 | ;;; company-matlab.el --- support for the Matlab programming language
2 |
3 | ;; Copyright (C) 2016 yuhonglin
4 |
5 | ;; Author: yuhonglin
6 | ;; Keywords: languages, terminals
7 |
8 | ;; This program is free software; you can redistribute it and/or modify
9 | ;; it under the terms of the GNU General Public License as published by
10 | ;; the Free Software Foundation, either version 3 of the License, or
11 | ;; (at your option) any later version.
12 |
13 | ;; This program is distributed in the hope that it will be useful,
14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | ;; GNU General Public License for more details.
17 |
18 | ;; You should have received a copy of the GNU General Public License
19 | ;; along with this program. If not, see .
20 |
21 | ;;; Commentary:
22 |
23 | ;; The company backend of matlab mode.
24 | ;; It is based on matlab-server.el.
25 |
26 | ;;; Code:
27 |
28 | (require 'cl-lib)
29 | (require 'company)
30 | (require 'matlab)
31 | (require 's)
32 | (require 'matlab-server)
33 |
34 | (defvar company-matlab-is-completing nil
35 | "Whether it is during the completing backend.")
36 |
37 | (defvar company-matlab-complete-output ""
38 | "The output of matlab used by completing")
39 |
40 | (defvar company-matlab-complete-candidates ""
41 | "The candidates")
42 |
43 | (defun company-matlab-process-received-data (inarg)
44 | (mapcar 'car
45 | (mapcar 'cdr
46 | (s-match-strings-all "'\\([^ \t\n']+\\)'" inarg))))
47 |
48 |
49 | ;; some are copied from matlab-shell-collect-command-output function
50 | (defun company-matlab-get-candidates (arg)
51 | (if (and matlab-server-process
52 | (eq (process-status matlab-server-process) 'run))
53 | (let ((res (company-matlab-process-received-data
54 | (matlab-send-request-sync
55 | (concat "tmp=com.mathworks.jmi.MatlabMCR;tmp.mtFindAllTabCompletions('"
56 | arg "'," (int-to-string (length arg)) ",0)")))))
57 | (if (eq res nil)
58 | ;; nil
59 | (company-other-backend)
60 | res))))
61 |
62 |
63 | (defun company-matlab-grab-symbol ()
64 | (let* ((prefix (buffer-substring (point)
65 | (save-excursion (skip-chars-backward "a-zA-Z0-9._")
66 | (point)))))
67 | (if (or (= (length prefix) 0)
68 | (string= (substring prefix 0 1) ".")
69 | (string= (substring prefix 0 1) "/"))
70 | nil
71 | prefix)))
72 |
73 |
74 | (defun company-matlab (command &optional arg &rest ignored)
75 | (interactive (list 'interactive))
76 | (cl-case command
77 | (interactive (company-begin-backend 'company-matlab))
78 | (prefix (and (or (eq major-mode 'matlab-mode)
79 | (string= (buffer-name) "*MATLAB*"))
80 | (company-matlab-grab-symbol)))
81 | (no-cache nil)
82 | (duplicates t)
83 | (ignore-case nil)
84 | (candidates
85 | (company-matlab-get-candidates arg))))
86 |
87 |
88 | (defun company-matlab-company-cancel-hook (arg))
89 |
90 | (add-to-list 'company-completion-cancelled-hook 'company-matlab-company-cancel-hook)
91 |
92 | (provide 'company-matlab)
93 |
--------------------------------------------------------------------------------
/doc-matlab.el:
--------------------------------------------------------------------------------
1 | (require 'matlab-server)
2 | (require 's)
3 |
4 | (defun doc-matlab-process-received-data (s)
5 | (when (> (length s) 4)
6 | (substring (s-chomp (s-trim s)) 0 -2)))
7 |
8 | (defun doc-matlab-grab-current-word ()
9 | (save-excursion
10 | (let (start end oldpos)
11 | (setq oldpos (point))
12 | (skip-chars-backward "A-Za-z0-9_") (setq start (point))
13 | (skip-chars-forward "A-Za-z0-9_") (setq end (point))
14 | (buffer-substring start end))))
15 |
16 | (defun matlab-view-current-word-doc-in-another-buffer ()
17 | "look up the matlab help info and show in another buffer"
18 | (interactive)
19 | (let* ((word (doc-matlab-grab-current-word))
20 | (doc (doc-matlab-process-received-data
21 | (matlab-send-request-sync
22 | (concat "help " word)))))
23 | (if (= (length doc) 0)
24 | (error (concat "doc of '" word "' not found")))
25 |
26 | (let ((buffer (get-buffer-create (concat "*matdoc:" word "*"))))
27 | (set-buffer buffer)
28 | (erase-buffer)
29 | (insert doc)
30 | (goto-char (point-min))
31 | (pop-to-buffer buffer))))
32 |
33 |
34 | (provide 'doc-matlab)
35 |
--------------------------------------------------------------------------------
/flycheck-matlab.el:
--------------------------------------------------------------------------------
1 | ;;; -*- lexical-binding: t; -*-
2 |
3 | (require 'flycheck)
4 | (require 'matlab-server)
5 | (require 's)
6 |
7 | (defun flycheck-matlab--verify (checker)
8 | "Verify the matlab syntax checker."
9 | (list
10 | (flycheck-verification-result-new
11 | :label "mlint path checking"
12 | :message (if (and matlab-server-process
13 | (eq (process-status matlab-server-process) 'run))
14 | "matlab is on" "matlab process not found")
15 | :face (if t
16 | 'success '(bold error)) )))
17 |
18 | (defun flycheck-matlab-get-error (filepath)
19 | (if (and matlab-server-process
20 | (eq (process-status matlab-server-process) 'run))
21 | (let ((rawerrstr (matlab-send-request-sync
22 | (s-trim (concat "arrayfun(@(x) display(sprintf('%d\t%d\t%s', x.line, x.column(1), x.message)), checkcode('"
23 | filepath "'))")))))
24 | (delq nil
25 | (mapcar (lambda (rrs)
26 | (let* ((cleanrs (s-replace ">>" "" rrs))
27 | (cleanrs-split (s-split "\t" (s-trim cleanrs))))
28 | (when (= (length cleanrs-split) 3)
29 | (cl-multiple-value-bind (lpostart cpostart estr) cleanrs-split
30 | `(,(string-to-int lpostart) ,(string-to-int lpostart) ,(string-to-int cpostart) ,(string-to-int cpostart) ,estr)))))
31 | (s-split "\n" rawerrstr))))))
32 |
33 |
34 | (defun flycheck-matlab--start (checker callback)
35 | (let* ((rawerror (flycheck-matlab-get-error (buffer-file-name)))
36 | (errors (mapcar (lambda (rerr)
37 | (cl-multiple-value-bind (lpostart lposend cpostart cposend estr) rerr
38 | (flycheck-error-new-at lpostart cpostart 'warning estr :checker checker)))
39 | rawerror)))
40 | (funcall callback 'finished errors)))
41 |
42 |
43 | (flycheck-define-generic-checker 'flycheck-matlab
44 | "A syntax checker for matlab."
45 | :start #'flycheck-matlab--start
46 | :verify #'flycheck-matlab--verify
47 | :modes '(matlab-mode)
48 | ;; :error-filter flycheck-matlab-error-filter
49 | :predicate (lambda ()
50 | (eq major-mode 'matlab-mode)))
51 |
52 | (add-to-list 'flycheck-checkers 'flycheck-matlab)
53 |
54 | (provide 'flycheck-matlab)
55 |
--------------------------------------------------------------------------------
/image/file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuhonglin/matlab-mode/3ec032e536d0d18efad84a010099e3a6e462f5af/image/file.png
--------------------------------------------------------------------------------
/image/flycheck-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuhonglin/matlab-mode/3ec032e536d0d18efad84a010099e3a6e462f5af/image/flycheck-demo.png
--------------------------------------------------------------------------------
/image/shell.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuhonglin/matlab-mode/3ec032e536d0d18efad84a010099e3a6e462f5af/image/shell.png
--------------------------------------------------------------------------------
/jtd-matlab.el:
--------------------------------------------------------------------------------
1 | ;; jump-to-definition of matlab
2 |
3 | (require 's)
4 | (require 'matlab-server)
5 |
6 |
7 | ;; the problem is that, sometime the return of `which`
8 | ;; function in matlab contains file extensions '.m',
9 | ;; but sometime does not.
10 | (defun jtd-matlab-process-received-data (s)
11 | (when (> (length s) 4)
12 | (let ((rawreturn (car (s-match "/[^><()\t\n,'\";:]+\\(:[0-9]+\\)?"
13 | (s-chomp (s-trim s))))))
14 | (if (= 2 (length (s-shared-end ".m" rawreturn)))
15 | rawreturn ;; contains '.m'
16 | (if (file-exists-p (concat rawreturn ".m"))
17 | (concat rawreturn ".m") ;; append the '.m'
18 | ;; else, may be a folder
19 | rawreturn)))))
20 |
21 | (defun jtd-matlab-grab-current-word ()
22 | (save-excursion
23 | (let (start end oldpos)
24 | (setq oldpos (point))
25 | (skip-chars-backward "A-Za-z0-9_") (setq start (point))
26 | (skip-chars-forward "A-Za-z0-9_") (setq end (point))
27 | (buffer-substring start end))))
28 |
29 | (defun matlab-jump-to-definition-of-word-at-cursor ()
30 | "open the source code of the word at cursor"
31 | (interactive)
32 | (if (not (buffer-file-name))
33 | (error "jtd-matlab: you should save the file first"))
34 |
35 | (let* ((word (jtd-matlab-grab-current-word))
36 | (scpath (jtd-matlab-process-received-data
37 | (matlab-send-request-sync
38 | (concat "which('" word "', 'in', " "'" (file-truename (buffer-file-name)) "')")))))
39 | (if scpath
40 | (find-file scpath)
41 | (error "can not find the definition"))))
42 |
43 |
44 | (provide 'jtd-matlab)
45 |
--------------------------------------------------------------------------------
/matlab-mode.el:
--------------------------------------------------------------------------------
1 | (require 'matlab)
2 | (require 'matlab-server)
3 | (require 'company-matlab)
4 | (require 'flycheck-matlab)
5 | (require 'doc-matlab)
6 | (require 'jtd-matlab)
7 |
8 |
9 | ;; update working path
10 | (defvar matlab-last-work-directory "~")
11 | (add-hook 'buffer-list-update-hook
12 | (lambda ()
13 | (if (and matlab-server-process
14 | (eq (process-status matlab-server-process) 'run))
15 | (if (and (or (string= "matlab-mode" major-mode)
16 | (string= (buffer-name) "*MATLAB*"))
17 | (not (string= matlab-last-work-directory default-directory)))
18 | (progn (matlab-send-request-sync (concat "cd " default-directory))
19 | (setq matlab-last-work-directory default-directory))))))
20 |
21 | ;; setup that can be changed
22 | (defun matlab-mode-common-setup ()
23 | ;; for .m files
24 | (add-to-list 'matlab-mode-hook
25 | (lambda ()
26 | ;; turn on the flycheck mode
27 | (flycheck-mode)
28 | ;; use flycheck only when saving the file or enabling the mode
29 | (setq-local flycheck-check-syntax-automatically '(save mode-enabled))
30 | ;; bind the key of checking document
31 | (local-set-key (kbd "C-c h")
32 | 'matlab-view-current-word-doc-in-another-buffer)
33 | ;; bind the key of jump to source code
34 | (local-set-key (kbd "C-c s")
35 | 'matlab-jump-to-definition-of-word-at-cursor)
36 | ;; set company-backends
37 | (setq-local company-backends '(company-files (company-matlab company-dabbrev)))
38 | ))
39 |
40 | ;; for matlab-shell
41 | (add-to-list 'matlab-shell-mode-hook
42 | (lambda ()
43 | ;; set company-backends
44 | (setq-local company-backends '((company-dabbrev company-matlab) company-files )))))
45 |
46 |
47 | (provide 'matlab-mode)
48 |
--------------------------------------------------------------------------------
/matlab-server.el:
--------------------------------------------------------------------------------
1 | ;; Only API: matlab-send-request-sync (request &rest args)
2 | ;; return command output as string
3 |
4 | (defvar matlab-server-executable "matlab")
5 | (defvar matlab-server-process nil)
6 | (defvar matlab-server-buffer " *matlab* "
7 | "The name of the buffer for the matlab process to run in")
8 | (defvar matlab-eot ">>")
9 | (defvar matlab-just-startup t)
10 | (defvar matlab-process-auto-start t)
11 |
12 | (defun matlab-start-server-process ()
13 | (if (and matlab-server-executable (matlab-check-server-executable))
14 | (let ((process-connection-type nil)
15 | (process-adaptive-read-buffering nil)
16 | process)
17 | (setq process
18 | (start-process-shell-command
19 | "matlab"
20 | matlab-server-buffer
21 | (format "%s -nodesktop -nosplash"
22 | (shell-quote-argument matlab-server-executable))))
23 | (buffer-disable-undo matlab-server-buffer)
24 | (set-process-query-on-exit-flag process nil)
25 | (set-process-sentinel process 'matlab-server-process-sentinel)
26 | (set-process-filter process 'matlab-server-process-filter)
27 | (setq matlab-just-startup t)
28 | process)
29 | (error "matlab: can't find the matlab executable, try to set matlab-server-executable")))
30 |
31 |
32 | (defun matlab-check-server-executable ()
33 | (executable-find matlab-server-executable))
34 |
35 | (defun matlab-get-server-process-create ()
36 | (if (and matlab-server-process
37 | (process-live-p matlab-server-process))
38 | matlab-server-process
39 | (setq matlab-server-process (matlab-start-server-process))))
40 |
41 |
42 | (defun matlab-server-kill ()
43 | "Kill the running matlab process, if any."
44 | (interactive)
45 | (when (and matlab-server-process (process-live-p matlab-server-process))
46 | (kill-process matlab-server-process)
47 | (setq matlab-server-process nil)))
48 |
49 |
50 | (defun matlab-server-process-sentinel (process event)
51 | (unless (process-live-p process)
52 | (setq matlab-server-process nil)
53 | (message "matlab process stopped")))
54 |
55 |
56 | (defun matlab-process-server-response (process response)
57 | (let ((sexp response)
58 | (callback (matlab-server-process-pop-callback process)))
59 | (with-demoted-errors "Warning: %S"
60 | (apply (car callback) sexp (cdr callback)))))
61 |
62 | (defun matlab-server-process-filter (process output)
63 | "Handle the output from matlab.
64 | 1. Split by matlab-eot and return the finished responses.
65 | 2. Then call the callbacks from stack on each of the finished responses."
66 | (let ((pbuf (process-buffer process))
67 | responses)
68 | ;; append output to process buffer
69 | (when (buffer-live-p pbuf)
70 | (with-current-buffer pbuf
71 | (save-excursion
72 | (goto-char (process-mark process))
73 | (insert output)
74 | (set-marker (process-mark process) (point))
75 | ;; check if the message is complete based on `matlab-eot'
76 | (goto-char (point-min))
77 | (while (search-forward matlab-eot nil t)
78 | (let ((response (buffer-substring-no-properties (point-min)
79 | (point))))
80 | (delete-region (point-min) (point))
81 | ;; ignore the first process
82 | (setq responses (cons response responses))))
83 | (goto-char (process-mark process)))))
84 | ;; handle all responses.
85 | (mapc #'(lambda (r)
86 | (matlab-process-server-response process r))
87 | (nreverse responses))))
88 |
89 | (defun matlab-server-process-push-callback (p cb)
90 | "push (process callback) into a stack"
91 | (let ((callbacks (process-get p 'matlab-callback-stack)))
92 | (if callbacks
93 | (nconc callbacks (list cb))
94 | (process-put p 'matlab-callback-stack (list cb)))))
95 |
96 | (defun matlab-server-process-pop-callback (p)
97 | "pop (process callback) into a stack and return the callback popped"
98 | (let ((callbacks (process-get p 'matlab-callback-stack)))
99 | (process-put p 'matlab-callback-stack (cdr callbacks))
100 | (car callbacks)))
101 |
102 | (defmacro matlab--without-narrowing (&rest body)
103 | "Remove the effect of narrowing for the current buffer.
104 | Note: If `save-excursion' is needed for BODY, it should be used
105 | before calling this macro."
106 | (declare (indent 0) (debug t))
107 | `(save-restriction
108 | (widen)
109 | (progn ,@body)))
110 |
111 | (defun matlab-send-request (request callback &rest args)
112 | "Send a request to the process.
113 | At the same time push the callback to the stack (number of callbacks
114 | and number of command sent are the same). After this, the filter
115 | will be called when process outputs."
116 | (let ((process (matlab-get-server-process-create))
117 | (argv (cons request args)))
118 | (when (and process (process-live-p process))
119 | (matlab-server-process-push-callback process callback)
120 | (matlab--without-narrowing
121 | (process-send-string process
122 | (format "%s\n" (car argv)))))))
123 |
124 | (defvar matlab-sync-id 0 "ID of next sync request.")
125 | (defvar matlab-sync-result '(-1 . nil)
126 | "The car stores the id of the result and cdr stores the return value.")
127 |
128 | (defun matlab-sync-request-callback (response id)
129 | (setq matlab-sync-result (cons id response)))
130 |
131 | (defun matlab-send-request-sync-helper (request &rest args)
132 | "Send a request to matlab and wait for the result.
133 | Called by matlab-send-request-sync"
134 | (let* ((id matlab-sync-id)
135 | (callback (list #'matlab-sync-request-callback id)))
136 | (setq matlab-sync-id (1+ matlab-sync-id))
137 | (with-local-quit
138 | (let ((process (matlab-get-server-process-create)))
139 | (when process
140 | (apply 'matlab-send-request request callback args)
141 | (while (not (= id (car matlab-sync-result)))
142 | (accept-process-output process))
143 | (cdr matlab-sync-result))))))
144 |
145 | (defun matlab-start ()
146 | (interactive)
147 | (if (not (and matlab-server-process
148 | (process-live-p matlab-server-process)))
149 | (progn
150 | (message "matlab: starting background matlab...")
151 | (matlab-send-request-sync-helper "import com;") ;; ignore first request
152 | (setq matlab-just-startup nil)
153 | (message "matlab: done")
154 | (if (and (or (string= "matlab-mode" major-mode)
155 | (string= (buffer-name) "*MATLAB*"))
156 | (not (string= matlab-last-work-directory default-directory)))
157 | (progn (matlab-send-request-sync (concat "cd " default-directory))
158 | (setq matlab-last-work-directory default-directory))))))
159 |
160 |
161 | (defun matlab-send-request-sync (request &rest args)
162 | "Send a request to matlab and wait for the result."
163 | (if matlab-process-auto-start
164 | (if (or matlab-just-startup (not (process-live-p matlab-server-process)))
165 | (progn
166 | (message "matlab: starting background matlab...")
167 | (matlab-send-request-sync-helper "import com;") ;; ignore first request
168 | (setq matlab-just-startup nil)
169 | (apply 'matlab-send-request-sync-helper request args)
170 | (message "matlab: done"))
171 | (apply 'matlab-send-request-sync-helper request args))))
172 |
173 |
174 | (provide 'matlab-server)
175 |
--------------------------------------------------------------------------------