Skip to content

Commit ab424cb

Browse files
committed
auto-import class after completion
Special-Thanks: Dominik Honnef (go-mode author) Feature description : After completion with company-phpactor, if the selected candidate is a class (see "info" key in phpactor's output), a proper "use" statement is added after the namespace declaration in case it was absent. In order to distinguish classes with the same name, fully qualified names are shown as annotations beside every completion candidate. What is changed : - completion candidates are given text properties with additionnal informations given by phpactor (info and type keys). - phpactor is called after the completion if the candidate had type 't' (class). - use of functions from go-mode.el to apply a patch on current buffer instead of replacing the whole buffer content in order to save the point. - also widen the buffer before completion, making completion also work during narrowing.
1 parent 763d009 commit ab424cb

File tree

2 files changed

+141
-11
lines changed

2 files changed

+141
-11
lines changed

company-phpactor.el

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,38 @@ Here we create a temporary syntax table in order to add $ to symbols."
5050
(let ((response (phpactor--rpc "complete" (phpactor--command-argments :source :offset))))
5151
(plist-get (plist-get (plist-get response :parameters) :value) :suggestions)))
5252

53+
(defun company-phpactor--get-candidates ()
54+
"Build a list of candidates with text-properties extracted from phpactor's output."
55+
(let ((suggestions (company-phpactor--get-suggestions)) candidate)
56+
(mapcar
57+
(lambda (suggestion)
58+
(setq candidate (plist-get suggestion :name))
59+
(put-text-property 0 1 'annotation (plist-get suggestion :info) candidate)
60+
(put-text-property 0 1 'type (plist-get suggestion :type) candidate)
61+
candidate)
62+
suggestions)))
63+
64+
(defun company-phpactor--post-completion (arg)
65+
"Trigger auto-import of completed item ARG when relevant."
66+
(if (string= (get-text-property 0 'type arg) "t")
67+
(phpactor-import-class (get-text-property 0 'annotation arg))))
68+
69+
(defun company-phpactor--annotation (arg)
70+
"Show additional info (ARG) from phpactor as lateral annotation."
71+
(message (concat " " (get-text-property 0 'annotation arg))))
72+
5373
;;;###autoload
5474
(defun company-phpactor (command &optional arg &rest ignored)
5575
"`company-mode' completion backend for Phpactor."
5676
(interactive (list 'interactive))
57-
(cl-case command
58-
(interactive (company-begin-backend 'company-phpactor))
59-
(prefix (company-phpactor--grab-symbol))
60-
(candidates (all-completions (substring-no-properties arg) (mapcar #'(lambda (suggestion) (plist-get suggestion :name)) (company-phpactor--get-suggestions))))))
77+
(save-restriction
78+
(widen)
79+
(cl-case command
80+
(post-completion (company-phpactor--post-completion arg))
81+
(annotation (company-phpactor--annotation arg))
82+
(interactive (company-begin-backend 'company-phpactor))
83+
(prefix (company-phpactor--grab-symbol))
84+
(candidates (all-completions arg (company-phpactor--get-candidates))))))
6185

6286
(provide 'company-phpactor)
6387
;;; company-phpactor.el ends here

phpactor.el

Lines changed: 113 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@
3838
;; See https://phpactor.github.io/phpactor/configuration.html
3939
;;
4040

41+
;; The following definitions from go-mode.el have been adapted :
42+
;; (Author: Dominik Honnef, url: https://github.com/dominikh/go-mode.el)
43+
;;
44+
;; go--apply-rcs-patch go--goto-line go--delete-whole-line
45+
4146
;;; Code:
4247
(require 'json)
4348
(require 'php-project)
@@ -265,14 +270,108 @@
265270
(find-file path)
266271
(goto-char (1+ offset)))
267272

268-
(cl-defun phpactor-action-replace-file-source (&key path source)
273+
;; this function was adapted from go-mode
274+
(defun phpactor--goto-line (line)
275+
"Goto line LINE."
276+
(goto-char (point-min))
277+
(forward-line (1- line)))
278+
279+
;; this function was adapted from go-mode
280+
(defun phpactor--delete-whole-line (&optional arg)
281+
"Delete the current line without putting it in the `kill-ring'.
282+
Derived from function `kill-whole-line'. ARG is defined as for that
283+
function."
284+
(setq arg (or arg 1))
285+
(if (and (> arg 0)
286+
(eobp)
287+
(save-excursion (forward-visible-line 0) (eobp)))
288+
(signal 'end-of-buffer nil))
289+
(if (and (< arg 0)
290+
(bobp)
291+
(save-excursion (end-of-visible-line) (bobp)))
292+
(signal 'beginning-of-buffer nil))
293+
(cond ((zerop arg)
294+
(delete-region (progn (forward-visible-line 0) (point))
295+
(progn (end-of-visible-line) (point))))
296+
((< arg 0)
297+
(delete-region (progn (end-of-visible-line) (point))
298+
(progn (forward-visible-line (1+ arg))
299+
(unless (bobp)
300+
(backward-char))
301+
(point))))
302+
(t
303+
(delete-region (progn (forward-visible-line 0) (point))
304+
(progn (forward-visible-line arg) (point))))))
305+
306+
;; this function was adapted from go-mode
307+
(defun phpactor--apply-rcs-patch (patch-buffer)
308+
"Apply an RCS-formatted diff from PATCH-BUFFER to the current buffer."
309+
(let ((target-buffer (current-buffer))
310+
;; Relative offset between buffer line numbers and line numbers
311+
;; in patch.
312+
;;
313+
;; Line numbers in the patch are based on the source file, so
314+
;; we have to keep an offset when making changes to the
315+
;; buffer.
316+
;;
317+
;; Appending lines decrements the offset (possibly making it
318+
;; negative), deleting lines increments it. This order
319+
;; simplifies the forward-line invocations.
320+
(line-offset 0)
321+
(column (current-column)))
322+
(save-excursion
323+
(with-current-buffer patch-buffer
324+
(goto-char (point-min))
325+
(while (not (eobp))
326+
(unless (looking-at "^\\([ad]\\)\\([0-9]+\\) \\([0-9]+\\)")
327+
(error "Invalid rcs patch or internal error in go--apply-rcs-patch"))
328+
(forward-line)
329+
(let ((action (match-string 1))
330+
(from (string-to-number (match-string 2)))
331+
(len (string-to-number (match-string 3))))
332+
(cond
333+
((equal action "a")
334+
(let ((start (point)))
335+
(forward-line len)
336+
(let ((text (buffer-substring start (point))))
337+
(with-current-buffer target-buffer
338+
(cl-decf line-offset len)
339+
(goto-char (point-min))
340+
(forward-line (- from len line-offset))
341+
(insert text)))))
342+
((equal action "d")
343+
(with-current-buffer target-buffer
344+
(phpactor--goto-line (- from line-offset))
345+
(cl-incf line-offset len)
346+
(phpactor--delete-whole-line len)))
347+
(t
348+
(error "Invalid rcs patch or internal error in phpactor--apply-rcs-patch")))))))
349+
(move-to-column column)))
350+
351+
(cl-defun phpactor-action-replace-file-source (&key path source)
269352
"Replace the source code in the current file."
270-
(save-window-excursion
271-
(with-current-buffer (find-file-noselect path)
272-
;; This is a simple implementation, so points will not be saved.
273-
;; Should I copy the implementation of gofmt? Umm...
274-
(erase-buffer)
275-
(insert source))))
353+
(interactive)
354+
(let ((tmpfile (make-temp-file "phpactor" nil ".php"))
355+
(patchbuf (get-buffer-create "*Phpactor patch*"))
356+
(coding-system-for-read 'utf-8)
357+
(coding-system-for-write 'utf-8))
358+
359+
(unwind-protect
360+
(save-restriction
361+
(widen)
362+
(with-current-buffer patchbuf
363+
(erase-buffer))
364+
365+
(with-temp-file tmpfile
366+
(insert source))
367+
368+
(if (zerop (call-process-region (point-min) (point-max) "diff" nil patchbuf nil "-n" "-" tmpfile))
369+
(message "Buffer was unchanged by phpactor")
370+
(phpactor--apply-rcs-patch patchbuf)
371+
(message "Buffer modified by phpactor")))
372+
373+
(kill-buffer patchbuf)
374+
(delete-file tmpfile))))
276375

277376
;; Dispatcher:
278377
(cl-defun phpactor-action-dispatch (&key action parameters)
@@ -369,5 +468,12 @@
369468
(let ((arguments (phpactor--command-argments :source :offset :path)))
370469
(apply #'phpactor-action-dispatch (phpactor--rpc "goto_definition" arguments))))
371470

471+
;;;###autoload
472+
(defun phpactor-import-class (name)
473+
"Execute Phpactor PRC import_class command for class NAME."
474+
(interactive)
475+
(let ((arguments (phpactor--command-argments :source :offset :path)))
476+
(apply #'phpactor-action-dispatch (phpactor--rpc "import_class" (append arguments (list :name name))))))
477+
372478
(provide 'phpactor)
373479
;;; phpactor.el ends here

0 commit comments

Comments
 (0)