Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
marvel
GitHub Repository: marvel/qnf
Path: blob/master/elisp/slime/contrib/swank-c-p-c.lisp
990 views
1
;;; swank-c-p-c.lisp -- ILISP style Compound Prefix Completion
2
;;
3
;; Author: Luke Gorrie <[email protected]>
4
;; Edi Weitz <[email protected]>
5
;; Matthias Koeppe <[email protected]>
6
;; Tobias C. Rittweiler <[email protected]>
7
;; and others
8
;;
9
;; License: Public Domain
10
;;
11
12
13
(in-package :swank)
14
15
(defslimefun completions (string default-package-name)
16
"Return a list of completions for a symbol designator STRING.
17
18
The result is the list (COMPLETION-SET COMPLETED-PREFIX), where
19
COMPLETION-SET is the list of all matching completions, and
20
COMPLETED-PREFIX is the best (partial) completion of the input
21
string.
22
23
Simple compound matching is supported on a per-hyphen basis:
24
25
(completions \"m-v-\" \"COMMON-LISP\")
26
==> ((\"multiple-value-bind\" \"multiple-value-call\"
27
\"multiple-value-list\" \"multiple-value-prog1\"
28
\"multiple-value-setq\" \"multiple-values-limit\")
29
\"multiple-value\")
30
31
\(For more advanced compound matching, see FUZZY-COMPLETIONS.)
32
33
If STRING is package qualified the result list will also be
34
qualified. If string is non-qualified the result strings are
35
also not qualified and are considered relative to
36
DEFAULT-PACKAGE-NAME.
37
38
The way symbols are matched depends on the symbol designator's
39
format. The cases are as follows:
40
FOO - Symbols with matching prefix and accessible in the buffer package.
41
PKG:FOO - Symbols with matching prefix and external in package PKG.
42
PKG::FOO - Symbols with matching prefix and accessible in package PKG.
43
"
44
(multiple-value-bind (name package-name package internal-p)
45
(parse-completion-arguments string default-package-name)
46
(let* ((symbol-set (symbol-completion-set
47
name package-name package internal-p
48
(make-compound-prefix-matcher #\-)))
49
(package-set (package-completion-set
50
name package-name package internal-p
51
(make-compound-prefix-matcher '(#\. #\-))))
52
(completion-set
53
(format-completion-set (nconc symbol-set package-set)
54
internal-p package-name)))
55
(when completion-set
56
(list completion-set (longest-compound-prefix completion-set))))))
57
58
59
;;;;; Find completion set
60
61
(defun symbol-completion-set (name package-name package internal-p matchp)
62
"Return the set of completion-candidates as strings."
63
(mapcar (completion-output-symbol-converter name)
64
(and package
65
(mapcar #'symbol-name
66
(find-matching-symbols name
67
package
68
(and (not internal-p)
69
package-name)
70
matchp)))))
71
72
(defun package-completion-set (name package-name package internal-p matchp)
73
(declare (ignore package internal-p))
74
(mapcar (completion-output-package-converter name)
75
(and (not package-name)
76
(find-matching-packages name matchp))))
77
78
(defun find-matching-symbols (string package external test)
79
"Return a list of symbols in PACKAGE matching STRING.
80
TEST is called with two strings. If EXTERNAL is true, only external
81
symbols are returned."
82
(let ((completions '())
83
(converter (completion-output-symbol-converter string)))
84
(flet ((symbol-matches-p (symbol)
85
(and (or (not external)
86
(symbol-external-p symbol package))
87
(funcall test string
88
(funcall converter (symbol-name symbol))))))
89
(do-symbols* (symbol package)
90
(when (symbol-matches-p symbol)
91
(push symbol completions))))
92
completions))
93
94
(defun find-matching-symbols-in-list (string list test)
95
"Return a list of symbols in LIST matching STRING.
96
TEST is called with two strings."
97
(let ((completions '())
98
(converter (completion-output-symbol-converter string)))
99
(flet ((symbol-matches-p (symbol)
100
(funcall test string
101
(funcall converter (symbol-name symbol)))))
102
(dolist (symbol list)
103
(when (symbol-matches-p symbol)
104
(push symbol completions))))
105
(remove-duplicates completions)))
106
107
(defun find-matching-packages (name matcher)
108
"Return a list of package names matching NAME with MATCHER.
109
MATCHER is a two-argument predicate."
110
(let ((converter (completion-output-package-converter name)))
111
(remove-if-not (lambda (x)
112
(funcall matcher name (funcall converter x)))
113
(mapcar (lambda (pkgname)
114
(concatenate 'string pkgname ":"))
115
(loop for package in (list-all-packages)
116
nconcing (package-names package))))))
117
118
119
;; PARSE-COMPLETION-ARGUMENTS return table:
120
;;
121
;; user behaviour | NAME | PACKAGE-NAME | PACKAGE
122
;; ----------------+--------+--------------+-----------------------------------
123
;; asdf [tab] | "asdf" | NIL | #<PACKAGE "DEFAULT-PACKAGE-NAME">
124
;; | | | or *BUFFER-PACKAGE*
125
;; asdf: [tab] | "" | "asdf" | #<PACKAGE "ASDF">
126
;; | | |
127
;; asdf:foo [tab] | "foo" | "asdf" | #<PACKAGE "ASDF">
128
;; | | |
129
;; as:fo [tab] | "fo" | "as" | NIL
130
;; | | |
131
;; : [tab] | "" | "" | #<PACKAGE "KEYWORD">
132
;; | | |
133
;; :foo [tab] | "foo" | "" | #<PACKAGE "KEYWORD">
134
;;
135
(defun parse-completion-arguments (string default-package-name)
136
"Parse STRING as a symbol designator.
137
Return these values:
138
SYMBOL-NAME
139
PACKAGE-NAME, or nil if the designator does not include an explicit package.
140
PACKAGE, generally the package to complete in. (However, if PACKAGE-NAME is
141
NIL, return the respective package of DEFAULT-PACKAGE-NAME instead;
142
if PACKAGE is non-NIL but a package cannot be found under that name,
143
return NIL.)
144
INTERNAL-P, if the symbol is qualified with `::'."
145
(multiple-value-bind (name package-name internal-p)
146
(tokenize-symbol string)
147
(if package-name
148
(let ((package (guess-package (if (equal package-name "")
149
(symbol-name :keyword)
150
package-name))))
151
(values name package-name package internal-p))
152
(let ((package (guess-package default-package-name)))
153
(values name package-name (or package *buffer-package*) internal-p))
154
)))
155
156
157
158
(defun completion-output-case-converter (input &optional with-escaping-p)
159
"Return a function to convert strings for the completion output.
160
INPUT is used to guess the preferred case."
161
(ecase (readtable-case *readtable*)
162
(:upcase (cond ((or with-escaping-p
163
(not (some #'lower-case-p input)))
164
#'identity)
165
(t #'string-downcase)))
166
(:invert (lambda (output)
167
(multiple-value-bind (lower upper) (determine-case output)
168
(cond ((and lower upper) output)
169
(lower (string-upcase output))
170
(upper (string-downcase output))
171
(t output)))))
172
(:downcase (cond ((or with-escaping-p
173
(not (some #'upper-case-p input)))
174
#'identity)
175
(t #'string-upcase)))
176
(:preserve #'identity)))
177
178
(defun completion-output-package-converter (input)
179
"Return a function to convert strings for the completion output.
180
INPUT is used to guess the preferred case."
181
(completion-output-case-converter input))
182
183
(defun completion-output-symbol-converter (input)
184
"Return a function to convert strings for the completion output.
185
INPUT is used to guess the preferred case. Escape symbols when needed."
186
(let ((case-converter (completion-output-case-converter input))
187
(case-converter-with-escaping (completion-output-case-converter input t)))
188
(lambda (str)
189
(if (or (multiple-value-bind (lowercase uppercase)
190
(determine-case str)
191
;; In these readtable cases, symbols with letters from
192
;; the wrong case need escaping
193
(case (readtable-case *readtable*)
194
(:upcase lowercase)
195
(:downcase uppercase)
196
(t nil)))
197
(some (lambda (el)
198
(or (member el '(#\: #\Space #\Newline #\Tab))
199
(multiple-value-bind (macrofun nonterminating)
200
(get-macro-character el)
201
(and macrofun
202
(not nonterminating)))))
203
str))
204
(concatenate 'string "|" (funcall case-converter-with-escaping str) "|")
205
(funcall case-converter str)))))
206
207
208
(defun determine-case (string)
209
"Return two booleans LOWER and UPPER indicating whether STRING
210
contains lower or upper case characters."
211
(values (some #'lower-case-p string)
212
(some #'upper-case-p string)))
213
214
215
;;;;; Compound-prefix matching
216
217
(defun make-compound-prefix-matcher (delimeter &key (test #'char=))
218
"Returns a matching function that takes a `prefix' and a
219
`target' string and which returns T if `prefix' is a
220
compound-prefix of `target', and otherwise NIL.
221
222
Viewing each of `prefix' and `target' as a series of substrings
223
delimited by DELIMETER, if each substring of `prefix' is a prefix
224
of the corresponding substring in `target' then we call `prefix'
225
a compound-prefix of `target'.
226
227
DELIMETER may be a character, or a list of characters."
228
(let ((delimeters (etypecase delimeter
229
(character (list delimeter))
230
(cons (assert (every #'characterp delimeter))
231
delimeter))))
232
(lambda (prefix target)
233
(declare (type simple-string prefix target))
234
(loop for ch across prefix
235
with tpos = 0
236
always (and (< tpos (length target))
237
(let ((delimeter (car (member ch delimeters :test test))))
238
(if delimeter
239
(setf tpos (position delimeter target :start tpos))
240
(funcall test ch (aref target tpos)))))
241
do (incf tpos)))))
242
243
244
;;;;; Extending the input string by completion
245
246
(defun longest-compound-prefix (completions &optional (delimeter #\-))
247
"Return the longest compound _prefix_ for all COMPLETIONS."
248
(flet ((tokenizer (string) (tokenize-completion string delimeter)))
249
(untokenize-completion
250
(loop for token-list in (transpose-lists (mapcar #'tokenizer completions))
251
if (notevery #'string= token-list (rest token-list))
252
collect (longest-common-prefix token-list) ; Note that we possibly collect
253
and do (loop-finish) ; the "" here as well, so that
254
else collect (first token-list))))) ; UNTOKENIZE-COMPLETION will
255
; append a hyphen for us.
256
(defun tokenize-completion (string delimeter)
257
"Return all substrings of STRING delimited by DELIMETER."
258
(loop with end
259
for start = 0 then (1+ end)
260
until (> start (length string))
261
do (setq end (or (position delimeter string :start start) (length string)))
262
collect (subseq string start end)))
263
264
(defun untokenize-completion (tokens)
265
(format nil "~{~A~^-~}" tokens))
266
267
(defun transpose-lists (lists)
268
"Turn a list-of-lists on its side.
269
If the rows are of unequal length, truncate uniformly to the shortest.
270
271
For example:
272
\(transpose-lists '((ONE TWO THREE) (1 2)))
273
=> ((ONE 1) (TWO 2))"
274
(cond ((null lists) '())
275
((some #'null lists) '())
276
(t (cons (mapcar #'car lists)
277
(transpose-lists (mapcar #'cdr lists))))))
278
279
280
;;;; Completion for character names
281
282
(defslimefun completions-for-character (prefix)
283
(let* ((matcher (make-compound-prefix-matcher #\_ :test #'char-equal))
284
(completion-set (character-completion-set prefix matcher))
285
(completions (sort completion-set #'string<)))
286
(list completions (longest-compound-prefix completions #\_))))
287
288
(provide :swank-c-p-c)
289