Skip to content
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

GDScript Symbol Tooltip #80044

Draft
wants to merge 62 commits into
base: master
Choose a base branch
from

Conversation

jwmcgettigan
Copy link

@jwmcgettigan jwmcgettigan commented Jul 30, 2023

Planned Features (WIP - subject to change)

  1. Create a basic tooltip component.

    • The tooltip appears when hovering over a symbol in the editor.
    • Consider basic design aspects: tooltip border, padding, border-radius, and a color scheme.
  2. Refine hover logic.

    • Define when a tooltip should appear or disappear.
      • Should not appear when hovering over an comment.
      • Should not appear when hovering over a string.
      • Should not appear (or disappear) when a context menu has been opened in the editor on the symbol.
      • Should not appear when hovering over identifier such as 'func', 'class', 'var', 'enum', etc.
    • Apply a 0.5s delay before the tooltip appears after hovering over a symbol.
    • Ensure the tooltip stays open while the mouse hovers over it.
  3. Establish tooltip content.

    • Populate the header with the symbol being hovered over.
    • Populate the body with Godot's official documentation.
    • Populate the header with basic content derived from symbol information.
      • Symbol Name: This should be the primary content of the header. The symbol could be a function, variable, constant, class, or any other relevant element.
      • Symbol Type: Indicate whether the symbol is a variable, function, class, module, etc.
      • Data Type (for Variables): For variable symbols, display the variable's data type (if it's known).
      • Return Type (for Functions): For function symbols, provide the return type of the function, if it's specified.
    • Create label-only tooltip variant for symbols that have no documentation.
    • Add additional DocumentSymbol members for each enum value.
      image
  4. Expand upon tooltip content.

    • Populate the header with advanced content derived from symbol information.
      • (Undecided, likely won't do) Icon for the Type: Display a small, stylized icon that represents the type of symbol (e.g., a different icon for class, function, variable, etc.). This can provide a quick visual cue for the developer. May just add a subtle icon to indicate the scope of the symbol.
      • Scope Information: Indicate if the symbol is global, local, or instance-specific. This is especially relevant for variables. (A clue may lie in the CompletionContext struct).
    • Consider a more complex implementation for enum. For example, add a way for the user to see all of the enum values within the tooltip. The current approach is not conducive to including those values. (Look into how other IDEs do it)
    • Look into if the type of a variable can be inferred or if it must be explicitly set. (e.g. var person = Person.new())
    • Account for built-in constants and functions. https://docs.godotengine.org/en/stable/classes/class_@gdscript.html
      • Populate the header with function definition or constant value from the @GDScript and @GlobalScope classes.
      • Populate the body with documentation from the @GDScript and @GlobalScope classes.
    • Make sure that the tooltip contents are for the correct context/scope. e.g. If identical variables are used in a module, class, or function - make sure it is retrieving info for the correct one.
      • Add additional DocumentSymbol members for each usage of a variable. (e.g. As parameter or within function.)
      • Add/find a DocumentSymbol property that contains the context.
      • Add/find a DocumentSymbol property that contains the parent member.
      • Resolve issue where function parameter variables are using the documentation of their function. Instead, their documentation should be empty unless a parameter doc has been set within the function documentation.
    • Hovering over self should provide info for the class it references.
  5. Parse and display documentation.

    • The tooltip should parse and display BBCode from Godot's official documentation.
    • Ensure that links are correctly interpreted and clickable, and that the formatting is preserved.
  6. Incorporate user code documentation.

    • Add support for documentation comments.
      • Display documentation comments using existing data structures.
      • Implement support for parameter specific user code documentation using [param name].
    • Decide how to prioritize and present multiple sources of documentation.
  7. Implement link behaviors.

    • Add functionality for symbol links that jump to a symbol's reference location in the code or the Godot documentation.
    • Add the ability to 'jump to' the declaration of the symbol being hovered over.
  8. Implement context menu.

    • Add the ability to open a context menu when you right click within the tooltip.
    • Include the following context menu options:
      • Copy
      • Select All
      • Lookup Symbol
      • Go To Definition
  9. Design refinements.

    • Dynamically adjust height and width to fit content. (Optional)
      • Implement a max width and have tooltip header wrap its contents if the max width is reached.
      • Implement a max height for the tooltip body - it will adjust to fit the content until this max height is reached, then it will show the scrollbar.
    • Implement a maximum width and height. Implement the scrollbar for overflow.
    • Dynamically anchor the tooltip depending on the position of the mouse relative to the edge of the editor.
    • Adapt the color scheme to the current theme.
  10. Add error and warning messages.

    • Show relevant error or warning messages related to the hovered symbol in the tooltip. Look into the code below.
    bool GDScriptParser::consume(GDScriptTokenizer::Token::Type p_token_type, const String &p_error_message) {
    	if (match(p_token_type)) {
    		return true;
    	}
    	push_error(p_error_message);
    	return false;
    }
  11. Add configurable settings.

    • Let users enable or disable the tooltip, adjust its delay time, choose whether or not to show warnings or user documentation, etc.
  12. Create class documentation

    • Create and populate docs/classes/SymbolTooltip.xml.
  13. Create unit tests

    • Add test cases.

Known Issues

NOTE: I will keep fixed issues here for the SEO if anyone runs into a similar issue in the future.

  • Resolve the conflict between selectable text and FLAG_NO_FOCUS. (It's looking like I will most likely need to stop inheriting from the PopupPanel to solve these issues...)
    • You cannot select text within the tooltip if FLAG_NO_FOCUS is set to true, but if FLAG_NO_FOCUS is set to false then the code editor loses focus which is undesirable.
    • MouseMotion events are not registered if FLAG_NO_FOCUS is set to true.
  • Tooltip doesn't hide() and immediately updates if the mouse position goes from (1) symbol to (2) tooltip to (3) another symbol.
  • Need to update the SymbolTooltip outside of the code editor's mouse event loop. Doing so will solve the following.
    • Tooltip doesn't hide() if the mouse position immediately leaves the CodeEditor.
    • Tooltip doesn't hide() sometimes even if the window has been closed.
  • Resolve issue where _init() functions return the class rather than void within the DocumentSymbol.reduced_detail property.
    image
  • Memory related exception occurs sometimes when launching. You may need to try building multiple times in order to successfully launch. Seems to sometimes occur if the window is not focused when launching.

Issues that I could use some help with

  • Need to test if this issue is caused by this PR. Autocomplete doesn't work for EnumValues. I get a "Unexpected "Identifier" in class body." error.
  • It takes 2-3 seconds for the tooltip to update when a document comment has been added or removed from a symbol.
  • The dynamic height and width adjustment (fit-content, min, max) works well the vast majority of the time, but I struggled to get it to behave using Godot's UI system and it can still have weird behavior at times. Thus, I would greatly appreciate if somebody else could refine this aspect of the tooltip.
  • I'm currently using an instance of TextEdit for the tooltip header to have syntax highlighting. This may "work" for the MVP, but we should instead implement syntax highlighting on a more appropriate component like RichTextLabel.
  • Triple-clicking within the tooltip body does not 'select all'. (But ctrl+a still does)

Stretch Goals/Features (Features I'm unlikely to implement but may be worth pursuing by anyone interested)

  1. Implement tooltip resizing.

    • Allow the user to adjust the size of the tooltip window.
  2. Implement nested tooltips.

    • Add support for tooltips within tooltips.
  3. Optional 'on hover' or 'context menu option'

    • If anybody finds 'on hover' tooltips to be annoying but still want the ability to open the tooltip, then I think it would make sense to add a context menu option "Show Tooltip" in editor's context menu that would allow the user to reveal a tooltip for a symbol when it is not enabled on hover.
  4. Handle conflicts between built-in and local

    • What should the tooltip content be if the user has created a variable or function that overrides a global/built-in variable or function? For now, I am going to prioritize the locally created variable or function.
  5. Show on auto completions as a sub popup

    • As noted here, we can use the tooltip as a sub popup for the auto completion popup.

Notes

  • I am rather inexperienced with C++, so I'm likely to make questionable choices. I'm also slowly familiarizing myself with the godot source code and code style. I appreciate any feedback regarding mistakes or improvements that can be made.
  • I am taking an iterative approach to developing the tooltip.
  • I am trying to implement features in order from simplest to most complex.
  • Advanced features, edge cases, and polish will wait until after we have something to build upon.
  • Perfection is the enemy of good. Premature optimization is the root of all evil.

Latest Demo GIF (09/08/2023)

godot windows editor dev x86_64_2023-09-07_23-06-33

jwmcgettigan and others added 2 commits July 30, 2023 17:48
Co-authored-by: A Thousand Ships <96648715+AThousandShips@users.noreply.github.com>
@MewPurPur
Copy link
Contributor

Should not appear when hovering over an annotation.

Annotations have corresponding docs, so this doesn't seem necessary.

Apply a 0.5s delay before the tooltip appears after hovering over a symbol.

I'd suggest using the tooltip delay in the editor settings (I think?) for a start. We could have a dedicated setting in the future if it's desired imo

@Calinou
Copy link
Member

Calinou commented Jul 31, 2023

I'd suggest using the tooltip delay in the editor settings (I think?) for a start. We could have a dedicated setting in the future if it's desired imo

The tooltip delay is actually a project setting (and it affects the editor in the currently edited project). See #35806.

@jwmcgettigan
Copy link
Author

Should not appear when hovering over an annotation.

Annotations have corresponding docs, so this doesn't seem necessary.

Thank you for catching that! I accidentally wrote 'annotation' here when I meant to put 'comment'... 🙃 so I've updated that task accordingly.


I'd suggest using the tooltip delay in the editor settings (I think?) for a start. We could have a dedicated setting in the future if it's desired imo

The tooltip delay is actually a project setting (and it affects the editor in the currently edited project). See #35806.

Thank you for the feedback and info. I planned to tackle making the delay configurable in the "Add configurable settings." step of the planned features for this PR, but since there is already a setting and it is easy to fetch it I've added it to the SymbolTooltip constructor. This can be updated to fetch from EditorSettings once that issue/PR is resolved.

I will note that I'm aware of the existing gui.tooltip_timer, but am unsure of how to go about leveraging it as I don't have a good understanding of the relationship between the PopupPanel that my SymbolTooltip class inherits from and the functions that interact with it on viewport.cpp.

@jwmcgettigan
Copy link
Author

My most recent pushes added tooltip support for:

  • populating the body with documentation comments - using DocumentSymbol
  • populating the header better information (still WIP) - also using DocumentSymbol
  • syntax highlighting - using GDScriptSyntaxHighlighter

image

This is still much work to be done, but this is a great step forward - and the DocumentSymbol object still has plenty of more useful information that has yet to be utilized.

Some things that I'm currently contemplating:

  • How should 'documentation comments' and 'official documentation' be treated? The approach I'm taking is that documentation comments will always have the highest priority, but I think it would be useful to also include official documentation if it exists for the symbol. First, I'm trying to determine whether it actually should be included. Second, if it should, how should it be included? Underneath the 'documentation comments' with a horizontal separator between them? Or perhaps using tabs within the tooltip body? We could also simply include a link/button icon to the relevant documentation.
  • Should we actually use the GDScript syntax highlighting within the tooltip header? I've noticed that some IDE's try to keep the tooltip colors neutral.

jwmcgettigan and others added 2 commits September 10, 2023 20:55
Co-authored-by: A Thousand Ships <96648715+AThousandShips@users.noreply.github.com>
@dalexeev
Copy link
Member

@YuriSizov
Copy link
Contributor

YuriSizov commented Sep 25, 2023

The implementation seems to be overly complicated for what we need from this feature. There are two distinct systems that need to exist:

  • Script editor needs to be able to tell what is the user currently hovering over;
  • Editor help needs to provide ready-to-use markup corresponding to that information.

For symbols which are documented (either natively, or via the comments) DocData should be able to provide the information needed. For symbols which aren't, some fallback logic must be present to generate a mock tooltip.

So I would expect 3 sets of changes, one to the script editor, one to the editor help, and one to the doc data. I don't particularly see the need for a dedicated class with so much logic in it. Granted, this PR is very much WIP and has a ton of code that should be removed or reworked, so perhaps it could be clearer why this approach was chosen once you finish your work. But at this point I'm skeptical this is going into the right direction.

We shouldn't need to hook into language parsers and LSP data structures to achieve the functionality needed here. In fact, none of this should be dependent on individual scripting languages. It should go through public data or through the script server as an abstraction layer.


The feature itself is very much desired. I'd appreciate if you and @Spartan322 could cooperate on finding the most appropriate solution.

@jwmcgettigan
Copy link
Author

jwmcgettigan commented Sep 26, 2023

@YuriSizov, first off, thanks for taking the time to review and share your thoughts; it's genuinely appreciated. I'll dive a bit into the "why" behind my approach to give you a clearer picture.

My Starting Point & Approach:

  • I began this feature with pretty much zero knowledge of the Godot Engine source code. It's been as much a learning process about the codebase as it has been about implementing the tooltip feature.

  • My initial approach was pretty straightforward: envision what a helpful tooltip would look like. This led me to look into tooltips from various IDEs, which helped shape my understanding and expectations. From there, I felt focusing on a "GDScript Tooltip" was the right first step, largely due to my initial constraints in understanding how to fetch the necessary tooltip data.

    I don't particularly see the need for a dedicated class with so much logic in it.

    • I did originally try to work with the script editor and editor help components, but here's why I felt a separate class was warranted:
      • It allowed me to work on the feature without worrying about causing regressions in the existing components.
      • The design of the tooltip, and the corresponding style and sizing logic, seemed to fit better in its own class.
      • Some scenarios I was trying to account for didn’t have built-in solutions that provided all the necessary information. A separate class gave me the flexibility to handle these scenarios.

Current Implementation:

  • With the above as my guide, the current PR was born. I aimed to create something functional, even if it wasn't perfectly entwined with the entire engine's architecture. My focus was on making a feature that works and feels right first, knowing I could refine it as I go along.
  • Yes, the current implementation does seem more complex than desired, but it's been a result of "finding my way" through the codebase and the feature requirements.

Collaborating and Making Things Happen:

  • Collaborating with @Spartan322 sounds like a great idea, and I'm open to it.

  • If you have any pointers or can steer me in the direction of resources that'd help me implement your suggestions, I'd be all ears. Anything to make this smoother and better aligned with how Godot does things.

    We shouldn't need to hook into language parsers and LSP data structures to achieve the functionality needed here. In fact, none of this should be dependent on individual scripting languages. It should go through public data or through the script server as an abstraction layer.

    • Where is the 'public data' and 'script server' in the code base? How do I access them?
    • How can I use them to retrieve the details and (user) documentation of a symbol (i.e. DocumentSymbol) within a GDScript document instead of my current approach of creating a ExtendGDScriptParser and parsing the ScriptTextEditor's content in order to do so?

My hope is that even if parts of my approach don't make the final cut, they can still serve as a stepping stone to a refined solution. Again, big thanks for the feedback. I want this feature to be successful and welcome constructive criticism like yours.

@YuriSizov
Copy link
Contributor

Where is the 'public data' and 'script server' in the code base? How do I access them?

By public data I mean mostly DocTools and DocData, which are responsible for storing and providing the information about documented class members. Scripting languages also provide facilities to get documentation from specific scripts, but ideally you shouldn't worry about it, as they also supply this information to EditorHelp to generate documentation pages.

I would advise you start solving this part of the problem by making a panel anywhere in the editor and figuring out a way to fetch the documentation for an arbitrary member of an arbitrary class. Say, pick Node::get_child and see what needs to be done to get the details about this method from documentation structures. Then do the same for a custom script with documentation comments. @anvilfolk and @dalexeev can help you with that part for GDScript, but remember that the implementation must know nothing about the language that is the source of the class. If at any point you need to do something with scripts, it must go through ScriptServer, which is a global class responsible for handling scripting languages.

How can I use them to retrieve the details and (user) documentation of a symbol

That's the second part of the problem. You need the script editor to give you some identifier for the hovered member. I can't tell you exactly what that identifier might look like. Can be some sort of structure or a string that uniquely identifies a property or a method. You can look at other features that hook into such stuff, like syntax highlighter, code completion, and navigation to definition/documentation triggered by ctrl-clicking on it. All of these should at least partially solve the same problem. Maybe @Paulb23 would have some pointers.

When you have solved both parts of this problem, it's only a matter of tying them together. Don't worry about the looks and user-facing functionality. For example you can just add a panel somewhere in the script editor and display the details there without bothering with hover behavior, styling, or sizing. Make it functional first. The original PR implemented several years ago didn't have any styling applied to it whatsoever, for instance.

@dalexeev
Copy link
Member

@jwmcgettigan Try starting with editor/plugins/script_text_editor.cpp, which uses the ScriptLanguage::lookup_code() method. You don't need to know the implementation details of specific languages.

struct LookupResult {
LookupResultType type;
Ref<Script> script;
String class_name;
String class_member;
String class_path;
int location;
};
virtual Error lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result) { return ERR_UNAVAILABLE; }

Also see EditorHelpBit (some refactoring may be required due to code duplication)..

@YuriSizov
Copy link
Contributor

Also see EditorHelpBit (some refactoring may be required due to code duplication)..

#82051

# Conflicts:
#	modules/gdscript/language_server/gdscript_extend_parser.cpp
#	modules/gdscript/language_server/godot_lsp.h
@Mickeon
Copy link
Contributor

Mickeon commented Feb 8, 2024

You're insane for this by the way. Would be amazing to have for 4.3 if you are around to continue this.

# Conflicts:
#	modules/gdscript/language_server/gdscript_extend_parser.cpp
@AThousandShips AThousandShips modified the milestones: 4.3, 4.4 Jul 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement script editor description hint on hover a symbol/word
9 participants