Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ require("wmc").setup({
})
```

| Option | Explanation |
| Option | Explanation |
|-|-|
| enabled | Toggle `wmc` |
| ui | Configure style rank display window (partially derived from `vim.api.keyset.win_config`). `row` and `col` support passing a function for dynamic Neovim window resize handling |
| logger | Toggle debug logging and change log destination |
| ui | Configure style rank display window (partially derived from `vim.api.keyset.win_config`). `row` and `col` support passing a function for dynamic Neovim window resize handling. `progress_bar` configures rank progression visualization |
| logger | Toggle debug logging and change log destination |

## Development

Expand Down
13 changes: 13 additions & 0 deletions lua/wmc/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ local M = {}
---@field col number|fun(): number
---@field zindex number
---@field border string
---@field progress_bar wmc.config.ui.progress_bar

---@class (exact) wmc.config.ui.progress_bar
---@field enabled boolean
---@field extra_width integer

---@type wmc.config
M.default = {
Expand All @@ -27,6 +32,10 @@ M.default = {
end,
zindex = 50,
border = "none",
progress_bar = {
enabled = false,
extra_width = 4,
},
},
logger = {
enabled = false,
Expand Down Expand Up @@ -63,6 +72,10 @@ function M.validate(config)
M.validate_entry({ zindex = { config.ui.zindex, "number", true } }, validation_errors)
M.validate_entry({ border = { config.ui.border, { "string", "table" }, true } }, validation_errors)

M.validate_entry({ progress_bar = { config.ui.progress_bar, "table", true } }, validation_errors)
M.validate_entry({ enabled = { config.ui.progress_bar.enabled, "boolean", true } }, validation_errors)
M.validate_entry({ padding = { config.ui.progress_bar.extra_width, "number", true } }, validation_errors)

M.validate_entry({ logger = { config.logger, "table", true } }, validation_errors)
M.validate_entry({ enabled = { config.logger.enabled, "boolean", true } }, validation_errors)
M.validate_entry({ log_file_path = { config.logger.log_file_path, "string", true } }, validation_errors)
Expand Down
36 changes: 32 additions & 4 deletions lua/wmc/constants.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,34 @@ M.RANK_LABEL = {
STYLISH = "Stylish!",
}

---@class wmc.rank_score
---@field min_score integer
---@field max_score integer|nil

---@type table<wmc.rank_label, wmc.rank_score>
M.RANK_SCORE = {
[M.RANK_LABEL.DULL] = {
min_score = 20,
max_score = 110,
},
[M.RANK_LABEL.COOL] = {
min_score = 110,
max_score = 200,
},
[M.RANK_LABEL.BRAVO] = {
min_score = 200,
max_score = 300,
},
[M.RANK_LABEL.ABSOLUTE] = {
min_score = 300,
max_score = 390,
},
[M.RANK_LABEL.STYLISH] = {
min_score = 390,
max_score = nil,
},
}

---@class wmc.rank
---@field label wmc.rank_label
---@field min_score integer
Expand All @@ -19,19 +47,19 @@ M.RANK_LABEL = {
M.RANK_PROGRESSION = {
[M.RANK_LABEL.DULL] = {
label = M.RANK_LABEL.COOL,
min_score = 110,
min_score = M.RANK_SCORE[M.RANK_LABEL.COOL].min_score,
},
[M.RANK_LABEL.COOL] = {
label = M.RANK_LABEL.BRAVO,
min_score = 200,
min_score = M.RANK_SCORE[M.RANK_LABEL.BRAVO].min_score,
},
[M.RANK_LABEL.BRAVO] = {
label = M.RANK_LABEL.ABSOLUTE,
min_score = 300,
min_score = M.RANK_SCORE[M.RANK_LABEL.ABSOLUTE].min_score,
},
[M.RANK_LABEL.ABSOLUTE] = {
label = M.RANK_LABEL.STYLISH,
min_score = 390,
min_score = M.RANK_SCORE[M.RANK_LABEL.STYLISH].min_score,
},
}

Expand Down
63 changes: 45 additions & 18 deletions lua/wmc/ranker.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
local config = require("wmc.config")
local constants = require("wmc.constants")
local logger = require("wmc.logger")
local timer = require("wmc.utils.timer")
Expand All @@ -7,7 +8,8 @@ local ui = require("wmc.ui")
---@class wmc.ranker
---@field private combo wmc.ring_buffer
---@field private is_combo_valid boolean
---@field private rank wmc.rank_label|nil
---@field private rank_label wmc.rank_label|nil
---@field private rank_progression number|nil
---@field private last_input_sec number
---@field private watcher uv.uv_timer_t
---@field private ui wmc.ui
Expand Down Expand Up @@ -38,7 +40,8 @@ function M:start_combo_watcher()
self.combo:clear()
self.is_combo_valid = true
self.last_input_sec = 0
self.rank = nil
self.rank_label = nil
self.rank_progression = nil

self.ui:clear()

Expand All @@ -56,7 +59,7 @@ function M:start_combo_watcher()
end

---@return fun(key: string, typed: string): string?
function M:on_key()
function M:on_key_entered()
return function(key, typed)
self.last_input_sec = 0

Expand Down Expand Up @@ -86,16 +89,17 @@ function M:on_key()
if not self.is_combo_valid then
local invalid_rank = constants.RANK_LABEL.DULL

if invalid_rank ~= self.rank then
self.ui:render(invalid_rank)
if invalid_rank ~= self.rank_label or self.rank_progression then
self.ui:render(invalid_rank, nil)
end

self.rank = invalid_rank
self.rank_label = invalid_rank
self.rank_progression = nil

logger.log(
("Length: %d, Rank - %s (Invalid)\n%s"):format(
self.combo:get_length(),
self.rank,
self.rank_label,
vim.fn.keytrans(self.combo:to_string())
)
)
Expand All @@ -107,12 +111,17 @@ function M:on_key()
local combo_score = self:get_combo_score(combo_entropy)

local new_rank = self:get_display_rank(combo_entropy, combo_score)
local rank_progression = new_rank
and config.options.ui.progress_bar.enabled
and self.get_rank_progression(new_rank, combo_score)
or nil

if new_rank and new_rank ~= self.rank then
self.ui:render(new_rank)
if new_rank and (new_rank ~= self.rank_label or rank_progression ~= self.rank_progression) then
self.ui:render(new_rank, rank_progression)
end

self.rank = new_rank
self.rank_label = new_rank
self.rank_progression = rank_progression

logger.log(
("%s: %d, %s: %f, %s: %f, %s: %s\n%s"):format(
Expand All @@ -123,7 +132,7 @@ function M:on_key()
"Score",
combo_score,
"Rank",
self.rank,
self.rank_label,
vim.fn.keytrans(self.combo:to_string())
)
)
Expand Down Expand Up @@ -154,28 +163,46 @@ function M:get_combo_entropy()
return entropy
end

---@private
---@param combo_entropy number
---@return number
function M:get_combo_score(combo_entropy)
return math.pow(combo_entropy, 2) * self.combo:get_length()
end

---@private
---@param combo_entropy number
---@param combo_score number
---@return wmc.rank_label|nil
function M:get_display_rank(combo_entropy, combo_score)
if not self.rank and combo_entropy > 1.5 then
if not self.rank_label and combo_entropy > 1.5 then
return constants.RANK_LABEL.DULL
end

local next_rank = constants.RANK_PROGRESSION[self.rank]
local next_rank = constants.RANK_PROGRESSION[self.rank_label]
if not next_rank or next_rank.min_score > combo_score then
return self.rank
return self.rank_label
end

return next_rank.label
end

---@private
---@param combo_entropy number
---@return number
function M:get_combo_score(combo_entropy)
return math.pow(combo_entropy, 2) * self.combo:get_length()
---@param combo_rank wmc.rank_label
---@param combo_score number
---@return number|nil
function M.get_rank_progression(combo_rank, combo_score)
if not combo_rank then
return nil
end

local rank_score_bounds = constants.RANK_SCORE[combo_rank]
if not rank_score_bounds.max_score then
return nil
end

return math.max(combo_score - rank_score_bounds.min_score, 0)
/ (rank_score_bounds.max_score - rank_score_bounds.min_score)
end

return M
48 changes: 38 additions & 10 deletions lua/wmc/ui.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
local config = require("wmc.config")
local constants = require("wmc.constants")
local find_max_length = require("wmc.utils.string").find_max_length

Expand All @@ -7,6 +8,7 @@ local find_max_length = require("wmc.utils.string").find_max_length
---@field private extmark_id integer
---@field private max_rendered_text_length integer
---@field private last_rendered_text string|nil
---@field private last_renderered_progress number|nil
local M = {}

---@type table<wmc.rank_label, string>
Expand Down Expand Up @@ -75,17 +77,18 @@ function M:open_window()
relative = "editor",
anchor = "NE",
width = 1,
height = 1,
height = config.options.ui.progress_bar.enabled and 2 or 1,
zindex = 50,
style = "minimal",
border = "none",
focusable = false,
noautocmd = true,
}, require("wmc.config").options.ui)
}, config.options.ui)

-- Override functions passed in config for the initial hidden window
window_config.row = 1
window_config.col = 1
window_config.progress_bar = nil

self.window_id = vim.api.nvim_open_win(self.buffer_id, false, window_config)
if self.window_id == 0 then
Expand All @@ -111,48 +114,73 @@ function M:reopen_window()
self:open_window()

if self.last_rendered_text then
self:render(self.last_rendered_text)
self:render(self.last_rendered_text, self.last_renderered_progress)
end
end

---@param rank_label wmc.rank_label
function M:render(rank_label)
---@param rank_progress number|nil
function M:render(rank_label, rank_progress)
vim.schedule(function()
if not self:is_window_active() or not self:is_buffer_active() then
return
end

local ui_config = require("wmc.config").options.ui
local label_color = M.RANK_HL_GROUP_NAME[rank_label]
local width = config.options.ui.progress_bar.enabled
and self.max_rendered_text_length + config.options.ui.progress_bar.extra_width
or #rank_label

local extmark_options = {
virt_text = { { rank_label, M.RANK_HL_GROUP_NAME[rank_label] } },
virt_text_win_col = 0,
virt_text = { { rank_label, label_color } },
virt_text_win_col = config.options.ui.progress_bar.enabled and math.floor((width / 2) - (#rank_label / 2))
or 0,
}

if config.options.ui.progress_bar.enabled and rank_progress then
extmark_options.virt_lines = {
{ { self.prepare_progress_bar(rank_progress, width), label_color } },
}
end

if self.extmark_id then
extmark_options.id = self.extmark_id
end

vim.api.nvim_win_set_config(self.window_id, {
hide = false,
relative = "editor",
row = self.resolve_window_position(ui_config.row),
col = self.resolve_window_position(ui_config.col) - (self.max_rendered_text_length - #rank_label),
width = #rank_label,
row = self.resolve_window_position(config.options.ui.row),
col = config.options.ui.progress_bar.enabled and self.resolve_window_position(config.options.ui.col)
or self.resolve_window_position(config.options.ui.col)
- (self.max_rendered_text_length - #rank_label),
width = width,
})

self.extmark_id = vim.api.nvim_buf_set_extmark(self.buffer_id, vim.g.wmc_namespace_id, 0, 0, extmark_options)
self.last_rendered_text = rank_label
self.last_renderered_progress = rank_progress
end)
end

---@private
---@param progress number
---@param width number
---@return string
function M.prepare_progress_bar(progress, width)
local completion_width = math.floor(progress * width + 0.5)

return ("█"):rep(completion_width) .. ("░"):rep(width - completion_width)
end

function M:clear()
vim.schedule(function()
if not self:is_window_active() or not self:is_buffer_active() then
return
end

self.last_rendered_text = nil
self.last_renderered_progress = nil

vim.api.nvim_win_set_config(self.window_id, {
hide = true,
Expand Down
2 changes: 1 addition & 1 deletion plugin/wmc.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ vim.schedule(function()
return
end

vim.on_key(require("wmc.ranker"):new():on_key(), vim.g.wmc_namespace_id)
vim.on_key(require("wmc.ranker"):new():on_key_entered(), vim.g.wmc_namespace_id)
end)
Loading
Loading