]>
Commit | Line | Data |
---|---|---|
1 | ;;; company-yasnippet.el --- company-mode completion backend for Yasnippet | |
2 | ||
3 | ;; Copyright (C) 2014-2015, 2020-2022 Free Software Foundation, Inc. | |
4 | ||
5 | ;; Author: Dmitry Gutov | |
6 | ||
7 | ;; This file is part of GNU Emacs. | |
8 | ||
9 | ;; GNU Emacs is free software: you can redistribute it and/or modify | |
10 | ;; it under the terms of the GNU General Public License as published by | |
11 | ;; the Free Software Foundation, either version 3 of the License, or | |
12 | ;; (at your option) any later version. | |
13 | ||
14 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | ;; GNU General Public License for more details. | |
18 | ||
19 | ;; You should have received a copy of the GNU General Public License | |
20 | ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. | |
21 | ||
22 | ||
23 | ;;; Commentary: | |
24 | ;; | |
25 | ||
26 | ;;; Code: | |
27 | ||
28 | (require 'company) | |
29 | (require 'cl-lib) | |
30 | ||
31 | (declare-function yas--table-hash "yasnippet") | |
32 | (declare-function yas--get-snippet-tables "yasnippet") | |
33 | (declare-function yas-expand-snippet "yasnippet") | |
34 | (declare-function yas--template-content "yasnippet") | |
35 | (declare-function yas--template-expand-env "yasnippet") | |
36 | (declare-function yas--warning "yasnippet") | |
37 | (declare-function yas-minor-mode "yasnippet") | |
38 | (declare-function yas--require-template-specific-condition-p "yasnippet") | |
39 | (declare-function yas--template-can-expand-p "yasnippet") | |
40 | (declare-function yas--template-condition "yasnippet") | |
41 | ||
42 | (defvar company-yasnippet-annotation-fn | |
43 | (lambda (name) | |
44 | (concat | |
45 | (unless company-tooltip-align-annotations " -> ") | |
46 | name)) | |
47 | "Function to format completion annotation. | |
48 | It has to accept one argument: the snippet's name.") | |
49 | ||
50 | (defun company-yasnippet--key-prefixes () | |
51 | ;; Mostly copied from `yas--templates-for-key-at-point'. | |
52 | (defvar yas-key-syntaxes) | |
53 | (save-excursion | |
54 | (let ((original (point)) | |
55 | (methods yas-key-syntaxes) | |
56 | prefixes | |
57 | method) | |
58 | (while methods | |
59 | (unless (eq method (car methods)) | |
60 | (goto-char original)) | |
61 | (setq method (car methods)) | |
62 | (cond ((stringp method) | |
63 | (skip-syntax-backward method) | |
64 | (setq methods (cdr methods))) | |
65 | ((functionp method) | |
66 | (unless (eq (funcall method original) | |
67 | 'again) | |
68 | (setq methods (cdr methods)))) | |
69 | (t | |
70 | (setq methods (cdr methods)) | |
71 | (yas--warning "Invalid element `%s' in `yas-key-syntaxes'" method))) | |
72 | (let ((prefix (buffer-substring-no-properties (point) original))) | |
73 | (unless (equal prefix (car prefixes)) | |
74 | (push prefix prefixes)))) | |
75 | prefixes))) | |
76 | ||
77 | (defun company-yasnippet--candidates (prefix) | |
78 | ;; Process the prefixes in reverse: unlike Yasnippet, we look for prefix | |
79 | ;; matches, so the longest prefix with any matches should be the most useful. | |
80 | (cl-loop with tables = (yas--get-snippet-tables) | |
81 | for key-prefix in (company-yasnippet--key-prefixes) | |
82 | ;; Only consider keys at least as long as the symbol at point. | |
83 | when (>= (length key-prefix) (length prefix)) | |
84 | thereis (company-yasnippet--completions-for-prefix prefix | |
85 | key-prefix | |
86 | tables))) | |
87 | ||
88 | (defun company-yasnippet--completions-for-prefix (prefix key-prefix tables) | |
89 | (cl-mapcan | |
90 | (lambda (table) | |
91 | (let ((keyhash (yas--table-hash table)) | |
92 | (requirement (yas--require-template-specific-condition-p)) | |
93 | res) | |
94 | (when keyhash | |
95 | (maphash | |
96 | (lambda (key value) | |
97 | (when (and (stringp key) | |
98 | (string-prefix-p key-prefix key)) | |
99 | (maphash | |
100 | (lambda (name template) | |
101 | (when (yas--template-can-expand-p | |
102 | (yas--template-condition template) requirement) | |
103 | (push | |
104 | (propertize key | |
105 | 'yas-annotation name | |
106 | 'yas-template template | |
107 | 'yas-prefix-offset (- (length key-prefix) | |
108 | (length prefix))) | |
109 | res))) | |
110 | value))) | |
111 | keyhash)) | |
112 | res)) | |
113 | tables)) | |
114 | ||
115 | (defun company-yasnippet--doc (arg) | |
116 | (let ((template (get-text-property 0 'yas-template arg)) | |
117 | (mode major-mode) | |
118 | (file-name (buffer-file-name))) | |
119 | (defvar yas-prompt-functions) | |
120 | (with-current-buffer (company-doc-buffer) | |
121 | (let ((buffer-file-name file-name)) | |
122 | (yas-minor-mode 1) | |
123 | (setq-local yas-prompt-functions '(yas-no-prompt)) | |
124 | (condition-case error | |
125 | (yas-expand-snippet (yas--template-content template)) | |
126 | (error | |
127 | (message "%s" (error-message-string error)))) | |
128 | (delay-mode-hooks | |
129 | (let ((inhibit-message t)) | |
130 | (if (eq mode 'web-mode) | |
131 | (progn | |
132 | (setq mode 'html-mode) | |
133 | (funcall mode)) | |
134 | (funcall mode))) | |
135 | (ignore-errors (font-lock-ensure)))) | |
136 | (current-buffer)))) | |
137 | ||
138 | ;;;###autoload | |
139 | (defun company-yasnippet (command &optional arg &rest ignore) | |
140 | "`company-mode' backend for `yasnippet'. | |
141 | ||
142 | This backend should be used with care, because as long as there are | |
143 | snippets defined for the current major mode, this backend will always | |
144 | shadow backends that come after it. Recommended usages: | |
145 | ||
146 | * In a buffer-local value of `company-backends', grouped with a backend or | |
147 | several that provide actual text completions. | |
148 | ||
149 | (add-hook \\='js-mode-hook | |
150 | (lambda () | |
151 | (set (make-local-variable \\='company-backends) | |
152 | \\='((company-dabbrev-code company-yasnippet))))) | |
153 | ||
154 | * After keyword `:with', grouped with other backends. | |
155 | ||
156 | (push \\='(company-semantic :with company-yasnippet) company-backends) | |
157 | ||
158 | * Not in `company-backends', just bound to a key. | |
159 | ||
160 | (global-set-key (kbd \"C-c y\") \\='company-yasnippet) | |
161 | " | |
162 | (interactive (list 'interactive)) | |
163 | (cl-case command | |
164 | (interactive (company-begin-backend 'company-yasnippet)) | |
165 | (prefix | |
166 | ;; Should probably use `yas--current-key', but that's bound to be slower. | |
167 | ;; How many trigger keys start with non-symbol characters anyway? | |
168 | (and (bound-and-true-p yas-minor-mode) | |
169 | (company-grab-symbol))) | |
170 | (annotation | |
171 | (funcall company-yasnippet-annotation-fn | |
172 | (get-text-property 0 'yas-annotation arg))) | |
173 | (candidates (company-yasnippet--candidates arg)) | |
174 | (doc-buffer (company-yasnippet--doc arg)) | |
175 | (no-cache t) | |
176 | (kind 'snippet) | |
177 | (post-completion | |
178 | (let ((template (get-text-property 0 'yas-template arg)) | |
179 | (prefix-offset (get-text-property 0 'yas-prefix-offset arg))) | |
180 | (yas-expand-snippet (yas--template-content template) | |
181 | (- (point) (length arg) prefix-offset) | |
182 | (point) | |
183 | (yas--template-expand-env template)))))) | |
184 | ||
185 | (provide 'company-yasnippet) | |
186 | ;;; company-yasnippet.el ends here |