monaqa / dial.nvim

enhanced increment/decrement plugin for Neovim.
MIT License
785 stars 12 forks source link
lua neovim neovim-plugin

dial.nvim

NOTICE: This plugin is work-in-progress yet. User interface is subject to change without notice.

FOR USERS OF THE PREVIOUS VERSION (v0.2.0)

This plugin was released v0.3.0 on 2022/02/20 and is no longer compatible with the old interface. If you have configured the settings for previous versions, please refer to TROUBLESHOOTING.md and reconfigure them.

Abstract

Extended increment/decrement plugin for Neovim. Written in Lua.

demo.gif

Features

Similar plugins

Installation

dial.nvim requires Neovim >=0.5.0 (>=0.6.1 is recommended). You can install dial.nvim by following the instructions of your favorite package manager.

Usage

This plugin does not provide or override any default key-mappings. To use this plugin, you need to assign the plugin key-mapping to the key you like, as shown below:

nmap  <C-a>  <Plug>(dial-increment)
nmap  <C-x>  <Plug>(dial-decrement)
nmap g<C-a> g<Plug>(dial-increment)
nmap g<C-x> g<Plug>(dial-decrement)
vmap  <C-a>  <Plug>(dial-increment)
vmap  <C-x>  <Plug>(dial-decrement)
vmap g<C-a> g<Plug>(dial-increment)
vmap g<C-x> g<Plug>(dial-decrement)

Note: When you use "g(dial-increment)" or "g(dial-decrement)" on the right side, remap option must be enabled.

Or you can configure it with Lua as follows:

vim.keymap.set("n", "<C-a>", function()
    require("dial.map").manipulate("increment", "normal")
end)
vim.keymap.set("n", "<C-x>", function()
    require("dial.map").manipulate("decrement", "normal")
end)
vim.keymap.set("n", "g<C-a>", function()
    require("dial.map").manipulate("increment", "gnormal")
end)
vim.keymap.set("n", "g<C-x>", function()
    require("dial.map").manipulate("decrement", "gnormal")
end)
vim.keymap.set("v", "<C-a>", function()
    require("dial.map").manipulate("increment", "visual")
end)
vim.keymap.set("v", "<C-x>", function()
    require("dial.map").manipulate("decrement", "visual")
end)
vim.keymap.set("v", "g<C-a>", function()
    require("dial.map").manipulate("increment", "gvisual")
end)
vim.keymap.set("v", "g<C-x>", function()
    require("dial.map").manipulate("decrement", "gvisual")
end)

Configuration

In this plugin, flexible increment/decrement rules can be set by using augend and group, where augend represents the target of the increment/decrement operation, and group represents a group of multiple augends.

local augend = require("dial.augend")
require("dial.config").augends:register_group{
  -- default augends used when no group name is specified
  default = {
    augend.integer.alias.decimal,   -- nonnegative decimal number (0, 1, 2, 3, ...)
    augend.integer.alias.hex,       -- nonnegative hex number  (0x01, 0x1a1f, etc.)
    augend.date.alias["%Y/%m/%d"],  -- date (2022/02/19, etc.)
  },

  -- augends used when group with name `mygroup` is specified
  mygroup = {
    augend.integer.alias.decimal,
    augend.constant.alias.bool,    -- boolean value (true <-> false)
    augend.date.alias["%m/%d/%Y"], -- date (02/19/2022, etc.)
  }
}

To specify the group of augends, you can use expression register (:h @=) as follows:

"=mygroup<CR><C-a>

If it is tedious to specify the expression register for each operation, you can "map" it:

nmap <Leader>a "=mygroup<CR><Plug>(dial-increment)

Alternatively, you can set the same mapping without expression register:

vim.keymap.set("n", "<Leader>a", require("dial.map").inc_normal("mygroup"), {noremap = true})

When you don't specify any group name in the way described above, the addends in the default group is used instead.

Example Configuration

lua << EOF
local augend = require("dial.augend")
require("dial.config").augends:register_group{
  default = {
    augend.integer.alias.decimal,
    augend.integer.alias.hex,
    augend.date.alias["%Y/%m/%d"],
  },
  typescript = {
    augend.integer.alias.decimal,
    augend.integer.alias.hex,
    augend.constant.new{ elements = {"let", "const"} },
  },
  visual = {
    augend.integer.alias.decimal,
    augend.integer.alias.hex,
    augend.date.alias["%Y/%m/%d"],
    augend.constant.alias.alpha,
    augend.constant.alias.Alpha,
  },
}

-- change augends in VISUAL mode
vim.keymap.set("v", "<C-a>", require("dial.map").inc_visual("visual"), {noremap = true})
vim.keymap.set("v", "<C-x>", require("dial.map").dec_visual("visual"), {noremap = true})
EOF

" enable only for specific FileType
autocmd FileType typescript lua vim.api.nvim_buf_set_keymap(0, "n", "<C-a>", require("dial.map").inc_normal("typescript"), {noremap = true})
autocmd FileType typescript lua vim.api.nvim_buf_set_keymap(0, "n", "<C-x>", require("dial.map").dec_normal("typescript"), {noremap = true})

List of Augends

For simplicity, we define the variable augend as follows.

local augend = require("dial.augend")

integer

n-based integer (2 <= n <= 36). You can use this rule with augend.integer.new{ ...opts }.

require("dial.config").augends:register_group{
  default = {
    -- uppercase hex number (0x1A1A, 0xEEFE, etc.)
    augend.integer.new{
      radix = 16,
      prefix = "0x",
      natural = true,
      case = "upper",
    },
  },
}

date

Date and time.

require("dial.config").augends:register_group{
  default = {
    -- date with format `yyyy/mm/dd`
    augend.date.new{
        pattern = "%Y/%m/%d",
        default_kind = "day",
        -- if true, it does not match dates which does not exist, such as 2022/05/32
        only_valid = true,
        -- if true, it only matches dates with word boundary
        word = false,
    },
  },
}

In the pattern argument, you can use the following escape sequences:

Sequence Meaning
%Y 4-digit year. (e.g. 2022)
%y Last 2 digits of year. The upper 2 digits are interpreted as 20. (e.g. 22)
%m 2-digit month. (e.g. 09)
%d 2-digit day. (e.g. 28)
%H 2-digit hour, expressed in 24 hours. (e.g. 15)
%I 2-digit hour, expressed in 12 hours. (e.g. 03)
%M 2-digit minute. (e.g. 05)
%S 2-digit second. (e.g. 08)
%-y 1- or 2-digit year. (e.g. 9 represents 2009)
%-m 1- or 2-digit month. (e.g. 9)
%-d 1- or 2-digit day. (e.g. 28)
%-H 1- or 2-digit hour, expressed in 24 hours. (e.g. 15)
%-I 1- or 2-digit hour, expressed in 12 hours. (e.g. 3)
%-M 1- or 2-digit minute. (e.g. 5)
%-S 1- or 2-digit second. (e.g. 8)
%a English weekdays (Sun, Mon, ..., Sat)
%A English full weekdays (Sunday, Monday, ..., Saturday)
%b English month names (Jan, ..., Dec)
%B English month full names (January, ..., December)
%p AM or PM.
%J Japanese weekdays (, , ..., )

constant

Predefined sequence of strings. You can use this rule with augend.constant.new{ ...opts }.

require("dial.config").augends:register_group{
  default = {
    -- uppercase hex number (0x1A1A, 0xEEFE, etc.)
    augend.constant.new{
      elements = {"and", "or"},
      word = true, -- if false, "sand" is incremented into "sor", "doctor" into "doctand", etc.
      cyclic = true,  -- "or" is incremented into "and".
    },
    augend.constant.new{
      elements = {"&&", "||"},
      word = false,
      cyclic = true,
    },
  },
}

hexcolor

RGB color code such as #000000 and #ffffff.

require("dial.config").augends:register_group{
  default = {
    -- uppercase hex number (0x1A1A, 0xEEFE, etc.)
    augend.hexcolor.new{
      case = "lower",
    },
  },
}

semver

Semantic versions. You can use this rule by augend alias described below.

It differs from a simple nonnegative integer increment/decrement in these ways:

user

Custom augends.

require("dial.config").augends:register_group{
  default = {
    -- uppercase hex number (0x1A1A, 0xEEFE, etc.)
    augend.user.new{
      find = require("dial.augend.common").find_pattern("%d+"),
      add = function(text, addend, cursor)
          local n = tonumber(text)
          n = math.floor(n * (2 ^ addend))
          text = tostring(n)
          cursor = #text
          return {text = text, cursor = cursor}
      end
    },
  },
}

Augend Alias

Some augend rules are defined as alias. It can be used directly without using new function.

require("dial.config").augends:register_group{
  default = {
    augend.integer.alias.decimal,
    augend.integer.alias.hex,
    augend.date.alias["%Y/%m/%d"],
  },
}
Alias Name Explanation Examples
augend.integer.alias.decimal decimal natural number 0, 1, ..., 9, 10, 11, ...
augend.integer.alias.decimal_int decimal integer (including negative number) 0, 314, -1592, ...
augend.integer.alias.hex hex natural number 0x00, 0x3f3f, ...
augend.integer.alias.octal octal natural number 0o00, 0o11, 0o24, ...
augend.integer.alias.binary binary natural number 0b0101, 0b11001111, ...
augend.date.alias["%Y/%m/%d"] Date in the format %Y/%m/%d (0 padding) 2021/01/23, ...
augend.date.alias["%m/%d/%Y"] Date in the format %m/%d/%Y (0 padding) 23/01/2021, ...
augend.date.alias["%d/%m/%Y"] Date in the format %d/%m/%Y (0 padding) 01/23/2021, ...
augend.date.alias["%m/%d/%y"] Date in the format %m/%d/%y (0 padding) 01/23/21, ...
augend.date.alias["%d/%m/%y"] Date in the format %d/%m/%y (0 padding) 23/01/21, ...
augend.date.alias["%m/%d"] Date in the format %m/%d (0 padding) 01/04, 02/28, 12/25, ...
augend.date.alias["%-m/%-d"] Date in the format %-m/%-d (no paddings) 1/4, 2/28, 12/25, ...
augend.date.alias["%Y-%m-%d"] Date in the format %Y-%m-%d (0 padding) 2021-01-04, ...
augend.date.alias["%d.%m.%Y"] Date in the format %d.%m.%Y (0 padding) 23.01.2021, ...
augend.date.alias["%d.%m.%y"] Date in the format %d.%m.%y (0 padding) 23.01.21, ...
augend.date.alias["%d.%m."] Date in the format %d.%m. (0 padding) 04.01., 28.02., 25.12., ...
augend.date.alias["%-d.%-m."] Date in the format %-d.%-m. (no paddings) 4.1., 28.2., 25.12., ...
augend.date.alias["%Y年%-m月%-d日"] Date in the format %Y年%-m月%-d日 (no paddings) 2021年1月4日, ...
augend.date.alias["%Y年%-m月%-d日(%ja)"] Date in the format %Y年%-m月%-d日(%ja) 2021年1月4日(月), ...
augend.date.alias["%H:%M:%S"] Time in the format %H:%M:%S 14:30:00, ...
augend.date.alias["%H:%M"] Time in the format %H:%M 14:30, ...
augend.constant.alias.de_weekday German weekday Mo, Di, ..., Sa, So
augend.constant.alias.de_weekday_full German full weekday Montag, Dienstag, ..., Sonntag
augend.constant.alias.ja_weekday Japanese weekday , , ..., ,
augend.constant.alias.ja_weekday_full Japanese full weekday 月曜日, 火曜日, ..., 日曜日
augend.constant.alias.bool elements in boolean algebra (true and false) true, false
augend.constant.alias.alpha Lowercase alphabet letter (word) a, b, c, ..., z
augend.constant.alias.Alpha Uppercase alphabet letter (word) A, B, C, ..., Z
augend.semver.alias.semver Semantic version 0.3.0, 1.22.1, 3.9.1, ...

If you don't specify any settings, the following augends is set as the value of the default group.

Changelog

See HISTORY.

Testing

This plugin uses PlenaryBustedDirectory in plenary.nvim.