77; ; Description: Eldoc support for meta-net
88; ; Keyword: eldoc c# dotnet sdk
99; ; Version: 0.1.0
10- ; ; Package-Requires: ((emacs "25.1") (meta-net "1.1.0"))
10+ ; ; Package-Requires: ((emacs "25.1") (meta-net "1.1.0") (ht "2.3") )
1111; ; URL: https://github.com/emacs-vs/eldoc-meta-net
1212
1313; ; This file is NOT part of GNU Emacs.
3232
3333; ;; Code:
3434
35+ (require 'cl-lib )
3536(require 'pcase )
3637(require 'subr-x )
3738
3839(require 'eldoc )
3940(require 'meta-net )
41+ (require 'ht )
4042
4143(defgroup eldoc-meta-net nil
4244 " Eldoc support for meta-net."
4345 :prefix " eldoc-meta-net-"
4446 :group 'tool
4547 :link '(url-link :tag " Repository" " https://github.com/emacs-vs/eldoc-meta-net" ))
4648
49+ ; ; These keywords are grab from `csharp-mode'
50+ (defconst eldoc-meta-net--csharp-keywords
51+ (append
52+ '(" class" " interface" " struct" )
53+ '(" bool" " byte" " sbyte" " char" " decimal" " double" " float" " int" " uint"
54+ " long" " ulong" " short" " ushort" " void" " object" " string" " var" )
55+ '(" typeof" " is" " as" )
56+ '(" enum" " new" )
57+ '(" using" )
58+ '(" abstract" " default" " final" " native" " private" " protected"
59+ " public" " partial" " internal" " readonly" " static" " event" " transient"
60+ " volatile" " sealed" " ref" " out" " virtual" " implicit" " explicit"
61+ " fixed" " override" " params" " async" " await" " extern" " unsafe"
62+ " get" " set" " this" " const" " delegate" )
63+ '(" select" " from" " where" " join" " in" " on" " equals" " into"
64+ " orderby" " ascending" " descending" " group" " when"
65+ " let" " by" " namespace" )
66+ '(" do" " else" " finally" " try" )
67+ '(" for" " if" " switch" " while" " catch" " foreach" " fixed" " checked"
68+ " unchecked" " using" " lock" )
69+ '(" break" " continue" " goto" " throw" " return" " yield" )
70+ '(" true" " false" " null" " value" )
71+ '(" base" " operator" ))
72+ " Some C# keywords to eliminate namespaces." )
73+
74+ (defvar-local eldoc-meta-net--namespaces nil
75+ " Where store all the parsed namespaces." )
76+
77+ (defvar eldoc-meta-net-show-debug nil
78+ " Show the debug message from this package." )
79+
4780; ;
4881; ; (@* "Util" )
4982; ;
5083
84+ (defun eldoc-meta-net-debug (fmt &rest args )
85+ " Debug message like function `message' with same argument FMT and ARGS."
86+ (when eldoc-meta-net-show-debug (apply 'message fmt args)))
87+
5188(defun eldoc-meta-net--inside-comment-p ()
5289 " Return non-nil if it's inside comment."
5390 (nth 4 (syntax-ppss )))
5491
92+ (defun eldoc-meta-net--inside-comment-or-string-p ()
93+ " Return non-nil if it's inside comment or string."
94+ (or (eldoc-meta-net--inside-comment-p) (nth 8 (syntax-ppss ))))
95+
96+ (defun eldoc-meta-net--recursive-count (regex string &optional start )
97+ " Count REGEX in STRING; return an integer value.
98+
99+ Optional argument START is use for recursive counting."
100+ (unless start (setq start 0 ))
101+ (if (string-match regex string start)
102+ (+ 1 (eldoc-meta-net--recursive-count regex string (match-end 0 )))
103+ 0 ))
104+
105+ ; ;
106+ ; ; (@* "Xmls" )
107+ ; ;
108+
109+ (defvar-local eldoc-meta-net--xmls nil
110+ " Cache records a list of assembly xml file path." )
111+
112+ (defun eldoc-meta-net--all-xmls (&optional refresh )
113+ " Return full list of assembly xml files.
114+ If REFRESH is non-nil, refresh cache once."
115+ (when (or refresh (null eldoc-meta-net--xmls))
116+ (setq eldoc-meta-net--xmls (meta-net-csproj-xmls meta-net-csproj-current))
117+ (cl-delete-duplicates eldoc-meta-net--xmls))
118+ eldoc-meta-net--xmls)
119+
55120; ;
56121; ; (@* "Core" )
57122; ;
58123
124+ (defun eldoc-meta-net--grab-namespaces ()
125+ " Parsed namespaces from current buffer."
126+ (setq eldoc-meta-net--namespaces nil )
127+ (save-excursion
128+ (goto-char (point-min ))
129+ (while (re-search-forward " [,.:]*[ \t\n ]*\\ ([a-z-A-Z0-9_-]+\\ )[ \t\n ]*[.;{]+" nil t )
130+ (save-excursion
131+ (forward-symbol -1 )
132+ (unless (eldoc-meta-net--inside-comment-or-string-p)
133+ (when-let ((symbol (thing-at-point 'symbol )))
134+ (push symbol eldoc-meta-net--namespaces))))))
135+ (setq eldoc-meta-net--namespaces (reverse eldoc-meta-net--namespaces)
136+ eldoc-meta-net--namespaces (delete-dups eldoc-meta-net--namespaces)
137+ eldoc-meta-net--namespaces (cl-remove-if (lambda (namespace )
138+ (member namespace eldoc-meta-net--csharp-keywords))
139+ eldoc-meta-net--namespaces)))
140+
59141(defun eldoc-meta-net--possible-function-point ()
60142 " This function get called infront of the opening curly bracket.
61143
@@ -102,37 +184,6 @@ This function also ignore generic type between < and >."
102184 (eldoc-meta-net--possible-function-point)
103185 (unless (eldoc-meta-net--inside-comment-p) (thing-at-point 'symbol ))))
104186
105- (defun eldoc-meta-net--arg-string ()
106- " Return argument string."
107- (save-excursion
108- (when (search-forward " (" nil t )
109- (forward-char -1 )
110- (let ((beg (point )) arg-string)
111- (forward-sexp 1 )
112- (setq arg-string (buffer-substring beg (point )))
113- ; ; Here we remove everything inside nested arguments
114- ; ;
115- ; ; For example,
116- ; ;
117- ; ; Add(a, b, (x, y, z) => { }, c)
118- ; ;
119- ; ; should return,
120- ; ;
121- ; ; (a, b, => , c)
122- ; ;
123- ; ; Of course, if you are inside the x y z scope then it would just
124- ; ; return (x, y, z). This is fine since ElDoc should just only need
125- ; ; to know the first layer's function.
126- (with-temp-buffer
127- (insert arg-string)
128- (goto-char (1+ (point-min )))
129- (while (re-search-forward " [({]" nil t )
130- (forward-char -1 )
131- (let ((start (point )))
132- (forward-sexp 1 )
133- (delete-region start (point ))))
134- (buffer-string ))))))
135-
136187(defun eldoc-meta-net--arg-boundaries ()
137188 " Return a list of cons cell represent arguments' boundaries.
138189
@@ -146,34 +197,142 @@ For example, (^ is start; $ is end)
146197
147198This function also get called infront of the opening curly bracket. See
148199function `eldoc-meta-net--possible-function-point' for the graph."
149- (let (boundaries start (max-pt (save-excursion (forward-sexp 1 ))))
200+ (let (bounds start (max-pt (save-excursion (forward-sexp 1 ))))
150201 (save-excursion
151202 (forward-char 1 )
152203 (setq start (point ))
153- (while (re-search-forward " [,{ ()]" max-pt t )
204+ (while (re-search-forward " [,<{[ ()]" max-pt t )
154205 (pcase (string (char-before ))
155206 ((or " ," " )" )
156- (push (cons start (1- (point ))) boundaries )
207+ (push (cons start (1- (point ))) bounds )
157208 (setq start (point )))
158- ((or " {" " (" ) (forward-char -1 ) (forward-sexp 1 )))))
159- (reverse boundaries)))
209+ ((or " {" " (" " <" " [" ) (forward-char -1 ) (forward-sexp 1 )))))
210+ (reverse bounds)))
211+
212+ (defun eldoc-meta-net-current-index (bounds point )
213+ " Return the index of current boundary from BOUNDS.
214+
215+ Argument POINT is the currnet check point."
216+ (cl-position-if
217+ (lambda (bound )
218+ (let ((start (car bound)) (end (cdr bound)))
219+ (and (<= start point) (<= point end))))
220+ bounds))
221+
222+ (defun eldoc-meta-net--match-name (type )
223+ " Return non-nil, if the TYPE match the current namespace list.
224+
225+ The argument TYPE is a list of namespace in string. For instance,
226+
227+ using System.Collections; => '(System Collections)
228+
229+ We use this to eliminate not possible candidates."
230+ (let ((match t ) (len (length type)) (index 0 ) item)
231+ (while (and match (< index len))
232+ (setq item (nth index type)
233+ index (1+ index)
234+ match (member item eldoc-meta-net--namespaces)))
235+ match ))
236+
237+ (defun eldoc-meta-net--grab-data (function-name )
238+ " Return data that matches FUNCTION-NAME."
239+ (unless meta-net-csproj-current (meta-net-read-project)) ; read it
240+ (eldoc-meta-net--grab-namespaces) ; first grab the data from `meta-net'
241+
242+ (let* ((xmls (eldoc-meta-net--all-xmls)) ; Get the list of xml files from current project
243+ (xmls-len (length xmls)) ; length of the xmls
244+ (xml-index 0 ) ; index search through all `xmls`
245+ xml ; current xml path as key
246+ break ; flag to stop
247+ type ; xml assembly type
248+ comp-name ; name of the type, the last component from the type
249+ splits ; temporary list to chop namespace, use to produce `comp-name`
250+ namespaces
251+ result)
252+ (while (and (not break) (< xml-index xmls-len))
253+ (setq xml (nth xml-index xmls)
254+ xml-index (1+ xml-index))
255+ (let* ((types (meta-net-xml-types xml))
256+ (types-len (length types))
257+ (type-index 0 ))
258+ (while (and (not break) (< type-index types-len))
259+ (setq type (nth type-index types)
260+ type-index (1+ type-index)
261+ splits (split-string type " \\ ." )
262+ comp-name (nth (1- (length splits)) splits)
263+ namespaces (butlast splits))
264+ ; ; Check if all namespaces appears in the buffer,
265+ ; ;
266+ ; ; We use `butlast' to get rid of the component name because we do
267+ ; ; allow the same level candidates.
268+ ; ;
269+ ; ; For example, `NamespaceA` contains `classA` and `classB`, and we
270+ ; ; ignore the check of `classA` and `classB` in order to let them
271+ ; ; both appears in candidates list.
272+ (when (eldoc-meta-net--match-name namespaces)
273+ (eldoc-meta-net-debug " \f " )
274+ (eldoc-meta-net-debug " xml: %s" xml)
275+ (eldoc-meta-net-debug " Type: %s" type)
276+ (eldoc-meta-net-debug " Name: %s" comp-name)
277+ (when-let* ((methods (meta-net-type-methods xml type))
278+ (methods-keys (ht-keys methods)))
279+ (dolist (key methods-keys) ; `key` is function name with arguments
280+ (when (string-match-p (format " \\ _<%s \\ _>" function-name) key)
281+ (push (cons key (ht-get methods key)) result)
282+ (setq break t ))))))))
283+ result))
160284
161285(defun eldoc-meta-net-function ()
162286 " Main eldoc entry."
163287 (save-excursion
164- (when-let* ((function-name (eldoc-meta-net--function-name ))
165- (arg-string (eldoc-meta-net--arg-string ))
288+ (when-let* ((start ( point ))
289+ (function-name (eldoc-meta-net--function-name ))
166290 (arg-bounds (eldoc-meta-net--arg-boundaries)) ; list of cons cell
167- (arg-count (length arg-bounds)))
168- (jcs-print function-name)
169- (jcs-print arg-string)
170- (jcs-print arg-bounds)
171- (jcs-print arg-count)
291+ (arg-index (eldoc-meta-net-current-index arg-bounds start))
292+ (arg-count (length arg-bounds))
293+ (methods (eldoc-meta-net--grab-data function-name)))
294+ (eldoc-meta-net-debug " Funtion name: %s" function-name)
295+ (eldoc-meta-net-debug " Arg Bounds: %s" arg-bounds)
296+ (eldoc-meta-net-debug " Arg Count: k%s" arg-count)
297+ ; ; Find match arguments count, for function overloading
298+ (let ((index 0 ) (len (length methods)) data break
299+ name info match-arg-count target)
300+ (while (and (not break) (< index len))
301+ (setq data (nth index methods)
302+ index (1+ index)
303+ name (car data) info (cdr data)
304+ match-arg-count 0 )
305+ (with-temp-buffer
306+ (insert name)
307+ (goto-char (point-min ))
308+ (when (search-forward " (" nil t )
309+ (forward-char -1 )
310+ (setq match-arg-count (length (eldoc-meta-net--arg-boundaries)))
311+ ; ; Notice that function overloading can has same argument count
312+ ; ; but with different type.
313+ ; ;
314+ ; ; For instance,
315+ ; ; ---
316+ ; ; OverloadingFunction(System.String)
317+ ; ; ---
318+ ; ; and,
319+ ; ; ---
320+ ; ; OverloadingFunction(System.Type)
321+ ; ; ---
322+ ; ;
323+ ; ; Since we cannot know the type from the uesr, we just pick one
324+ ; ; that matches.
325+ (when (= match-arg-count arg-count)
326+ (setq target data ; found
327+ break t ))))
328+ (jcs-print name)
329+ (jcs-print (ht-get info 'summary ))
330+ (jcs-print " Match arg count: " match-arg-count))
331+ name)
172332 )))
173333
174334(defun eldoc-meta-net--turn-on ()
175335 " Start the `eldoc-meta-net' worker."
176- (unless meta-net-csproj-current (meta-net-read-project))
177336 (add-function :before-until (local 'eldoc-documentation-function ) #'eldoc-meta-net-function )
178337 (eldoc-mode 1 ))
179338
0 commit comments