banner
isolcat

isolcat

I am not afraid of storms, for I am learning how to sail my ship
github

Try using VScode as your own RSS reader and develop your own VScode plugin.

Preface#

RSS (RDF Site Summary) has been a format specification for news sources, used to aggregate content updates from multiple websites and automatically notify website subscribers, for 24 years now. RSS is also used on various blogs for easy subscriptions, but when choosing an RSS reader, it often becomes a headache, not knowing which one to choose. After trying several well-known RSS readers, I still felt somewhat dissatisfied. Looking at VScode lying in the taskbar, a fun idea was born—I would turn VScode into my own RSS reader.

💡 Requirement Analysis#

Since there is this need, we need to analyze it:

  1. Add RSS Link: Users input or select an RSS link and save it in the plugin's configuration.
  2. Parse RSS: Use an RSS parsing library to parse the saved RSS link and extract blog information.
  3. Display Blog List: Show the blog list in the plugin, allowing users to select which blog to view.
  4. Select Article: Allow users to select an article from the blog.
  5. Display Article Content: Display the content of the selected article.

🛠️ Development Process#

Project Initialization#

As stated in the VScode Plugin Development Documentation, to develop our first VScode plugin, we need to install Yeoman and VS Code Extension Generator:

npm install -g yo generator-code

Run the command: yo code

image

Here, select New Plugin (JavaScript), then choose the extension name (which can be changed later). After running this command, our plugin is also initialized.

The project structure is as follows:

.
├── .vscode
│   ├── launch.json     // Configuration for launching and debugging the extension
│   └── tasks.json      // Build task configuration for compiling JavaScript
├── .gitignore          // Ignore build outputs and node_modules
├── README.md           // Readable description of the plugin's functionality
├── src
│   └── extension.js    // Plugin source code
├── package.json        // Plugin manifest
├── jsconfig.json       // JavaScript configuration

Running the Project#

We open extension.js and see code filled with comments. Here, we only need to pay attention to two functions:

  1. activate

This is the function executed when the plugin is activated.

  1. deactivate

This is the method called when the plugin is destroyed, such as for releasing memory.

Our main development will be executed in the activate function. Now let's run this project. On the extension.js page, we press F5 and select VScode Plugin Development. This will open a new VScode window where we can run our developing plugin. Press ctrl+Shift+P, input the default execution command of the plugin Hello world, and we can see a small pop-up in the lower right corner.
image
This is the default command to run the plugin.

To better run our plugin, we enter package.json and modify the configuration:

  "activationEvents": [
    "onCommand:allblog.searchBlog"
  ],
  "main": "./extension.js",
  "contributes": {
    "commands": [
      {
        "command": "allblog.searchBlog",
        "title": "searchBlog"
      }
    ]
  },

Note that after modifying here, we should synchronize the command to execute the plugin: vscode.commands.registerCommand binds the command ID to the handler function in the extension.

image

Now we run the command Reload window in the newly opened VScode window to reload the window. When we run the plugin again, we can use our defined command: searchBlog.

Basic Functionality Implementation#

When we usually subscribe to RSS, we find that it is mostly in XML (eXtensible Markup Language) format. To implement our functionality, we must parse it into a format that can be called with usable Js code. We introduce the utility library: fast-xml-parser, using Antfu's blog as a test example:

const vscode = require("vscode");
const xmlParser = require("fast-xml-parser");

async function activate(context) {
  // Get RSS feed and parse it
  const response = await axios.get("https://antfu.me/feed.xml");
  const articles = xmlParser.parse(response.data).rss.channel.item;
  
  // Save article list
  const articleList = articles.map((item, index) => ({
    label: item.title,
    description: item.description,
    index
  }));
...

To allow users to freely choose from the list of articles, we need to display the list of articles. At this point, we have implemented basic display:

image

However, at this point, clicking on an article does not yield any response, as we have not yet written the display method. 😂

Here, we check the API provided by VScode and find that the Webview API is very suitable for implementing this functionality. We treat webview as iframe content controlled by your extension in VSCode. Thus, we modify the code as follows:

    if (selectedArticle) {
      const { link } = selectedArticle;
      const webViewPanel = vscode.window.createWebviewPanel(
        'blogWebView',
        'Blog Article',
        vscode.ViewColumn.One,
        {
          enableScripts: true,
          retainContextWhenHidden: true
        }
      );

      // Set the HTML content of the Webview
      webViewPanel.webview.html = `
        <!DOCTYPE html>
        <html>
          <head>
            <style>
              body {
                font-family: Arial, sans-serif;
              }
            </style>
          </head>
          <body>
            <iframe src="${link}" width="100%" height="100%"></iframe>
          </body>
        </html>
      `;
    }
  }

Successfully opened!
image

Shall we optimize it a bit? Let it freely set the height of the window:

if (selectedArticle) {
			const { link } = selectedArticle;

			const heightInput = await vscode.window.showInputBox({
				prompt: 'Enter the height of the window (in px)',
				placeHolder: '500'
			});

			const webViewPanel = vscode.window.createWebviewPanel(
				'blogWebView',
				customName, // Use the custom name as the title
				vscode.ViewColumn.One,
				{
					enableScripts: true,
					retainContextWhenHidden: true
				}
			);

			let height = parseInt(heightInput) || 500;
			webViewPanel.webview.html = `
        <!DOCTYPE html>
        <html>
          <head>
            <style>
              body {
                font-family: Arial, sans-serif;
              }
            </style>
          </head>
          <body>
            <iframe src="${link}" width="100%" height="${height}px"></iframe>
          </body>
        </html>
      `;
		}

At this point, we have completed the basic functionality.

Optimize Again?#

When we run the command again, we find that the already subscribed RSS displays the previous links, which can be quite troublesome. Considering that after subscribing to more RSS, just looking at the links may not be very memorable, we might as well try adding a custom name so that we can remember the subscribed RSS in our habitual way. Let's modify the code:

...
if (customName) {
            // Check for duplicate custom names
            const duplicateCustomName = Object.values(rssLinks).includes(customName);
            if (duplicateCustomName) {
              vscode.window.showErrorMessage('This custom name is already in use. Please choose a different name.');
              return;
            }
            // Add new RSS link and corresponding custom name
            rssLinks[newLink] = customName;
            vscode.workspace.getConfiguration().update(RSS_LINKS_CONFIG_KEY, rssLinks, vscode.ConfigurationTarget.Global);
            await getArticleContent(newLink, customName);
          }

Ok, now we have the custom name.
image

🧐 Discover Problems and Solve Them#

Next comes the choice to publish the plugin. Here, you can look for related articles; this article will not explain too much. After the plugin is successfully published, we download the plugin and add the RSS blogs we are interested in. However, when we close and reopen it, oh no! The previously subscribed RSS is gone! 😭
No big deal; discovering the problem means we need to solve it. We check the previous code and find a huge issue: we forgot to handle data persistence for the already subscribed RSS. We consult the VScode documentation and find that it provides four data storage solutions:

Here, we use ExtensionContext.globalState for global storage, storing data in the user's local configuration file. This way, even if VScode is closed and reopened, there will be no data loss. We modify the code:

...
// Add new RSS link and corresponding custom name
      rssLinks[newLink] = customName;
      // Save subscription information to the user's local storage
      context.globalState.update(RSS_LINKS_CONFIG_KEY, rssLinks);
      await getArticleContent(newLink, customName);
...

After republishing the plugin, done! Now a rudimentary RSS reader plugin is complete!

image

🐱 Summary#

The plugin is now online (though not yet a formal version) and can be downloaded on VScode: Download Link, or directly search for RSS-searchBlog in the VScode extensions. Currently, the functionality is not yet perfect, such as not providing pop-up notifications for updates after subscribing. Future updates will improve this plugin. I previously saw an interesting open-source project called RSSHub, which can generate RSS feeds for any strange content. I plan to integrate the plugin with RSSHub and continuously improve its functionality. I believe the official version of the plugin will satisfy everyone. Thank you for reading. The source code for this project: RSS-searchBlog

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.