Widget extension: Cannot import README

I created a widget and I need to render the README.md content in it.
I’ve been looking for some time about this but the truth is I don’t even know where to start.

That extension needs to identify whatever the README is and render it.
And right now I’m having trouble importing README.md.

I am using react-markdown to render markdowns.

render(): React.ReactElement {
  const header = `App Sec Engineer - README`
  // const markdown = `# Hello, *world*!`
  const readme = require('../README.md')
  return (
    <div id="widget-container">
      <AlertMessage type="INFO" header={header} />
      {/* <ReactMarkdown children={markdown} /> */}
      <ReactMarkdown children={readme} />
    </div>
  )
}

I already tried to use import, but without success.

Knowing that it is always in the root of any project, how would I import it in this .tsx?

Hey @arnonrdp,

Let’s say you initialize your view similar to how we initialize our own navigator-widget. You can just look through the workspace and search for a README.md in the workspace root:

@inject(WorkspaceService) private readonly workspaceService: WorkspaceService;
@inject(FileService) private readonly fileService: FileService;
@inject(EditorManager) private readonly editorManager: EditorManager;

async initializeLayout(app: FrontendApplication): Promise<void> {
  const view = await this.openView();
  const readme = this.workspaceService.getWorkspaceRootUri(undefined)?.resolve('README.md');
  if (readme && await this.fileService.exists(readme)) {
    const content = await this.fileService.readFile(readme);
    const markdownString = content.value.toString();
    // render markdown in the widget
  }
}

1 Like

Hey @msujew, thanks for your reply.

Where should I import WorkspaceService, FileService and EditorManager from?

Also, what is openView expected to be?

Where should I import WorkspaceService, FileService and EditorManager from?

If you’re using vscode, that should already propose some imports. Otherwise from the @theia/workspace/lib/browser and @theia/filesystem/lib/browser packages respectively. EditorManager is not necessary, I just copy & pasted some code for this.

Also, what is openView expected to be?

If you follow the link, that I posted earlier, you can see that it’s part of the AbstractViewContribution. See here for some more documentation on that.

1 Like

I am missing something, I don’t know what is.

I can’t see the console.

Everything seems to be well imported, right:

import { FrontendApplication } from '@theia/core/lib/browser'
import { AlertMessage } from '@theia/core/lib/browser/widgets/alert-message'
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget'
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'
import { FileService } from '@theia/filesystem/lib/browser/file-service'
import { WorkspaceService } from '@theia/workspace/lib/browser'
import * as React from 'react'
import ReactMarkdown from 'react-markdown'

@injectable()
export class WidgetWidget extends ReactWidget {
  static readonly ID = 'widget:widget'
  static readonly LABEL = 'ASE README'

  @postConstruct()
  protected async init(): Promise<void> {
    this.id = WidgetWidget.ID
    this.title.label = WidgetWidget.LABEL
    this.title.caption = WidgetWidget.LABEL
    this.title.closable = true
    this.title.iconClass = 'fa fa-book'
    this.update()
  }

  @inject(WorkspaceService) private readonly workspaceService: WorkspaceService
  @inject(FileService) private readonly fileService: FileService

  async initializeLayout(app: FrontendApplication): Promise<void> {
    // const view = await this.openView()
    const readme = this.workspaceService.getWorkspaceRootUri(undefined)?.resolve('README.md')
    if (readme && (await this.fileService.exists(readme))) {
      const content = await this.fileService.readFile(readme)
      const markdownString = content.value.toString()
      console.log('TEST', markdownString)
    }
  }

  render(): React.ReactElement {
    const header = `App Sec Engineer - README`
    const markdown = `# Hello, *world*!`
    return (
      <div id="widget-container">
        <AlertMessage type="INFO" header={header} />
        <ReactMarkdown children={markdown} />
      </div>
    )
  }
}

Do you know what I am missing?

@msujew, to be honest, I see no use for this.openView().

I tried using part of the README read function inside the render() property and it manages to find the README.
The problem is that to “read it” the function needs to be asynchronous and apparently this render property cannot return a Promise.

I got stuck lol.

The problem is that to “read it” the function needs to be asynchronous and apparently this render property cannot return a Promise.

That’s exactly what I proposed (i.e. move the code into the initializeLayout function). Note that @postConstruct() shouldn’t be async, since these functions are not awaited by our dependency injection system.

The purpose of calling this.openView is to get a reference to the widget, so we can stuff the readme content in there for it to render correctly.

1 Like

I cannot ignore render() promperty because class WidgetWidget extends from ReactWidget, and this one has an abstract render(): React.ReactNode.

I understood I have to implement everything inside initializeLayout, I just don’t know how to.

@arnonrdp initializeLayout isn’t there to replace render, but to complement it. You should get your data during initializeLayout, and store it somewhere so you can use it when render is called.

1 Like

@msujew, after a long try (an entire day) and without success I am back here. I am a Vue Dev trying to handle with React because it is the task. That’s why this is a bit complicated to me, everything is so different.

Well, it seems render() is rendered before initializeLayout(). So I am not able to “re-render” the widget.

I still couldn’t apply AbstractViewContribution to solve the openView() issue.

Could you give me a direction?

Since you try to implement initializeLayout in your ReactWidget, I assume that it doesn’t get called at all. In one of my previous posts I mentioned how initializeLayout needs to be part of your AbstractViewContribution. Only there will initializeLayout be actually called (and openView actually available. It seems like your class doesn’t compile).

If correctly implemented initializeLayout will always be called before any render of any widget.

Right, I inserted the initializeLayout inside the contribution that is calling the WidgetWidget:

I’m still not seeing the console.logs, which alerts me that I need to call initializeLayout somewhere, right? But where?

If I manage to get it called, I believe I can get its content inside that render() .


UPDATE 1:

Added postConstruct() above initializeLayout() and now I can see the logs, but for some reason readme is undefined at this point.


UPDATE 2:

Checking what this.workspaceService.getWorkspaceRootUri(undefined) and this.workspaceService returns, I respectively have this:


UPDATE 3:

I was able to get to the root README.md by going around this way.
I don’t know if it’s the best (or most recommended) method, but it worked.

@inject(WorkspaceService) private readonly workspaceService: WorkspaceService
@inject(FileService) private readonly fileService: FileService
@postConstruct()
async initializeLayout(app: FrontendApplication): Promise<void> {
  await this.openView()

  const roots = await this.workspaceService.roots
  const readmeURI = roots[0].children?.find((child) => child.name === 'README.md')?.resource
  const readme = this.workspaceService.getWorkspaceRootUri(readmeURI)?.resolve('README.md')

  if (readme && (await this.fileService.exists(readme))) {
    const content = await this.fileService.readFile(readme)
    const markdownString = content.value.toString()

    console.log('markdownString', markdownString)
  }
}

Now just send it to render().
Thanks for the help @msujew.