DEV Community

Marco Trulla
Marco Trulla

Posted on • Edited on

A Comment Divider for Zed using Nushell

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)
}
Enter fullscreen mode Exit fullscreen mode

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"]
      }
    }
  }
Enter fullscreen mode Exit fullscreen mode

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" }]
    }
  }
Enter fullscreen mode Exit fullscreen mode

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

  1. Select the text you want to transform into a comment.
  2. Press Alt+Shift+T and 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
// ===========================================================================
Enter fullscreen mode Exit fullscreen mode

Header Single Line and Sub-header Single Line

//                              THIS IS A COMMENT
Enter fullscreen mode Exit fullscreen mode

Header Filled

// ===========================================================================
// ===========================  THIS IS A COMMENT  ===========================
// ===========================================================================
Enter fullscreen mode Exit fullscreen mode

Header Filled Single Line

// ============================ THIS IS A COMMENT ============================
Enter fullscreen mode Exit fullscreen mode

Sub-header

// ---------------------------------------------------------------------------
//                              THIS IS A COMMENT
// ---------------------------------------------------------------------------
Enter fullscreen mode Exit fullscreen mode

Sub-header Filled

// ---------------------------------------------------------------------------
// ---------------------------  THIS IS A COMMENT  ---------------------------
// ---------------------------------------------------------------------------
Enter fullscreen mode Exit fullscreen mode

Sub-header Filled Single Line

// ---------------------------- THIS IS A COMMENT ----------------------------
Enter fullscreen mode Exit fullscreen mode

Comment Divider

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Enter fullscreen mode Exit fullscreen mode

Multi-lines Comment Header

// ===========================================================================
//                            THIS IS A MULTI-LINE
//                               COMMENT HEADER
//                          WITH THREE LINES OF TEXT
// ===========================================================================
Enter fullscreen mode Exit fullscreen mode

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)