diff --git a/editors/emacs/README.md b/editors/emacs/README.md index fbfc019..8e1f30d 100644 --- a/editors/emacs/README.md +++ b/editors/emacs/README.md @@ -1,59 +1,4 @@ # Emacs Integration -## Requirements - -* You must have the -[auto-complete](https://github.com/auto-complete/auto-complete) package. Also, -[yasnippet](https://github.com/capitaomorte/yasnippet) and -[popwin](https://github.com/m2ym/popwin-el) are recommended. -* Make sure `dcd-client` and `dcd-server` are in your exec-path. Otherwise, - please set the variables `dcd-exectutable` and `dcd-server-executable` using - `M-x customize`. - -## Setup - -* Follow the Setup section in the root README. -* Add the following to your .emacs. With this setting, dcd-server starts - automatically when you open file in d-mode. (Of course, you should edit - `path_to_ac-dcd.el` to suit your environment.) - - ;;; ac-dcd - (add-to-list 'load-path "path_to_ac-dcd.el") - (require 'ac-dcd) - - (add-hook 'd-mode-hook - '(lambda () "set up ac-dcd" - (auto-complete-mode t) - (yas-minor-mode-on) - (ac-dcd-maybe-start-server) - (add-to-list 'ac-sources 'ac-source-dcd))) - - (define-key d-mode-map (kbd "C-c ?") 'ac-dcd-show-ddoc-with-buffer) - (define-key d-mode-map (kbd "C-c .") 'ac-dcd-goto-definition) - (define-key d-mode-map (kbd "C-c ,") 'ac-dcd-goto-def-pop-marker) - - (when (featurep 'popwin) - (add-to-list 'popwin:special-display-config - `(,ac-dcd-error-buffer-name :noselect t)) - (add-to-list 'popwin:special-display-config - `(,ac-dcd-document-buffer-name :position right :width 80))) - -* You can set import paths using `M-x customize-variable RET ac-dcd-flags`. -* Alternatively, if you're using [DUB](http://code.dlang.org/) to manage your - project, you can use `M-x ac-dcd-add-imports` to add import paths of the - current project automatically. -* When something is wrong, please, check variables with `M-x customize-apropos - RET ac-dcd` and restart server with `M-x ac-dcd-init-server`. - -## Features - -* Dlang source for auto-complete -* Function calltip expansion with yasnippet -* Show ddoc with `C-c ?` -* Goto definition with `C-c .` -* After goto definition, you can pop to previous position with `C-c ,` - -## TODO - -* UTF-8 support is in place. However, UTF-16 and UTF-32 may not work correctly. - (Need help!) +ac-dcd for Emacs is now a package that can be installed with the Emacs package +manager. You can get it [here](http://github.com/atilaneves/ac-dcd). diff --git a/editors/emacs/ac-dcd.el b/editors/emacs/ac-dcd.el deleted file mode 100644 index 08e95c6..0000000 --- a/editors/emacs/ac-dcd.el +++ /dev/null @@ -1,612 +0,0 @@ -;;; 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 . - - -;;; 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) -(require 'rx) -(require 'yasnippet nil t) -(require 'eshell) - -(defcustom ac-dcd-executable - "dcd-client" - "Location of dcd-client executable." - :group 'auto-complete - :type 'file) - -(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]\\)" - "Regex to parse dcd output. -\\1 is candidate itself, \\2 is kind of candidate.") - -(defconst ac-dcd-error-buffer-name "*dcd-error*") -(defconst ac-dcd-output-buffer-name "*dcd-output*") -(defconst ac-dcd-document-buffer-name "*dcd-document*") -(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. -If `ac-dcd-init-server' doesn't work correctly, please set bigger number for this variable.") - - -;;server handle functions - -(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)) - - -;; output parser functions - -(defun ac-dcd-parse-output (prefix) - "Parse dcd output." - (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)) - -(defvar ac-dcd-error-message-regexp - (rx (and (submatch (* nonl)) ": " (submatch (* nonl)) ": " (submatch (* nonl) eol))) - "If it matches first line of dcd-output, it would be error message.") - -(defun ac-dcd-handle-error (res args) - "Notify error." - (let* ((errbuf (get-buffer-create ac-dcd-error-buffer-name)) - (outbuf (get-buffer ac-dcd-output-buffer-name)) - (cmd (concat ac-dcd-executable " " (mapconcat 'identity args " "))) - (errstr - (with-current-buffer outbuf - (goto-char (point-min)) - (re-search-forward ac-dcd-error-message-regexp) - (concat - (match-string 2) " : " (match-string 3))) - )) - (with-current-buffer errbuf - (erase-buffer) - (insert (current-time-string) - "\n\"" cmd "\" failed." - (format "\nError type is: %s\n" errstr) - ) - (goto-char (point-min))) - (display-buffer errbuf))) - - -;; utility functions to call process -(defun ac-dcd-call-process (prefix args) - (if (null ac-dcd-executable) - (error (format "Could not find dcd-client executable")) - (let ((buf (get-buffer-create ac-dcd-output-buffer-name)) - 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-cursor-position () - "Get cursor position to pass to dcd-client. -TODO: multi byte character support" - (position-bytes (point))) - -(defsubst ac-dcd-build-complete-args (pos) - (list - "-c" - (format "%s" pos) - "-p" - (format "%s" ac-dcd-server-port) - )) - - -(defsubst ac-in-string/comment () - "Return non-nil if point is in a literal (a comment or string)." - (nth 8 (syntax-ppss))) - - -;; interface to communicate with auto-complete.el - -(defun ac-dcd-candidate () - (unless (ac-in-string/comment) - (save-restriction - (widen) - (ac-dcd-call-process - ac-prefix - (ac-dcd-build-complete-args (ac-dcd-cursor-position)))))) - -(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))))) - -(defun ac-dcd-document (item) - "Return popup document of `ITEM'." - (if (stringp item) - (let (s) - (setq s (get-text-property 0 'ac-dcd-help item)) - (cond - ((equal s "c") "class") - ((equal s "i") "interface") - ((equal s "s") "struct") - ((equal s "u") "union") - ((equal s "v") "variable") - ((equal s "m") "member variable") - ((equal s "k") "keyword") - ((equal s "f") "function") - ((equal s "g") "enum") - ((equal s "e") "enum member") - ((equal s "P") "package") - ((equal s "M") "module") - ((equal s "a") "array") - ((equal s "A") "associative array") - ((equal s "l") "alias") - ((equal s "t") "template") - ((equal s "T") "mixin template") - (t (format "candidate kind undetected: %s" s)) - )))) - - - -(defun ac-dcd-action () - "Try function calltip expansion." - (when (featurep 'yasnippet) - - (let ((lastcompl (cdr ac-last-completion))) - (cond - ((equal "f" (get-text-property 0 'ac-dcd-help lastcompl)) ; when it was a function - (progn - (ac-complete-dcd-calltips))) - ((equal "s" (get-text-property 0 'ac-dcd-help lastcompl)) ; when it was a struct - (progn - (ac-complete-dcd-calltips-for-struct-constructor))) - (t nil) - )))) - -(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") - )) - - -;; function calltip expansion with yasnippet - -(defun ac-dcd-calltip-candidate () - "Do calltip completion of the D symbol at point. -The cursor must be at the end of a D symbol. -When the symbol is not a function, returns nothing" - (let ((buf (get-buffer-create ac-dcd-output-buffer-name))) - (ac-dcd-call-process-for-calltips) - (with-current-buffer buf (ac-dcd-parse-calltips)) - )) - -(defun ac-dcd-call-process-for-calltips () - "Call process to get calltips of the function at point." - (insert "( ;") - (backward-char 2) - - (ac-dcd-call-process - (concat (cdr ac-last-completion) "(") - (ac-dcd-build-complete-args (ac-dcd-cursor-position))) - - (forward-char 2) - (delete-char -3) - - ) - - -(defconst ac-dcd-calltip-pattern - (rx bol (submatch (* nonl)) (submatch "(" (* nonl) ")") eol) - "Regexp to parse calltip completion output. -\\1 is function return type (if exists) and name, and \\2 is args.") - -(defsubst ac-dcd-cleanup-function-candidate (s) - "Remove return type of the head of the function. -`S' is candidate string." - (let (res) - (with-temp-buffer - (insert s) - - ;;goto beggining of function name - (progn - (end-of-line) - (backward-sexp) - (re-search-backward (rx (or bol " ")))) - - (setq res (buffer-substring - (point) - (progn - (end-of-line) - (point)))) - (when (equal " " (substring res 0 1)) - (setq res (substring res 1))) - res - ))) - -(defun ac-dcd-parse-calltips () - "Parse dcd output for calltip completion. -It returns a list of calltip candidates." - (goto-char (point-min)) - (let ((pattern ac-dcd-calltip-pattern) - lines - match - (prev-match "")) - (while (re-search-forward pattern nil t) - (setq match - (ac-dcd-cleanup-function-candidate - (concat (match-string-no-properties 1) (match-string-no-properties 2)) - )) - (push match lines)) - lines - )) - -(defun ac-dcd-calltip-action () - "Format the calltip to yasnippet style. -This function should be called at *dcd-output* buf." - (let (beg end) - (save-excursion - (setq end (point)) - (setq beg (progn - (backward-sexp) - (point) - )) - (kill-region beg end)) - (let ((str (car kill-ring)) - yasstr) - (setq kill-ring (cdr kill-ring)); clean up kill-ring - - ;;remove parenthesis - (setq str (substring str 1 (- (length str) 1))) - - (setq yasstr - (mapconcat - (lambda (s) "format each args to yasnippet style" (concat "${" s "}")) - (split-string str ", ") - ", ")) - (setq yasstr (concat "(" yasstr ")")) - ;; ;;debug - ;; (message (format "str: %s" str)) - ;; (message (format "yasstr: %s" yasstr)) - (yas-expand-snippet yasstr) - ))) - -(defun ac-dcd-calltip-prefix () - (car ac-last-completion)) - -(defvar dcd-calltips - '((candidates . ac-dcd-calltip-candidate) - (prefix . ac-dcd-calltip-prefix) - (action . ac-dcd-calltip-action) - (cache) - )) - -(defun ac-complete-dcd-calltips () - (auto-complete '(dcd-calltips))) - - -;; struct constructor calltip expansion - -(defsubst ac-dcd-replace-this-to-struct-name (struct-name) - "When to complete struct constructor calltips, dcd-client outputs candidates which begins with\"this\", -so I have to replace it with struct name." - (while (search-forward "this" nil t)) - (replace-match struct-name)) - -(defun ac-dcd-calltip-candidate-for-struct-constructor () - "Almost the same as `ac-dcd-calltip-candidate', but calls `ac-dcd-replace-this-to-struct-name' before parsing." - (let ((buf (get-buffer-create ac-dcd-output-buffer-name))) - (ac-dcd-call-process-for-calltips) - (with-current-buffer buf - (ac-dcd-replace-this-to-struct-name (cdr ac-last-completion)) - (ac-dcd-parse-calltips)) - )) - -(defvar dcd-calltips-for-struct-constructor - '((candidates . ac-dcd-calltip-candidate-for-struct-constructor) - (prefix . ac-dcd-calltip-prefix) - (action . ac-dcd-calltip-action) - (cache) - )) - -(defun ac-complete-dcd-calltips-for-struct-constructor () - (auto-complete '(dcd-calltips-for-struct-constructor))) - - -;;show document - -(defun ac-dcd-reformat-document () - "Currently, it just decodes \n and \\n." - (with-current-buffer (get-buffer ac-dcd-document-buffer-name) - - ;;doit twice to catch '\n\n' - (goto-char (point-min)) - (while (re-search-forward (rx (and (not (any "\\")) (submatch "\\n"))) nil t) - (replace-match "\n" nil nil nil 1)) - - (goto-char (point-min)) - (while (re-search-forward (rx (and (not (any "\\")) (submatch "\\n"))) nil t) - (replace-match "\n" nil nil nil 1)) - - ;; replace '\\n' in D src to '\n' - (while (re-search-forward (rx "\\\\n") nil t) - (replace-match "\\\\n")) - )) - -(defun ac-dcd-get-ddoc () - "Get document with `dcd-client --doc'." - (save-buffer) - (let ((args - (append - (ac-dcd-build-complete-args (ac-dcd-cursor-position)) - '("-d") - (list (buffer-file-name)))) - (buf (get-buffer-create ac-dcd-document-buffer-name))) - - ;; If I use `call-process', dcd-client errors out when to get long doc(e.g. doc of writef). - ;; I have no idea why. - (with-current-buffer buf - (erase-buffer) - (eshell-command - (mapconcat 'identity `(,(executable-find ac-dcd-executable) ,@args) " ") - t) - (when (or - (string= (buffer-string) "") - (string= (buffer-string) "\n\n\n") ;when symbol has no doc - ) - (error "No document for the symbol at point!")) - (buffer-string) - ))) - -(defun ac-dcd-show-ddoc-with-buffer () - "Display Ddoc at point using `display-buffer'." - (interactive) - (ac-dcd-get-ddoc) - (ac-dcd-reformat-document) - (display-buffer (get-buffer-create ac-dcd-document-buffer-name))) - - -;; goto definition -;; thanks to jedi.el by Takafumi Arakaki - -(defcustom ac-dcd-goto-definition-marker-ring-length 16 - "Length of marker ring to store `ac-dcd-goto-definition' call positions." - :group 'auto-complete) - -(defvar ac-dcd-goto-definition-marker-ring - (make-ring ac-dcd-goto-definition-marker-ring-length) - "Ring that stores ac-dcd-goto-symbol-declaration.") - -(defsubst ac-dcd-goto-def-push-marker () - "Push marker at point to goto-def ring." - (ring-insert ac-dcd-goto-definition-marker-ring (point-marker))) - -(defun ac-dcd-goto-def-pop-marker () - "Goto the point where `ac-dcd-goto-definition' was last called." - (interactive) - (if (ring-empty-p ac-dcd-goto-definition-marker-ring) - (error "Marker ring is empty. Can't pop.") - (let ((marker (ring-remove ac-dcd-goto-definition-marker-ring 0))) - (switch-to-buffer (or (marker-buffer marker) - (error "Buffer has been deleted"))) - (goto-char (marker-position marker)) - ;; Cleanup the marker so as to avoid them piling up. - (set-marker marker nil nil)))) - -(defun ac-dcd-goto-definition () - "Goto declaration of symbol at point." - (interactive) - (save-buffer) - (ac-dcd-call-process-for-symbol-declaration) - (let* ((data (ac-dcd-parse-output-for-get-symbol-declaration)) - (file (car data)) - (offset (cdr data))) - (if (equal data '(nil . nil)) - (message "Not found") - (progn - (ac-dcd-goto-def-push-marker) - (unless (string= file "stdin") ; the declaration is in the current file - (find-file file)) - (goto-char (byte-to-position (string-to-number offset))))))) - - -;; utilities for goto-definition - -(defun ac-dcd-call-process-for-symbol-declaration () - "Get location of symbol declaration with `dcd-client --symbolLocation'." - (let ((args - (append - (ac-dcd-build-complete-args (ac-dcd-cursor-position)) - '("-l") - (list (buffer-file-name)))) - (buf (get-buffer-create ac-dcd-output-buffer-name))) - (with-current-buffer - buf (erase-buffer) - (apply 'call-process ac-dcd-executable nil buf nil args)) - (let ((output (with-current-buffer buf (buffer-string)))) - output))) - -(defun ac-dcd-parse-output-for-get-symbol-declaration () - "Parse output of `ac-dcd-get-symbol-declaration'. -output is just like following.\n -`(cons \"PATH_TO_IMPORT/import/std/stdio.d\" \"63946\")'" - (let ((buf (get-buffer-create ac-dcd-output-buffer-name))) - (with-current-buffer buf - (goto-char (point-min)) - (if (not (string= "Not found\n" (buffer-string))) - (progn (re-search-forward (rx (submatch (* nonl)) "\t" (submatch (* nonl)) "\n")) - (cons (match-string 1) (match-string 2))) - (cons nil nil))) - )) - -(defun ac-dcd-parent-directory (dir) - "Returns parent directory of dir" - (when dir - (file-name-directory (directory-file-name (expand-file-name dir))))) - -(defun ac-dcd-search-file-up (name &optional path) - "Searches for file `name' in parent directories recursively" - (let* ((tags-file-name (concat path name)) - (parent (ac-dcd-parent-directory path)) - (path (or path default-directory)) - ) - (cond - ((file-exists-p tags-file-name) tags-file-name) - ((string= parent path) nil) - (t (ac-dcd-search-file-up name parent))))) - -(defun ac-dcd-find-imports-dub () - "Extract import flags from \"dub describe\" output." - (let ((dub-root-dir (ac-dcd-parent-directory - (or (ac-dcd-search-file-up "dub.json" default-directory) - (ac-dcd-search-file-up "package.json" default-directory)))) - (dub-executable "dub")) - - (when dub-root-dir - (with-temp-buffer - (let ((default-directory dub-root-dir)) - (call-process dub-executable nil (current-buffer) nil "describe")) - (require 'json) - (let* ((json-object-type 'hash-table) - (describe-hash (json-read-from-string (buffer-string))) - (packages-array (gethash "packages" describe-hash)) - (imports-list '())) - (mapcar - (lambda (package) - (let ((package-path (gethash "path" package)) - (import-paths-array (gethash "importPaths" package))) - (mapcar - (lambda (import-path) - (add-to-list 'imports-list - (concat "-I" package-path import-path))) - import-paths-array))) - packages-array) - imports-list))))) - -(defun ac-dcd-find-imports-std () - "Extract import flags from dmd.conf file." - (require 'cl) - (let ((dmd-conf-filename - (find-if 'file-exists-p - (list - ;; TODO: the first directory to look into should be dmd's current - ;; working dir - (concat (getenv "HOME") "/dmd.conf") - (concat (ac-dcd-parent-directory (executable-find "dmd")) "dmd.conf") - "/etc/dmd.conf")))) - - ;; TODO: this extracting procedure is pretty rough, it just searches for - ;; the first occurrence of the DFLAGS - (save-window-excursion - (with-temp-buffer - (find-file dmd-conf-filename) - (goto-char (point-min)) - (search-forward "\nDFLAGS") - (skip-chars-forward " =") - (let ((flags-list (split-string (buffer-substring-no-properties - (point) (line-end-position))))) - (remove-if-not '(lambda (s) - (string-prefix-p "-I" s)) - flags-list)))))) - -(defun ac-dcd-add-imports () - "Send import flags of the current DUB project to dcd-server. - -The root of the project is determined by the \"closest\" dub.json -or package.json file." - (interactive) - (ac-dcd-call-process "" - (append - (ac-dcd-find-imports-std) - (ac-dcd-find-imports-dub)))) - -(provide 'ac-dcd) -;;; ac-dcd.el ends here