In-Depth

AI Faceoff: Customized VS Code Commands

After a long-unused VS Code extension stopped working, I compared multiple AI tools to recreate the same HTML-wrapping workflow using built-in snippets and keybindings. Mileage among the systems varied. And the approach I used for my text-writing use case can be easily translated to coding scenarios.

When a valued Visual Studio Code extension died, I turned to advanced AI to duplicate the functionality it provided. I tried several different systems, and the results were educating. Here I'll detail how they compared.

For background, I write all my Visual Studio Magazine articles in VS Code in HTML so I can drop them into our CMS. I believe the rest of the editorial team uses Dreamweaver, but I like the customization options in the lightweight VS Code editor to tailor the writing environment to my needs (the heavyweight Dreamweaver seems like using an industrial pile driver to pound in a thumbtack). These options include extensions and code snippets triggered by customized keyboard shortcut commands. Together, these features can do just about anything, streamlining and speeding up my writing process and helping me maintain focus.

Key to my process was the extension htmltagwrap by Brad Gashler, with more than 890,000 installs. It allowed me to highlight text and then use a keyboard shortcut to wrap that text in HTML tags of my choosing. For example, I could click on a number line in the left gutter and press Alt+W to wrap the selected text (a discrete paragraph) in <p></p> tags for paragraphs, or just type in strong to get <strong></strong> tags to boldface manually selected inline text -- basically any tag you want.

However the extension hasn't been updated since August 2023 according its VS Code Marketplace entry, and it recently died on me, no longer working in VS Code 1.107.1. No surprise as its GitHub repo shows the most recent commits about 10 years ago. Also, the repo has numerous open issues and feature requests dating back to 2017 that remain unaddressed

So I turned to my company-paid ChatGPT and free Microsoft Copilot, Claude, Perplexity and Gemini (I will never touch Meta or Grok). The upshot is I basically have the same exact functionality I had before, but the experience varied among the different AI systems.

Microsoft Copilot
Copilot's first suggestion was to write an extension. It said "the most flexible and future-proof solution" was to build a tiny VS Code extension that registers a command to wrap selected text in an HTML tag--using the editor API to insert <tag>...</tag> , optionally prompting for the tag name, and allowing a keyboard shortcut for instant use. I know a tiny little bit about coding and I have actually written VS Code extensions, including one with help from AI, but I didn't want to go that route. I wanted something quick and easy.

Its second suggestion was a code snippet:

"Wrap with <p>": {
  "prefix": "wrapp",
  "body": [
    "<p>$TM_SELECTED_TEXT</p>"
  ],
  "description": "Wrap selected text in <p> tags"
}

It said I could add similar snippets for other tags, with the downside being: "You need different prefixes for each tag, and you can't easily change the tag on the fly."

Its third suggestion was a multi-command extension, which it offered to help me build. No thanks.

All the Others
Every other system suggested I use variations of VS Code's built-in user snippets feature, similar to Copilot's second suggestion, but without the need to write separate snippets. They all suggested multi-tag functionality, which seems kind of, duh, obvious. Copilot seem to have dropped the ball here.

Following are the examples from each system.

ChatGPT suggested this code snippet, which you insert into VS Code's keybindings.json file to associate with a keyboard shortcut command:

{
  "key": "ctrl+alt+p",
  "command": "editor.action.insertSnippet",
  "when": "editorTextFocus && editorHasSelection",
  "args": {
    "snippet": "<p>${TM_SELECTED_TEXT}</p>"
  }
}

It acknowledged that didn't give me 100% of the functionality that I wanted, to have a default <p> tag but also be able to change the tag on the fly by overwriting the p character. When I replied that wanted 100% duplicate functionality, it said that was "extension territory" and said a "hacky" workaround would give me 98% of the functionality with a placeholder:

"<${1:p>${TM_SELECTED_TEXT}</${1}>"

However it said: "But it forces you to tab or type every time, even when you just want <p> . Most people find this annoying quickly."

It insisted 100% functionality wasn't possible with snippets, only an extension.

I pointed out that Gemini's approach worked fine for me:

{
    "key": "ctrl+shift+w", 
    "command": "editor.action.insertSnippet",
    "when": "editorTextFocus && editorHasSelection",
    "args": {
      "snippet": "<${1:p}>${TM_SELECTED_TEXT}</${1:p}>"
    }
  }

ChatGPT agreed that this was a workable solution, so I think its previous insistence on extensions was based on not understanding the functionality I was asking for, apparently due to a sloppy prompt (though the others seemed to understand what I wanted just fine). There was a lot of back and forth on that, too long to list here.

It also suggested an improvement on the Gemini snippet to use ${1} instead of ${1:p} in the closing tag, because "Your version defines two separate placeholders; mine references one placeholder." Here's what ChatGPT settled on:

{
  "key": "alt+w",
  "command": "editor.action.insertSnippet",
  "when": "editorTextFocus && editorHasSelection",
  "args": {
    "snippet": "<${1:p}>${TM_SELECTED_TEXT}</${1}>"
  }
}

ChatGPT also helped me out solving some minor formatting problems with other tags, and actually improved on the htmltagwrap approach by simplifying the process of creating subheads.

Different Snippet Approaches
For the record, both Perplexity and Claude suggested the user snippet approach, which involves File > Preferences > Configure Snippets UI.

Their suggested user snippets were basically the same:

Perplexity :

{
  "Wrap with HTML tag": {
    "scope": "html",
    "prefix": "wraptag",
    "body": [
      "<${0:p}>$TM_SELECTED_TEXT</${0:p}>"
    ],
    "description": "Wrap selection in an HTML tag, default p, tag name selected"
  }
}

Claude:

{
  "Wrap with HTML tag": {
    "prefix": "wrap",
    "body": [
      "<${1:p}>$TM_SELECTED_TEXT</${1:p}>"
    ],
    "description": "Wrap selection with HTML tag"
  }
}

ChatGPT explained the difference between the user snippets approach and editor.action.insertSnippet keybindings:

In Visual Studio Code, user snippets and editor.action.insertSnippet keybindings use the same underlying snippet engine, but they are designed for different purposes. User snippets, which you configure through File > Preferences > Configure Snippets , are primarily meant for inserting boilerplate or template-style content.

They are triggered by typing a prefix and pressing Tab or Enter, appear in IntelliSense suggestions, and are well suited for inserting larger blocks of predefined text such as HTML structures, page skeletons, or recurring layout elements.

By contrast, snippets invoked through editor.action.insertSnippet and stored directly in keybindings.json are intended to act as editing commands. They are triggered by a keyboard shortcut, do not appear in autocomplete, and are commonly used to operate on existing text selections, such as wrapping selected text in HTML tags or applying formatting during an editing pass.

In practical terms, user snippets are best for starting or inserting content, while keybinding-based snippets are best for transforming content that already exists.

Emmet
Further for the record, another approach could utilize Emmet, a productivity feature built into VS Code (and many other editors). Emmet takes a different approach by expanding shorthand abbreviations into HTML or CSS structures, either as you type or by prompting you for an abbreviation when wrapping selected text. Compared with snippets and keybinding-based insertSnippet commands, Emmet is more flexible and expressive but less deterministic, making it better suited for building ad hoc markup than for applying fixed, CMS-specific formatting with a single keystroke.

Perplexity suggested the Emmet approach as an alternative, as did ChatGPT and Copilot. Here's what Perplexity said:

Alternative: Emmet with fixed abbreviation If you prefer Emmet's engine, you can also bind Emmet: Wrap with Abbreviation with a default tag:

text
{
  "key": "ctrl+i",
  "command": "editor.emmet.action.wrapWithAbbreviation",
  "when": "editorTextFocus && editorHasSelection",
  "args": { "abbreviation": "p" }
}

This will wrap the selection in <p> without prompting, and you can still re-edit the tag manually afterward, though it does not auto-select the tag name the way the snippet trick does.

For your workflow (HTML files, default

with quick change to other tags), the snippet + keybinding pattern is usually the closest match to that old extension's behavior.

Here's ChatGPTs Emmet alternative suggestion:

{
  "key": "ctrl+alt+w",
  "command": "editor.emmet.action.wrapWithAbbreviation",
  "when": "editorTextFocus && editorHasSelection"
}

Unlike snippet-based approaches, Emmet does not provide a built-in notion of a default tag that is pre-filled and immediately overwriteable. Each invocation requires either typing an abbreviation or explicitly configuring one in the keybinding. That makes Emmet slightly more interactive and slightly less deterministic than the snippet-based solution I ultimately chose.

On the other hand, Emmet can do things that snippets and htmltagwrap cannot. For example, if I selected a sentence and entered the abbreviation blockquote>p, Emmet would wrap the text in a nested structure in a single step:

<blockquote>
  <p>Selected text</p>
</blockquote>

That expressive power is one reason Emmet remains popular for authoring HTML from scratch. In my case, however, the additional prompt and lack of a default-first workflow meant that Emmet was close, but not quite as efficient as a custom editor.action.insertSnippet keybinding that exactly matched my existing muscle memory.

Here's a comparison of the approaches suggested by the AI systems:

Aspect User Snippets insertSnippet in keybindings.json Emmet Wrap With Abbreviation
Trigger Typed prefix + Tab/Enter Keyboard shortcut Keyboard shortcut or prompt
Shows in IntelliSense Yes No No
Intended use Insert boilerplate/templates Transform existing text Generate or restructure markup
Selection-aware Optional Common/expected Yes
Default-first workflow No Yes No
Supports nested output No No Yes
Determinism High High Lower

So How Did the htmltagwrap Extension Work?
ChatGPT reports my handy, now-dead tool used none of the above approaches. By examining the GitHub repo, it said that at a code level the tool implemented its own wrapping logic using the VS Code editor API rather than delegating to built-in features.

When the command was invoked, the extension retrieved the active editor and current selection, extracted the selected text, and performed a direct text replacement using an editor edit operation. It programmatically inserted an opening and closing tag around the selection, then positioned the cursor so the tag name itself was selected. As the user typed a different tag name, the extension mirrored that change in both the opening and closing tags, keeping them synchronized.

This approach avoided prompts, abbreviations, or predefined snippets, and instead treated wrapping as a lightweight text transformation with live cursor management, which is why it felt fast, predictable, and well suited to repetitive editing passes.

So I used editor.action.insertSnippet constructs stored in the keybindings.json file to try to duplicate that functionality. It's working fine.

Lessons Learned
Although this article focuses on a text-writing workflow in VS Code, the same approach can be useful for coding tasks as well. Trying the same problem across multiple AI systems can surface alternative solutions that go beyond what a single assistant, such as Copilot, might suggest. That can be especially helpful for developers without a Copilot subscription -- thus not enjoying the tool's tight integration in VS Code -- or for those who prefer not to rely on one tool for editor customization and workflow decisions.

About the Author

David Ramel is an editor and writer at Converge 360.

comments powered by Disqus

Featured

  • VS Code 1.123 Adds Agent Session Sync, 1M Context Windows

    Microsoft released Visual Studio Code 1.123 on June 3, adding agent-focused features, larger model context support, integrated browser updates and a new delay for some automatic extension updates.

  • Copilot Billing Shock Hits Developers

    Developer complaints about GitHub Copilot's new usage-based billing model have centered on unexpectedly rapid AI credit consumption, and neither GitHub nor Microsoft has responded directly to the backlash, though they have previously published guidance to lessen model usage costs.

  • Hands On with GitHub Copilot App Technical Preview: Turning a Blazor Issue into a PR

    GitHub's brand-new Copilot desktop app, in technical preview, handled a small Blazor issue from planning through pull request creation, but the hands-on test also showed why developers still need to verify agent work in the running app before merging.

  • At Build 2026, Microsoft Sets Up Windows as an OS for AI Agents

    Microsoft's Build 2026 Windows developer announcements point to a broader platform strategy for agentic AI, spanning terminal workflows, local models, app-building skills, Cloud PCs and operating system-level containment.

Subscribe on YouTube