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

An option for IntelliSense in code-fenced examples #323

Closed
4 tasks done
kizu opened this issue May 17, 2023 · 12 comments
Closed
4 tasks done

An option for IntelliSense in code-fenced examples #323

kizu opened this issue May 17, 2023 · 12 comments
Labels
🙅 no/wontfix This is not (enough of) an issue for this project 👎 phase/no Post cannot or will not be acted on

Comments

@kizu
Copy link

kizu commented May 17, 2023

Initial checklist

Problem

Provide IntelliSense for the code examples in.mdx

We're using a pattern, where we place our code examples in code-fenced blocks like this:

import { Button } from '';

## Example

Here is a button:

```jsx
<Button label="I am a button" />
```

Then, we're using react-live to render those examples as live playgrounds.

The issue is that while mdx-analyzer handles the regular code in the MDX (awesome!), it does not treat the code inside the code-fenced blocks as actual code.

Solution

What we would like to see — an option (as I don't this is something that should be available by default, as not everyone is using this pattern of doing things) to treat certain code-fenced blocks as actual code.

Alternatives

Not that I know of. With the requirement that we need to have the examples' source code available, we cannot just use a component wrapper around the code we want — we need the code-fenced blocks specifically to have the access to the actual code, with all the comments etc.

In addition to this, having the example code in code-fenced blocks makes authoring and reviewing markdown changes much better — in various editors and on GitHub we can see the code examples with proper markup, rather than as jsx content.

@wooorm
Copy link
Member

wooorm commented May 17, 2023

Hmm, this feels a bit weird. The point of using code is to not evaluate things

How are you evaluating the code inside those code blocks?


in various editors and on GitHub we can see the code examples with proper markup, rather than as jsx content.

Use this project or for other editors use https://github.com/wooorm/markdown-tm-language. Soon also in GH. So I don’t think that’s a solid reason.

@kizu
Copy link
Author

kizu commented May 17, 2023

How are you evaluating the code inside those code blocks?

We're gathering the imports (and exported consts) from the .mdx, then provide them as the evaluation context to react-live (alongside some additional internal hooks that can be used in the example but which we don't want to import in every .mdx).

The main idea for those code examples — they're their source code first (not evaluated), then as a progressive enhancement they're evaluated and shown alongside an editable source, where we still want to keep all the comments, original formatting etc.

So, if we'd have to do something like

<Example>
    <Button label="I am a button" />
</Example>

instead, we would lose the ability to get the source code without some very serious hacks (like, is it even possible from the mdxast point of view get the source of anything that is nested inside the Example?)

@wooorm
Copy link
Member

wooorm commented May 17, 2023

we would lose the ability to get the source code without some very serious hacks (like, is it even possible from the mdxast point of view get the source of anything that is nested inside the Example?)

Nope, I think that’s possible: we have an AST, with positional info.
Flipping it that way, seems so much much simpler to me.

Here’s some code:

/**
 * @typedef {import('mdast').Root} Root
 * @typedef {import('remark-mdx')}
 */

import {visit} from 'unist-util-visit'

/** @type {import('unified').Plugin<[], Root>} */
export default function myRemarkMdxPluginAddingSourceToExampleComponents() {
  return (tree, file) => {
    const source = String(file)
    visit(tree, (node) => {
      if (
        (node.type === 'mdxJsxFlowElement' ||
          node.type === 'mdxJsxTextElement') &&
        node.name === 'Example'
      ) {
        const head = node.children[0]
        const tail = node.children[node.children.length - 1]
        const start = head.position?.start.offset
        const end = tail.position?.end.offset

        if (start !== undefined && end !== undefined) {
          node.attributes.push({
            type: 'mdxJsxAttribute',
            name: 'source',
            value: source.slice(start, end)
          })
        }
      }
    })
  }
}

@kizu
Copy link
Author

kizu commented May 17, 2023

Thanks, I'll investigate this, however I would say that ability to have IntelliSense inside regular code examples would still be helpful — like, we'd want the users of the docs to be sure that the docs are up-to-date, so if we could improve the examples in any way — it would be awesome.

@wooorm
Copy link
Member

wooorm commented May 17, 2023

we'd want the users of the docs to be sure that the docs are up-to-date

How do you mean? How doesn’t my above solution not do that?
What is an example of out-of-date code that isn’t up-to-date?

Or are you saying that all ```js/jsx/ts/tsx/md/mdx code blocks should be (optionally) handled by this project?

@kizu
Copy link
Author

kizu commented May 17, 2023

How do you mean? How doesn’t my above solution not do that?

I'm not telling this, but I'll need to spend some time validating it for our use-case — it would be really nice if we would make it work for all our cases! Would get back to you with the result when I'll have some time to play with it. Sounds very promising indeed.

Or are you saying that all ```js/jsx/ts/tsx/md/mdx code blocks should be (optionally) handled by this project?

Yes, exactly: it would be really nice to have the IntelliSence for any fenced code blocks, like if we're writing a text code example on some library or code usage, having the autocomplete for the example code, and maybe even linting for the content of the example could be nice. But I can understand how this might be out of scope of this project maybe? Especially if using mdxast would handle our use-case :)

@wooorm
Copy link
Member

wooorm commented May 17, 2023

Hmm, that becomes super complex.

This feels much more like a separate project than something that should be in the same language server (which perhaps could be maintained here, but is not solely related to MDX)

See also https://shikijs.github.io/twoslash/

@kizu
Copy link
Author

kizu commented May 17, 2023

Yes, agree with all of this, and we're basing some of the features of our playgrounds on shiki twoslash actually :) So yes, probably something for a separate project indeed.

@remcohaszing
Copy link
Member

I think #287 comes very close to what you want in a generic matter.

Your code requires some domain specific logic though. What you might want is to use a custom remark plugin for this. This is a more complex feature and is tracked in #297.

@remcohaszing remcohaszing closed this as not planned Won't fix, can't repro, duplicate, stale Oct 24, 2023
@github-actions

This comment was marked as resolved.

@remcohaszing remcohaszing added the 🙅 no/wontfix This is not (enough of) an issue for this project label Oct 24, 2023
@github-actions

This comment was marked as outdated.

@remcohaszing
Copy link
Member

I’ve closed this in favor of #287 and #297.

@github-actions github-actions bot added the 👎 phase/no Post cannot or will not be acted on label Oct 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🙅 no/wontfix This is not (enough of) an issue for this project 👎 phase/no Post cannot or will not be acted on
Development

No branches or pull requests

3 participants