-
Notifications
You must be signed in to change notification settings - Fork 831
Clickable QuickInfo with "go to type" #2574
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
Conversation
|
@majocha, |
|
That is totally awesome :) |
|
OMG it's awesome! I've seen such functionality only once, in Nemerle VS integration. |
|
Is it possible to make Quick Info hierarchical? I mean if you hover over a symbol on a tooltip, another tooltip appears and so on. |
|
In future we could have right click / find usages as well? |
src/fsharp/TastOps.fs
Outdated
| | Some pathText -> pathText +.+ nmF xref | ||
|
|
||
| let tagEntityRefName (xref: EntityRef) name = | ||
| let rng = Some ( box xref.DefinitionRange ) |
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 box 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.
ah, because it's a hack. extra info passed around is just obj option because I initially wasn't sure what's needed to navigate.
|
@vasily-kirichenko I guess it is. It's all just a WPF FrameworkElement. With some work put in sky is the limit. |
src/utils/sformat.fs
Outdated
| | Delegate of string | ||
| | Enum of string | ||
| | Event of string | ||
| | Alias of obj option * string |
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 not put range 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.
I'm not sure if it is defined and accessible yet 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.
Yeah, it's defined later. Maybe introduce a new type just for this? I feel myself (very) uncomfortable looking at type erasure.
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. Even if you need the hack you can use type BoxRange = BoxRange of obj and then it's very easy to check the casts in/out of the type are valid
|
You committed ValudTuple.dll again :) |
|
@vasily-kirichenko dang it! |
src/utils/sformat.fs
Outdated
| module TaggedTextOps = | ||
| #endif | ||
| let tagAlias = TaggedText.Alias | ||
| let private blank name = None, name |
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.
no need for private here because there it has signature file.
src/utils/sformat.fsi
Outdated
| val tagUnionCase : (string -> TaggedText) | ||
| val tagDelegate : (string -> TaggedText) | ||
| val tagEnum : (string -> TaggedText) | ||
| val tagEvent : (string -> TaggedText) |
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 these parens? Whether add them to all the functions or remove everywhere.
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.
They were needed for some reason. Something about function values...
| open System.Windows | ||
| open Microsoft.CodeAnalysis.Editor | ||
| open Microsoft.CodeAnalysis.Editor.Shared.Utilities | ||
| open Microsoft.CodeAnalysis |
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 last is unused.
| for NavigableRoslynText(tag, text, xopt) in content do | ||
| let rangeOpt = | ||
| match xopt with | ||
| | Some xref -> |
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.
xopt and xref are very puzzling names.
|
@vasily-kirichenko thanks for the review. I'll clean it up and work some more soon. And I need to find a way to get rid of ValueTuple :) |
|
|
||
| let h = Documents.Hyperlink(Documents.Run(text)) | ||
| h.Click.Add <| fun _ -> | ||
| Logging.Logging.logInfof "click! %A" range |
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.
Remove logging, please. It will appear on VS output pane even if vsix is built in release configuration.
| let documentNavigationService =workspace.Services.GetService<IDocumentNavigationService>() | ||
| let filePath = System.IO.Path.GetFullPathSafe range.FileName | ||
| match workspace.CurrentSolution.GetDocumentIdsWithFilePath(filePath) |> List.ofSeq with | ||
| | id :: _ -> |
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 file is linked to several projects, it's possible to jump to the wrong document. We need to find a document for which FSharpSymbol.Assembly.SimpleName = document.Project.AssemblyName.
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 means that we have to pass assembly name alongside with range.
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.
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 one is tricky. It seems workspace will not open another editor if we already have a file opened in IDE from the wrong project. But you're right, main concern is to not open from wrong project during navigation.
Range doesn't carry assembly info but we have the Symbol because we're in the context of quickinfo, so it's doable.
src/utils/sformat.fs
Outdated
| let tagEvent = TaggedText.Event | ||
|
|
||
| let tagClass name = if Set.contains name keywordTypes then TaggedText.Keyword name else TaggedText.Class(None, name) | ||
| let tagUnionCase = blank >> TaggedText.UnionCase |
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 you want to avoid the parentheses in the signature file then just use an explicit argument rather than >>
| | Some xref -> | ||
| match xref with | ||
| | :? Microsoft.FSharp.Compiler.Range.range as range | ||
| when range.FileName <> "startup" -> |
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 make "startup" a named constant in the compiler and replace all its occurrences in the compiler codebase with a reference to that constant?
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.
There already is one in defined in TcGlogals.fs :) but I use a value Range.rangeStartup instead of the string now.
|
@majocha I think this PR is ready? Remove WIP from the title, please. |
|
@vasily-kirichenko I don't think it's worthwhile to revert it as the default behavior, adding an option to disable it is plenty. |
|
@Pilchie this PR uses Roslyn implementation of QuickInfo controller, only rendering is augmented. Tooltip should show up and disappear exactly as it normally does. |
|
@Pilchie to address your first question, about ctrl+click extension: |
|
The editor also supports advanced interactive QuickInfo popup if you want more control over its behavior. This is how "Show potential fixes" link and floating LightBulb in QuickInfo over an error squiggle are implemented. |
| | Some(range) when canGoto range -> | ||
| let h = Documents.Hyperlink(Documents.Run(text), ToolTip = range.FileName) | ||
| h.Click.Add <| fun _ -> | ||
| Forms.SendKeys.Send("{ESC}") //TODO: Dismiss QuickInfo properly, maybe using IQuickInfoSession.Dismiss() |
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 we fix this hack before merging?
src/utils/sformat.fs
Outdated
| open ReflectionAdapters | ||
| #endif | ||
|
|
||
| [<NoEquality; NoComparison>] |
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 still makes me really uneasy. I don't know what @KevinRansom and @dsyme think of this - surely there's a way we can sort this out without having to resort to 'obj'. We're just completely subverting the type system here and saying 'trust me I know what I'm doing'.
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 guess this is fixable, by moving range file on it's own (if it doesn't contain too much stuff as well), maybe try a PR on this PR which just moves the files in the relevant fsprojs if that's doable.
I think the hack is fine (looking at unboxRange and its usage) but it needs an explicit comment where the type is defined as well as in unboxRange, and having a statement (beside the picture from @vasily-kirichenko) that this move was attempted but seemed disruptive would be good too.
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.
Sure, I understand the rationale, but it's easy to move the file to be on its own. There's no need for us to hack around like this. Just move the file up to the top of the project - like you said it's only a small type.
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.
@saul I'm ok with moving the file unless it proves very difficult, it's totally fair to push back on this kind of hack.
This file is a bit odd because it also forms part of FSHarp.Core - we should probably just duplicate it out so that the compiler and FSHarp.Core are independent code bases, and we don't have all the whacky #if COMPILER
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.
@saul (I've done this kind of hack in the past but should have been scolded at the time!)
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'm afraid I can't do this one without help. I admit what I did here with the #if directives and attributes is a copy paste job without fully understanding what's going on.
|
It works great. However, I find showing full file path in a link tooltip a bit useless. What about showing full name instead? Or full name + file name, like ? |
|
@vasily-kirichenko Doable. I think it is a matter of passing more useful info from AST than just the definition range? Alternatively, a lot more info can be fetched and filled in the background, up to getting another quickinfo and making it recursive. Maybe ctrl-click on a link could enter such "drill-down" mode? I'll experiment as soon as I can. |
|
@KevinRansom any reason why this awesome PR still has not been merged? |
|
According to this |
|
@vasily-kirichenko ... I hadn't got around to re-reviewing it yet |
|
Thank you for this PR. Kevin |
|
@majocha I've noticed that the links in the tooltips seem to favor the |
|
@cloudRoutine Could you create an issue? |
|
sure, I've got some other ideas for functionality around this feature so I'll put it all together there |
|
This feature rocks, @majocha should it also enable clicking on the symbol itself (when not at the definition)? I know we might support ctrl+click later, but when using the mouse to get the tooltip, it also makes sense to go to the definition on the symbol itself. |
|
@smoothdeveloper Makes sense! @cloudRoutine I think a discussion issue would be great, where we could talk ideas, design and also implementation and integration of stuff. |
* custom tooltip test * tagClass augmented * kinda works * getting there * module, alias * not sure if works * fix build * go away! * parens out * halleluyah it builds * blank space on empty doc removed * more sensible * handle linked files * collapse empty TextBlocks * styling, simple tooltip, handle scripts * dismiss quickinfo on navigation * reliably dismiss quickinfo session * interface instead of boxing

Reading unfamiliar code with scarce type annotations I often wish I could click the types in quickinfo to go directly to some definition.

I put together a proof of concept. The code is hacky, usability is not that good, but it's a start and shows that a lot is possible :)
EDIT
Changed links to show when mouse enters the QuickInfo.
Linked(shared among many projects) files also are handled.
Added small tooltip showing file path on hover over link.