-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy patheldoc-meta-net.el
383 lines (333 loc) · 14.7 KB
/
eldoc-meta-net.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
;;; eldoc-meta-net.el --- Eldoc support for meta-net -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2025 Shen, Jen-Chieh
;; Created date 2021-07-12 23:32:13
;; Author: Shen, Jen-Chieh <[email protected]>
;; URL: https://github.com/emacs-vs/eldoc-meta-net
;; Version: 0.1.0
;; Package-Requires: ((emacs "26.1") (meta-net "1.1.0") (ht "2.3") (csharp-mode "1.0.2"))
;; Keywords: convenience eldoc c# dotnet sdk
;; This file is NOT part of 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 <https://www.gnu.org/licenses/>.
;;; Commentary:
;;
;; Eldoc support for meta-net
;;
;;; Code:
(require 'cl-lib)
(require 'pcase)
(require 'subr-x)
(require 'csharp-mode)
(require 'eldoc)
(require 'meta-net)
(require 'ht)
(defgroup eldoc-meta-net nil
"Eldoc support for meta-net."
:prefix "eldoc-meta-net-"
:group 'tool
:link '(url-link :tag "Repository" "https://github.com/emacs-vs/eldoc-meta-net"))
(defcustom eldoc-meta-net-display-summary nil
"If non-nil, display summary if valid."
:type 'boolean
:group 'eldoc-meta-net)
;; These keywords are grab from `csharp-mode'
(defconst eldoc-meta-net--csharp-keywords
(append
'("class" "interface" "struct")
'("bool" "byte" "sbyte" "char" "decimal" "double" "float" "int" "uint"
"long" "ulong" "short" "ushort" "void" "object" "string" "var")
'("typeof" "is" "as")
'("enum" "new")
'("using")
'("abstract" "default" "final" "native" "private" "protected"
"public" "partial" "internal" "readonly" "static" "event" "transient"
"volatile" "sealed" "ref" "out" "virtual" "implicit" "explicit"
"fixed" "override" "params" "async" "await" "extern" "unsafe"
"get" "set" "this" "const" "delegate")
'("select" "from" "where" "join" "in" "on" "equals" "into"
"orderby" "ascending" "descending" "group" "when"
"let" "by" "namespace")
'("do" "else" "finally" "try")
'("for" "if" "switch" "while" "catch" "foreach" "fixed" "checked"
"unchecked" "using" "lock")
'("break" "continue" "goto" "throw" "return" "yield")
'("true" "false" "null" "value")
'("base" "operator"))
"Some C# keywords to eliminate namespaces.")
(defvar-local eldoc-meta-net--namespaces nil
"Where store all the parsed namespaces.")
(defvar eldoc-meta-net-show-debug nil
"Show the debug message from this package.")
;;
;; (@* "Util" )
;;
(defun eldoc-meta-net-debug (fmt &rest args)
"Debug message like function `message' with same argument FMT and ARGS."
(when eldoc-meta-net-show-debug (apply 'message fmt args)))
(defun eldoc-meta-net--inside-comment-p ()
"Return non-nil if it's inside comment."
(nth 4 (syntax-ppss)))
(defun eldoc-meta-net--inside-comment-or-string-p ()
"Return non-nil if it's inside comment or string."
(or (eldoc-meta-net--inside-comment-p) (nth 8 (syntax-ppss))))
(defun eldoc-meta-net--recursive-count (regex string &optional start)
"Count REGEX in STRING; return an integer value.
Optional argument START is use for recursive counting."
(unless start (setq start 0))
(if (string-match regex string start)
(+ 1 (eldoc-meta-net--recursive-count regex string (match-end 0)))
0))
;;
;; (@* "Xmls" )
;;
(defvar-local eldoc-meta-net--xmls nil
"Cache records a list of assembly xml file path.")
(defun eldoc-meta-net--all-xmls (&optional refresh)
"Return full list of assembly xml files.
If REFRESH is non-nil, refresh cache once."
(when (or refresh (null eldoc-meta-net--xmls))
(setq eldoc-meta-net--xmls (meta-net-csproj-xmls meta-net-csproj-current))
(cl-delete-duplicates eldoc-meta-net--xmls))
eldoc-meta-net--xmls)
;;
;; (@* "Core" )
;;
(defun eldoc-meta-net--grab-namespaces ()
"Parsed namespaces from current buffer."
(setq eldoc-meta-net--namespaces nil)
(save-excursion
(goto-char (point-min))
(while (re-search-forward "[,.:]*[ \t\n]*\\([a-z-A-Z0-9_-]+\\)[ \t\n]*[.;{]+" nil t)
(save-excursion
(forward-symbol -1)
(unless (eldoc-meta-net--inside-comment-or-string-p)
(when-let ((symbol (thing-at-point 'symbol)))
(push symbol eldoc-meta-net--namespaces))))))
(setq eldoc-meta-net--namespaces (reverse eldoc-meta-net--namespaces)
eldoc-meta-net--namespaces (delete-dups eldoc-meta-net--namespaces)
eldoc-meta-net--namespaces (cl-remove-if (lambda (namespace)
(member namespace eldoc-meta-net--csharp-keywords))
eldoc-meta-net--namespaces)))
(defun eldoc-meta-net--possible-function-point ()
"This function get called infront of the opening curly bracket.
For example,
SomeFunction<TypeA, TypeB>(a, b, c);
^
This function also ignore generic type between < and >."
(let ((start (point))
(normal (save-excursion (forward-symbol -1) (point)))
(generic
(ignore-errors
(save-excursion (search-backward ">" nil t)
(forward-char 1)
(forward-sexp -1)
(forward-symbol -1)
(point))))
result search-pt)
;; Make sure the result is number to avoid error
(setq normal (or normal (point))
generic (or generic (point))
result (min normal generic))
(when (<= generic (line-beginning-position))
(setq generic (point)))
(goto-char result) ; here suppose to be the start of the function name
;; We check to see if there is comma right behind the symbol
(save-excursion
(forward-symbol 1)
(setq search-pt (point))
(when (and (re-search-forward "[^,]*" nil t)
(string-empty-p (string-trim (buffer-substring search-pt (point)))))
(setq result start)))
(goto-char result)))
(defun eldoc-meta-net--function-name ()
"Return the function name at point."
(let* ((right 0) (left 0))
(while (and (<= left right) (re-search-backward "\\((\\|)\\)" nil t))
(if (equal (buffer-substring (point) (+ (point) 1)) "(")
(setq left (+ left 1))
(setq right (+ right 1))))
(while (equal (buffer-substring (- (point) 1) (point)) " ")
(goto-char (- (point) 1))))
(save-excursion
(eldoc-meta-net--possible-function-point)
(unless (eldoc-meta-net--inside-comment-p) (thing-at-point 'symbol))))
(defun eldoc-meta-net--arg-boundaries ()
"Return a list of cons cell represent arguments' boundaries.
The start boundary should either behind of `(` or `,`; the end boundary should
either infront of `,` or `)`.
For example, (^ is start; $ is end)
Add(var1, var2, var3)
^ $ ^ $ ^ $
This function also get called infront of the opening curly bracket. See
function `eldoc-meta-net--possible-function-point' for the graph."
(let (bounds start (max-pt (save-excursion (forward-sexp 1))))
(save-excursion
(forward-char 1)
(setq start (point))
(while (re-search-forward "[,<{[()]" max-pt t)
(pcase (string (char-before))
((or "," ")")
(push (cons start (1- (point))) bounds)
(setq start (point)))
((or "{" "(" "<" "[") (forward-char -1) (forward-sexp 1)))))
(reverse bounds)))
(defun eldoc-meta-net-current-index (bounds point)
"Return the index of current boundary from BOUNDS.
Argument POINT is the currnet check point."
(cl-position-if
(lambda (bound)
(let ((start (car bound)) (end (cdr bound)))
(and (<= start point) (<= point end))))
bounds))
(defun eldoc-meta-net--match-name (type)
"Return non-nil, if the TYPE match the current namespace list.
The argument TYPE is a list of namespace in string. For instance,
using System.Collections; => '(System Collections)
We use this to eliminate not possible candidates."
(let ((match t) (len (length type)) (index 0) item)
(while (and match (< index len))
(setq item (nth index type)
index (1+ index)
match (member item eldoc-meta-net--namespaces)))
match))
(defun eldoc-meta-net--grab-data (function-name)
"Return data that match FUNCTION-NAME."
(eldoc-meta-net--grab-namespaces) ; first grab the data from `meta-net'
(let* ((xmls (eldoc-meta-net--all-xmls)) ; Get the list of xml files from current project
(xmls-len (length xmls)) ; length of the xmls
(xml-index 0) ; index search through all `xmls`
xml ; current xml path as key
break ; flag to stop
type ; xml assembly type
comp-name ; name of the type, the last component from the type
splits ; temporary list to chop namespace, use to produce `comp-name`
namespaces
result)
(while (and (not break) (< xml-index xmls-len))
(setq xml (nth xml-index xmls)
xml-index (1+ xml-index))
(let* ((types (meta-net-xml-types xml))
(types-len (length types))
(type-index 0))
(while (and (not break) (< type-index types-len))
(setq type (nth type-index types)
type-index (1+ type-index)
splits (split-string type "\\.")
comp-name (nth (1- (length splits)) splits)
namespaces (butlast splits))
;; Check if all namespaces appears in the buffer,
;;
;; We use `butlast' to get rid of the component name because we do
;; allow the same level candidates.
;;
;; For example, `NamespaceA` contains `classA` and `classB`, and we
;; ignore the check of `classA` and `classB` in order to let them
;; both appears in candidates list.
(when (eldoc-meta-net--match-name namespaces)
(eldoc-meta-net-debug "\f")
(eldoc-meta-net-debug "xml: %s" xml)
(eldoc-meta-net-debug "Type: %s" type)
(eldoc-meta-net-debug "Name: %s" comp-name)
(when-let* ((methods (meta-net-type-methods xml type))
(methods-keys (ht-keys methods)))
(dolist (key methods-keys) ; `key` is function name with arguments
(when (string-match-p (format "\\_<%s\\_>" function-name) key)
(push (cons key (ht-get methods key)) result)
(setq break t))))))))
result))
(defun eldoc-meta-net-function ()
"Main eldoc entry."
(save-excursion
(when-let* ((start (point))
(function-name (eldoc-meta-net--function-name))
(arg-bounds (eldoc-meta-net--arg-boundaries)) ; list of cons cell
(arg-index (eldoc-meta-net-current-index arg-bounds start))
(arg-count (length arg-bounds))
(methods (eldoc-meta-net--grab-data function-name)))
(eldoc-meta-net-debug "Funtion name: %s" function-name)
(eldoc-meta-net-debug "Arg Bounds: %s" arg-bounds)
(eldoc-meta-net-debug "Arg Count: k%s" arg-count)
;; Find match arguments count, for function overloading
(let ((index 0) (len (length methods)) data found
name info match-arg-count match-bounds)
(while (and (not found) (< index len))
(setq data (nth index methods)
index (1+ index)
name (car data) info (cdr data)
match-arg-count 0)
(with-temp-buffer
(insert name)
(goto-char (point-min))
(when (search-forward "(" nil t)
(forward-char -1)
(setq match-bounds (eldoc-meta-net--arg-boundaries)
match-arg-count (length match-bounds))
;; Notice that function overloading can has same argument count
;; but with different type.
;;
;; For instance,
;; ---
;; OverloadingFunction(System.String)
;; ---
;; and,
;; ---
;; OverloadingFunction(System.Type)
;; ---
;;
;; Since we cannot know the type from the uesr, we just pick one
;; that matches.
(when (= match-arg-count arg-count)
(setq found t)))))
;; Start display buffer
(with-temp-buffer
(delay-mode-hooks (funcall 'csharp-mode))
(ignore-errors (font-lock-ensure))
(insert name)
(let ((params (ht-get info 'params)) (param-index (1- (length match-bounds)))
param param-name param-summary)
(dolist (bound (reverse match-bounds)) ; we replace from the back
(delete-region (car bound) (cdr bound)) ; deletion for replacement
(goto-char (car bound)) ; navigate back to starting position
(setq param (nth param-index params)
param-name (car param))
(when (= param-index arg-index) ; check if we need to highlight argument's name
(setq param-summary (cdr param) ; record target's (param) summary later insertion
;; Highlight it!
param-name (propertize param-name 'face 'eldoc-highlight-function-argument)))
;; Make sure we don't add a space infront of (
(insert (if (= param-index 0) "" " ") param-name) ; insert replacement
(setq param-index (1- param-index)))
;; Insert parameter's summary
(when (and eldoc-meta-net-display-summary param-summary)
(goto-char (point-max))
(insert "\n\n" param-summary)))
;; Done, return it
(buffer-string))))))
(defun eldoc-meta-net--turn-on ()
"Start the `eldoc-meta-net' worker."
(unless meta-net-csproj-current (meta-net-read-project)) ; read project
(add-function :before-until (local 'eldoc-documentation-function) #'eldoc-meta-net-function)
(eldoc-mode 1))
;;;###autoload
(defun eldoc-meta-net-enable ()
"Turn on `eldoc-meta-net'."
(interactive)
(add-hook 'csharp-mode-hook #'eldoc-meta-net--turn-on)
(add-hook 'csharp-tree-sitter-mode #'eldoc-meta-net--turn-on)
(eldoc-meta-net--turn-on))
;;;###autoload
(defun eldoc-meta-net-disable ()
"Turn off `eldoc-meta-net'."
(interactive)
(remove-hook 'csharp-mode-hook #'eldoc-meta-net--turn-on)
(remove-hook 'csharp-tree-sitter-mode #'eldoc-meta-net--turn-on))
(provide 'eldoc-meta-net)
;;; eldoc-meta-net.el ends here