286 lines
9.0 KiB
EmacsLisp
286 lines
9.0 KiB
EmacsLisp
;;; ac-dcd.el --- Auto Completion source for dcd for GNU Emacs
|
||
|
||
;; 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:
|
||
;;
|
||
;; Auto Completion source for dcd. This code was modified from ac-dscanner.el,
|
||
;; which originally came from auto-complete-clang-async.el
|
||
|
||
;;; Code:
|
||
|
||
(require 'auto-complete)
|
||
|
||
(defcustom ac-dcd-executable
|
||
"dcd-client"
|
||
"Location of dcd-client executable."
|
||
:group 'auto-complete
|
||
:type 'file)
|
||
|
||
;;; Extra compilation flags to pass to dcd.
|
||
(defcustom ac-dcd-flags nil
|
||
"Extra flags to pass to the dcd-server.
|
||
This variable will typically contain include paths, e.g., (\"-I~/MyProject\", \"-I.\").
|
||
You can't put port number flag here. Set `ac-dcd-server-port' instead."
|
||
:group 'auto-complete
|
||
:type '(repeat (string :tag "Argument" "")))
|
||
|
||
(defconst ac-dcd-completion-pattern
|
||
"^\\(%s[^\s\n]*\\)[ \t]+[cisuvmkfgepM]")
|
||
|
||
(defconst ac-dcd-error-buffer-name "*dcd error*")
|
||
|
||
(defcustom ac-dcd-server-executable
|
||
"dcd-server"
|
||
"Location of dcd-server executable."
|
||
:group 'auto-complete
|
||
:type 'file)
|
||
|
||
(defcustom ac-dcd-server-port 9166
|
||
"Port number of dcd-server. default is 9166."
|
||
:group 'auto-complete)
|
||
|
||
(defvar ac-dcd-delay-after-kill-process 200
|
||
"Duration after killing server process in milli second.")
|
||
|
||
|
||
|
||
(defun ac-dcd-stop-server ()
|
||
"Stop dcd-server manually. Ordinary, you don't have to call it.
|
||
If you want to restart server, use `ac-dcd-init-server' instead."
|
||
(interactive)
|
||
(interrupt-process "dcd-server"))
|
||
|
||
(defsubst ac-dcd-start-server ()
|
||
"Start dcd-server."
|
||
(let ((buf (get-buffer-create "*dcd-server*")))
|
||
(with-current-buffer buf (start-process "dcd-server" (current-buffer)
|
||
ac-dcd-server-executable
|
||
(mapconcat 'identity ac-dcd-flags " ")
|
||
"-p"
|
||
(format "%s" ac-dcd-server-port)
|
||
))))
|
||
|
||
(defun ac-dcd-maybe-start-server ()
|
||
"Start dcd-server. When the server process is already running, do nothing."
|
||
(unless (get-process "dcd-server")
|
||
(ac-dcd-start-server)))
|
||
|
||
(defun ac-dcd-init-server ()
|
||
"Start dcd-server. When the server process is already running, restart it."
|
||
(interactive)
|
||
(when (get-process "dcd-server")
|
||
(ac-dcd-stop-server)
|
||
(sleep-for 0 ac-dcd-delay-after-kill-process))
|
||
(ac-dcd-start-server))
|
||
|
||
|
||
|
||
(defun ac-dcd-parse-output (prefix)
|
||
(goto-char (point-min))
|
||
(let ((pattern (format ac-dcd-completion-pattern
|
||
(regexp-quote prefix)))
|
||
lines match detailed_info
|
||
(prev-match ""))
|
||
(while (re-search-forward pattern nil t)
|
||
(setq match (match-string-no-properties 1))
|
||
(unless (string= "Pattern" match)
|
||
(setq detailed_info (match-string-no-properties 2))
|
||
(if (string= match prev-match)
|
||
(progn
|
||
(when detailed_info
|
||
(setq match (propertize match
|
||
'ac-dcd-help
|
||
(concat
|
||
(get-text-property 0 'ac-dcd-help (car lines))
|
||
"\n"
|
||
detailed_info)))
|
||
(setf (car lines) match)))
|
||
(setq prev-match match)
|
||
(when detailed_info
|
||
(setq match (propertize match 'ac-dcd-help detailed_info)))
|
||
(push match lines))))
|
||
lines))
|
||
|
||
(defun ac-dcd-handle-error (res args)
|
||
(goto-char (point-min))
|
||
(let* ((buf (get-buffer-create ac-dcd-error-buffer-name))
|
||
(cmd (concat ac-dcd-executable " " (mapconcat 'identity args " ")))
|
||
(pattern (format ac-dcd-completion-pattern ""))
|
||
(err (if (re-search-forward pattern nil t)
|
||
(buffer-substring-no-properties (point-min)
|
||
(1- (match-beginning 0)))
|
||
;; Warn the user more agressively if no match was found.
|
||
(message "dcd-client failed with error %d:\n%s" res cmd)
|
||
(buffer-string))))
|
||
(with-current-buffer buf
|
||
(let ((inhibit-read-only t))
|
||
(erase-buffer)
|
||
(insert (current-time-string)
|
||
(format "\ndcd-client failed with error %d:\n" res)
|
||
cmd "\n\n")
|
||
(insert err)
|
||
(setq buffer-read-only t)
|
||
(goto-char (point-min))))))
|
||
|
||
|
||
|
||
(defun ac-dcd-call-process (prefix args)
|
||
(let ((buf (get-buffer-create "*dcd-output*"))
|
||
res)
|
||
(with-current-buffer buf (erase-buffer))
|
||
(setq res (apply 'call-process-region (point-min) (point-max)
|
||
ac-dcd-executable nil buf nil
|
||
args
|
||
))
|
||
(with-current-buffer buf
|
||
(unless (eq 0 res)
|
||
(ac-dcd-handle-error res args))
|
||
;; Still try to get any useful input.
|
||
(ac-dcd-parse-output prefix))))
|
||
|
||
(defsubst ac-dcd-build-complete-args (pos)
|
||
(list
|
||
"-c"
|
||
(format "%s" pos)
|
||
"-p"
|
||
(format "%s" ac-dcd-server-port)
|
||
))
|
||
|
||
|
||
(defsubst ac-dcd-clean-document (s)
|
||
(when s
|
||
(setq s (replace-regexp-in-string "<#\\|#>\\|\\[#" "" s))
|
||
(setq s (replace-regexp-in-string "#\\]" " " s)))
|
||
s)
|
||
|
||
(defun ac-dcd-document (item)
|
||
(if (stringp item)
|
||
(let (s)
|
||
(setq s (get-text-property 0 'ac-dcd-help item))
|
||
(ac-dcd-clean-document s))))
|
||
|
||
(defsubst ac-in-string/comment ()
|
||
"Return non-nil if point is in a literal (a comment or string)."
|
||
(nth 8 (syntax-ppss)))
|
||
|
||
(defun ac-dcd-candidate ()
|
||
(unless (ac-in-string/comment)
|
||
(save-restriction
|
||
(widen)
|
||
(ac-dcd-call-process
|
||
ac-prefix
|
||
(ac-dcd-build-complete-args (point))))))
|
||
|
||
(defvar ac-template-start-point nil)
|
||
(defvar ac-template-candidates (list "ok" "no" "yes:)"))
|
||
|
||
(defun ac-dcd-action ()
|
||
(interactive)
|
||
(let ((help (ac-dcd-clean-document (get-text-property 0 'ac-dcd-help (cdr ac-last-completion))))
|
||
(raw-help (get-text-property 0 'ac-dcd-help (cdr ac-last-completion)))
|
||
(candidates (list)) ss fn args (ret-t "") ret-f)
|
||
(setq ss (split-string raw-help "\n"))
|
||
(dolist (s ss)
|
||
(when (string-match "\\[#\\(.*\\)#\\]" s)
|
||
(setq ret-t (match-string 1 s)))
|
||
(setq s (replace-regexp-in-string "\\[#.*?#\\]" "" s)))
|
||
(cond (candidates
|
||
(setq candidates (delete-dups candidates))
|
||
(setq candidates (nreverse candidates))
|
||
(setq ac-template-candidates candidates)
|
||
(setq ac-template-start-point (point))
|
||
(ac-complete-template)
|
||
(unless (cdr candidates) ;; unless length > 1
|
||
(message (replace-regexp-in-string "\n" " ; " help))))
|
||
(t
|
||
(message (replace-regexp-in-string "\n" " ; " help))))))
|
||
|
||
(defun ac-dcd-prefix ()
|
||
(or (ac-prefix-symbol)
|
||
(let ((c (char-before)))
|
||
(when (or (eq ?\. c)
|
||
(and (eq ?> c)
|
||
(eq ?- (char-before (1- (point)))))
|
||
(and (eq ?: c)
|
||
(eq ?: (char-before (1- (point))))))
|
||
(point)))))
|
||
|
||
(ac-define-source dcd
|
||
'((candidates . ac-dcd-candidate)
|
||
(prefix . ac-dcd-prefix)
|
||
(requires . 0)
|
||
(document . ac-dcd-document)
|
||
(action . ac-dcd-action)
|
||
(cache)
|
||
(symbol . "D")))
|
||
|
||
(defun ac-dcd-same-count-in-string (c1 c2 s)
|
||
(let ((count 0) (cur 0) (end (length s)) c)
|
||
(while (< cur end)
|
||
(setq c (aref s cur))
|
||
(cond ((eq c1 c)
|
||
(setq count (1+ count)))
|
||
((eq c2 c)
|
||
(setq count (1- count))))
|
||
(setq cur (1+ cur)))
|
||
(= count 0)))
|
||
|
||
(defun ac-dcd-split-args (s)
|
||
(let ((sl (split-string s ", *")))
|
||
(cond ((string-match "<\\|(" s)
|
||
(let ((res (list)) (pre "") subs)
|
||
(while sl
|
||
(setq subs (pop sl))
|
||
(unless (string= pre "")
|
||
(setq subs (concat pre ", " subs))
|
||
(setq pre ""))
|
||
(cond ((and (ac-dcd-same-count-in-string ?\< ?\> subs)
|
||
(ac-dcd-same-count-in-string ?\( ?\) subs))
|
||
(push subs res))
|
||
(t
|
||
(setq pre subs))))
|
||
(nreverse res)))
|
||
(t
|
||
sl))))
|
||
|
||
|
||
|
||
(defun ac-template-candidate ()
|
||
ac-template-candidates)
|
||
|
||
(defun ac-template-action ()
|
||
(interactive)
|
||
(unless (null ac-template-start-point)
|
||
(let ((pos (point)) sl (snp "")
|
||
(s (get-text-property 0 'raw-args (cdr ac-last-completion)))))))
|
||
|
||
(defun ac-template-prefix ()
|
||
ac-template-start-point)
|
||
|
||
|
||
;; this source shall only be used internally.
|
||
(ac-define-source template
|
||
'((candidates . ac-template-candidate)
|
||
(prefix . ac-template-prefix)
|
||
(requires . 0)
|
||
(action . ac-template-action)
|
||
(document . ac-dcd-document)
|
||
(cache)
|
||
(symbol . "t")))
|
||
|
||
(provide 'ac-dcd)
|
||
;;; ac-dcd.el ends here
|