Full Code of terrortylor/nvim-comment for AI

main e9ac16ab0566 cached
10 files
35.3 KB
10.4k tokens
1 requests
Download .txt
Repository: terrortylor/nvim-comment
Branch: main
Commit: e9ac16ab0566
Files: 10
Total size: 35.3 KB

Directory structure:
gitextract_xu0egi6i/

├── .github/
│   └── workflows/
│       └── make.yml
├── .luacheckrc
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── lua/
│   ├── nvim_comment.lua
│   └── spec/
│       └── nvim_comment_spec.lua
└── tests/
    ├── comment_spec.lua
    └── minimal_init.vim

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/make.yml
================================================
name: Run Tests

on: [push, pull_request]

jobs:
  Tests:
    name: Linting and Test
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v2

      - name: Prepare
        run: |
          # sudo apt-get update
          sudo apt-get install lua-check
          mkdir -p ~/.local/share/nvim/site/pack/vendor/start
          git clone --depth 1 https://github.com/nvim-lua/plenary.nvim ~/.local/share/nvim/site/pack/vendor/start/plenary.nvim
          ln -s $(pwd) ~/.local/share/nvim/site/pack/vendor/start
          mkdir -p build
          wget https://github.com/neovim/neovim/releases/download/nightly/nvim.appimage
          chmod +x nvim.appimage
          mv nvim.appimage ./build/nvim
      - name: Run Lint
        run: make lint

      - name: Run unit tests
        run: |
          export PATH="${PWD}/build/:${PATH}"
          make unit
          echo "exit code $?"

      - name: Run tests
        run: |
          export PATH="${PWD}/build/:${PATH}"
          make gh-integration
          echo "exit code $?"


================================================
FILE: .luacheckrc
================================================
globals = {"Snake", "Cell", "vim", "_TEST", "LOG_LEVEL"}
read_globals = {
  print = {
    fields = {
      revert = {}
    }
  },
  os = {
    fields = {
      execute = {
        fields = {
          revert = {}
        }
      },
    }
  }
}


================================================
FILE: CHANGELOG.md
================================================
Changelog
======================

2021-07-10 - Add hook function to be called before reading `commentstring`
2021-02-16 - Fix issue with `gv` causing entire buffer range to be acted on
2021-02-12 - Fix issue with motion acting on last visual selection


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2021 Alex Tylor

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Makefile
================================================
all: lint test

lint:
	@echo "Linting..."
	luacheck --no-color .
	@echo

unit:
	@echo "Run unit tests..."
	nvim --headless --noplugin -c 'packadd plenary.nvim' -c "PlenaryBustedDirectory lua/spec"
	@echo

gh-integration:
	@echo "Run integration tests..."
	nvim --headless --noplugin -u tests/minimal_init.vim  -c "PlenaryBustedDirectory tests  { minimal_init = './tests/minimal_init.vim' }"
	@echo

integration:
	@echo "Run integration tests..."
	nvim --headless --noplugin -c 'packadd plenary.nvim' -c "PlenaryBustedDirectory tests"
	@echo

test: unit integration


================================================
FILE: README.md
================================================
# nvim-comment

Toggle comments in Neovim, using built in `commentstring` filetype option;
written in Lua. Without a doubt this plugin **is not required** and is a rip off
of [TPope's Commentary](https://github.com/tpope/vim-commentary) with less
features! What makes this plugin stand out over the numerous other comment
plugins written in Lua are:

- Comments each line, rather than adds block comments; making it easier to
  toggle code when debugging
- Uses the built in `commentstring` buffer option to define comment markers
- Where a marker doesn't have a **space** character as padding this is added,
  configurable (this can be disabled in the options, see below but is useful
  when working with numerous linters)
- Supports motions
- Dot `.` repeatable

When the plugin is called it works out the range to comment/uncomment; if all
lines in the given range are commented then it uncomments, otherwise it comments
the range. This is useful when commenting a block out for testing with a real
like comment in it; as for the plugin a comment is a comment.

## Usage

Either use the command `CommentToggle`, e.g.:

- `CommentToggle` comment/uncomment current line
- `67,69CommentToggle` comment/uncomment a range
- `'<,'>CommentToggle` comment/uncomment a visual selection

Or use the default mappings:

- `gcc` comment/uncomment current line, this does not take a count, if you want
  a count use the `gc{count}{motion}`
- `gc{motion}` comment/uncomment selection defined by a motion (as lines are
  commented, any comment toggling actions will default to a linewise):
  - `gcip` comment/uncomment a paragraph
  - `gc4w` comment/uncomment current line
  - `gc4j` comment/uncomment 4 lines below the current line
  - `dic` delete comment block
  - `gcic` uncomment commented block

### Configure

The comment plugin needs to be initialised using:

```lua
require('nvim_comment').setup()
```

However you can pass in some config options, the defaults are:

```lua
{
  -- Linters prefer comment and line to have a space in between markers
  marker_padding = true,
  -- should comment out empty or whitespace only lines
  comment_empty = true,
  -- trim empty comment whitespace
  comment_empty_trim_whitespace = true,
  -- Should key mappings be created
  create_mappings = true,
  -- Normal mode mapping left hand side
  line_mapping = "gcc",
  -- Visual/Operator mapping left hand side
  operator_mapping = "gc",
  -- text object mapping, comment chunk,,
  comment_chunk_text_object = "ic",
  -- Hook function to call before commenting takes place
  hook = nil
}
```

- Ignore Empty Lines

```lua
require('nvim_comment').setup({comment_empty = false})
```

- Don't trim trailing comment whitespace when commenting empty line
```lua
require('nvim_comment').setup({comment_empty_trim_whitespace = false})
```

The default for this is `true`, meaning that a commented empty line will not
contain any whitespace. Most `commentstring` comment prefixes have some
whitespace padding, disable this to keep that padding on empty lines.

- Disable mappings

```lua
require('nvim_comment').setup({create_mappings = false})
```

- Custom mappings

```lua
require('nvim_comment').setup({line_mapping = "<leader>cl", operator_mapping = "<leader>c", comment_chunk_text_object = "ic"})
```

- Disable marker padding

```lua
require('nvim_comment').setup({marker_padding = false})
```

- Hook function called before reading `commentstring`

You can run arbitrary function which will be called before plugin reads value of
`commentstring`. This can be used to integrate with
[JoosepAlviste/nvim-ts-context-commentstring](https://github.com/JoosepAlviste/nvim-ts-context-commentstring):

```lua
require('nvim_comment').setup({
  hook = function()
    if vim.api.nvim_buf_get_option(0, "filetype") == "vue" then
      require("ts_context_commentstring.internal").update_commentstring()
    end
  end
})
```

- Changing/Setting `commentstring`

If you want to override the comment markers or add a new filetype just set the
`commentstring` options:

```lua
-- Assumes this is being run in the context of the filetype...
vim.api.nvim_buf_set_option(0, "commentstring", "# %s")
```

You can also use an autocommand to automatically load your `commentstring` for
certain file types:

```vim
" when you enter a (new) buffer
augroup set-commentstring-ag
autocmd!
autocmd BufEnter *.cpp,*.h :lua vim.api.nvim_buf_set_option(0, "commentstring", "// %s")
" when you've changed the name of a file opened in a buffer, the file type may have changed
autocmd BufFilePost *.cpp,*.h :lua vim.api.nvim_buf_set_option(0, "commentstring", "// %s")
augroup END
```

Or add the comment string option in the relevant `filetype` file:

```vim
let commentstring="# %s"
```

```lua
vim.api.nvim_buf_set_option(0, "commentstring", "# %s")
```

## Installation

Install just as you would a normal plugin, here are some options:

### Built in package manager

```bash
mkdir -p ~/.local/share/nvim/site/pack/plugins/start
cd ~/.local/share/nvim/site/pack/plugins/start
git clone https://github.com/terrortylor/nvim-comment
```

### Via a plugin manager

Using [packer.nvim](https://github.com/wbthomason/packer.nvim):

```lua
use "terrortylor/nvim-comment"
require('nvim_comment').setup()
```


================================================
FILE: lua/nvim_comment.lua
================================================
local api = vim.api

local M = {}

M.config = {
  -- Linters prefer comment and line to have a space in between
  marker_padding = true,
  -- should comment out empty or whitespace only lines
  comment_empty = true,
  -- trim empty comment whitespace
  comment_empty_trim_whitespace = true,
  -- Should key mappings be created
  create_mappings = true,
  -- Normal mode mapping left hand side
  line_mapping = "gcc",
  -- Visual/Operator mapping left hand side
  operator_mapping = "gc",
  -- text object mapping, comment chunk
  comment_chunk_text_object = "ic",
  -- Hook function to call before commenting takes place
  hook = nil,
}

function M.get_comment_wrapper()
  local cs = api.nvim_buf_get_option(0, "commentstring")

  -- make sure comment string is understood
  if cs:find("%%s") then
    local left, right = cs:match("^(.*)%%s(.*)")
    if right == "" then
      right = nil
    end

    -- left comment markers should have padding as linterers preffer
    if M.config.marker_padding then
      if not left:match("%s$") then
        left = left .. " "
      end
      if right and not right:match("^%s") then
        right = " " .. right
      end
    end

    return left, right
  else
    api.nvim_command('echom "Commentstring not understood: ' .. cs .. '"')
  end
end

function M.comment_line(l, indent, left, right, comment_empty, comment_empty_trim_whitespace)
  if not comment_empty and l:match("^%s*$") then
    return l
  end

  local line_empty = l:match("^%s*$")

  -- standarise indentation before adding
  local line = l:gsub("^" .. indent, "")
  if right then
    line = line .. right
  end

  local ret_line = indent .. left .. line
  if comment_empty_trim_whitespace and line_empty then
    return ret_line:gsub("^(.*)%s+$", "%1")
  end

  return ret_line
end

function M.uncomment_line(l, left, right, comment_empty_trim_whitespace)
  local line = l
  if right and right ~= "" then
    line = line:gsub(vim.pesc(right) .. "$", "")
    return line:gsub(vim.pesc(left), "", 1)
  end

  if comment_empty_trim_whitespace and left:match("%s+$") then
    local left_nw = left:match("^(%S+)%s+$")
    if line:match("^%s*" .. left_nw .. "$") then
      return line:gsub(vim.pesc(left_nw), "", 1)
    end
  end

  return line:gsub(vim.pesc(left), "", 1)
end

function M.operator(mode)
  local line1, line2
  if not mode then
    line1 = api.nvim_win_get_cursor(0)[1]
    line2 = line1
  elseif mode:match("[vV]") then
    line1 = api.nvim_buf_get_mark(0, "<")[1]
    line2 = api.nvim_buf_get_mark(0, ">")[1]
  else
    line1 = api.nvim_buf_get_mark(0, "[")[1]
    line2 = api.nvim_buf_get_mark(0, "]")[1]
  end

  M.comment_toggle(line1, line2)
end

function M.comment_toggle(line_start, line_end)
  if type(M.config.hook) == "function" then
    M.config.hook()
  end

  local left, right = M.get_comment_wrapper()
  if not left or (not left and not right) then
    return
  end

  local lines = api.nvim_buf_get_lines(0, line_start - 1, line_end, false)
  if not lines then
    return
  end

  -- check if any lines commented, capture indent
  local esc_left = vim.pesc(left)
  if M.config.comment_empty_trim_whitespace and left:match("%s+$") then
    local left_nw = left:match("^(%S+)%s+$")
    esc_left = vim.pesc(left_nw) .. "%s*"
  end
  local commented_lines_counter = 0
  local empty_counter = 0
  local indent

  for _, v in pairs(lines) do
    if v:find("^%s*" .. esc_left) then
      commented_lines_counter = commented_lines_counter + 1
    elseif v:match("^%s*$") then
      empty_counter = empty_counter + 1
    end

    if not v:match("^%s*$") then
      local line_indent = v:match("^%s+") or ""
      if not indent or string.len(line_indent) < string.len(indent) then
        indent = line_indent
      end
    end
  end

  for i, v in pairs(lines) do
    if commented_lines_counter ~= (#lines - empty_counter) then
      lines[i] = M.comment_line(
        v,
        indent,
        left,
        right,
        M.config.comment_empty,
        M.config.comment_empty_trim_whitespace
      )
    else
      lines[i] = M.uncomment_line(v, left, right, M.config.comment_empty_trim_whitespace)
    end
  end
  -- the LUA API doesn't seem to keep marks for lines that are changes with
  -- nvim_buf_set_lines
  api.nvim_call_function("setline", {line_start, lines})

  -- The lua call seems to clear the visual selection so reset it
  -- 2147483647 is vimL built in
  api.nvim_call_function("setpos", { "'<", { 0, line_start, 1, 0 } })
  api.nvim_call_function("setpos", { "'>", { 0, line_end, 2147483647, 0 } })
end

function M.select_comment_chunk()
  vim.cmd([[execute "normal! \<esc>"]])
  local up = vim.fn.search("\\v^(\\s*--)@!", "Wbcn")
  up = up + 1
  local down = vim.fn.search("\\v^(\\s*--)@!", "Wzn")
  if down ~= 0 then
    down = down - 1
  end

  local lines = vim.api.nvim_buf_line_count(0)
  local pos = vim.api.nvim_win_get_cursor(0)[1]

  if down == 0 then
    down = lines
  end

  if up <= down and up <= pos and down >= pos then
    vim.api.nvim_buf_set_mark(0, "<", up, 1, {})
    vim.api.nvim_buf_set_mark(0, ">", down, 1, {})
    vim.cmd("normal! `<V`>")
  end
end

function M.setup(user_opts)
  M.config = vim.tbl_extend("force", M.config, user_opts or {})

  -- Messy, change with nvim_exec once merged
  local vim_func = [[
  function! CommentOperator(type) abort
    let reg_save = @@
    execute "lua require('nvim_comment').operator('" . a:type. "')"
    let @@ = reg_save
  endfunction
  ]]

  vim.api.nvim_call_function("execute", { vim_func })
  vim.api.nvim_command("command! -range CommentToggle lua require('nvim_comment').comment_toggle(<line1>, <line2>)")

  if M.config.create_mappings then
    local opts = { noremap = true }
    api.nvim_set_keymap("n", M.config.line_mapping, "<Cmd>set operatorfunc=CommentOperator<CR>g@l", opts)
    api.nvim_set_keymap("n", M.config.operator_mapping, "<Cmd>set operatorfunc=CommentOperator<CR>g@", opts)
    api.nvim_set_keymap("x", M.config.operator_mapping, ":<C-u>call CommentOperator(visualmode())<CR>", opts)
    api.nvim_set_keymap(
      "x",
      M.config.comment_chunk_text_object,
      "<Cmd>lua require('nvim_comment').select_comment_chunk()<CR>",
      opts
    )
    api.nvim_set_keymap(
      "o",
      M.config.comment_chunk_text_object,
      "<Cmd>lua require('nvim_comment').select_comment_chunk()<CR>",
      opts
    )
  end
end

return M


================================================
FILE: lua/spec/nvim_comment_spec.lua
================================================
local testModule
local mock = require('luassert.mock')
local api_mock

describe('nvim-comment', function()

  before_each(function()
    testModule = require('nvim_comment')
    api_mock = mock(vim.api, true)
    -- reset config to default
    testModule.config = {
      marker_padding = true
    }
  end)

  after_each(function()
    mock.revert(api_mock)
  end)

  describe('get_comment_wrapper', function()
    local commentstrings = {
      ['COMMENT %s'] = {'COMMENT ', nil},
      ['{% comment %}%s{% endcomment %}'] = {'{% comment %}', '{% endcomment %}'},
      ['c# %s'] = {'c# ', nil},
      ['dnl %s'] = {'dnl ', nil},
      ['NB. %s'] = {'NB. ', nil},
      ['! %s'] = {'! ', nil},
      ['#%s'] = {'#', nil},
      ['# %s'] = {'# ', nil},
      ['%%s'] = {'%', nil},
      ['% %s'] = {'% ', nil},
      ['(*%s*)'] = {'(*', '*)'},
      ['(;%s;)'] = {'(;', ';)'},
      ['**%s'] = {'**', nil},
      ['-# %s'] = {'-# ', nil},
      ['-- %s'] = {'-- ', nil},
      ['--  %s'] = {'--  ', nil},
      ['.. %s'] = {'.. ', nil},
      ['.\\"%s'] = {'.\\"', nil},
      ['/*%s*/'] = {'/*', '*/'},
      ['/* %s */'] = {'/* ', ' */'},
      ['//%s'] = {'//', nil},
      ['// %s'] = {'// ', nil},
      [':: %s'] = {':: ', nil},
      [';%s'] = {';', nil},
      ['; %s'] = {'; ', nil},
      ['; // %s'] = {'; // ', nil},
      ['<!--%s-->'] = {'<!--', '-->'},
      ['<%#%s%>'] = {'<%#', '%>'},
      ['> %s'] = {'> ', nil},
      ['      *%s'] = {'      *', nil},
      ['"%s'] = {'"', nil},
    }

    for string,expected in pairs(commentstrings) do
      it('Should return comment wrapper(s) for: ' .. string .. '  no left marker padding', function()
        testModule.config.marker_padding = false
        api_mock.nvim_buf_get_option.on_call_with(0, 'commentstring').returns(string)
        local left, right = testModule.get_comment_wrapper(string)

        assert.equals(left, expected[1])
        assert.equals(right, expected[2])
      end)
    end

    local padded_commentstrings = {
      ['COMMENT %s'] = {'COMMENT ', nil},
      ['{% comment %}%s{% endcomment %}'] = {'{% comment %} ', ' {% endcomment %}'},
      ['c# %s'] = {'c# ', nil},
      ['dnl %s'] = {'dnl ', nil},
      ['NB. %s'] = {'NB. ', nil},
      ['! %s'] = {'! ', nil},
      ['#%s'] = {'# ', nil},
      ['# %s'] = {'# ', nil},
      ['%%s'] = {'% ', nil},
      ['% %s'] = {'% ', nil},
      ['(*%s*)'] = {'(* ', ' *)'},
      ['(;%s;)'] = {'(; ', ' ;)'},
      ['**%s'] = {'** ', nil},
      ['-# %s'] = {'-# ', nil},
      ['-- %s'] = {'-- ', nil},
      ['--  %s'] = {'--  ', nil},
      ['.. %s'] = {'.. ', nil},
      ['.\\"%s'] = {'.\\" ', nil},
      ['/*%s*/'] = {'/* ', ' */'},
      ['/* %s */'] = {'/* ', ' */'},
      ['//%s'] = {'// ', nil},
      ['// %s'] = {'// ', nil},
      [':: %s'] = {':: ', nil},
      [';%s'] = {'; ', nil},
      ['; %s'] = {'; ', nil},
      ['; // %s'] = {'; // ', nil},
      ['<!--%s-->'] = {'<!-- ', ' -->'},
      ['<%#%s%>'] = {'<%# ', ' %>'},
      ['> %s'] = {'> ', nil},
      ['      *%s'] = {'      * ', nil},
      ['"%s'] = {'" ', nil},
    }

    for string,expected in pairs(padded_commentstrings) do
      it('Should return comment wrapper(s) for: ' .. string .. ' with marker padding', function()
        api_mock.nvim_buf_get_option.on_call_with(0, 'commentstring').returns(string)
        local left, right = testModule.get_comment_wrapper(string)

        assert.equals(left, expected[1])
        assert.equals(right, expected[2])
      end)
    end

    it('Should return nil,nil if unsuported commentstring', function()
      api_mock.nvim_buf_get_option.on_call_with(0, 'commentstring').returns('something here')
      local left, right = testModule.get_comment_wrapper('something here')

      assert.equals(left, nil)
      assert.equals(right, nil)
    end)
  end)

  describe('comment_line', function()
    local commentstrings = {
      ['COMMENT line'] = {'COMMENT ', ''},
      ['{% comment %}line{% endcomment %}'] = {'{% comment %}', '{% endcomment %}'},
      ['c# line'] = {'c# ', ''},
      ['dnl line'] = {'dnl ', ''},
      ['NB. line'] = {'NB. ', ''},
      ['! line'] = {'! ', ''},
      ['#line'] = {'#', ''},
      ['# line'] = {'# ', ''},
      ['%line'] = {'%', ''},
      ['% line'] = {'% ', ''},
      ['(*line*)'] = {'(*', '*)'},
      ['(;line;)'] = {'(;', ';)'},
      ['**line'] = {'**', ''},
      ['-# line'] = {'-# ', ''},
      ['-- line'] = {'-- ', ''},
      ['--  line'] = {'--  ', ''},
      ['.. line'] = {'.. ', ''},
      ['.\\"line'] = {'.\\"', ''},
      ['/*line*/'] = {'/*', '*/'},
      ['/* line */'] = {'/* ', ' */'},
      ['//line'] = {'//', ''},
      ['// line'] = {'// ', ''},
      [':: line'] = {':: ', ''},
      [';line'] = {';', ''},
      ['; line'] = {'; ', ''},
      ['; // line'] = {'; // ', ''},
      ['<!--line-->'] = {'<!--', '-->'},
      ['<%#line%>'] = {'<%#', '%>'},
      ['> line'] = {'> ', ''},
      ['      *line'] = {'      *', ''},
      ['"line'] = {'"', ''},
    }

    for expected,comment_parts in pairs(commentstrings) do
      it('Should comment line as expected, with no padding: ' .. expected, function()
        local actual = testModule.comment_line('line', "", comment_parts[1], comment_parts[2], true, true)

        assert.equals(expected, actual)
      end)
    end

    it("Should add comment after any whitespace, with padding", function()
      local actual = testModule.comment_line("  line", "  ",  "-- ", "", true, true)

      assert.equals("  -- line", actual)
    end)

    it("Should add comment after any whitespace, with extra padding", function()
      local actual = testModule.comment_line("    line", "  ",  "-- ", "", true, true)

      assert.equals("  --   line", actual)
    end)

    it("Should trim whitespace", function()
      local actual = testModule.comment_line("", "  ",  "-- ", "", true, true)

      assert.equals("  --", actual)
    end)

    it("Should not trim whitespace", function()
      local actual = testModule.comment_line("", "  ",  "-- ", "", true, false)

      assert.equals("  -- ", actual)
    end)

    it("Should ignore line if empty or just whitespace", function()
      local actual = testModule.comment_line("    line", "  ",  "-- ", "", false, true)

      assert.equals("  --   line", actual)

      actual = testModule.comment_line("", "  ",  "-- ", "", false, true)

      assert.equals("", actual)

      -- spaces
      actual = testModule.comment_line("  ", "  ",  "-- ", "", false, true)

      assert.equals("  ", actual)

      -- Tabs
      actual = testModule.comment_line("   ", "  ",  "-- ", "", false, true)

      assert.equals("   ", actual)
    end)
  end)

  describe('uncomment_line', function()
    local commentstrings = {
      ['COMMENT line'] = {'COMMENT ', ''},
      ['{% comment %}line{% endcomment %}'] = {'{% comment %}', '{% endcomment %}'},
      ['c# line'] = {'c# ', ''},
      ['dnl line'] = {'dnl ', ''},
      ['NB. line'] = {'NB. ', ''},
      ['! line'] = {'! ', ''},
      ['#line'] = {'#', ''},
      ['# line'] = {'# ', ''},
      ['%line'] = {'%', ''},
      ['% line'] = {'% ', ''},
      ['(*line*)'] = {'(*', '*)'},
      ['(;line;)'] = {'(;', ';)'},
      ['**line'] = {'**', ''},
      ['-# line'] = {'-# ', ''},
      ['-- line'] = {'-- ', ''},
      ['--  line'] = {'--  ', ''},
      ['.. line'] = {'.. ', ''},
      ['.\\"line'] = {'.\\"', ''},
      ['/*line*/'] = {'/*', '*/'},
      ['/* line */'] = {'/* ', ' */'},
      ['//line'] = {'//', ''},
      ['// line'] = {'// ', ''},
      [':: line'] = {':: ', ''},
      [';line'] = {';', ''},
      ['; line'] = {'; ', ''},
      ['; // line'] = {'; // ', ''},
      ['<!--line-->'] = {'<!--', '-->'},
      ['<%#line%>'] = {'<%#', '%>'},
      ['> line'] = {'> ', ''},
      -- ['      *line'] = {'      *', ''},
      ['"line'] = {'"', ''},
    }

    for input,comment_parts in pairs(commentstrings) do
      it('Should uncomment line as expected: ' .. input, function()
        local actual = testModule.uncomment_line(input, comment_parts[1], comment_parts[2], false)

        assert.equals('line', actual)
      end)
    end

    it('Should uncomment if trailing whitespace in left hand side removed, no right hand side', function()
        local actual = testModule.uncomment_line("--", "-- ", "", true)

        assert.equals('', actual)
    end)

    it('Should uncomment and not leave padding when comment_empty_trim_whitespace false, no right hand side', function()
        local actual = testModule.uncomment_line("-- test", "-- ", "", true)

        assert.equals('test', actual)
    end)
  end)

  describe('comment_toggle', function()
    it('Should add left hand side comments only on entire range', function()
      api_mock.nvim_buf_get_option.on_call_with(0, 'commentstring').returns('-- %s')
      api_mock.nvim_buf_get_lines.on_call_with(0, 0, 3, false).returns({
        "line1",
        "line2",
        "line3",
      })

      testModule.comment_toggle(1, 3)

      assert.stub(api_mock.nvim_call_function).was_called_with("setline", {1, {
        "-- line1",
        "-- line2",
        "-- line3",
      }})
      assert.stub(api_mock.nvim_call_function).was_called_with('setpos', {"'<", {0, 1, 1, 0}})
      assert.stub(api_mock.nvim_call_function).was_called_with('setpos', {"'>", {0, 3, 2147483647, 0}})

    end)

    it('Should remove left hand side comments only on entire range', function()
      api_mock.nvim_buf_get_option.on_call_with(0, 'commentstring').returns('-- %s')
      api_mock.nvim_buf_get_lines.on_call_with(0, 0, 3, false).returns({
        "-- line1",
        "-- line2",
        "-- line3",
      })

      testModule.comment_toggle(1, 3)

      assert.stub(api_mock.nvim_call_function).was_called_with("setline", {1, {
        "line1",
        "line2",
        "line3",
      }})
      assert.stub(api_mock.nvim_call_function).was_called_with('setpos', {"'<", {0, 1, 1, 0}})
      assert.stub(api_mock.nvim_call_function).was_called_with('setpos', {"'>", {0, 3, 2147483647, 0}})
    end)

    it('Should add comments on uncommented lines to entire range', function()
      api_mock.nvim_buf_get_option.on_call_with(0, 'commentstring').returns('-- %s')
      api_mock.nvim_buf_get_lines.on_call_with(0, 0, 3, false).returns({
        "line1",
        "-- line2",
        "line3",
      })

      testModule.comment_toggle(1, 3)

      assert.stub(api_mock.nvim_call_function).was_called_with("setline", {1, {
        "-- line1",
        "-- -- line2",
        "-- line3",
      }})
      assert.stub(api_mock.nvim_call_function).was_called_with('setpos', {"'<", {0, 1, 1, 0}})
      assert.stub(api_mock.nvim_call_function).was_called_with('setpos', {"'>", {0, 3, 2147483647, 0}})
    end)

    it('Should add left and right hand side comments to entire range', function()
      api_mock.nvim_buf_get_option.on_call_with(0, 'commentstring').returns('(*%s*)')
      api_mock.nvim_buf_get_lines.on_call_with(0, 0, 3, false).returns({
        "line1",
        "line2",
        "line3",
      })

      testModule.comment_toggle(1, 3)

      assert.stub(api_mock.nvim_call_function).was_called_with("setline", {1, {
        "(* line1 *)",
        "(* line2 *)",
        "(* line3 *)",
      }})
      assert.stub(api_mock.nvim_call_function).was_called_with('setpos', {"'<", {0, 1, 1, 0}})
      assert.stub(api_mock.nvim_call_function).was_called_with('setpos', {"'>", {0, 3, 2147483647, 0}})
    end)

    it('Should remove left and right hand side comments to entire range', function()
      api_mock.nvim_buf_get_option.on_call_with(0, 'commentstring').returns('(*%s*)')
      api_mock.nvim_buf_get_lines.on_call_with(0, 0, 3, false).returns({
        "(* line1 *)",
        "(* line2 *)",
        "(* line3 *)",
      })

      testModule.comment_toggle(1, 3)

      assert.stub(api_mock.nvim_call_function).was_called_with("setline", {1, {
        "line1",
        "line2",
        "line3",
      }})
      assert.stub(api_mock.nvim_call_function).was_called_with('setpos', {"'<", {0, 1, 1, 0}})
      assert.stub(api_mock.nvim_call_function).was_called_with('setpos', {"'>", {0, 3, 2147483647, 0}})
    end)

    it('Should not do anything if commentstring not supported', function()
      api_mock.nvim_buf_get_option.on_call_with(0, 'commentstring').returns('whatwhat')

      testModule.comment_toggle(1, 3)

      assert.stub(api_mock.nvim_buf_get_lines).was_not_called()
      assert.stub(api_mock.nvim_buf_set_lines).was_not_called()
    end)
  end)
end)


================================================
FILE: tests/comment_spec.lua
================================================
local function setUpBuffer(input, filetype)
	local buf = vim.api.nvim_create_buf(false, true)
	vim.api.nvim_buf_set_option(buf, "filetype", filetype)
	vim.api.nvim_command("buffer " .. buf)

	vim.api.nvim_buf_set_lines(0, 0, -1, true, vim.split(input, "\n"))
end

local function goToLineRunKeys(line, feedkeys)
	vim.api.nvim_win_set_cursor(0, { line, 0 })
	local keys = vim.api.nvim_replace_termcodes(feedkeys, true, false, true)
	vim.api.nvim_feedkeys(keys, "x", false)
end
local function getBufLines()
	local result = vim.api.nvim_buf_get_lines(0, 0, vim.api.nvim_buf_line_count(0), false)
	return result
end

local function runCommandAndAssert(line, feedkeys, expected)
	goToLineRunKeys(line, feedkeys)
	local result = getBufLines()
	assert.are.same(vim.split(expected, "\n"), result)
end

describe("comment/uncomment", function()
	local input = [[
local function dummy_func()
  print("This is a dummy func")
end

local function another_dummy_func()
  print("This is a another dummy func")
end
]]

	before_each(function()
		local testModule = require("nvim_comment")
		testModule.setup({
			marker_padding = true,
		})
	end)

	after_each(function() end)

	it("Should comment/uncomment line with dot repeatable", function()
		local expected = [[
-- local function dummy_func()
  print("This is a dummy func")
end

local function another_dummy_func()
  print("This is a another dummy func")
end
]]

		setUpBuffer(input, "lua")
		-- comment
		runCommandAndAssert(1, "gcc", expected)
		-- uncomment
		runCommandAndAssert(1, "gcc", input)
		-- comment, via dot
		runCommandAndAssert(1, ".", expected)
	end)

	it("Should comment/uncomment via motion and dot", function()
		local expected = [[
-- local function dummy_func()
  print("This is a dummy func")
end

local function another_dummy_func()
  print("This is a another dummy func")
end
]]

		setUpBuffer(input, "lua")
		-- comment
		runCommandAndAssert(1, "gcl", expected)
		-- uncomment
		runCommandAndAssert(1, "gcl", input)
		-- comment, via dot
		runCommandAndAssert(1, ".", expected)
	end)

	it("Should comment/uncomment motion with count and dot", function()
		local expected = [[
-- local function dummy_func()
--   print("This is a dummy func")
-- end

local function another_dummy_func()
  print("This is a another dummy func")
end
]]

		setUpBuffer(input, "lua")
		-- comment
		runCommandAndAssert(1, "gc2j", expected)
		-- uncomment
		runCommandAndAssert(1, "gc2j", input)
		-- comment, via dot
		runCommandAndAssert(1, ".", expected)
	end)

	it("Should comment out another pararaph via dot", function()
		local first_expected = [[
-- local function dummy_func()
--   print("This is a dummy func")
-- end

local function another_dummy_func()
  print("This is a another dummy func")
end
]]

		local second_expected = [[
-- local function dummy_func()
--   print("This is a dummy func")
-- end

-- local function another_dummy_func()
--   print("This is a another dummy func")
-- end
]]

		setUpBuffer(input, "lua")
		-- comment
		runCommandAndAssert(2, "gcip", first_expected)
		-- comment, via dot
		runCommandAndAssert(7, ".", second_expected)
		-- uncomment, via dot
		runCommandAndAssert(7, "gcip", first_expected)
	end)
end)

describe("padding flag", function()
	local input = [[
<note>
  <to>Tove</to>
  <from>Jani</from>
</note>
]]

	it("Should add padding", function()
		require("nvim_comment").setup({ marker_padding = true })

		local expected = [[
<note>
  <to>Tove</to>
  <!-- <from>Jani</from> -->
</note>
]]

		setUpBuffer(input, "xml")
		-- comment
		runCommandAndAssert(3, "gcl", expected)
	end)
	it("Should not add padding", function()
		require("nvim_comment").setup({ marker_padding = false })

		local expected = [[
<note>
  <to>Tove</to>
  <!--<from>Jani</from>-->
</note>
]]

		setUpBuffer(input, "xml")
		-- comment
		runCommandAndAssert(3, "gcl", expected)
	end)
end)

describe("mapping with leader as space", function()
	local input = [[
<note>
  <to>Tove</to>
  <from>Jani</from>
</note>
]]

	it("Should toggle comment with line mapping", function()
		require("nvim_comment").setup({
			line_mapping = " cc",
			operator_mapping = " c",
		})

		local expected = [[
<note>
  <to>Tove</to>
  <!--<from>Jani</from>-->
</note>
]]

		setUpBuffer(input, "xml")
		-- comment
		runCommandAndAssert(3, " cc", expected)
	end)

	it("Should toggle comment with operator mapping", function()
		require("nvim_comment").setup({
			line_mapping = " cc",
			operator_mapping = " c",
		})

		local expected = [[
<note>
  <to>Tove</to>
<!--  <from>Jani</from>-->
<!--</note>-->
]]

		setUpBuffer(input, "xml")
		-- comment
		runCommandAndAssert(3, " c1j", expected)
	end)
end)

describe("comment empty flag", function()
	local input = [[
local function dummy_func()
  print("This is a dummy func")
end

local function another_dummy_func()
  print("This is a another dummy func")
end]]

	it("Should comment empty lines", function()
		require("nvim_comment").setup({
			marker_padding = true,
			comment_empty = true,
		})

		-- luacheck: ignore
		local expected = [[
-- local function dummy_func()
--   print("This is a dummy func")
-- end
--
-- local function another_dummy_func()
--   print("This is a another dummy func")
-- end]]

		setUpBuffer(input, "lua")
		-- comment
		runCommandAndAssert(1, "vGgc", expected)
		-- uncomment
		runCommandAndAssert(1, "vGgc", input)
	end)

	it("Should not comment empty lines", function()
		require("nvim_comment").setup({
			marker_padding = true,
			comment_empty = false,
		})

		local expected = [[
-- local function dummy_func()
--   print("This is a dummy func")
-- end

-- local function another_dummy_func()
--   print("This is a another dummy func")
-- end]]

		setUpBuffer(input, "lua")
		-- comment
		runCommandAndAssert(1, "vGgc", expected)
		-- comment, via dot
		runCommandAndAssert(1, "vGgc", input)
	end)
end)

describe("comment chunks", function()
	before_each(function()
		local testModule = require("nvim_comment")
		testModule.setup({
			marker_padding = true,
			comment_empty = true,
		})
	end)

	local input = [[
-- local foo = 'foo'
-- local bar = 'bar'
-- local baz = 'baz'
local foo = 'foo'
local baz = 'baz'
-- local foo = 'foo'
local baz = 'baz'
-- local foo = 'foo'
-- local foo = 'foo'
local bar = 'bar'
local baz = 'baz'
-- local foo = 'foo'
-- local bar = 'bar']]

	it("Should handle text obeject at top of buffer", function()
		local expected = [[
local foo = 'foo'
local bar = 'bar'
local baz = 'baz'
local foo = 'foo'
local baz = 'baz'
-- local foo = 'foo'
local baz = 'baz'
-- local foo = 'foo'
-- local foo = 'foo'
local bar = 'bar'
local baz = 'baz'
-- local foo = 'foo'
-- local bar = 'bar']]

		setUpBuffer(input, "lua")
		runCommandAndAssert(1, "gcic", expected)
	end)

	it("Should handle text obeject at botton of buffer", function()
		local expected = [[
-- local foo = 'foo'
-- local bar = 'bar'
-- local baz = 'baz'
local foo = 'foo'
local baz = 'baz'
-- local foo = 'foo'
local baz = 'baz'
-- local foo = 'foo'
-- local foo = 'foo'
local bar = 'bar'
local baz = 'baz'
local foo = 'foo'
local bar = 'bar']]

		setUpBuffer(input, "lua")
		runCommandAndAssert(1, "Ggcic", expected)
	end)

	it("Should handle single line text object", function()
		local expected = [[
-- local foo = 'foo'
-- local bar = 'bar'
-- local baz = 'baz'
local foo = 'foo'
local baz = 'baz'
local baz = 'baz'
-- local foo = 'foo'
-- local foo = 'foo'
local bar = 'bar'
local baz = 'baz'
-- local foo = 'foo'
-- local bar = 'bar']]

		setUpBuffer(input, "lua")
		runCommandAndAssert(6, "dic", expected)
	end)

	it("Should handle multi line text object", function()
		local expected = [[
-- local foo = 'foo'
-- local bar = 'bar'
-- local baz = 'baz'
local foo = 'foo'
local baz = 'baz'
-- local foo = 'foo'
local baz = 'baz'
local bar = 'bar'
local baz = 'baz'
-- local foo = 'foo'
-- local bar = 'bar']]

		setUpBuffer(input, "lua")
		runCommandAndAssert(9, "dic", expected)
	end)
end)

describe("issues", function()
	before_each(function()
		local testModule = require("nvim_comment")
		testModule.setup({
			marker_padding = true,
			comment_empty = true,
		})
	end)

	it("issue 22", function()
		local input = [[
local foo = 'foo'
local bar = 'bar'
local baz = 'baz'
local foo = 'foo'
local bar = 'bar'
local baz = 'baz'
]]
		local expected = [[
-- local foo = 'foo'
-- local bar = 'bar'
-- local baz = 'baz'
local foo = 'foo'
local bar = 'bar'
local baz = 'baz'
]]

		setUpBuffer(input, "lua")
		runCommandAndAssert(1, "gg0<c-v>jjgc", expected)
	end)

	it("issue 31", function()
		local input = [[
this is some block
  of idented text

  with empty lines
yeah]]

		local expected = [[
this is some block
  -- of idented text
  --
  with empty lines
yeah]]
		setUpBuffer(input, "lua")
		runCommandAndAssert(1, "ggjVjgc", expected)
	end)
end)


================================================
FILE: tests/minimal_init.vim
================================================

set rtp+=.
set rtp+=../plenary.nvim/
set hidden

runtime! plugin/plenary.vim
Download .txt
gitextract_xu0egi6i/

├── .github/
│   └── workflows/
│       └── make.yml
├── .luacheckrc
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── lua/
│   ├── nvim_comment.lua
│   └── spec/
│       └── nvim_comment_spec.lua
└── tests/
    ├── comment_spec.lua
    └── minimal_init.vim
Condensed preview — 10 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (39K chars).
[
  {
    "path": ".github/workflows/make.yml",
    "chars": 1041,
    "preview": "name: Run Tests\n\non: [push, pull_request]\n\njobs:\n  Tests:\n    name: Linting and Test\n    runs-on: ubuntu-20.04\n    steps"
  },
  {
    "path": ".luacheckrc",
    "chars": 244,
    "preview": "globals = {\"Snake\", \"Cell\", \"vim\", \"_TEST\", \"LOG_LEVEL\"}\nread_globals = {\n  print = {\n    fields = {\n      revert = {}\n "
  },
  {
    "path": "CHANGELOG.md",
    "chars": 252,
    "preview": "Changelog\n======================\n\n2021-07-10 - Add hook function to be called before reading `commentstring`\n2021-02-16 "
  },
  {
    "path": "LICENSE",
    "chars": 1067,
    "preview": "MIT License\n\nCopyright (c) 2021 Alex Tylor\n\nPermission is hereby granted, free of charge, to any person obtaining a copy"
  },
  {
    "path": "Makefile",
    "chars": 565,
    "preview": "all: lint test\n\nlint:\n\t@echo \"Linting...\"\n\tluacheck --no-color .\n\t@echo\n\nunit:\n\t@echo \"Run unit tests...\"\n\tnvim --headle"
  },
  {
    "path": "README.md",
    "chars": 5252,
    "preview": "# nvim-comment\n\nToggle comments in Neovim, using built in `commentstring` filetype option;\nwritten in Lua. Without a dou"
  },
  {
    "path": "lua/nvim_comment.lua",
    "chars": 6376,
    "preview": "local api = vim.api\n\nlocal M = {}\n\nM.config = {\n  -- Linters prefer comment and line to have a space in between\n  marker"
  },
  {
    "path": "lua/spec/nvim_comment_spec.lua",
    "chars": 12518,
    "preview": "local testModule\nlocal mock = require('luassert.mock')\nlocal api_mock\n\ndescribe('nvim-comment', function()\n\n  before_eac"
  },
  {
    "path": "tests/comment_spec.lua",
    "chars": 8772,
    "preview": "local function setUpBuffer(input, filetype)\n\tlocal buf = vim.api.nvim_create_buf(false, true)\n\tvim.api.nvim_buf_set_opti"
  },
  {
    "path": "tests/minimal_init.vim",
    "chars": 78,
    "preview": "\nset rtp+=.\nset rtp+=../plenary.nvim/\nset hidden\n\nruntime! plugin/plenary.vim\n"
  }
]

About this extraction

This page contains the full source code of the terrortylor/nvim-comment GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 10 files (35.3 KB), approximately 10.4k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!