Skip to content

feat(language-service): ✨ add rich hover alternatives#6066

Draft
jak-ch-ll wants to merge 19 commits into
vuejs:masterfrom
jak-ch-ll:add_text_option_for_hover_docs
Draft

feat(language-service): ✨ add rich hover alternatives#6066
jak-ch-ll wants to merge 19 commits into
vuejs:masterfrom
jak-ch-ll:add_text_option_for_hover_docs

Conversation

@jak-ch-ll

@jak-ch-ll jak-ch-ll commented May 24, 2026

Copy link
Copy Markdown

Hello,

a while ago the Vue language tools got a new feature in form of rich hover documentation. This is a pretty useful feature, but because it is based on HTML it does not work in terminal editors like Neovim. I wanted to change that and created two possible alternatives (more on those variants below).

Notes

  • I refactored the existing code structure to make it easier to extend
  • While doing that I tried leaving the core table rendering logic as is
  • I created a hover playground in the test-workspace to test the different possibilities
  • The playground also helped me find a small bug in the table rendering triggered by code blocks at the end of a JSDoc comment

Open points

  • Changes compared to the table
    • Models are their own category
      • Reasoning:
        • Models are 'special' and will usually be used differently than props
        • Reduces the overall size because models aren't doubled between props and events
        • I don't think the hover docs need to 'teach' that a model is actually a prop and and event
    • Event props (props starting with onX) are moved into the event list
      • Reasoning:
        • The auto-complete of Vue will also complete these with @ or v-on
    • @johnsoncodehk As the creator of the original table I would like to hear your opinion on these changes and if I should integrate these into the table as well
  • File structure
    • Because the vue-template.ts file is quite long, I opted for moving most of the logic concerning this component hover into a separate file to make it a bit easier to digest
    • Does this and the resulting structure fit with the project convetions?
  • Which variant to go with? Or keep both?
  • How to name the settings options?
    • I don't no how clear these names are. At least "Markdown" might be a bit confusing because technically all of them are Markdown
  • My intention is to cleanup the commit history before merge. Any preferences for the final PR? Everything squashed or separate commits for refactoring, bugfix and feature?

The variants

1. Markdown

I originally started with the markdown version and ran into the challenge with VSCode that the descriptions were slightly closer to the following meta element than to the element they belonged to. This made it confusing to see for which meta the description would actually be, especially because we are used to description on top because of JSDoc. I did not find a way with pure Markdown to create more space, which is why I decided to indent the description via blockquote (>).

Screenshot VSCode image
Details

## Models

`modelValue?: string | undefined = "default value"`

> This is a model

## Props

`requiredProp: string`

> This is a requird prop

`withMarkdown?: string | undefined = "and default value"`

> With some markdown
> 
> # Who puts a title in a jsdoc comment?
> 
> > This is a blockquote
> 
> - This is a list item
> - This is another list item
> 
> ```ts
> const foo: string = 'bar'
> ```

~~`deprecatedProp?: string | undefined`~~

> 
> 
> ***@deprecated*** This prop is deprecated and should not be used

## Events

`@customEvent: () => any`

`@eventWithPayload: (payload: string, payload2: number) => any`

## Slots

`#default` - `{ foo: string; }`

> Default slot with props

## Exposed

`exposedMethod: () => void`

> This is a method that can be called from the parent component

Upsides:

  • Deprecations can be marked with strikethrough
  • Allows for Markdown rendering of the descriptions

Downsides:

  • Descriptions are Markdown rendered, which can mess with the structure a bit, like with the heading in the example
  • The props themselves are mono colored

2. JSDoc

I then realized that Markdown supports code blocks, so I put everything into TS codeblock and added the descriptions as "JSDoc comments", which resulted in the second variant.

Screenshot VSCode image
Resulting Markdown

## Models

~~~ts
/**
 * This is a model
 */
modelValue?: string | undefined = "default value"
~~~

## Props

~~~ts
/**
 * This is a requird prop
 */
requiredProp: string

/**
 * With some markdown
 * 
 * # Who puts a title in a jsdoc comment?
 * 
 * > This is a blockquote
 * 
 * - This is a list item
 * - This is another list item
 * 
 * ```ts
 * const foo: string = 'bar'
 * ```
 */
withMarkdown?: string | undefined = "and default value"

/**
 * @deprecated This prop is deprecated and should not be used
 */
deprecatedProp?: string | undefined
~~~

## Events

~~~ts
@customEvent: () => any

@eventWithPayload: () => any
~~~

## Slots

~~~ts
/**
 * Default slot with props
 */
#default: { foo: string; }
~~~

## Exposed

~~~ts
/**
 * This is a method that can be called from the parent component
 */
exposedMethod: () => void
~~~

Upsides:

  • Props are colored (albeit not 100% correct compared to code)
  • Looks and reads similar to the documentation inside of a component
  • @tags are colored the same as in JSDoc

Downsides:

  • The description is otherwise not formatted

Conclusion

I personally have a light preference for the JSDoc version, because I think it puts a stronger emphasize on the meta information (name, type, default) instead of the description, which is what I want from this kind of documentation. To get the fully rendered description I can always open the hover docs for just that specific meta element.

But ultimately I think both variants are fine. I even asked my colleagues but there were proponents for both versions, so it might just come down to taste, which would be an argument to keep both and let the users decide themselves.

Comment thread extensions/vscode/package.json
Comment thread test-workspace/tsc/hover/tsconfig.json Outdated
Comment thread packages/language-service/lib/plugins/vue-template/componentHoverDocs.ts Outdated
@jak-ch-ll
jak-ch-ll force-pushed the add_text_option_for_hover_docs branch from 9344a7a to 17239db Compare May 24, 2026 14:31
@jak-ch-ll
jak-ch-ll force-pushed the add_text_option_for_hover_docs branch from 17239db to 648547d Compare May 24, 2026 14:35
@KazariEX

Copy link
Copy Markdown
Member

I think we can keep only the jsdoc and table formats. The current markdown format doesn't provide the dense information display of the table format, nor does it feel as intuitive for js/ts as the jsdoc format. Rather than maintaining multiple sets of similar logic, I think it would be more valuable to focus on polishing the jsdoc format.

@KazariEX

KazariEX commented May 25, 2026

Copy link
Copy Markdown
Member

For the jsdoc format, one idea I have is to put each kind of metadata inside a synthetic interface, as if it actually existed:

interface Props {
  /**
   * @description foo
   * @default "..."
   */
  foo?: string;
  /**
   * @deprecated
   */
  bar?: boolean;
}

This would make the output feel closer to how JS/TS developers already read and write type information, while still leaving room to present metadata in a structured way.

This approach would also avoid the styling friction caused by rendering markdown text and code text together.

@jak-ch-ll
jak-ch-ll marked this pull request as draft May 25, 2026 16:06
@jak-ch-ll

Copy link
Copy Markdown
Author

For the jsdoc format, one idea I have is to put each kind of metadata inside a synthetic interface, as if it actually existed:

interface Props {
  /**
   * @description foo
   * @default "..."
   */
  foo?: string;
  /**
   * @deprecated
   */
  bar?: boolean;
}

This would make the output feel closer to how JS/TS developers already read and write type information, while still leaving room to present metadata in a structured way.

This approach would also avoid the styling friction caused by rendering markdown text and code text together.

I played around with that idea. The first version just wraps everything in interfaces, but keeps the default and prefixes (@ and #) as is:

Fake interfaces image

The second version uses fully legal interfaces and the @default tag:

Valid interfaces image

I kinda like @ and # because they immediately make it clear that these are events/slots. But I can live without them if we want to keep the syntax valid.

For default I can see arguments for both sides. The one downside of @default that I see is that it requires more space, especially if there would otherwise not be a JSDoc comment. But on the plus side it makes the interfaces valid.

I pushed my changes, if you wanna try it. The code needs some clean up; I will do that once we decided with which solution to move forward.

@KazariEX

Copy link
Copy Markdown
Member

I think using these template syntax specific characters to distinguish them is indeed a good idea, but I still lean toward using fully valid jsdoc to reduce potential cognitive friction.

A few other minor changes:

  • Use named tuples to represent emits
  • Remove blank lines between properties

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants