-
-
Couldn't load subscription status.
- Fork 939
lsp-rename: improve UI
#2317
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
lsp-rename: improve UI
#2317
Conversation
|
I am not sure we need anything more than... in the |
|
Don't merge yet - there is a bug that I need to fix. As to your comment, if we use that new function, there will be a nice LSP :: message instead. Do you consider that undesirable? If so, I'll revert. |
|
Also, the prompt is now a bit nicer, because I don't use buffer-substring-no-properties. |
|
lsp-mode.el
Outdated
| "Get symbol to rename and placeholder at point. | ||
| Returns a cons (SYMBOL-TO-RENAME . PLACEHOLDER)." | ||
| ;; Is there some connected server that supports renaming? | ||
| (if-let (lsp-feature? "textDocument/rename") |
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.
(-if-let (lsp-feature? "xxx")
lsp-feature?
...)
returns "xxx".
Can you use the unless form as suggested?
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.
The if-let was wrong, and was a refactoring leftover. I use unless instead as you suggested now.
lsp-mode.el
Outdated
| ;; We don't support prepare-rename, so just yield the symbol at point. | ||
| (let ((sym (thing-at-point 'symbol))) | ||
| (cons sym sym))) | ||
| ;; No connected server supports renaming. |
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.
can you avoid putting comments like that? This should be evident from the content of the next line.
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 usually don't do that - this one was an accident. Fixed it.
|
Note that the error messages aren't propertized, which may require further thought. |
|
Because of the unpropertized error messages, I'd advise you to wait before merging. |
lsp-mode.el
Outdated
| (if (lsp-feature? "textDocument/prepareRename") | ||
| (when-let ((prepare | ||
| (lsp-request "textDocument/prepareRename" | ||
| (lsp--text-document-position-params)))) | ||
| (-let* (((start . end) (lsp--range-to-region | ||
| (if (lsp-range? response) | ||
| response | ||
| (lsp:prepare-rename-result-range response)))) | ||
| (symbol (buffer-substring-no-properties start end)) | ||
| (placeholder (lsp:prepare-rename-result-placeholder response))) | ||
| (if (lsp-range? prepare) | ||
| prepare | ||
| (lsp:prepare-rename-result-range prepare)))) | ||
| (symbol (buffer-substring start end)) | ||
| (placeholder (lsp:prepare-rename-result-placeholder prepare))) | ||
| (cons symbol (or placeholder symbol)))) | ||
| (let ((symbol (thing-at-point 'symbol t))) | ||
| (cons symbol symbol)))) | ||
| ;; We don't support prepare-rename, so just yield the symbol at point. | ||
| (let ((sym (thing-at-point 'symbol))) | ||
| (cons sym sym)))) |
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.
- prepareRename does not work with
lsp-feature? - Can you revert the renamed symbols?
- Can you remove irrelevant comments?
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.
- What should I use instead? The old code?
2, 3. Will do.
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.
The old code should be fine.
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.
@yyoncho Are you sure that it doesn't though? From stepping trough that with edebug, lsp-feature? returned a complex result for "textDocument/prepareRename", while consistently returning nil if I evaluate it somewhere without lsp-mode enabled (a scratch buffer, for instance).
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 checked and lsp-method-requirements are actually implemented so lsp-feature? will work. It is odd that we haven't replaced the lsp--get-symbol-to-rename
lsp-mode.el
Outdated
| "Display lsp error message with FORMAT with ARGS." | ||
| (message "%s :: %s" (propertize "LSP" 'face 'error) (apply #'format format args))) | ||
|
|
||
| (defun lsp--fatal-error (format &rest args) |
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.
seems like this is redundant: there is lsp--error.
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.
Ignore... I missed the fact that it is using error not message. But still, is this function used in this PR? Are we going to replace all error calls with that?
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.
lsp--error logs a message, which doesn't cause evaluation to abort.
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 function is used by this PR, which is why I added it. However, replacing all error calls might be a bit much. From looking at the code, the (error (lsp-error ...)) idiom seems common, however it doesn't solve the text-properties issue.
lsp-mode.el
Outdated
| (error "%s :: %s" (propertize "LSP" 'face 'error) | ||
| (apply #'format format args))) | ||
|
|
||
| (defun lsp--user-error (format &rest args) |
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 see any effect from this propertize - the message is displayed with default font.
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.
Yes, that is the issue currently.
c80fee1 to
e4fbe8b
Compare
|
I cleaned up the history a bit. Aside from that, I eliminated the new error functions, since they don't work they way I wanted anyway, and use |
|
This PR is now ready. |
lsp-mode.el
Outdated
| (interactive | ||
| (-if-let ((symbol . placeholder) (lsp--get-symbol-to-rename)) | ||
| (list (read-string (format "Rename %s to: " symbol) | ||
| placeholder nil symbol)) | ||
| (user-error "`lsp-rename' is invalid here"))) |
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.
please revert that.
lsp-mode.el
Outdated
| (cons symbol (or placeholder symbol)))) | ||
| (let ((symbol (thing-at-point 'symbol t))) | ||
| (cons symbol symbol)))) | ||
| (when-let ((sym (thing-at-point 'symbol))) |
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.
why do you rename symbol? Why do you introduce when-let?
Let's clarify that for future reference - do not introduce random renames, restructuring, etc - nobody is doing this PR in this repo. You are making the life of the reviewers harder than it should be. I have to go and test each that you have changed to verify that each line of all lines that you have changed does not introduce a regression. I cannot spend that much time reviewing trivial changes chasing for regressions.
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.
Ok, I won't do that in the future. The when-let is because that function should return nil if there is nothing to rename at point. Without that when-let, if (thing-at-point symbol) were to yield nil, the function would return (cons nil nil), instead of nil as it should. This, unlike I had assumed, didn't cause any real problems, because -if-let checks the bindings, not the input against nil.
(eq nil nil) ; => true
(eq nil (cons nil nil)) ; => false
(equal nil (cons nil nil)) ; => falseThere 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.
You will still rename all local variables, introduce random changes without documenting/testing them based on assumptions and you will expect the rest of the team to do that for you?!
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.
Sorry, I missspelled won't...
lsp-mode.el
Outdated
| response | ||
| (lsp:prepare-rename-result-range response)))) | ||
| (symbol (buffer-substring-no-properties start end)) | ||
| (symbol (buffer-substring start end)) |
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.
Why we need to use buffer-substring instead of -no-properties version?
Do we need the highlighting when prompting?
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.
| (when-let ((sym (thing-at-point 'symbol))) | ||
| (cons sym sym)))) | ||
|
|
||
| (defun lsp-rename (newname) |
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.
Why don't we just check the capability in this command (public) instead of changing internal one?
I don't think guarding every internal one is desirable/good from perf POV. Just guard once in public one should be enough, shouldn't it?
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.
If we guard only in lsp-rename, the user would be prompted to enter a new name even though renaming is unsupported.
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.
Also, it's fast enough:
(with-current-buffer "main.c"
(benchmark-run 1000 (ignore (lsp-feature? "textDocument/rename"))))yields 0.037 seconds.
Calling it only twice gives me 2ms timings, which is less than a 120FPS monitor's refresh latency.
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.
If we guard only in
lsp-rename, the user would be prompted to enter a new name even though renaming is unsupported.
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 it better to move the check outside of lsp--get-symbol-to-rename though?
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 it would be better to do that. lsp--get-symbol-to-rename is internal function, which should only be called once the condition (has capability for rename) has been cleared.
That would be a good convention to follow, else we will have to add the check for every function, which can be overwhelming.
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.
It's actually called only once though, in lsp--get-symbol-to-rename. We needn't add checks in every function, because the server will respond with a proper "not implemented" error if it isn't supported. Here, we cannot rely on that though, since we mustn't prompt the user for a name if the rename wouldn't work anyway.
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 you don't get my point. Let say if the lsp--get-symbol-to-rename is gone/refactored, which can happen since it's internal function, now we will need to find a new place to put the capacity checking.
On other hand, lsp-rename is public function, and is unlikely to change without a breaking change, put the capacity guard there is the best, since user has to use that function to actually go through rename.
Is there any specific concern why the capacity guard has to be put in lsp--get-symbol-to-rename?
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.
We could add an additional lsp-feature? check to lsp-rename. However, the check in lsp--get-symbol-to-rename must stay: the interactive block is executed before the body, meaning the user would get prompted for the symbol to rename even though rename might not be possible at all or at point.
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.
You can put the check in (interactive) form too. Like what @yyoncho suggested.
If you hate that, you can even make (interactive '(nil)) and check for newName outside of interactive block and call to lsp--get-symbol-to-rename.
|
I'll extend this to highlight the symbol being renamed; with the refactoring necessary do that, I can add tests ensuring that it will work in all cases. |
|
The various `lsp-rename' helper functions are now thoroughly tested, and the symbol being renamed is highlighted. |
|
In summary:
|
fc5cc8b to
42d522f
Compare
lsp-rename: check if supportedlsp-rename: check if supported, improve UI
42d522f to
280b1e1
Compare
lsp-rename: check if supported, improve UIlsp-rename: improve UI
| (defun lsp--read-rename (at-point) | ||
| "Read a new name for a `lsp-rename' at `point' from the user. | ||
| AT-POINT shall be a structure as returned by | ||
| `lsp--get-symbol-to-rename'. |
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.
It would be better to specify what the structure look like instead of just saying it will be the return value from lsp--get-symbol-to-rename
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.
The structure is defined in lsp--get-symbol-to-rename's docstring.
lsp-mode.el
Outdated
| ;; overlay to linger. | ||
| (unwind-protect | ||
| (progn | ||
| (setq overlay (-doto (make-overlay start end) |
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.
nit: is there a need for -doto in this case, since there's only on form?
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.
Fixed, thanks.
|
There are two issues with this as of now (hence the "Draft" status):
And perhaps some ToDos:
|
I'm working on this feature at the moment. It'll be orthogonal to the changes proposed here (if accepted). You'll enter the rename preview mode if you invoke |
`lsp-rename' doesn't check whether renaming is supported first. Do that, throwing an error otherwise. Fixes #2314.
In `lsp--get-symbol-to-rename', handle the server not providing a placeholder by limiting the `when-let' block to the prepare result.
- Use an `unless' form instead of `if-let' to check for "textDocument/rename". The latter was wrong anyway, and should've been an `if'. - Remove useless comments. - Revert the variable name change "response" -> "prepare" - Use `lsp-feature?' instead of reinventing it inline
Drop `lsp--fatal-error' and `lsp--user-error', since they cannot display propertized messages anyway. Use `user-error' and `error' in `lsp-rename' and `lsp--get-symbol-to-rename' instead.
In `lsp--get-symbol-to-rename', return nil instead of (nil . nil) if there is no symbol at point.
"rename is invalid here" makes no sense if `lsp-rename' is not called interactively.
`lsp-rename' now highlights the identifier being renamed. To do so, refactor `lsp--get-symbol-to-rename' to return the bounds of the symbol being renamed instead of it, and allow its placeholder to be nil. The latter is necessary to reduce code duplication, as the two code paths of that function (has prepareRename, doesn't have prepareRename) would need to do the same `buffer-substring' with the same bounds. Add a new function, `lsp--read-rename', which takes the result of a call to `lsp--get-symbol-to-rename' and either `user-error's (if the result was nil) or asks the user for a new symbol, highlighting the identifier being renamed. The split was done to make them independently testable. `lsp-rename': no longer `user-error' if NEW-NAME is nil, since that only makes sense if it is called interactively. However, in the latter case, `lsp--read-rename' would do the necessary error handling already.
`lsp--read-rename' and `lsp--get-symbol-to-rename' now should really work, since they have a comprehensive test-suite to cover them. It has to rely on mocking a lot, though. Fix a bug in `lsp--get-symbol-to-rename': if RESPONSE is a range, getting its placeholder is wrong. Guard that with an `and' `not' `lsp-range?'.
Using `lsp--remove-overlay' didn't actually bring any additional safety, given that the user could still theoretically C-g between `overlay-put' `lsp-face-rename' and `make-overlay', causing it to stay. Use a local variable to hold it instead, which doesn't decrease safety but reduces overhead. Note that it is still very unlikely (probably impossible) that a user could do that using only C-g, without a debugger. There is also no 100% safe way to do this.
The test for `lsp--read-rename' now checks whether the highlighting overlay appears and disappears again, in-between calls to a mocked `read-string', not just whether it disappears again. This is now done by checking the `face' attribute of overlays, not by checking the `lsp--read-rename' property, since that was removed with the last commit.
The new defcustom `lsp-rename-use-prepare' now controls whether prepareRename should be used or not for `lsp--get-symbol-to-rename'.
The overlay is modified once, and assigned to a variable anyway. `setq' it to `make-overlay' directly and `overlay-put' the face to it afterwards.
`lsp-rename-placeholder-face' is now used to display the new name instead of reusing the face of the identifier in the buffer. The reasoning here is that reusing the symbols's face directly would yield inconsistent placeholders, while conveying no additional information, given that the thing being renamed is shown in that very same prompt as it appears in the buffer.
Because `lsp-face-highlight-*' also derive from `highlight', the symbol being renamed could only have either a documentHighlight or a rename face. This caused inconsistent results with `lsp-idle-delay' = 0, where either would sometimes win. By using :underline instead, the symbol being renamed can be marked as such while having a documentHighlight overlay active.
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.
Looks good. Please drop a note which servers you have tested with.
Few minor nits suggestions:
lsp-mode.el
Outdated
| the new name." | ||
| :group 'lsp-faces) | ||
|
|
||
| (defvar lsp--rename-history '() |
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.
Shouldn't this variable be public to avoid persisting it?
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.
You make a good point, and from looking at other packages (magit, evil (evil-ex)), they also seem to be using public history variables. I'm making this public.
| ;; Do the `buffer-substring' first to not include `lsp-face-rename' | ||
| (rename-me (buffer-substring start end)) | ||
| (placeholder (or placeholder? rename-me)) | ||
| (placeholder (propertize placeholder 'face 'lsp-rename-placeholder-face)) |
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 would vote for using different faces for placeholder and for the thing to rename. Using bold face seems to be a good candidate for one of them (to avoid being too colorful).
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.
Do you propose overriding the face of the thing to be renamed the same way as is done for the placeholder? What we might also want to explore is adding a face to it, using add-face-text-property. I'll post some screenshots of various approaches soon.
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 am proposing using 2 different faces like this:
(let ((placeholder "foo")
(rename-me "foo"))
(read-string (format "Rename %s to: " (propertize rename-me 'face 'bold))
(propertize placeholder 'face 'font-lock-variable-name-face)
'lsp-rename-history))|
I tested with |
`lsp-rename-history' should be private so that it can be excluded from savehist. In addition, from looking at the code of other packages, history variables tend to be public: `magit-revision-history', `evil-ex-history', ....
06e8631 to
ae280ae
Compare
|
|
|
Digging trough RA's code, it seems that the strange results are fixed, at least in master, because there is no code returning a uri in |
|
Thank you. I merged it as it is - the cosmetics about the faces can be discussed separatelly. |



lsp-renamedoesn't check whether renaming is supported first. Do that,throwing an error otherwise.
Fixes #2314.