From e8bf51576918474f9924ecd94449c7b8b8144e53 Mon Sep 17 00:00:00 2001 From: tsukimizake Date: Tue, 24 Jun 2014 19:57:35 +0900 Subject: [PATCH 1/4] complete calltips, show doc support --- editors/emacs/ac-dcd.el | 289 ++++++++++++++++++++++++++-------------- 1 file changed, 191 insertions(+), 98 deletions(-) diff --git a/editors/emacs/ac-dcd.el b/editors/emacs/ac-dcd.el index 4addbb2..8d00e9c 100644 --- a/editors/emacs/ac-dcd.el +++ b/editors/emacs/ac-dcd.el @@ -22,6 +22,8 @@ ;;; Code: (require 'auto-complete) +(require 'rx) +(require 'yasnippet) (defcustom ac-dcd-executable "dcd-client" @@ -29,16 +31,18 @@ :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.\"). +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]") + "^\\(%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*") @@ -56,6 +60,7 @@ You can't put port number flag here. Set `ac-dcd-server-port' instead." "Duration after killing server process in milli second.") +;;server handle functions (defun ac-dcd-stop-server () "Stop dcd-server manually. Ordinary, you don't have to call it. @@ -87,34 +92,37 @@ If you want to restart server, use `ac-dcd-init-server' instead." (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 + 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)) + (setq detailed-info (match-string-no-properties 2)) (if (string= match prev-match) (progn - (when detailed_info + (when detailed-info (setq match (propertize match 'ac-dcd-help (concat (get-text-property 0 'ac-dcd-help (car lines)) "\n" - detailed_info))) + detailed-info))) (setf (car lines) match))) (setq prev-match match) - (when detailed_info - (setq match (propertize match 'ac-dcd-help detailed_info))) + (when detailed-info + (setq match (propertize match 'ac-dcd-help detailed-info))) (push match lines)))) lines)) (defun ac-dcd-handle-error (res args) + "Notify error on parse failure." (goto-char (point-min)) (let* ((buf (get-buffer-create ac-dcd-error-buffer-name)) (cmd (concat ac-dcd-executable " " (mapconcat 'identity args " "))) @@ -136,6 +144,7 @@ If you want to restart server, use `ac-dcd-init-server' instead." (goto-char (point-min)))))) +;; utility functions to call process (defun ac-dcd-call-process (prefix args) (let ((buf (get-buffer-create "*dcd-output*")) @@ -151,6 +160,11 @@ If you want to restart server, use `ac-dcd-init-server' instead." ;; 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" + (point)) + (defsubst ac-dcd-build-complete-args (pos) (list "-c" @@ -160,53 +174,20 @@ If you want to restart server, use `ac-dcd-init-server' instead." )) -(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))) + +;; 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 (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)))))) + (ac-dcd-build-complete-args (ac-dcd-cursor-position)))))) (defun ac-dcd-prefix () (or (ac-prefix-symbol) @@ -218,68 +199,180 @@ If you want to restart server, use `ac-dcd-init-server' instead." (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 (get-text-property 0 'ac-dcd-help lastcompl) "f") ; when it was a function + (progn + (insert "(") ;dcd-client requires open parenthesis to complete calltip. + (ac-complete-dcd-calltips))) + (t nil) + )))) + (ac-define-source dcd '((candidates . ac-dcd-candidate) (prefix . ac-dcd-prefix) (requires . 0) (document . ac-dcd-document) - (action . ac-dcd-action) + (action . ac-dcd-action) (cache) - (symbol . "D"))) + (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))) + +;; function calltip expansion with yasnippet -(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-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 "*dcd-output*"))) + (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." + (ac-dcd-call-process + (concat (cdr ac-last-completion) "(") + (ac-dcd-build-complete-args (ac-dcd-cursor-position)))) + + +(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-remove-function-return-type (s) + "Remove return type of function." + (let ((sl (split-string s))) + (if (string-match "(" (car sl)) ; filter calltip candidate which has no return type. e.g. std.regex.match + s + (mapconcat 'identity (cdr sl) " ") + ))) + + +(defun ac-dcd-parse-calltips () + "Parse dcd output for calltips 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-remove-function-return-type (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 + ;; find the end of function name. some function arguments have parenthesis of its own, + ;; so I had to do it like this. + (search-backward (cdr ac-last-completion)) + (- (search-forward "(" ) 1))) + (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)) + +(ac-define-source dcd-calltips + '((candidates . ac-dcd-calltip-candidate) + (prefix . ac-dcd-calltip-prefix) + (action . ac-dcd-calltip-action) + (cache) + )) + + +;;show document + +(defun ac-dcd-get-ddoc (pos) + "Get document with `dcd-client --doc'. `POS' is cursor position. +TODO:reformat it." + (let ((args + (append + (ac-dcd-build-complete-args (ac-dcd-cursor-position)) + '("--doc") + (list (buffer-file-name)))) + (buf (get-buffer-create "*dcd-output*"))) + (with-current-buffer + buf (erase-buffer) + (apply 'call-process ac-dcd-executable nil buf nil args)) + (let ((raw-doc (with-current-buffer buf (buffer-string)))) + ;; ;; TODO: format document string. + ;; (setq raw-doc (replace-regexp-in-string + ;; (rx (and (not (any "\\\\")) (submatch "\\n"))) + ;; " " raw-doc nil nil 1 nil)); replace \n with space + ;; ;; (setq raw-doc (replace-regexp-in-string + ;; ;; (rx "\\n") "\n" raw-doc));replace \\n(RET in D src) with \n + ;; (setq raw-doc (replace-regexp-in-string + ;; (rx (and "$(D" (submatch (* anything)) ")")) + ;; " `\\1' " raw-doc)) ;format D src + raw-doc))) + +(defun ac-dcd-popup-ddoc-at-point () + "Popup Ddoc at point using popup.el." + (interactive) + (let ((doc (ac-dcd-get-ddoc (ac-dcd-cursor-position)))) + (when (or(string= doc "") + (string= doc "\n\n\n") ;when symbol has no doc + ) + (message "No document for the symbol at point!")))) -(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 From 6b8fcaad5643d9da7bbf29efc044f0628e0c0684 Mon Sep 17 00:00:00 2001 From: tsukimizake Date: Tue, 24 Jun 2014 20:05:57 +0900 Subject: [PATCH 2/4] Update README.md --- editors/emacs/README.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/editors/emacs/README.md b/editors/emacs/README.md index d7c2742..44df6d9 100644 --- a/editors/emacs/README.md +++ b/editors/emacs/README.md @@ -1,13 +1,14 @@ -#EMACS Integration +#Emacs Integration ##Requirements -* You must have the [auto-complete](https://github.com/auto-complete/auto-complete) package. +* You must have the [auto-complete](https://github.com/auto-complete/auto-complete) package, and [yasnippet](https://github.com/capitaomorte/yasnippet) package is recommended. * Make sure dcd-client and dcd-server is in your exec-path. Otherwise, please set the variable ```dcd-exectutable``` and ```dcd-server-executable``` using ```M-x customize```. ## Setup * First, follow the Setup section in the root README. * Second, add the following to your .emacs. With this setting, dcd-server starts automatically when you open file in d-mode. ``` +;;; ac-dcd (add-to-list 'load-path "path_to_ac-dcd.el") (require 'ac-dcd) (add-to-list 'ac-modes 'd-mode) @@ -16,13 +17,15 @@ (add-to-list 'ac-sources 'ac-source-dcd) (auto-complete-mode t)) (add-hook 'd-mode-hook 'ac-d-mode-setup) + +(define-key d-mode-map (kbd "C-c C-h") 'ac-dcd-popup-ddoc-at-point) ;of course, you can change this keybind. + ``` + * Third, set import path using ```M-x customize-variable RET ac-dcd-flags```. * 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```. ## TODO -* better error detection -* detailed ac-source symbol * goto definition -* show doc +* Multi byte character support (Need help!) * and so on... From 337efaa30362858eab2437faa77f97027abca00a Mon Sep 17 00:00:00 2001 From: tsukimizake Date: Wed, 25 Jun 2014 19:13:57 +0900 Subject: [PATCH 3/4] Update README.md --- editors/emacs/README.md | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/editors/emacs/README.md b/editors/emacs/README.md index 44df6d9..1b1e60f 100644 --- a/editors/emacs/README.md +++ b/editors/emacs/README.md @@ -1,31 +1,40 @@ #Emacs Integration ##Requirements -* You must have the [auto-complete](https://github.com/auto-complete/auto-complete) package, and [yasnippet](https://github.com/capitaomorte/yasnippet) package is recommended. +* You must have the [auto-complete](https://github.com/auto-complete/auto-complete) package. +And [yasnippet](https://github.com/capitaomorte/yasnippet) package is recommended. * Make sure dcd-client and dcd-server is in your exec-path. Otherwise, please set the variable ```dcd-exectutable``` and ```dcd-server-executable``` using ```M-x customize```. ## Setup * First, follow the Setup section in the root README. * Second, add the following to your .emacs. With this setting, dcd-server starts automatically when you open file in d-mode. + ``` ;;; ac-dcd (add-to-list 'load-path "path_to_ac-dcd.el") (require 'ac-dcd) (add-to-list 'ac-modes 'd-mode) -(defun ac-d-mode-setup () - (ac-dcd-maybe-start-server) - (add-to-list 'ac-sources 'ac-source-dcd) - (auto-complete-mode t)) -(add-hook 'd-mode-hook 'ac-d-mode-setup) -(define-key d-mode-map (kbd "C-c C-h") 'ac-dcd-popup-ddoc-at-point) ;of course, you can change this keybind. +(add-hook 'd-mode-hook + '(lambda () "set up ac-dcd" + (ac-dcd-maybe-start-server) + (add-to-list 'ac-sources 'ac-source-dcd))) +(define-key d-mode-map (kbd "C-c ?") 'ac-dcd-popup-ddoc-at-point) +(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) ``` * Third, set import path using ```M-x customize-variable RET ac-dcd-flags```. * 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 -* goto definition +* Better error handling * Multi byte character support (Need help!) -* and so on... From 8c740965dc5cfc62ae9f6289c7ce906a07e2de32 Mon Sep 17 00:00:00 2001 From: tsukimizake Date: Wed, 25 Jun 2014 19:16:05 +0900 Subject: [PATCH 4/4] Goto definition support, bug fix --- editors/emacs/ac-dcd.el | 88 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 4 deletions(-) diff --git a/editors/emacs/ac-dcd.el b/editors/emacs/ac-dcd.el index 8d00e9c..c704fb0 100644 --- a/editors/emacs/ac-dcd.el +++ b/editors/emacs/ac-dcd.el @@ -274,9 +274,9 @@ When the symbol is not a function, returns nothing" \\1 is function return type (if exists) and name, and \\2 is args.") (defsubst ac-dcd-remove-function-return-type (s) - "Remove return type of function." + "Remove return type of the function." (let ((sl (split-string s))) - (if (string-match "(" (car sl)) ; filter calltip candidate which has no return type. e.g. std.regex.match + (if (string-match "(" (car sl)) ; s (mapconcat 'identity (cdr sl) " ") ))) @@ -312,7 +312,7 @@ This function should be called at *dcd-output* buf." yasstr) (setq kill-ring (cdr kill-ring)); clean up kill-ring - ;remove parenthesis + ;;remove parenthesis (setq str (substring str 1 (- (length str) 1))) (setq yasstr @@ -342,6 +342,7 @@ This function should be called at *dcd-output* buf." (defun ac-dcd-get-ddoc (pos) "Get document with `dcd-client --doc'. `POS' is cursor position. TODO:reformat it." + (save-buffer) (let ((args (append (ac-dcd-build-complete-args (ac-dcd-cursor-position)) @@ -370,9 +371,88 @@ TODO:reformat it." (when (or(string= doc "") (string= doc "\n\n\n") ;when symbol has no doc ) - (message "No document for the symbol at point!")))) + (message "No document for the symbol at point!")) + (popup-tip doc))) +;; 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 (point)) + (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) + (if (string= file "stdin") ; When the declaration is in the current file + (progn + (goto-char (point-min)) + (forward-char (string-to-number offset))) + (progn + (find-file file) + (goto-char (point-min)) + (forward-char (string-to-number offset)))))))) + +;; utilities for goto-definition + +(defun ac-dcd-call-process-for-symbol-declaration (pos) + "Get location of symbol declaration with `dcd-client --symbolLocation'. +`POS' is cursor position." + (let ((args + (append + (ac-dcd-build-complete-args (ac-dcd-cursor-position)) + '("--symbolLocation") + (list (buffer-file-name)))) + (buf (get-buffer-create "*dcd-output*"))) + (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 "*dcd-output*"))) + (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))) + )) + (provide 'ac-dcd) ;;; ac-dcd.el ends here