-
Notifications
You must be signed in to change notification settings - Fork 141
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support multi-level imenu indexes #179
Conversation
Great! |
great! will read after hours
…On Wed, Sep 12, 2018, 11:40 yyoncho ***@***.***> wrote:
Great!
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#179 (comment)>, or mute
the thread
<https:/notifications/unsubscribe-auth/AAZaQoAbeBHMh8BtDJKdauKjs_XGGYtNks5uaVUmgaJpZM4Wl8Nq>
.
|
https://microsoft.github.io/language-server-protocol/specification Do you mean |
lsp-ui-imenu.el
Outdated
@@ -112,62 +149,120 @@ | |||
(when (overlayp lsp-ui-imenu-ov) | |||
(delete-overlay lsp-ui-imenu-ov)))))) | |||
|
|||
(defun lsp-ui-imenu--move-to-line-beginning () | |||
(-when-let* ((padding (get-char-property (point) 'padding)) | |||
(bars-count (get-char-property (point) 'bars-count))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't fully understand the code, but it seems to me depth
is a better name than bars-count
(which counts confusing "invisible ones")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
depth is a good name. Done.
lsp-ui-imenu.el
Outdated
@@ -112,62 +149,120 @@ | |||
(when (overlayp lsp-ui-imenu-ov) | |||
(delete-overlay lsp-ui-imenu-ov)))))) | |||
|
|||
(defun lsp-ui-imenu--move-to-line-beginning () |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a better name for this function? This actually moves to an indented position.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed
lsp-ui-imenu.el
Outdated
Return the updated COLOR-INDEX." | ||
(let ((len (length items))) | ||
(--each-indexed items | ||
(let ((is-last (eq (1+ it-index) len))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think eq
is more efficient than =
when comparing two fixnums but the latter seems more common.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
lsp-ui-imenu.el
Outdated
(setq color-index (1+ color-index)))) | ||
(insert (lsp-ui-imenu--make-line title it-index it | ||
padding bars bars-count color-index | ||
(eq (1+ it-index) len))))))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be just is-last
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
I wonder, rather than use |
LSP has added support for hierarchical document symbols. lsp-ui-imenu will throw error if the imenu index has more than 2 nested alists. This change aims at keeping the rendering for 2-level indexes unchanged, while supporting more level indexes. For multi-level indexes, left title placement doesn't make much sense for all levels, so I only keep the left title placement for the first level title.
fa37e5c
to
953c3c0
Compare
I have a PR for lsp-mode to support DocumentSymbol: emacs-lsp/lsp-mode#421. It's better to add hierarchical support to lsp-ui-imenu before the PR is merged and breaks it. Besides, lsp-ui-imenu doesn't depend on anything about LSP. The screenshots in this PR are actually taken in typescript-mode, which doesn't use LSP.
I've considered using string chunks, but elisp doesn't have a built-in deque data structure. I can use a reverse list but that would require rendering in reverse order. I think bool vector is quite intuitive for this use case. Only drawback is it's fix length. |
The bars are organized as a stack (LIFO; operations are only done to the top element) so I think a list will be a perfect fit. When rendering, you'll reverse the list then join them. I don't fully understand the UI code so I might miss something. |
True that the bars are organized as a stack, but that doesn't mean using a list-simulated-stack to implement it is the best option. Consider the usage here: ;; initialization
(make-bool-vector 8 t)
;; manipulate for last node
(when is-last
(aset bars depth nil))
(when is-last
(aset bars depth t))
;; rendering
(dotimes (i depth)
(get-bar bars i is-title is-last)) As opposed to: ;; initialization
nil
;; manipuate for next depth
(cons
(get-bar is-title is-last) bars)
;; rendering
(when depth > 1 (render-bar (cdr bars) (1- depth)))
(car bars)
;; rendering, alternative
(concat (reverse bars)) I don't think the stack-based version is easier to read than the bool vector version. |
What if you don't allocate i.e rather than
use
|
It's not as simple as you showed. The logic for stack would look like this:
|
This can be written as:
|
It's not better than the bool vector, and people reading the code have to keep in mind that the vector is in reversed order, which is less intuitive than bool vector |
That way you can get rid of the hard limit |
Can you dump some data for me to test? I'll take a stab to improve it. |
I would agree with you if I don't need to render the bars with recursion or Here is the data I used to generate the screenshot: (("<function>"
(#("<function> function" 11 19
(face tide-imenu-type-face))
. 616)
(#("<function> function" 11 19
(face tide-imenu-type-face))
. 798)
("<function>"
(#("<function> function" 11 19
(face tide-imenu-type-face))
. 1207)
("<function>"
(#("<function> function" 11 19
(face tide-imenu-type-face))
. 1270)
(#("doesContains var" 13 16
(face tide-imenu-type-face))
. 1311))))
(#("args var" 5 8
(face tide-imenu-type-face))
. 295)
(#("cp alias" 3 8
(face tide-imenu-type-face))
. 164)
(#("fs alias" 3 8
(face tide-imenu-type-face))
. 202)
("jake"
(#("jake var" 5 8
(face tide-imenu-type-face))
. 549)
(#("<function> function" 11 19
(face tide-imenu-type-face))
. 584))
("tsc"
(#("tsc function" 4 12
(face tide-imenu-type-face))
. 326)
(#("<function> function" 11 19
(face tide-imenu-type-face))
. 488)
("tsc"
(#("tsc var" 4 7
(face tide-imenu-type-face))
. 402)
(#("<function> function" 11 19
(face tide-imenu-type-face))
. 453)))) |
I tried but it is too troublesome.. There are still some issues.. diff --git a/lsp-ui-imenu.el b/lsp-ui-imenu.el
index 465ca54..70f4fa7 100644
--- a/lsp-ui-imenu.el
+++ b/lsp-ui-imenu.el
@@ -67,36 +67,32 @@
(declare-function imenu--subalist-p 'imenu)
(defvar imenu--index-alist)
-(defun lsp-ui-imenu--pad (s len bars depth color-index for-title is-last)
+(defun lsp-ui-imenu--pad (entry s len bars color-index for-title is-last)
+ (message (format "=== %s %s" bars entry))
(let ((n (- len (length s))))
(apply #'concat
(make-string n ?\s)
(propertize s 'face `(:foreground ,(lsp-ui-imenu--get-color color-index)))
(let (bar-strings)
- (dotimes (i depth)
+ (dolist (bar bars)
(push
- (propertize (lsp-ui-imenu--get-bar bars i depth for-title is-last)
+ (propertize (lsp-ui-imenu--get-bar bar (not (consp bar)) for-title is-last)
'face `(:foreground
- ,(lsp-ui-imenu--get-color (+ color-index i))))
- bar-strings))
+ ,(lsp-ui-imenu--get-color color-index)))
+ bar-strings)
+ (cl-incf color-index))
(reverse bar-strings)))))
-(defun lsp-ui-imenu--get-bar (bars index depth for-title is-last)
+(defun lsp-ui-imenu--get-bar (bar hang for-title is-last)
(cond
- ((>= index lsp-ui-imenu--max-bars)
- ;; Exceeding maximum bars
- " ")
- ((not (aref bars index))
+ ((not bar)
;; No bar for this level
" ")
- ((and (= depth 1) (not for-title))
+ ((and hang (not for-title))
;; For the first level, the title is rendered differently, so leaf items are
;; decorated with the full height bar regardless if it's the last item or
;; not.
" ┃ ")
- ((< (1+ index) depth)
- ;; Full height bar for levels other than the rightmost one.
- " ┃ ")
(is-last
;; The rightmost bar for the last item.
" ┗ " )
@@ -110,14 +106,15 @@
(defun lsp-ui-imenu--get-color (index)
(nth (mod index (length lsp-ui-imenu-colors)) lsp-ui-imenu-colors))
-(defun lsp-ui-imenu--make-line (title index entry padding bars depth color-index is-last)
- (let* ((prefix (if (and (= index 0) (eq lsp-ui-imenu-kind-position 'left)) title " "))
- (text (concat (lsp-ui-imenu--pad prefix padding bars depth color-index nil is-last)
+(defun lsp-ui-imenu--make-line (title index entry padding bars color-index is-last)
+ (let* ((prefix (if (and (null bars) (eq lsp-ui-imenu-kind-position 'left))
+ title " "))
+ (text (concat (lsp-ui-imenu--pad (car entry) prefix padding bars color-index nil is-last)
(propertize (car entry) 'face 'default)
"\n"))
(len (length text)))
(add-text-properties 0 len `(index ,index title ,title marker ,(cdr entry)
- padding ,padding depth, depth)
+ padding ,padding depth ,(length bars))
text)
text))
@@ -173,16 +170,16 @@
;; Left placement, title is put with the first sub item. Only put a separator here.
(lsp-ui-imenu--put-separator)))
-(defun lsp-ui-imenu--put-subtitle (title padding bars depth color-index is-last)
+(defun lsp-ui-imenu--put-subtitle (title padding bars color-index is-last)
(let ((ov (make-overlay (point) (point)))
- (title-color (lsp-ui-imenu--get-color (+ color-index depth))))
+ (title-color (lsp-ui-imenu--get-color (+ color-index (length bars)))))
(overlay-put
ov 'after-string
- (concat (lsp-ui-imenu--pad " " padding bars depth color-index t is-last)
+ (concat (lsp-ui-imenu--pad title " " padding bars color-index t is-last)
(propertize title 'face `(:foreground ,title-color))
(propertize "\n" 'face '(:height 1))))))
-(defun lsp-ui-imenu--insert-items (title items padding bars depth color-index)
+(defun lsp-ui-imenu--insert-items (title items padding bars color-index)
"Insert ITEMS for TITLE.
PADDING is the length of whitespaces to the left of the first bar.
@@ -190,8 +187,6 @@ PADDING is the length of whitespaces to the left of the first bar.
BARS is a bool vector of length `lsp-ui-imenu--max-bars'. The ith
value indicates whether the ith bar from the left is visible.
-DEPTH is the depth of the items in the index tree, starting from 0.
-
COLOR-INDEX is the index of the color of the leftmost bar.
Return the updated COLOR-INDEX."
@@ -200,25 +195,25 @@ Return the updated COLOR-INDEX."
(let ((is-last (= (1+ it-index) len)))
(if (imenu--subalist-p it)
(-let* (((sub-title . entries) it))
- (if (= depth 0)
+ (if (null bars)
(lsp-ui-imenu--put-toplevel-title sub-title color-index)
- (lsp-ui-imenu--put-subtitle sub-title padding bars depth color-index is-last))
- (when (and is-last (> depth 0))
- (aset bars (1- depth) nil))
- (let ((lsp-ui-imenu-kind-position (if (> depth 0) 'top
+ (when is-last
+ (setq bars (cons nil (cdr bars)))
+ )
+ (lsp-ui-imenu--put-subtitle sub-title padding
+ bars
+ color-index is-last))
+ (let ((lsp-ui-imenu-kind-position (if (consp bars) 'top
lsp-ui-imenu-kind-position)))
(lsp-ui-imenu--insert-items sub-title
entries
padding
- bars
- (1+ depth)
+ (cons t bars)
color-index))
- (when (and is-last (> depth 0))
- (aset bars (1- depth) t))
- (when (= depth 0)
+ (when (null bars)
(setq color-index (1+ color-index))))
(insert (lsp-ui-imenu--make-line title it-index it
- padding bars depth color-index
+ padding bars color-index
is-last))))))
color-index)
@@ -243,15 +238,14 @@ Return the updated COLOR-INDEX."
(let* ((padding (lsp-ui-imenu--get-padding list))
(grouped-by-subs (-partition-by 'imenu--subalist-p list))
(color-index 0)
- (bars (make-bool-vector lsp-ui-imenu--max-bars t))
buffer-read-only)
(remove-overlays)
(erase-buffer)
(dolist (group grouped-by-subs)
(if (imenu--subalist-p (car group))
- (setq color-index (lsp-ui-imenu--insert-items "" group padding bars 0 color-index))
+ (setq color-index (lsp-ui-imenu--insert-items "" group padding nil color-index))
(lsp-ui-imenu--put-separator)
- (lsp-ui-imenu--insert-items "" group padding bars 1 color-index)
+ (lsp-ui-imenu--insert-items "" group padding '(t) color-index)
(setq color-index (1+ color-index))))
(lsp-ui-imenu-mode)
(setq mode-line-format '(:eval (lsp-ui-imenu--win-separator))) |
Which language server did you use to generate the hierarchical data? |
@bizzyman The data was not generated by any language server. The data was generated from With emacs-lsp/lsp-mode#421, I can generate the following data from JavaComp: (("DocumentSymbolHandler"
("(Class)" . #<marker at 1072 in DocumentSymbolHandler.java>)
("<init> (Method)" . #<marker at 1893 in DocumentSymbolHandler.java>)
("addDocumentSymbolsInScope"
("(Method)" . #<marker at 3756 in DocumentSymbolHandler.java>)
("childScope (Variable)" . #<marker at 4769 in DocumentSymbolHandler.java>)
("children (Variable)" . #<marker at 4090 in DocumentSymbolHandler.java>)
("entity (Variable)" . #<marker at 4131 in DocumentSymbolHandler.java>)
("newSymbol (Variable)" . #<marker at 4030 in DocumentSymbolHandler.java>)
("symbolKind (Variable)" . #<marker at 4196 in DocumentSymbolHandler.java>))
("addSymbolInformationsInScope"
("(Method)" . #<marker at 5002 in DocumentSymbolHandler.java>)
("entity (Variable)" . #<marker at 5308 in DocumentSymbolHandler.java>)
("newContainerName (Variable)" . #<marker at 5867 in DocumentSymbolHandler.java>)
("symbol (Variable)" . #<marker at 5488 in DocumentSymbolHandler.java>)
("symbolKind (Variable)" . #<marker at 5372 in DocumentSymbolHandler.java>))
("getSymbolKind"
("(Method)" . #<marker at 6219 in DocumentSymbolHandler.java>)
("parentEntity (Variable)" . #<marker at 6343 in DocumentSymbolHandler.java>)
("symbolKind (Variable)" . #<marker at 6303 in DocumentSymbolHandler.java>))
("handleRequest"
("(Method)" . #<marker at 2103 in DocumentSymbolHandler.java>)
("clientCapabilities (Variable)" . #<marker at 2407 in DocumentSymbolHandler.java>)
("documentSymbolCapabilities (Variable)" . #<marker at 2491 in DocumentSymbolHandler.java>)
("fileScope (Variable)" . #<marker at 2206 in DocumentSymbolHandler.java>)
("supportDocumentSymbol (Variable)" . #<marker at 2613 in DocumentSymbolHandler.java>)
("supportedSymbolKinds (Variable)" . #<marker at 2546 in DocumentSymbolHandler.java>)
("symbols (Variable)" . #<marker at 3220 in DocumentSymbolHandler.java>)
("symbols (Variable)" . #<marker at 3449 in DocumentSymbolHandler.java>))
("DEFAULT_SUPPORTED_SYMBOL_KINDS (Field)" . #<marker at 1265 in DocumentSymbolHandler.java>)
("logger (Field)" . #<marker at 1172 in DocumentSymbolHandler.java>)
("server (Field)" . #<marker at 1882 in DocumentSymbolHandler.java>))) |
LSP has added support for hierarchical document symbols. lsp-ui-imenu will throw
error if the imenu index has more than 2 nested alists.
This change aims at keeping the rendering for 2-level indexes unchanged, while
supporting more level indexes. For multi-level indexes, left title placement
doesn't make much sense for all levels, so I only keep the left title placement
for the toplevel title.