local vim = assert(vim) local M = {} local cons = "tnshrdlcumwfgypkbvjxqz" -- 21 local vowel = "aeiou" -- 5 local hsel1 = "tnshrdlcumwfgypkbvjxqz" -- 21 local hsel2 = "aeiou" -- 5 local function char_at(s, i) local m = (i % #s) + 1 return string.sub(s, m, m) end local function new_panel(buf, row, col, width, height) local w = vim.api.nvim_open_win(buf, false, { relative = 'win', row = row, col = col, width = width, height = height, focusable = false, style = 'minimal' }) vim.api.nvim_win_set_option(w, "winhighlight", "Normal:WarpNormal") return w end M.open_vertical = function() local current_win = vim.api.nvim_get_current_win() local height = vim.api.nvim_win_get_height(current_win) local topline = vim.fn.line('w0') local width_of_garbage = vim.fn.getwininfo(current_win)[1].textoff M.row_map = {} M.vert_buf = vim.api.nvim_create_buf(0, 1) local line = 0 M.vert_lines = {} local real_width = math.max(3, width_of_garbage) while line < height do local t = char_at(cons, line) .. char_at(vowel, line) table.insert(M.vert_lines, (' '):rep(real_width - 3) .. t .. ' ') M.row_map[t] = topline + line line = line + 1 end vim.api.nvim_buf_set_lines(M.vert_buf, 0, -1, 0, M.vert_lines) return new_panel(M.vert_buf, 0, 0, real_width, height); end M.open_horiz = function() local current_win = vim.api.nvim_get_current_win() local curpos = vim.api.nvim_win_get_cursor(0) local topline = vim.fn.line('w0') local width_of_garbage = vim.fn.getwininfo(current_win)[1].textoff local width = vim.api.nvim_win_get_width(current_win) - width_of_garbage M.horiz_bufnr = vim.api.nvim_create_buf(0, 1) local col = 0 local line = '' local v = '' local c = nil local i = 0 local line_at = vim.fn.getline(curpos[1]) M.col_map = {} local max_width = math.min(width, #line_at + 1) while col < max_width do v = char_at(hsel1, i) if c then if M.col_map[v .. c] then break end M.col_map[c .. v] = col - 1 end c = char_at(hsel2, i) if M.col_map[c .. v] then break end line = line .. v col = col + 1 if col < max_width then line = line .. c M.col_map[v .. c] = col - 1 col = col + 1 i = i + 1 end end vim.api.nvim_buf_set_lines(M.horiz_bufnr, 0, -1, 0, {' ' .. line}) M.horiz_line = line return new_panel(M.horiz_bufnr, curpos[1] - topline + 1, width_of_garbage - 1, max_width + 2, 1) end M.filter_horiz_string = function(ch) local new_horiz_line = '' local i = 1 while i <= #M.horiz_line do local curch = char_at(M.horiz_line, i - 1) if curch == ch then new_horiz_line = new_horiz_line .. char_at(M.horiz_line, i) else new_horiz_line = new_horiz_line .. ' ' end i = i + 1 end vim.api.nvim_buf_set_lines(M.horiz_bufnr, 0, -1, 0, {' ' .. new_horiz_line}) end M.filter_vert_text = function(ch) local new_vert_text = {} for _, t in pairs(M.vert_lines) do local str = t:gsub("^%s*" .. ch, function(m) return (' '):rep(#m) end) if t:match('^%s*' .. ch) then table.insert(new_vert_text, str) else table.insert(new_vert_text, "") end end M.vert_lines = new_vert_text vim.api.nvim_buf_set_lines(M.vert_buf, 0, -1, 0, M.vert_lines) end local function next_char() vim.cmd("redraw!") return vim.fn.nr2char(vim.fn.getchar()) end M.run = function() local current_pos = vim.api.nvim_win_get_cursor(0) local old_scroll = vim.o.scrolloff vim.o.scrolloff = 0 (function() local w local cleanup = function(win) vim.api.nvim_buf_delete(vim.api.nvim_win_get_buf(win), {force = true}) end local read_row = function() w = M.open_vertical() local vch1 = next_char() if vch1 == '\x1b' then return elseif vch1 == '.' then return current_pos[1] else M.filter_vert_text(vch1) local vch2 = next_char() if vch1 == '\x1b' then return end return M.row_map[vch1 .. vch2] end end local r = read_row() if w then cleanup(w) w = nil end if not r then return end vim.api.nvim_win_set_cursor(0, {r, current_pos[2]}) local read_col = function() w = M.open_horiz() local hch1 = next_char() if hch1 == '\x1b' then return nil elseif hch1 == '.' then return current_pos[2] elseif not hch1:match('[a-z]') then return hch1 else M.filter_horiz_string(hch1) local hch2 = next_char() if hch2 == '\x1b' then return end return M.col_map[hch1 .. hch2] end end local c = read_col() if w then cleanup(w) end if not c then return end if c == '$' or c == '^' then vim.cmd("normal! " .. c) elseif type(c) == 'string' then vim.cmd("normal! f" .. c) else vim.cmd("normal! " .. (c + 1) .. "|") -- handle multibyte characters end end)() vim.o.scrolloff = old_scroll end return M