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

debug: support customizable pretty-printing of a struct #158

Closed
stamblerre opened this issue Jun 3, 2020 · 13 comments
Closed

debug: support customizable pretty-printing of a struct #158

stamblerre opened this issue Jun 3, 2020 · 13 comments
Labels
Debug Issues related to the debugging functionality of the extension. FeatureRequest FrozenDueToAge upstream-vscode Issues that are by problems in VS Code itself.

Comments

@stamblerre
Copy link
Contributor

stamblerre commented Jun 3, 2020

See the original request in microsoft/vscode-go#3032.
As @hyangah says, this is related to #100.

@stamblerre stamblerre added Debug Issues related to the debugging functionality of the extension. FeatureRequest labels Jun 3, 2020
@esinek
Copy link

esinek commented Jun 3, 2020

Found the feature I was referring to in the original ticket. Would be amazing if vscode-go had something similar. Feature and example linked below (using CPoint as an example)
https://docs.microsoft.com/en-us/visualstudio/debugger/create-custom-views-of-native-objects?view=vs-2019#BKMK_DisplayString

image

@polinasok
Copy link
Contributor

@esinek Where exactly does a variable get displayed like this?
We are limited to whatever features the vscode editor and the debug adapter protocol support.

Are you looking for a mechanism to specify your own prettyprint logic or for the editor to provide some default option? Would it print something like what dlv cli prints in the terminal with the print command?

You can view the variable in the VARIABLES panel with all the nesting and options to expand and load each level. It can be tedious to expand all the nested fields, but I think you only need to do it once. The UI will keep them expanded and you will see as they change as the session progresses.

You can also view the variable in the WATCH panel. The same behavior with expanded fields preserved applies. Or you can use a function to print the variable with a recently implemented "call" expression.

We could also look into enabling supportsEvaluateForHovers, so the data hover triggers an Evaluate Request, which could return some kind of flattened string representation instead of a nested variable.

@esinek
Copy link

esinek commented Sep 29, 2020

@polinasok

Where exactly does a variable get displayed like this?

In either VARIABLES or WATCH panes.

Are you looking for a mechanism to specify your own prettyprint logic or for the editor to provide some default option? Would it print something like what dlv cli prints in the terminal with the print command?

Yes, I think it would be reasonable to say I'm looking for a way to specify the prettyprint logic for a type, such that when that type appears in the VARIABLES or WATCH panes, VSCode will prettyprint in the format specified. Also with a way to switch between the prettyprinted value represented as a string and the raw value showing all the properties of the type.

You can view the variable in the VARIABLES panel with all the nesting and options to expand and load each level. It can be tedious to expand all the nested fields, but I think you only need to do it once. The UI will keep them expanded and you will see as they change as the session progresses.

The value add of having a configurable prettyprint per type is to reduce the noise in the VARIABLES or WATCH panes - in a single prettyprinted string, you can see the relevant information without having to use all the available real estate with opened trees of variables

You can also view the variable in the WATCH panel. The same behavior with expanded fields preserved applies. Or you can use a function to print the variable with a recently implemented "call" expression.

Will the "call" expression support printf like support (I haven't looked into that yet)? That would be a nice feature. It would still require making the "call" multiple times during a single debug session. And if you had multiple types you wanted prettyprinted, it could get tedious.

We could also look into enabling supportsEvaluateForHovers, so the data hover triggers an Evaluate Request, which could return some kind of flattened string representation instead of a nested variable.

The hover eval also sounds promising. If the hover is possible with some config per type, I would imagine the prettyprint config per type would also be possible

Thank you for your feedback and work on this topic and VSCode in general. It's always a tough trade-off between having an integrated set of really useful features vs a bag of unrelated features (which by themselves may be useful) which, in the end, makes the product a mess.

@polinasok
Copy link
Contributor

polinasok commented Oct 14, 2020

We could look into some built-in pretty printing features on our end, but they would be based on built-in types and predefined. This could be made available either with hover or with a custom "print expression" option from the Debug Console or Watch, but there will not be a way for you to configure the pretty printing logic with those. The best I can think of is defining myTypeToString function per type in your program and then calling those using "call expression".

I am not aware of a central way to configure anything type-specific by the user. Maybe there are some custom editor plugins that could intercept the VariablesRequest in the editor and alter the way the UI reports them? But as far as I know nothing like this is available from inside the debug adapter. I do see supportsValueFormattingOptions in the spec, but it would only allow you to configure if the variable or expression value is to be displayed in hex only (details). Plus according to microsoft/VSDebugAdapterHost#10, this is supported in full Visual Studio, not VS Code (as of 2019 at least).

If you want this type of functionality made available in the editor+adapter, you need to file issues against:
Debug Adapter Protocol: https://github.com/Microsoft/debug-adapter-protocol/issues
VS Code: https://github.com/Microsoft/vscode/issues
Maybe we will get lucky and they will point us to some existing obscure hooks that I missed.

@hyangah
Copy link
Contributor

hyangah commented Nov 6, 2020

Thanks @polinasok for investigating this issue and providing background, and @esinek for providing constructive user feedback.

To summarize, currently feasible options are:

  1. predefine a pretty printing logic for type and call it using the call expression. Note that due to the complexity involved in Go's runtime, the call expression has its own limitations. See https://github.com/go-delve/delve/blob/master/Documentation/cli/README.md#call for the details.

  2. users can enter a suitable delve expression (https://github.com/go-delve/delve/blob/master/Documentation/cli/expr.md) in the WATCH panel or DEBUG panel. (@polinasok is it not a possibility to introduce richer expression support in Delve DAP's evaluateRequest's expression than what delve's expression? I expect LogPoints support to be implemented in the future Delve DAP and then delve will need to implement interpolation of {} in expressions*. Then, can't that be utilized as the general evaluateRequest expression syntax? )

  3. the custom view feature of visual studio is not available in vscode. The UI & interaction with the debug adapter is implemented by vscode, so this needs support from the vscode.

I don't see anything else which is feasible from this extension beyond these. I propose to close this issue.

(*) this expression interpolation is too javascript specific :-(

@rajveermalviya
Copy link

Will this cover formatting/pretty-printing time.Time values in some human readable format?
If not is it possible because currently debugging time related code, fmt.Println seems like the only way.

@polinasok
Copy link
Contributor

@rajveermalviya If we were to add better pretty-printing, it would not be customized for time.Time. It would be more like what you would get using p <expression> from dlv cli or %#v formatting directive in Go - designed to inspect the underlying fields at a glance without needing to expand each one. If you want to view times in human readable format, I suggest using call expressions from DEBUG CONSOLE or WATCH.

image

@polinasok
Copy link
Contributor

@hyangah Currently evaluate request just passes the expression to delve, which essentially returns back an anonymous variable with a type and a value. If the type is a scalar (int, string, etc), we display that scalar in the UI. If the type is a compound, we display some very basic single-line representation (usually just the type) with an option to expand to view the children variables. The expansion may trigger more requests to delve to evaluate additional expressions. If we were to treat expressions in {} as expressions, wouldn't we evaluate them the same way? We can always skip the expand-for-children part and only return the string representation of the value. In that case, we would need that string representation to be richer, more like what delve prints with multiple lines.

@polinasok polinasok changed the title debug: support pretty-printing a struct debug: support customizable pretty-printing of a struct Mar 1, 2021
@gopherbot gopherbot added this to the Untriaged milestone Apr 8, 2021
@hyangah hyangah modified the milestones: Untriaged, Backlog Apr 14, 2021
@myitcv
Copy link
Member

myitcv commented May 27, 2021

@hyangah kindly pointed out this issue on the back of https://groups.google.com/g/delve-dev/c/jXDUDvZErGM/m/9WQSb8mGBAAJ.

In my case, such pretty-printing is not limited to structs. I would like to see different string representations of a value. For example, a UX in which different string representations of a variable are presented in a hierarchical display, e.g. nodes for:

  • fmt.Sprintf("%v", v)
  • fmt.Sprintf("%+v", v)
  • fmt.Sprintf("%#v", v)

for some variable v (in either the variables or watch windows).

@aarzilli raised the following questions/concerns:

  1. How something like this would interact with, for example, reading a slice?
  2. The suggestion that this be implemented on the client, so that it would work uniformly for all programming languages
  3. There is currently no way at the moment to only resume a single function, so every time a function call is injected the state of every other goroutine in the system will advance.

Regarding point 1, I would simply see the different string representations as apparent elements of the slice. Slice elements are already indicated by their index, so I don't think it would be confusing to the user. Perhaps something like:

 *- x ([]int): []int len: 3, cap: 3, [1,2,3]
   *- [0] (int): 1
   *- [1] (int): 2
   *- [2] (int): 3
   *- "%v": [1, 2, 3]
   *- "%#v": []int{1, 2, 3}
   *- "%+v": [1, 2, 3]

They could potentially be gathered under a single node that needs to be expanded to keep things tidy. (The example above is not very interesting obviously, but hopefully the sketch gives an idea of what I mean). And this feature could be something that is not turned on by default - but again, I'm straying into territory I know little about when it comes to options etc with respect to DAP.

Regarding point 2, I'm not clear how something like this would be useful cross-language, because the very nature of this is Go specific (unless I misunderstood the point?). Also, pushing this to the client means replicating the work for each client (assuming it is possible for each client), when arguably something like this is a common requirement and hence better implemented in the server (from an efficiency and consistency point of view at least).

Regarding point 3: is this something that is being tracked elsewhere? I would certainly be interested in following progress on that, thank you.

@hyangah also replied in Slack to say "I don't know how to call these for many values rendered in the Variable view safely and efficiently."

Whilst it does sound like there are currently challenges in doing something like this, I think it would be a hugely powerful feature. So please consider my comment as a "wish list" item!

Thanks again for the work on Delve, dlv-dap et al.

@aarzilli
Copy link
Contributor

Regarding point 3: is this something that is being tracked elsewhere? I would certainly be interested in following progress on that, thank you.

It's something that would have to be supported by the runtime: golang/go#31132

Regarding point 2, I'm not clear how something like this would be useful cross-language, because the very nature of this is Go specific

If you scroll up to the top message you can see an example of an XML formatting language that applies to C# but could apply just as well to Go.

Regarding point 1, I would simply see the different string representations as apparent elements of the slice. Slice elements are already indicated by their index, so I don't think it would be confusing to the user

For slices yes, but what about maps or structs? Also is it always the three formatting strings %v, %#v and %+v, or is it something configurable?

@myitcv
Copy link
Member

myitcv commented May 27, 2021

It's something that would have to be supported by the runtime: golang/go#31132

Thank you; I've subscribed to that and related issues!

Regarding point 2, I'm not clear how something like this would be useful cross-language, because the very nature of this is Go specific

If you scroll up to the top message you can see an example of an XML formatting language that applies to C# but could apply just as well to Go.

In the case I am considering types implement fmt.Stringer, fmt.GoString and fmt.Formatter to encode representations that are impossible to define in terms of such templates because they rely on a function of internal state. As such I'm not clear it would even be possible for a developer to define such templates (in the general case). Nor would it be desirable (assuming I've understood the concept of these templates correctly, I could simply be holding it wrong) - the author of the type has implemented some/all of those interfaces for the benefit of the developer, it doesn't seem efficient for the N developers using that package to have to redefine such templates again. I grant this second argument is much less significant that the first, however (see below).

Regarding point 1, I would simply see the different string representations as apparent elements of the slice. Slice elements are already indicated by their index, so I don't think it would be confusing to the user

For slices yes, but what about maps or structs? Also is it always the three formatting strings %v, %#v and %+v, or is it something configurable?

I would suggest it needs to be configurable in some way, configurable by type. Hence my suggestion of a node under which to gather these different string representations. Configurable because it's impossible in the general case to know a) what the user wants and b) what verbs etc are supported by a given type. That way, developers using a given package could simply copy-paste and append the configuration to add pretty-printing of types from that package according to certain fmt.Sprintf "templates".

@polinasok
Copy link
Contributor

There is a related discussion at #1459, which resulted in adding a string representation "child" to arrays/slices of runes/bytes.

@polinasok
Copy link
Contributor

Per-type customization would be a large undertaking that we are not equipped to take on at this time. To do this with custom XML as shown in a comment on on Jun 2, 2020, we would need support from the vscode editor (you can file a request here). An alternative approach is being discussed in the delve repo at go-delve/delve#2887, so let's move the discussion there.

In the meantime, we made a number of improvements to general pretty printing that should make variable inspection easier. See updates below on some specific points/concerns from before:


on Sep 29, 2020 The value add of having a configurable prettyprint per type is to reduce the noise in the VARIABLES or WATCH panes - in a single prettyprinted string, you can see the relevant information without having to use all the available real estate with opened trees of variables

Since this comment, we added single-line pretty printing of the values like in dlv cli. This is not customizable, but does supply much of the underlying field information without having to expand.

Before:
image

Now:
image


on Nov 6, 2020 I expect LogPoints support to be implemented in the future Delve DAP and then delve will need to implement interpolation of {} in expressions*. Then, can't that be utilized as the general evaluateRequest expression syntax?

Logpoints have been added and we have code that interprets embedded {expression} (here & here), but those expressions must included fully qualified top-level variables, not embedded fields in the context of a struct of a certain type without references to its name. I don't think this would be helpful without more customizations to the syntax.


on Dec 21, 2020 Will this cover formatting/pretty-printing time.Time values in some human readable format?

This has been addressed as a special case in go-delve/delve#2865, which is will be available with the next release (1.8.3) in a couple of days (or if you install @master)

Before:
image

Now:
image


on May 26, 2021 I would like to see different string representations of a value. For example, a UX in which different string representations of a variable are presented in a hierarchical display, e.g. nodes for: fmt.Sprintf("%v", v), fmt.Sprintf("%+v", v), fmt.Sprintf("%#v", v)

Since this comment was made, we started printing top-level variable values similar to %#v(visuals above). Also, for bytes we added custom string representation for bytes (#1459) and time (go-delve/delve#999).

@polinasok polinasok added WillNotFix upstream-vscode Issues that are by problems in VS Code itself. labels Apr 7, 2022
@golang golang locked and limited conversation to collaborators Apr 7, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Debug Issues related to the debugging functionality of the extension. FeatureRequest FrozenDueToAge upstream-vscode Issues that are by problems in VS Code itself.
Projects
None yet
Development

No branches or pull requests

8 participants