Home

Awesome

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<Plug>(dial-increment)" or "g<Plug>(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:

SequenceMeaning
%Y4-digit year. (e.g. 2022)
%yLast 2 digits of year. The upper 2 digits are interpreted as 20. (e.g. 22)
%m2-digit month. (e.g. 09)
%d2-digit day. (e.g. 28)
%H2-digit hour, expressed in 24 hours. (e.g. 15)
%I2-digit hour, expressed in 12 hours. (e.g. 03)
%M2-digit minute. (e.g. 05)
%S2-digit second. (e.g. 08)
%-y1- or 2-digit year. (e.g. 9 represents 2009)
%-m1- or 2-digit month. (e.g. 9)
%-d1- or 2-digit day. (e.g. 28)
%-H1- or 2-digit hour, expressed in 24 hours. (e.g. 15)
%-I1- or 2-digit hour, expressed in 12 hours. (e.g. 3)
%-M1- or 2-digit minute. (e.g. 5)
%-S1- or 2-digit second. (e.g. 8)
%aEnglish weekdays (Sun, Mon, ..., Sat)
%AEnglish full weekdays (Sunday, Monday, ..., Saturday)
%bEnglish month names (Jan, ..., Dec)
%BEnglish month full names (January, ..., December)
%pAM or PM.
%JJapanese 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 NameExplanationExamples
augend.integer.alias.decimaldecimal natural number0, 1, ..., 9, 10, 11, ...
augend.integer.alias.decimal_intdecimal integer (including negative number)0, 314, -1592, ...
augend.integer.alias.hexhex natural number0x00, 0x3f3f, ...
augend.integer.alias.octaloctal natural number0o00, 0o11, 0o24, ...
augend.integer.alias.binarybinary natural number0b0101, 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:%S14:30:00, ...
augend.date.alias["%H:%M"]Time in the format %H:%M14:30, ...
augend.constant.alias.de_weekdayGerman weekdayMo, Di, ..., Sa, So
augend.constant.alias.de_weekday_fullGerman full weekdayMontag, Dienstag, ..., Sonntag
augend.constant.alias.ja_weekdayJapanese weekday, , ..., ,
augend.constant.alias.ja_weekday_fullJapanese full weekday月曜日, 火曜日, ..., 日曜日
augend.constant.alias.boolelements in boolean algebra (true and false)true, false
augend.constant.alias.alphaLowercase alphabet letter (word)a, b, c, ..., z
augend.constant.alias.AlphaUppercase alphabet letter (word)A, B, C, ..., Z
augend.semver.alias.semverSemantic version0.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.