From cce0e90544e2ec1a32c2a1afe5dfae03068ac9d2 Mon Sep 17 00:00:00 2001 From: ray-x Date: Sat, 11 Jun 2022 15:03:28 +1000 Subject: [PATCH] gui listview updates, allow listview cover more spaces --- README.md | 13 ++- lua/navigator.lua | 6 +- lua/navigator/ctags.lua | 172 ++++++++++++++++++++++++++++ lua/navigator/gui.lua | 30 +++-- lua/navigator/lspclient/attach.lua | 4 +- lua/navigator/lspclient/clients.lua | 4 +- lua/navigator/lspclient/mapping.lua | 16 +++ lua/navigator/lspwrapper.lua | 9 +- lua/navigator/reference.lua | 12 +- lua/navigator/render.lua | 15 +-- lua/navigator/symbols.lua | 10 +- lua/navigator/util.lua | 39 +++++-- lua/navigator/workspace.lua | 10 +- 13 files changed, 283 insertions(+), 57 deletions(-) create mode 100644 lua/navigator/ctags.lua diff --git a/README.md b/README.md index bd9feb5..ff19287 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ - A plugin combines the power of LSP and 🌲🏡 Treesitter together. Not only provids a better highlight but also help you analyse symbol context effectively. +- Fuzzy search & build ctags symbols + - [![a short intro of navigator](https://user-images.githubusercontent.com/1681295/147378905-51eede5f-e36d-48f4-9799-ae562949babe.jpeg)](https://youtu.be/P1kd7Y8AatE) Here are some examples @@ -117,7 +119,7 @@ I'd like to go beyond what the system is offering. # Install -Require nvim-0.6.1, nightly prefered +Require nvim-0.6.1 or above, nightly (0.8) prefered You can remove your lspconfig setup and use this plugin. The plugin depends on lspconfig and [guihua.lua](https://github.com/ray-x/guihua.lua), which provides GUI and fzy support(migrate from [romgrk's project](romgrk/fzy-lua-native)). @@ -128,7 +130,7 @@ Plug 'ray-x/guihua.lua', {'do': 'cd lua/fzy && make' } Plug 'ray-x/navigator.lua' ``` -Note: Highly recommened: 'nvim-treesitter/nvim-treesitter' +Note: Highly recommend: 'nvim-treesitter/nvim-treesitter' Packer @@ -292,6 +294,11 @@ require'navigator'.setup({ filetypes = {'typescript'} -- disable javascript etc, -- set to {} to disable the lspclient for all filetypes }, + ctags ={ + cmd = 'ctags', + tagfile = 'tags' + options = '-R --exclude=.git --exclude=node_modules --exclude=test --exclude=vendor --excmd=number' + } gopls = { -- gopls setting on_attach = function(client, bufnr) -- on_attach for gopls -- your special on attach here @@ -415,6 +422,8 @@ In `playground` folder, there is a `init.lua` and source code for you to play wi | n | g\ | implementation | | n | \gt | treesitter document symbol | | n | \gT | treesitter symbol for all open buffers | +| n | \ ct | ctags symbol search | +| n | \ cg | ctags symbol generate | | n | K | hover doc | | n | \ca | code action (when you see 🏏 ) | | n | \la | code lens action (when you see a codelens indicator) | diff --git a/lua/navigator.lua b/lua/navigator.lua index 36d08ea..13cd7c2 100755 --- a/lua/navigator.lua +++ b/lua/navigator.lua @@ -32,6 +32,7 @@ _NgConfigValues = { lsp_signature_help = true, -- if you would like to hook ray-x/lsp_signature plugin in navigator -- setup here. if it is nil, navigator will not init signature help signature_help_cfg = { debug = false }, -- if you would like to init ray-x/lsp_signature plugin in navigator, pass in signature help + ctags = {cmd='ctags', tagfile='.tags'}, lsp = { code_action = { enable = true, @@ -113,11 +114,6 @@ _NgConfigValues = { }, } -vim.cmd("command! -nargs=0 LspLog lua require'navigator.lspclient.config'.open_lsp_log()") -vim.cmd("command! -nargs=0 LspRestart lua require'navigator.lspclient.config'.reload_lsp()") -vim.cmd("command! -nargs=0 LspToggleFmt lua require'navigator.lspclient.mapping'.toggle_lspformat()") -vim.cmd("command! -nargs=0 LspKeymaps lua require'navigator.lspclient.mapping'.get_keymaps_help()") - M.deprecated = function(cfg) if cfg.code_action_prompt then warn('code_action_prompt moved to lsp.code_action') diff --git a/lua/navigator/ctags.lua b/lua/navigator/ctags.lua new file mode 100644 index 0000000..5c0cefb --- /dev/null +++ b/lua/navigator/ctags.lua @@ -0,0 +1,172 @@ +local type_to_lspkind = { c = 5, m = 7, f = 6, s = 5 } +local util = require('navigator.util') +local log = util.log +local sep = util.path_sep() +local vfn = vim.fn +local cur_dir = vfn.getcwd() + +-- convert ctags line to lsp entry +local function entry_to_item(entry) + local item = {} + item.name, item.filename, item.line, item.remain = string.match(entry, '(.*)\t(.*)\t(%d+);(.*)') + local type = 'combine' + item.remain = item.remain or '' + if item.remain:sub(1, 1) == [["]] then + type = 'number' + end + if item.name == nil or item.filename == nil then + return + end + + if type == 'combine' then + -- '/^type ServerResponse struct {$/;"\ts\tpackage:client' + item.inline, item.type, item.containerName, item.ref = string.match(item.remain, '/^(.*)$/;"\t(%a)\t(.+)') + else + -- '"\tm\tstruct:store.Customer\ttyperef:typename:string' + item.type, item.containerName, item.ref = string.match(item.remain, '"\t(%a)\t(.+)') + end + item.kind = type_to_lspkind[item.type] or 13 + item.lnum = tonumber(item.line) - 1 + item.location = { + uri = 'file://' .. cur_dir .. sep .. item.filename, + range = { + start = { line = item.lnum, character = 0 }, + ['end'] = { line = item.lnum, character = 0 }, + }, + } + + item.uri = 'file://' .. cur_dir .. sep .. item.filename + item.range = { + start = { line = item.lnum, character = 0 }, + ['end'] = { line = item.lnum, character = 0 }, + } + + -- item.detail = (item.containerName or '') .. (item.ref or '') + -- item.text = '[' .. kind .. ']' .. item.name .. ' ' .. item.detail + + if item.lnum == nil then + vim.notify('incorrect ctags format, need run ctag with "-excmd=number|combine" option') + end + item.remain = nil + return item +end + +local function ctags_gen() + local cmd = 'ctags' -- -x -n -u -f - ' .. vfn.expand('%:p') + local output = _NgConfigValues.ctags.tagfile + local options = '-R --exclude=.git --exclude=node_modules --exclude=test --exclude=vendor --excmd=number ' + if _NgConfigValues.ctags then + cmd = _NgConfigValues.ctags.cmd + options = _NgConfigValues.ctags.options or options + end + + local lang = vim.o.ft + options = options .. '--language=' .. lang + cmd = cmd .. ' ' .. options + cmd = string.format('%s -f %s %s --language=%s', cmd, output, options, lang) + log(cmd) + vfn.jobstart(cmd, { + on_stdout = function(_, _, _) + vim.notify('ctags completed') + end, + + on_exit = function(_, data, _) -- id, data, event + -- log(vim.inspect(data) .. "exit") + if data and data.code ~= 0 then + return vim.notify(cmd .. ' failed ' .. tostring(data), vim.lsp.log_levels.ERROR) + else + vim.notify('ctags generated') + end + end, + }) +end + +local symbols_to_items = require('navigator.lspwrapper').symbols_to_items +local function ctags_symbols() + local height = _NgConfigValues.height or 0.4 + local width = _NgConfigValues.width or 0.7 + height = math.floor(height * vfn.winheight('%')) + width = math.floor(width * vfn.winwidth('%')) + local items = {} + local ctags_file = _NgConfigValues.ctags.tagfile + if not util.file_exists(ctags_file) then + ctags_gen() + end + local cnts = util.io_read(ctags_file) + if cnts == nil then + return vim.notify('ctags file ' .. ctags_file .. ' not found') + end + cnts = vfn.split(cnts, '\n') + for _, value in pairs(cnts) do + local it = entry_to_item(value) + if it then + table.insert(items, it) + end + end + cnts = nil + + local ft = vim.o.ft + local result = symbols_to_items(items) + log(result) + if next(result) == nil then + return vim.notify('no symbols found') + end + local opt = { + api = ' ', + ft = ft, + bg = 'GHListDark', + data = result, + items = result, + enter = true, + loc = 'top_center', + transparency = 50, + prompt = true, + rawdata = true, + rect = { height = height, pos_x = 0, pos_y = 0, width = width }, + } + + require('navigator.gui').new_list_view(opt) +end + +-- gen_ctags() + +local function ctags(...) + local gen = select(1, ...) + log(gen) + if gen == '-g' then + ctags_gen() + ctags_symbols() + else + ctags_symbols() + end +end + +local function testitem() + local e = [[ServerResponse internal/clients/server.go /^type ServerResponse struct {$/;" s package:client]] + local ecombine = [[ServerResponse internal/clients/server.go 5;/^type ServerResponse struct {$/;" s package:client]] + local enumber = [[CustomerID internal/store/models.go 17;" m struct:store.Customer typeref:typename:string]] + local enumber2 = [[CustomerDescription internal/controllers/customer.go 27;" c package:controllers]] + local enumber3 = [[add_servers lua/navigator/lspclient/clients.lua 680;" f]] + local i = entry_to_item(ecombine) + print(vim.inspect(i)) + + i = entry_to_item(enumber) + print(vim.inspect(i)) + + i = entry_to_item(enumber2) + print(vim.inspect(i)) + + i = entry_to_item(enumber3) + print(vim.inspect(i)) + i = entry_to_item(e) + print(vim.inspect(i)) +end +-- testitem() +-- gen_ctags() +-- ctags_symbols() + +return { + ctags_gen = ctags_gen, + ctags = ctags, + ctags_symbols = ctags_symbols, +} diff --git a/lua/navigator/gui.lua b/lua/navigator/gui.lua index d4ac0fc..24c7259 100644 --- a/lua/navigator/gui.lua +++ b/lua/navigator/gui.lua @@ -23,19 +23,16 @@ function M.new_list_view(opts) end local items = opts.items - opts.min_width = opts.min_width or 0.3 - opts.min_height = opts.min_height or 0.3 - - opts.height_ratio = config.height - opts.width_ratio = config.width - opts.preview_height_ratio = _NgConfigValues.preview_height or 0.3 - opts.preview_lines = _NgConfigValues.preview_lines + opts.height_ratio = opts.height or config.height + opts.width_ratio = opts.height or config.width + opts.preview_height_ratio = opts.preview_height or config.preview_height + opts.preview_lines = config.preview_lines if opts.rawdata then opts.data = items else opts.data = require('navigator.render').prepare_for_render(items, opts) end - opts.border = _NgConfigValues.border or 'shadow' + opts.border = config.border or 'shadow' if vim.fn.hlID('TelescopePromptBorder') > 0 then opts.border_hl = 'TelescopePromptBorder' end @@ -44,21 +41,22 @@ function M.new_list_view(opts) return end - opts.transparency = _NgConfigValues.transparency - if #items >= _NgConfigValues.lines_show_prompt then + opts.transparency = config.transparency + if #items >= config.lines_show_prompt then opts.prompt = true end - opts.external = _NgConfigValues.external - opts.preview_lines_before = 3 - log(opts) + opts.external = config.external + opts.preview_lines_before = 4 + if _NgConfigValues.debug then + local logopts = { items = {}, data = {} } + logopts = vim.tbl_deep_extend('keep', logopts, opts) + log(logopts) + end active_list_view = require('guihua.gui').new_list_view(opts) return active_list_view end -function M.select(items, opts, on_choice) -end - return M -- Doc diff --git a/lua/navigator/lspclient/attach.lua b/lua/navigator/lspclient/attach.lua index 667a612..6895ce0 100644 --- a/lua/navigator/lspclient/attach.lua +++ b/lua/navigator/lspclient/attach.lua @@ -60,9 +60,11 @@ M.on_attach = function(client, bufnr) end if _NgConfigValues.lsp.code_action.enable then - if client.server_capabilities.codeActionProvider then + if client.server_capabilities.codeActionProvider and client.name ~= 'null-ls' then log('code action enabled for client', client.server_capabilities.codeActionProvider) + api.nvim_command('augroup NCodeAction') vim.cmd([[autocmd CursorHold,CursorHoldI lua require'navigator.codeAction'.code_action_prompt()]]) + api.nvim_command('augroup end') end end end diff --git a/lua/navigator/lspclient/clients.lua b/lua/navigator/lspclient/clients.lua index bee93fc..a277d01 100644 --- a/lua/navigator/lspclient/clients.lua +++ b/lua/navigator/lspclient/clients.lua @@ -697,7 +697,7 @@ local function setup(user_opts, cnt) user_opts = user_opts or {} local ft = vim.bo.filetype local bufnr = user_opts.bufnr or vim.api.nvim_get_current_buf() - if ft == '' then + if ft == '' or ft == nil then log('nil filetype, callback') local ext = vim.fn.expand('%:e') if ext ~= '' then @@ -716,6 +716,7 @@ local function setup(user_opts, cnt) return else log('no filetype, no ext return') + return end end local uri = vim.uri_from_bufnr(bufnr) @@ -743,6 +744,7 @@ local function setup(user_opts, cnt) 'packer', 'gitcommit', 'windline', + 'notify', } for i = 1, #disable_ft do if ft == disable_ft[i] then diff --git a/lua/navigator/lspclient/mapping.lua b/lua/navigator/lspclient/mapping.lua index 18ddba0..f833865 100644 --- a/lua/navigator/lspclient/mapping.lua +++ b/lua/navigator/lspclient/mapping.lua @@ -33,6 +33,7 @@ local key_maps = { { key = 'gp', func = "require('navigator.definition').definition_preview()" }, { key = 'gt', func = "require('navigator.treesitter').buf_ts()" }, { key = 'gT', func = "require('navigator.treesitter').bufs_ts()" }, + { key = 'ct', func = "require('navigator.ctags').ctags()" }, { key = 'K', func = 'hover({ popup_opts = { border = single, max_width = 80 }})' }, { key = 'ca', mode = 'n', func = "require('navigator.codeAction').code_action()" }, { key = 'cA', mode = 'v', func = 'range_code_action()' }, @@ -61,6 +62,14 @@ local key_maps = { { key = 'la', mode = 'n', func = "require('navigator.codelens').run_action()" }, } +local commands = { + [[command! -nargs=* Nctags lua require("navigator.ctags").ctags({})]], + "command! -nargs=0 LspLog lua require'navigator.lspclient.config'.open_lsp_log()", + "command! -nargs=0 LspRestart lua require'navigator.lspclient.config'.reload_lsp()", + "command! -nargs=0 LspToggleFmt lua require'navigator.lspclient.mapping'.toggle_lspformat()", + "command! -nargs=0 LspKeymaps lua require'navigator.lspclient.mapping'.get_keymaps_help()", +} + local key_maps_help = {} -- LuaFormatter on local M = {} @@ -104,6 +113,12 @@ local check_cap = function(opts) return fmt, rfmt, ccls end +local function set_cmds(_) + for _, value in pairs(commands) do + vim.cmd(value) + end +end + local function set_mapping(user_opts) local opts = { noremap = true, silent = true } user_opts = user_opts or {} @@ -256,6 +271,7 @@ end function M.setup(user_opts) user_opts = user_opts or _NgConfigValues set_mapping(user_opts) + set_cmds(user_opts) autocmd() set_event_handler(user_opts) diff --git a/lua/navigator/lspwrapper.lua b/lua/navigator/lspwrapper.lua index 9079c9d..3404197 100644 --- a/lua/navigator/lspwrapper.lua +++ b/lua/navigator/lspwrapper.lua @@ -75,7 +75,7 @@ end function M.symbols_to_items(result) local locations = {} result = result or {} - -- log(result) + log(#result) for i = 1, #result do local item = result[i].location if item ~= nil and item.range ~= nil then @@ -87,8 +87,9 @@ function M.symbols_to_items(result) if kind ~= nil then item.text = kind .. ': ' .. item.text end - item.filename = vim.uri_to_fname(item.uri) - + if not item.filename then + item.filename = vim.uri_to_fname(item.uri) + end item.display_filename = item.filename:gsub(cwd .. path_sep, path_cur, 1) if item.range == nil or item.range.start == nil then log('range not set', result[i], item) @@ -417,7 +418,7 @@ function M.locations_to_items(locations, ctx) vim.cmd([[set eventignore-=FileType]]) - log(items) + trace(items) return items, width + 24, second_part -- TODO handle long line? end diff --git a/lua/navigator/reference.lua b/lua/navigator/reference.lua index e0a2941..6b4a8e1 100644 --- a/lua/navigator/reference.lua +++ b/lua/navigator/reference.lua @@ -26,7 +26,11 @@ local ref_view = function(err, locations, ctx, cfg) end local definitions = ctx.results.definitions local references = ctx.results.references - log(ctx) + if _NgConfigValues.debug then + local logctx = { results = {} } + logctx = vim.tbl_extend('keep', logctx, ctx) + log(logctx, 'result size', #ctx.results, 'item', ctx.results[1]) + end if definitions.error and references.error then vim.notify('lsp ref callback error' .. vim.inspect(ctx.result), vim.lsp.log_levels.WARN) end @@ -43,8 +47,8 @@ local ref_view = function(err, locations, ctx, cfg) local vrange = value.range or { start = { line = 0 }, ['end'] = { line = 0 } } for i = 1, #refs, 1 do local rg = refs[i].range or {} - log(value, refs[i]) - log(rg, vrange) + trace(value, refs[i]) + trace(rg, vrange) if rg.start.line == vrange.start.line and rg['end'].line == vrange['end'].line then table.remove(refs, i) break @@ -54,7 +58,7 @@ local ref_view = function(err, locations, ctx, cfg) vim.list_extend(locations, refs) end err = nil - log(locations) + trace(locations) end -- log("num", num) -- log("bfnr", bufnr) diff --git a/lua/navigator/render.lua b/lua/navigator/render.lua index 130b13a..fb8153f 100644 --- a/lua/navigator/render.lua +++ b/lua/navigator/render.lua @@ -69,17 +69,9 @@ function M.prepare_for_render(items, opts) icon = devicons.get_icon(fn, ext) or icon end -- local call_by_presented = false - local width = 100 - opts.width = opts.width or width + opts.width = opts.width or 100 local win_width = opts.width -- buf - -- for i = 1, #items do - -- if items[i].call_by and #items[i].call_by > 0 then - -- call_by_presented = true - -- end - -- end - -- log(items[1]) - for i = 1, #items do local space local trim @@ -97,7 +89,7 @@ function M.prepare_for_render(items, opts) display_items[last_summary_idx].filename_only = true -- trace(items[i], items[i].filename, last_summary_idx, display_items[last_summary_idx].filename) -- TODO refact display_filename generate part - if items[i].filename == fn then + if items[i].filename == fn or opts.hide_filename then space, trim = get_pads(opts.width, icon .. ' ' .. dfn, lspapi_display .. ' 14 of 33 ') if trim and opts.width > 50 and #dfn > opts.width - 20 then local fn1 = string.sub(dfn, 1, opts.width - 50) @@ -148,7 +140,8 @@ function M.prepare_for_render(items, opts) ts_report = _NgConfigValues.icons.value_changed end - log(item.text, item.symbol_name, item.uri) + -- log(item.text, item.symbol_name, item.uri) + -- log(item.text) if item.definition then log('definition', item) ts_report = ts_report .. _NgConfigValues.icons.value_definition .. ' ' diff --git a/lua/navigator/symbols.lua b/lua/navigator/symbols.lua index 7ff3d03..92c4e35 100644 --- a/lua/navigator/symbols.lua +++ b/lua/navigator/symbols.lua @@ -100,7 +100,15 @@ M.document_symbol_handler = function(err, result, ctx) end local ft = vim.api.nvim_buf_get_option(bufnr, 'ft') - gui.new_list_view({ items = locations, prompt = true, rawdata = true, ft = ft, api = ' ' }) + gui.new_list_view({ + items = locations, + prompt = true, + rawdata = true, + height = 0.62, + preview_height = 0.1, + ft = ft, + api = ' ', + }) end M.workspace_symbol_handler = function(err, result, ctx, cfg) diff --git a/lua/navigator/util.lua b/lua/navigator/util.lua index 32fefab..7de3fe8 100644 --- a/lua/navigator/util.lua +++ b/lua/navigator/util.lua @@ -6,6 +6,8 @@ local M = { log_path = vim.lsp.get_log_path() } local guihua = require('guihua.util') local nvim_0_6_1 local nvim_0_8 +local vfn = vim.fn +local api = vim.api M.path_sep = function() local is_win = vim.loop.os_uname().sysname:find('Windows') @@ -42,10 +44,10 @@ function M.get_data_from_file(filename, startLine) end local uri = 'file:///' .. filename local bufnr = vim.uri_to_bufnr(uri) - if not vim.api.nvim_buf_is_loaded(bufnr) then - vim.fn.bufload(bufnr) + if not api.nvim_buf_is_loaded(bufnr) then + vfn.bufload(bufnr) end - local data = vim.api.nvim_buf_get_lines(bufnr, startLine, startLine + 8, false) + local data = api.nvim_buf_get_lines(bufnr, startLine, startLine + 8, false) if data == nil or vim.tbl_isempty(data) then startLine = nil else @@ -59,6 +61,26 @@ function M.get_data_from_file(filename, startLine) return { data = data, line = displayLine } end +function M.io_read(filename, total) + local f = io.open(filename, 'r') + if f == nil then + return nil + end + local content = f:read('*a') -- *a or *all reads the whole file + f:close() + return content +end + + +function M.file_exists(name) + local f = io.open(name, "r") + if f ~= nil then + io.close(f) + return true + end + return false +end + M.merge = function(t1, t2) for k, v in pairs(t2) do t1[k] = v @@ -77,9 +99,9 @@ M.map = function(modes, key, result, options) for i = 1, #modes do if buffer then - vim.api.nvim_buf_set_keymap(0, modes[i], key, result, options) + api.nvim_buf_set_keymap(0, modes[i], key, result, options) else - vim.api.nvim_set_keymap(modes[i], key, result, options) + api.nvim_set_keymap(modes[i], key, result, options) end end end @@ -302,7 +324,7 @@ function M.trim_and_pad(txt) end M.open_file = function(filename) - vim.api.nvim_command(string.format('e! %s', filename)) + api.nvim_command(string.format('e! %s', filename)) end M.open_file_at = guihua.open_file_at @@ -329,7 +351,6 @@ end -- name space search local nss -local api = vim.api local bufs function M.set_virt_eol(bufnr, lnum, chunks, priority, id) @@ -371,7 +392,7 @@ function M.nvim_0_6_1() if nvim_0_6_1 ~= nil then return nvim_0_6_1 end - nvim_0_6_1 = vim.fn.has('nvim-0.6.1') == 1 + nvim_0_6_1 = vfn.has('nvim-0.6.1') == 1 if nvim_0_6_1 == false then M.warn('Please use navigator 0.3 version for neovim version < 0.6.1') end @@ -382,7 +403,7 @@ function M.nvim_0_8() if nvim_0_8 ~= nil then return nvim_0_8 end - nvim_0_8 = vim.fn.has('nvim-0.8') == 1 + nvim_0_8 = vfn.has('nvim-0.8') == 1 if nvim_0_8 == false then M.log('Please use navigator 0.4 version for neovim version < 0.8') end diff --git a/lua/navigator/workspace.lua b/lua/navigator/workspace.lua index f474829..82e8d7b 100644 --- a/lua/navigator/workspace.lua +++ b/lua/navigator/workspace.lua @@ -34,8 +34,12 @@ M.workspace_symbol = function() end function M.workspace_symbol_live() - local height = 15 + local height = _NgConfigValues.height or 0.4 + height = math.floor(height * vim.fn.winheight('%')) + local width = _NgConfigValues.width or 0.7 + width = math.floor(width * vfn.winwidth('%')) local bufnr = vim.api.nvim_get_current_buf() + local ft = vim.o.ft local data = { { text = 'input the symbol name to start fuzzy search' } } for _ = 1, height do table.insert(data, { text = '' }) @@ -47,7 +51,7 @@ function M.workspace_symbol_live() data = data, items = data, enter = true, - ft = 'go', + ft = ft, loc = 'top_center', transparency = 50, prompt = true, @@ -77,7 +81,7 @@ function M.workspace_symbol_live() items = gutil.dedup(items, 'name', 'kind') return items end, - rect = { height = height, pos_x = 0, pos_y = 0, width = 120 }, + rect = { height = height, pos_x = 0, pos_y = 0, width = width }, } local win = ListView:new(opt)