├── .editorconfig ├── .luacheckrc ├── .luarc.json ├── LICENSE ├── README.md └── lua └── fFHighlight.lua /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | indent_size = 4 4 | tab_width = 4 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | 9 | [*.lua] 10 | align_continuous_assign_statement = false 11 | space_around_table_field_list = false 12 | align_call_args = false 13 | quote_style = single 14 | 15 | [*.json,*.jsonc] 16 | indent_style = tab 17 | 18 | [*.js] 19 | indent_style = tab 20 | 21 | [{Makefile,**.mk}] 22 | indent_style = tab 23 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | ignore = { 2 | "122" 3 | } 4 | 5 | read_globals = { 6 | "vim" 7 | } 8 | -------------------------------------------------------------------------------- /.luarc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", 3 | "runtime.version": "LuaJIT", 4 | "completion.callSnippet": "Replace", 5 | "completion.displayContext": 50, 6 | "completion.keywordSnippet": "Disable", 7 | "completion.postfix": ".", 8 | "IntelliSense.traceBeSetted": true, 9 | "IntelliSense.traceFieldInject": true, 10 | "IntelliSense.traceLocalSet": true, 11 | "IntelliSense.traceReturn": true, 12 | "diagnostics.neededFileStatus": { 13 | "missing-parameter": "Opened" 14 | }, 15 | "diagnostics.globals": [ 16 | "vim", 17 | "jit" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022-2023, kevinhwang91 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nvim-fFHighlight 2 | 3 | Highlight the chars and words searched by `f` and `F`. 4 | 5 | 6 | 7 | --- 8 | 9 | ## Table of contents 10 | 11 | * [Table of contents](#table-of-contents) 12 | * [Features](#features) 13 | * [Quickstart](#quickstart) 14 | * [Requirements](#requirements) 15 | * [Installation](#installation) 16 | * [Minimal configuration](#minimal-configuration) 17 | * [Usage](#usage) 18 | * [Documentation](#documentation) 19 | * [Setup and description](#setup-and-description) 20 | * [Highlight](#highlight) 21 | * [API](#api) 22 | * [Customize configuration](#customize-configuration) 23 | * [Feedback](#feedback) 24 | * [License](#license) 25 | 26 | ## Features 27 | 28 | - Highlight the chars searched by `f` and `F` 29 | - Highlight the words including the searched chars 30 | - Overlap the chars as numbers to jump faster 31 | 32 | ## Quickstart 33 | 34 | ### Requirements 35 | 36 | - [Neovim](https://github.com/neovim/neovim) 0.6.1 37 | 38 | ### Installation 39 | 40 | Install nvim-fFHighlight with [Packer.nvim](https://github.com/wbthomason/packer.nvim): 41 | 42 | ```lua 43 | use {'kevinhwang91/nvim-fFHighlight'} 44 | ``` 45 | 46 | ### Minimal configuration 47 | 48 | ```lua 49 | require('fFHighlight').setup() 50 | ``` 51 | 52 | ### Usage 53 | 54 | After using [Minimal configuration](#Minimal-configuration): 55 | 56 | The built-in `f` and `F` have been improved, enjoy! 57 | 58 | ## Documentation 59 | 60 | ### Setup and description 61 | 62 | ```lua 63 | { 64 | disable_keymap = { 65 | description = [[Disable keymaps, users should map them manually]], 66 | default = false 67 | }, 68 | disable_words_hl = { 69 | description = [[Disable the feature of highlighting words]], 70 | default = false 71 | }, 72 | number_hint_threshold = { 73 | description = [[If the count of repeating latest `f` or `F` to the char is equal or greater 74 | than this value, use number to overlap char. minimal value is 2]], 75 | default = 3 76 | }, 77 | prompt_sign_define = { 78 | description = [[The optional dict argument for sign_define(), `:h sign_define()` for 79 | more details. If this value is `{}`, will disable sign for prompt]], 80 | default = {text = '->', text_hl = 'fFPromptSign', culhl = 'fFPromptSign'} 81 | } 82 | } 83 | ``` 84 | 85 | ### Highlight 86 | 87 | ```vim 88 | hi default fFHintChar ctermfg=yellow cterm=bold guifg=yellow gui=bold 89 | hi default fFHintNumber ctermfg=yellow cterm=bold guifg=yellow gui=bold 90 | hi default fFHintWords cterm=underline gui=underline 91 | hi default link fFHintCurrentWord fFHintWords 92 | hi default fFPromptSign ctermfg=yellow cterm=bold guifg=yellow gui=bold 93 | ``` 94 | 95 | 1. fFHintChar: highlight the hint of chars 96 | 2. fFHintNumber: highlight the hint of number 97 | 3. fFHintWords: highlight the hint of words 98 | 4. fFHintCurrentWord: highlight the hint of current word 99 | 5. fFPromptSign: highlight the prompt sign before searching a char 100 | 101 | ### API 102 | 103 | ```lua 104 | -- All API under this module 105 | local m = require('fFHighlight') 106 | 107 | --- Find the character to be typed on the current line 108 | ---@param backward? boolean the direction of finding character. true is backward, otherwise is forward 109 | m.findChar(backward) 110 | ``` 111 | 112 | ## Customize configuration 113 | 114 | ```lua 115 | use { 116 | 'kevinhwang91/nvim-fFHighlight', 117 | config = function() 118 | vim.cmd([[ 119 | hi fFHintChar ctermfg=yellow cterm=bold,undercurl guifg=yellow gui=bold,undercurl 120 | hi fFHintWords cterm=undercurl gui=undercurl guisp=yellow 121 | hi fFPromptSign ctermfg=yellow cterm=bold guifg=yellow gui=bold 122 | ]]) 123 | require('fFHighlight').setup({ 124 | disable_keymap = false, 125 | disable_words_hl = false, 126 | number_hint_threshold = 3, 127 | prompt_sign_define = {text = '✹'} 128 | }) 129 | end, 130 | keys = {{'n', 'f'}, {'x', 'f'}, {'n', 'F'}, {'x', 'F'}} 131 | } 132 | ``` 133 | 134 | ## Feedback 135 | 136 | - If you get an issue or come up with an awesome idea, don't hesitate to open an issue in github. 137 | - If you think this plugin is useful or cool, consider rewarding it a star. 138 | 139 | ## License 140 | 141 | The project is licensed under a BSD-3-clause license. See [LICENSE](./LICENSE) file for details. 142 | -------------------------------------------------------------------------------- /lua/fFHighlight.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local cmd = vim.cmd 4 | local api = vim.api 5 | local fn = vim.fn 6 | 7 | local ns 8 | local ffi 9 | 10 | local initialized 11 | local signGroup 12 | local signPriority 13 | local wordRegex 14 | local has09 15 | local disableWordsHl 16 | local disablePromptSign 17 | local numberHintThreshold 18 | 19 | local function setVirtTextOverlap(bufnr, row, col, char, hlName, opts) 20 | opts = opts or {} 21 | -- may throw error: value outside range while editing 22 | local ok, res = pcall(api.nvim_buf_set_extmark, bufnr, ns, row, col, { 23 | id = opts.id, 24 | virt_text = {{char, hlName}}, 25 | virt_text_pos = 'overlay', 26 | hl_mode = 'combine', 27 | priority = opts.priority 28 | }) 29 | return ok and res or nil 30 | end 31 | 32 | local function getHighestVirtTextPriorityInLine(bufnr, row) 33 | local marks 34 | if has09 then 35 | marks = api.nvim_buf_get_extmarks(bufnr, -1, {row, 0}, {row + 1, 0}, {details = true}) 36 | else 37 | marks = {} 38 | for _, n in pairs(api.nvim_get_namespaces()) do 39 | if n ~= ns then 40 | for _, m in ipairs(api.nvim_buf_get_extmarks(bufnr, n, {row, 0}, {row + 1, 0}, {details = true})) do 41 | table.insert(marks, m) 42 | end 43 | end 44 | end 45 | end 46 | local max = 2048 47 | for _, m in ipairs(marks) do 48 | local details = m[4] 49 | if details.virt_text and max < details.priority then 50 | max = details.priority 51 | end 52 | end 53 | return max 54 | end 55 | 56 | local function clearVirtText(bufnr) 57 | bufnr = bufnr or api.nvim_get_current_buf() 58 | api.nvim_buf_clear_namespace(bufnr, ns, 0, -1) 59 | end 60 | 61 | ---@class Context 62 | ---@field char string 63 | ---@field lnum number 64 | ---@field cols number[] 65 | ---@field virtTextIds? number[] 66 | ---@field wordRanges? table 67 | ---@field curWordVirtTextId? number 68 | ---@field bufnr number 69 | ---@field winid number 70 | ---@field hlPriority number 71 | local Context = {} 72 | function Context:build(char, lnum, cols, wordRanges, bufnr, winid, hlPriority) 73 | self.char = char 74 | self.lnum = lnum 75 | self.cols = cols 76 | self.virtTextIds = nil 77 | self.wordRanges = wordRanges 78 | self.curWordVirtTextId = nil 79 | self.bufnr = bufnr 80 | self.winid = winid 81 | self.hlPriority = hlPriority 82 | end 83 | 84 | function Context:valid() 85 | local charValid = type(self.char) == 'string' and #self.char == 1 86 | local indexValid = type(self.lnum) == 'number' and type(self.cols) == 'table' 87 | local winValid = self.winid and self.winid > 0 and api.nvim_win_is_valid(self.winid) 88 | return charValid and indexValid and winValid 89 | end 90 | 91 | function Context:refreshHint(backwardColIdx, forwardColIdx) 92 | local bufnr, lnum, cols, char = self.bufnr, self.lnum, self.cols, self.char 93 | local changedIds = {} 94 | local priority = self.hlPriority 95 | if not self.virtTextIds then 96 | local virtTextIds = {} 97 | for _, col in ipairs(cols) do 98 | local id = setVirtTextOverlap(bufnr, lnum - 1, col - 1, char, 'fFHintChar', 99 | {priority = priority}) 100 | if id then 101 | table.insert(virtTextIds, id) 102 | changedIds[id] = true 103 | end 104 | end 105 | self.virtTextIds = virtTextIds 106 | end 107 | 108 | for i = math.min(backwardColIdx - numberHintThreshold, #cols), 1, -1 do 109 | local id = self.virtTextIds[i] 110 | local col = cols[i] 111 | local num = backwardColIdx - i 112 | if num > 9 then 113 | break 114 | end 115 | setVirtTextOverlap(bufnr, lnum - 1, col - 1, tostring(num), 'fFHintNumber', 116 | {id = id, priority = priority}) 117 | changedIds[id] = true 118 | end 119 | for i = forwardColIdx + numberHintThreshold, #cols do 120 | local id = self.virtTextIds[i] 121 | local col = cols[i] 122 | local num = i - forwardColIdx 123 | if num > 9 then 124 | break 125 | end 126 | setVirtTextOverlap(bufnr, lnum - 1, col - 1, tostring(num), 'fFHintNumber', 127 | {id = id, priority = priority}) 128 | changedIds[id] = true 129 | end 130 | for i, id in ipairs(self.virtTextIds) do 131 | if not changedIds[id] then 132 | local col = cols[i] 133 | setVirtTextOverlap(bufnr, lnum - 1, col - 1, char, 'fFHintChar', 134 | {id = id, priority = priority}) 135 | end 136 | end 137 | end 138 | 139 | function Context:refreshCurrentWord(curColIdx) 140 | if not self.wordRanges then 141 | return 142 | end 143 | local bufnr, lnum = self.bufnr, self.lnum 144 | local curCol = self.cols[curColIdx] 145 | local startCol, endCol 146 | for _, range in ipairs(self.wordRanges) do 147 | local s, e = unpack(range) 148 | if s <= curCol and curCol <= e then 149 | startCol, endCol = s, e 150 | break 151 | end 152 | end 153 | 154 | if startCol and endCol then 155 | local curLine = api.nvim_get_current_line() 156 | if not self.curWordVirtTextId then 157 | Context.curWordVirtTextId = setVirtTextOverlap(bufnr, lnum - 1, startCol - 1, 158 | curLine:sub(startCol, endCol), 'fFHintCurrentWord', {priority = self.hlPriority - 1}) 159 | else 160 | setVirtTextOverlap(bufnr, lnum - 1, startCol - 1, curLine:sub(startCol, endCol), 161 | 'fFHintCurrentWord', {id = self.curWordVirtTextId, priority = self.hlPriority - 1}) 162 | end 163 | end 164 | end 165 | 166 | local function binarySearch(items, element, comp) 167 | vim.validate({items = {items, 'table'}, comp = {comp, 'function', true}}) 168 | if not comp then 169 | comp = function(a, b) 170 | return a - b 171 | end 172 | end 173 | local min, max = 1, #items 174 | while min <= max do 175 | local mid = math.floor((min + max) / 2) 176 | local r = comp(items[mid], element) 177 | if r == 0 then 178 | return mid 179 | elseif r > 0 then 180 | max = mid - 1 181 | else 182 | min = mid + 1 183 | end 184 | end 185 | return -min 186 | end 187 | 188 | local function findCharColsInLine(line, char, col) 189 | local cols = {} 190 | local i = 0 191 | local s 192 | local e = 0 193 | while true do 194 | s, e = line:find(char, e + 1, true) 195 | if not s then 196 | break 197 | end 198 | table.insert(cols, s) 199 | if s == col then 200 | i = #cols 201 | elseif s < col then 202 | i = - #cols 203 | end 204 | end 205 | return cols, i 206 | end 207 | 208 | local function findWordRangesInLineWithCols(line, cols) 209 | local ranges = {} 210 | local lastIdx = 1 211 | local lastOff = 1 212 | local col = cols[lastIdx] 213 | while col and #line > 0 do 214 | -- s is inclusive and e is exclusive 215 | local s, e = wordRegex:match_str(line) 216 | if not s then 217 | break 218 | end 219 | local startCol, endCol = s + lastOff, e + lastOff - 1 220 | if col >= startCol and col <= endCol then 221 | table.insert(ranges, {startCol, endCol}) 222 | while col and col <= endCol do 223 | lastIdx = lastIdx + 1 224 | col = cols[lastIdx] 225 | end 226 | end 227 | lastOff = lastOff + e 228 | line = line:sub(e + 1) 229 | end 230 | return ranges 231 | end 232 | 233 | local function getKeystroke() 234 | ---@diagnostic disable-next-line: undefined-field 235 | local nr = ffi and ffi.C.get_keystroke(nil) or fn.getchar() 236 | -- C-c throw E5108: Error executing lua Keyboard interrupt 237 | -- `got_int` have been set to true, need an extra pcall command to eat it 238 | if ffi and nr == 3 then 239 | pcall(vim.cmd, '') 240 | end 241 | return nr > 0 and nr < 128 and ('%c'):format(nr) or '' 242 | end 243 | 244 | local function isFloatWin(winid) 245 | return fn.win_gettype(winid) == 'popup' 246 | end 247 | 248 | local function validMode(mode) 249 | if mode == 'n' or mode == 'nt' or mode:lower():sub(1, 1) == 'v' or mode:byte(1, 1) == 22 then 250 | return true 251 | end 252 | return false 253 | end 254 | 255 | --- Find the character to be typed on the current line 256 | ---@param backward? boolean the direction of finding character. true is backward, otherwise is forward 257 | function M.findChar(backward) 258 | assert(initialized, [[Not initialized yet, `require('fFHighlight').setup()` is required]]) 259 | local mode = api.nvim_get_mode().mode 260 | assert(validMode(mode), 'Only support normal or visual mode') 261 | 262 | local bufnr = api.nvim_get_current_buf() 263 | local winid = api.nvim_get_current_win() 264 | local lnum, curCol = unpack(api.nvim_win_get_cursor(0)) 265 | curCol = curCol + 1 266 | local signId 267 | if not disablePromptSign then 268 | if not (vim.wo.signcolumn == 'auto' and isFloatWin(winid)) then 269 | signId = fn.sign_place(0, signGroup, 'PromptSign', bufnr, 270 | {lnum = lnum, priority = signPriority}) 271 | cmd('redraw') 272 | end 273 | end 274 | local char = getKeystroke() 275 | if signId then 276 | fn.sign_unplace(signGroup, {buffer = bufnr, id = signId}) 277 | end 278 | if #char == 0 then 279 | return 280 | end 281 | local curLine = api.nvim_get_current_line() 282 | local cols, curColIdx = findCharColsInLine(curLine, char, curCol) 283 | clearVirtText(bufnr) 284 | local hlPriority = getHighestVirtTextPriorityInLine(bufnr, lnum - 1) + 3 285 | 286 | local wordRanges 287 | if not disableWordsHl then 288 | wordRanges = findWordRangesInLineWithCols(curLine, cols) 289 | for _, range in ipairs(wordRanges) do 290 | local startCol, endCol = unpack(range) 291 | setVirtTextOverlap(bufnr, lnum - 1, startCol - 1, curLine:sub(startCol, endCol), 292 | 'fFHintWords', {priority = hlPriority - 2}) 293 | end 294 | end 295 | Context:build(char, lnum, cols, wordRanges, bufnr, winid, hlPriority) 296 | cmd([[ 297 | augroup fFHighlight 298 | au! 299 | au CursorMoved * lua require('fFHighlight').move() 300 | au InsertEnter,TextChanged * lua require('fFHighlight').dispose() 301 | augroup END 302 | ]]) 303 | local nextColIdx 304 | local cnt = vim.v.count1 305 | if backward then 306 | nextColIdx = curColIdx <= 0 and -curColIdx or curColIdx - cnt 307 | else 308 | nextColIdx = curColIdx < 0 and -curColIdx + cnt or curColIdx + cnt 309 | end 310 | if 0 < nextColIdx and nextColIdx <= #cols then 311 | api.nvim_win_set_cursor(0, {lnum, cols[nextColIdx] - 1}) 312 | else 313 | local backwardColIdx, forwardColIdx = curColIdx, curColIdx 314 | if curColIdx < 0 then 315 | curColIdx = -curColIdx 316 | backwardColIdx, forwardColIdx = curColIdx + 1, curColIdx 317 | end 318 | Context:refreshHint(backwardColIdx, forwardColIdx) 319 | end 320 | fn.setcharsearch({char = char, forward = backward and 0 or 1, ['until'] = 0}) 321 | end 322 | 323 | function M.move() 324 | if not Context:valid() then 325 | M.dispose() 326 | return 327 | end 328 | 329 | local winid = api.nvim_get_current_win() 330 | local cursor = api.nvim_win_get_cursor(winid) 331 | local lnum, col = unpack(cursor) 332 | local curColIdx = binarySearch(Context.cols, col + 1) 333 | if winid == Context.winid and lnum == Context.lnum and curColIdx > 0 then 334 | Context:refreshHint(curColIdx, curColIdx) 335 | Context:refreshCurrentWord(curColIdx) 336 | local fdo = vim.o.foldopen 337 | if fdo:find('all', 1, true) or fdo:find('hor', 1, true) then 338 | cmd('norm! zv') 339 | end 340 | else 341 | M.dispose() 342 | end 343 | end 344 | 345 | function M.dispose() 346 | local bufnr 347 | if Context.bufnr and Context.bufnr > 0 and api.nvim_buf_is_valid(Context.bufnr) then 348 | bufnr = Context.bufnr 349 | end 350 | Context:build() 351 | clearVirtText(bufnr) 352 | cmd('au! fFHighlight') 353 | end 354 | 355 | local function initialize(config) 356 | local ok, res = pcall(require, 'ffi') 357 | if ok then 358 | ffi = res 359 | ffi.cdef([[ 360 | int get_keystroke(void *dummy_ptr); 361 | ]]) 362 | end 363 | ns = api.nvim_create_namespace('fF-highlight') 364 | 365 | if not config.disable_keymap then 366 | local kopt = {noremap = true, silent = true} 367 | api.nvim_set_keymap('n', 'f', [[lua require('fFHighlight').findChar()]], kopt) 368 | api.nvim_set_keymap('x', 'f', [[lua require('fFHighlight').findChar()]], kopt) 369 | api.nvim_set_keymap('n', 'F', [[lua require('fFHighlight').findChar(true)]], kopt) 370 | api.nvim_set_keymap('x', 'F', [[lua require('fFHighlight').findChar(true)]], kopt) 371 | end 372 | disableWordsHl = config.disable_words_hl 373 | numberHintThreshold = config.number_hint_threshold 374 | 375 | cmd([[ 376 | hi default fFHintChar ctermfg=yellow cterm=bold guifg=yellow gui=bold 377 | hi default fFHintNumber ctermfg=yellow cterm=bold guifg=yellow gui=bold 378 | hi default fFHintWords cterm=underline gui=underline 379 | hi default link fFHintCurrentWord fFHintWords 380 | hi default fFPromptSign ctermfg=yellow cterm=bold guifg=yellow gui=bold 381 | ]]) 382 | 383 | wordRegex = vim.regex([[\k\+]]) 384 | has09 = fn.has('nvim-0.9') == 1 385 | signPriority = 90 386 | signGroup = 'fFSignGroup' 387 | if type(config.prompt_sign_define) ~= 'table' then 388 | disablePromptSign = true 389 | else 390 | disablePromptSign = false 391 | fn.sign_define('PromptSign', config.prompt_sign_define) 392 | end 393 | end 394 | 395 | function M.setup(opts) 396 | local config = vim.tbl_deep_extend('keep', opts or {}, { 397 | disable_keymap = false, 398 | disable_words_hl = false, 399 | number_hint_threshold = 3 400 | }) 401 | 402 | if config.prompt_sign_define and vim.tbl_isempty(config.prompt_sign_define) then 403 | config.prompt_sign_define = nil 404 | else 405 | config.prompt_sign_define = vim.tbl_deep_extend('keep', config.prompt_sign_define or {}, { 406 | text = '->', 407 | text_hl = 'fFPromptSign', 408 | culhl = 'fFPromptSign' 409 | }) 410 | end 411 | vim.validate({ 412 | disable_keymap = {config.disable_keymap, 'boolean'}, 413 | disable_words_hl = {config.disable_words_hl, 'boolean'}, 414 | number_hint_threshold = { 415 | config.number_hint_threshold, function(v) 416 | return type(v) == 'number' and v > 1 417 | end, 'a number greater than 1' 418 | }, 419 | prompt_sign_define = {config.prompt_sign_define, 'table', true} 420 | }) 421 | 422 | if not initialized then 423 | initialize(config) 424 | initialized = true 425 | end 426 | end 427 | 428 | return M 429 | --------------------------------------------------------------------------------