Like many of you, I recently switched from VS Code to Zed. Despite having a few limitations compared to the "programming behemoth," I absolutely love it. It’s fast, reliable, and packed with useful features.
However, there is always a "but." Occasionally, I miss specific extensions from my VS Code configuration. One such example is the small but helpful Comment Divider extension, which allows you to select text and convert it into a stylized comment block.
Since Zed doesn't have an equivalent extension yet, I decided to replicate the functionality using Nushell (my daily shell of choice).
NOTE: I primarily use Arch Linux on WSL, so you may need to adjust the code slightly (specifically the clipboard command) to suit your environment.
UPDATE 2026-02-10: since the command is part of my (finally updated) dotfiles repo, I'm gonna update it here too, with few more additions.
1. The Nushell Script
First, you need to create a command to generate the comments. You can save this as a standalone script or place it in your $nu.user-autoload-dir to ensure it's sourced at startup.
# Generate a comment from a text or list, into the style of a specified programming language.
export def "comment generate" [
--align (-a): string = 'center' # comment alignment (`left`, `center`, `right`)
--block (-b): string = 'single' # block format (`single`, `multi-single`, `multi`)
--filled (-f) # fill the spaces with a filler character
--filler-divider: string = '~' # filler character for divider comment
--filler-header: string = '=' # filler character for header comment
--filler-subheader: string = '-' # filler character for sub-header comment
--language (-l): string = 'c' # language style
--list # prints out all supported language styles
--margin-left: int = 1 # left margin
--margin-right: int = 1 # right margin
--padding-left: int = 0 # left padding
--padding-right: int = 0 # right padding
--style (-s): string = 'header' # comment style (`header`, `sub-header`, `divider`)
--width (-w): int = 80 # max line width
]: [nothing -> string string -> string list<string> -> string] {
const LANGUAGES = [
{
ext_filter: '^(c|cpp|c3|d|jsx?|tsx?|jsonc|rs)$'
support: { single: single multi-single: multi-single multi: multi }
symbol: { single_open: '//' single_close: '' multi_open: '/*' multi_close: '*/' }
}
{
ext_filter: '^(zig)$'
support: { single: single multi-single: single multi: single }
symbol: { single_open: '//' single_close: '' multi_open: '' multi_close: '' }
}
{
ext_filter: '^(sh|zsh|bash|nu|py)$'
support: { single: single multi-single: multi-single multi: multi-single }
symbol: { single_open: '#' single_close: '' multi_open: '#' multi_close: '' }
}
{
ext_filter: '^(sql)$'
support: { single: single multi-single: multi-single multi: multi }
symbol: { single_open: '--' single_close: '' multi_open: '/*' multi_close: '*/' }
}
{
ext_filter: '^(html|xhtml|xml)$'
support: { single: single multi-single: multi-single multi: multi }
symbol: { single_open: '<!--' single_close: '-->' multi_open: '<!--' multi_close: '-->' }
}
{
ext_filter: '^(css)$'
support: { single: single multi-single: multi-single multi: multi }
symbol: { single_open: '/*' single_close: '*/' multi_open: '/*' multi_close: '*/' }
}
]
let get_net_width: closure = {|open: string, close: string|
$width - ($open | str length) - $margin_left - $margin_right - ($close | str length)
}
let fill_line: closure = {|w: int, text: oneof<string, nothing>, solid: bool = false|
let data: string = if not $solid {
('' | fill -w $padding_left) + $text + ('' | fill -w $padding_right)
} else {
''
}
match $style {
header => { $data | fill -w $w -a $align -c (if $filled or $solid { $filler_header } else { ' ' }) }
sub-header => { $data | fill -w $w -a $align -c (if $filled or $solid { $filler_subheader } else { ' ' }) }
divider => { '' | fill -w $w -a $align -c $filler_divider }
}
}
let input = $in
mut data: list<string> = if ($input | describe) == string { $input | split row (char newline) } else { $input }
if $list {
return ($LANGUAGES | each {|l|
$l.ext_filter | str replace '^(' '' | str replace ')$' '' | split row '|'
} | flatten | sort -i)
}
for lang in $LANGUAGES {
if ($language | find -ir $lang.ext_filter | is-empty) { continue }
if $style == divider {
let net_width: int = do $get_net_width $lang.symbol.single_open $lang.symbol.single_close
$data = [($lang.symbol.single_open
+ ('' | fill -w $margin_left) + (do $fill_line $net_width null true) + ('' | fill -w $margin_right)
+ $lang.symbol.single_close)]
break
}
match ($lang.support | get $block) {
single => {
let net_width: int = do $get_net_width $lang.symbol.single_open $lang.symbol.single_close
let delimiter: list<string> = [($lang.symbol.single_open + ('' | fill -w $margin_left) + (do $fill_line $net_width null true))]
$data = $data | each {|l|
($lang.symbol.single_open
+ ('' | fill -w $margin_left) + (do $fill_line $net_width $l) + ('' | fill -w $margin_right)
+ $lang.symbol.single_close)
}
$data = $delimiter ++ $data ++ $delimiter
}
multi-single => {
let net_width: int = do $get_net_width $lang.symbol.multi_open $lang.symbol.multi_close
let delimiter: list<string> = [($lang.symbol.multi_open + ('' | fill -w $margin_left) + (do $fill_line $net_width null true) + ('' | fill -w $margin_right) + $lang.symbol.multi_close)]
$data = $data | each {|l|
($lang.symbol.multi_open
+ ('' | fill -w $margin_left) + (do $fill_line $net_width $l) + ('' | fill -w $margin_right)
+ $lang.symbol.multi_close)
}
$data = $delimiter ++ $data ++ $delimiter
}
multi => {
let net_width: int = do $get_net_width $lang.symbol.multi_open $lang.symbol.multi_close
let margin: int = ($lang.symbol.multi_open | str length) + $margin_left
let header: list<string> = [($lang.symbol.multi_open + ('' | fill -w $margin_left) + (do $fill_line $net_width null true))]
let footer: list<string> = [(('' | fill -w $margin) + (do $fill_line $net_width null true) + ('' | fill -w $margin_right) + $lang.symbol.multi_close)]
$data = $data | each {|l|
(('' | fill -w $margin) + (do $fill_line $net_width $l) + ('' | fill -w $margin_right))
}
$data = $header ++ $data ++ $footer
}
}
}
$data | str join (char newline)
}
2. Configuring Zed Tasks
The next step is to open your tasks configuration by pressing Ctrl+Shift+P (or Cmd+Shift+P on Mac) and searching for zed: open tasks and add all the tasks you need. You can add the following example or customize my own tasks:
{
"label": "Comment Divide - Header - Single Line",
"command": "use strings/comments.nu 'comment generate' ; r#'$ZED_SELECTED_TEXT'# | comment generate -l (\"$ZED_FILENAME\" | path parse | get extension) -w 79 | wl-copy",
"use_new_terminal": true,
"reveal": "no_focus",
"hide": "on_success",
"shell": {
"with_arguments": {
"program": "nu",
"args": ["-c"]
}
}
},
{
"label": "Comment Divide - Sub-Header - Single Line",
"command": "use strings/comments.nu 'comment generate' ; r#'$ZED_SELECTED_TEXT'# | comment generate -l (\"$ZED_FILENAME\" | path parse | get extension) -w 79 -s sub-header | wl-copy",
"use_new_terminal": true,
"reveal": "no_focus",
"hide": "on_success",
"shell": {
"with_arguments": {
"program": "nu",
"args": ["-c"]
}
}
},
{
"label": "Comment Divide - Divider",
"command": "use strings/comments.nu 'comment generate' ; comment generate -l (\"$ZED_FILENAME\" | path parse | get extension) -s divider -w 79 | wl-copy",
"use_new_terminal": true,
"reveal": "no_focus",
"hide": "on_success",
"shell": {
"with_arguments": {
"program": "nu",
"args": ["-c"]
}
}
}
You will need to adjust the path to the script accordingly to your system configuration.
3. Keymapping
As an extra bonus, if you want to speed up the things a little bit more, you can open your keymap file by pressing Ctrl+Shift+P (or Cmd+Shift+P on Mac) and searching for zed: open keymap file, and add the following:
{
"context": "Editor && mode == full",
"bindings": {
"F14": ["task::Spawn", { "task_name": "Comment Divide - Header - Single Line" }],
"F15": ["task::Spawn", { "task_name": "Comment Divide - Sub-Header - Single Line" }],
"F16": ["task::Spawn", { "task_name": "Comment Divide - Divider" }]
}
}
In my configuration I'm using function keys from F14 to F16 to prevent conflicts. Feel free to customize them as you need.
How to Use It
- Select the text you want to transform into a comment.
- Press
Alt+Shift+Tand select your desired comment style; or use your keymapped short-cut.
The task will generate the formatted comment and copy it into your clipboard (I haven't found a way to inject it directly at the cursor position yet). Simply paste it over your selection, and you're good to go!
Examples
Header
// ===========================================================================
// THIS IS A COMMENT
// ===========================================================================
Header Single Line and Sub-header Single Line
// THIS IS A COMMENT
Header Filled
// ===========================================================================
// =========================== THIS IS A COMMENT ===========================
// ===========================================================================
Header Filled Single Line
// ============================ THIS IS A COMMENT ============================
Sub-header
// ---------------------------------------------------------------------------
// THIS IS A COMMENT
// ---------------------------------------------------------------------------
Sub-header Filled
// ---------------------------------------------------------------------------
// --------------------------- THIS IS A COMMENT ---------------------------
// ---------------------------------------------------------------------------
Sub-header Filled Single Line
// ---------------------------- THIS IS A COMMENT ----------------------------
Comment Divider
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Multi-lines Comment Header
// ===========================================================================
// THIS IS A MULTI-LINE
// COMMENT HEADER
// WITH THREE LINES OF TEXT
// ===========================================================================
Known Limitations
- Manual Injection: As previously mentioned, there is currently no way to inject the text directly into the editor. You must rely on an external clipboard command to copy and paste the result.
- File Naming: The command "guesses" the language style by reading the file extension. Therefore, the file must be saved with a filename; it will not work on "Untitled" buffers.
Top comments (0)