diff options
author | 2020-03-03 19:39:02 -0500 | |
---|---|---|
committer | 2020-03-03 19:39:02 -0500 | |
commit | fceb1a37df623649d191c3c1a881e5b0538b1391 (patch) | |
tree | 87a34dfc2397dc4afdaa0c8ec189f037300f458e /core | |
parent | 1618f5017abb3c9bacc9ba346bf22a936ef5dd06 (diff) | |
download | textadept-fceb1a37df623649d191c3c1a881e5b0538b1391.tar.gz textadept-fceb1a37df623649d191c3c1a881e5b0538b1391.zip |
Added test suite and API type checking for more helpful error messages.
Diffstat (limited to 'core')
-rw-r--r-- | core/args.lua | 45 | ||||
-rw-r--r-- | core/assert.lua | 57 | ||||
-rw-r--r-- | core/events.lua | 15 | ||||
-rw-r--r-- | core/file_io.lua | 85 | ||||
-rw-r--r-- | core/init.lua | 21 | ||||
-rw-r--r-- | core/keys.lua | 17 | ||||
-rw-r--r-- | core/lfs_ext.lua | 21 | ||||
-rw-r--r-- | core/locale.lua | 6 | ||||
-rw-r--r-- | core/ui.lua | 85 |
9 files changed, 228 insertions, 124 deletions
diff --git a/core/args.lua b/core/args.lua index 0dbf0efb..dc2af94d 100644 --- a/core/args.lua +++ b/core/args.lua @@ -30,8 +30,12 @@ local switches = {} -- help. -- @name register function M.register(short, long, narg, f, description) - local t = {f, narg, description} - switches[short], switches[long] = t, t + local switch = { + narg = assert_type(narg, 'number', 3), f = assert_type(f, 'function', 4), + description = assert_type(description, 'string', 5) + } + switches[assert_type(short, 'string', 1)] = switch + switches[assert_type(long, 'string', 2)] = switch end -- Processes command line argument table *arg*, handling switches previously @@ -48,9 +52,8 @@ local function process(arg, no_emit_arg_none) while i <= #arg do local switch = switches[arg[i]] if switch then - local f, n = table.unpack(switch) - f(table.unpack(arg, i + 1, i + n)) - i = i + n + switch.f(table.unpack(arg, i + 1, i + switch.narg)) + i = i + switch.narg else io.open_file(lfs.abspath(arg[i], arg[-1])) no_args = false @@ -67,14 +70,14 @@ if not CURSES then -- Shows all registered command line switches on the command line. M.register('-h', '--help', 0, function() print('Usage: textadept [args] [filenames]') - local line = " %s [%d args]: %s" local list = {} - for switch in pairs(switches) do list[#list + 1] = switch end + for name in pairs(switches) do list[#list + 1] = name end table.sort(list, function(a, b) return a:match('[^-]+') < b:match('[^-]+') end) for i = 1, #list do - local switch = list[i] - print(line:format(switch, table.unpack(switches[switch], 2))) + local switch = switches[list[i]] + print(string.format(' %s [%d args]: %s', list[i], switch.narg, + switch.description)) end os.exit() end, 'Shows this') @@ -103,12 +106,30 @@ for i = 1, #arg do break end end -if not lfs.attributes(_USERHOME) then lfs.mkdir(_USERHOME) end -local f = io.open(_USERHOME..'/init.lua', 'a+') -- ensure existence -if f then f:close() end +local mode = lfs.attributes(_USERHOME, 'mode') +assert(not mode or mode == 'directory', '"%s" is not a directory', _USERHOME) +if not mode then assert(lfs.mkdir(_USERHOME), 'cannot create %s', _USERHOME) end +local user_init = _USERHOME..'/init.lua' +mode = lfs.attributes(user_init, 'mode') +assert(not mode or mode == 'file', '"%s" is not a file', user_init) +if not mode then + assert(io.open(user_init, 'w'), 'unable to create %s', user_init):close() +end -- Placeholders. M.register('-u', '--userhome', 1, function() end, 'Sets alternate _USERHOME') M.register('-f', '--force', 0, function() end, 'Forces unique instance') +-- Run unit tests. +-- Note: have them run after the last `events.INITIALIZED` handler so everything +-- is completely initialized (e.g. menus, macro module, etc.). +M.register('-t', '--test', 1, function(patterns) + events.connect(events.INITIALIZED, function() + local arg = {} + for patt in (patterns or ''):gmatch('[^,]+') do arg[#arg + 1] = patt end + local env = setmetatable({arg = arg}, {__index = _G}) + assert(loadfile(_HOME..'/test.lua', 't', env))() + end) +end, 'Runs unit tests indicated by comma-separated list of patterns (or all)') + return M diff --git a/core/assert.lua b/core/assert.lua new file mode 100644 index 00000000..5517aaa3 --- /dev/null +++ b/core/assert.lua @@ -0,0 +1,57 @@ +-- Copyright 2020 Mitchell mitchell.att.foicica.com. See LICENSE. + +--[[ This comment is for LuaDoc. +--- +-- Extends `_G` with formatted assertions and function argument type checks. +module('assert')]] + +--- +-- Asserts that value *v* is not `false` or `nil` and returns *v*, or calls +-- `error()` with *message* as the error message, defaulting to "assertion +-- failed!". If *message* is a format string, the remaining arguments are passed +-- to `string.format()` and the resulting string becomes the error message. +-- @param v Value to assert. +-- @param message Optional error message to show on error. The default value is +-- "assertion failed!". +-- @param ... If *message* is a format string, these arguments are passed to +-- `string.format()`. +-- @name _G.assert +function assert(v, message, ...) + if v then return v end + if type(message) == 'string' and message:find('%%') then + message = string.format(message, ...) + end + error(message or 'assertion failed!', 2) +end + +--- +-- Asserts that value *v* has type string *expected_type* and returns *v*, or +-- calls `error()` with an error message that implicates function argument +-- number *narg*. +-- This is intended to be used with API function arguments so users receive more +-- helpful error messages. +-- @param v Value to assert the type of. +-- @param expected_type String type to assert. It may be a punctuation-delimited +-- list of type options. +-- @param narg The positional argument number *v* is associated with. This is +-- not required to be a number. +-- @usage assert_type(filename, 'string/nil', 1) +-- @usage assert_type(option.setting, 'number', 'setting') -- implicates key +-- @name _G.assert_type +function assert_type(v, expected_type, narg) + if type(v) == expected_type then return v end + -- Note: do not use assert for performance reasons. + if type(expected_type) ~= 'string' then + error(string.format("bad argument #2 to '%s' (string expected, got %s)", + debug.getinfo(1, 'n').name, type(expected_type)), 2) + elseif narg == nil then + error(string.format("bad argument #3 to '%s' (value expected, got %s)", + debug.getinfo(1, 'n').name, type(narg)), 2) + end + for type_option in expected_type:gmatch('%a+') do + if type(v) == type_option then return v end + end + error(string.format("bad argument #%s to '%s' (%s expected, got %s)", narg, + debug.getinfo(2, 'n').name or '?', expected_type, + type(v)), 3) +end diff --git a/core/events.lua b/core/events.lua index 543133da..438bb5bf 100644 --- a/core/events.lua +++ b/core/events.lua @@ -311,7 +311,9 @@ end}) -- @see disconnect -- @name connect function M.connect(event, f, index) - assert(type(event) == 'string', 'string expected') + assert_type(event, 'string', 1) + assert_type(f, 'function', 2) + assert_type(index, 'number/nil', 3) M.disconnect(event, f) -- in case it already exists table.insert(handlers[event], index or #handlers[event] + 1, f) end @@ -323,7 +325,8 @@ end -- @see connect -- @name disconnect function M.disconnect(event, f) - for i = 1, #handlers[event] do + assert_type(f, 'function', 2) + for i = 1, #handlers[assert_type(event, 'string', 1)] do if handlers[event][i] == f then table.remove(handlers[event], i) break end end end @@ -344,10 +347,10 @@ local error_emitted = false -- @usage events.emit('my_event', 'my message') -- @name emit function M.emit(event, ...) - assert(type(event) == 'string', 'string expected') + local event_handlers = handlers[assert_type(event, 'string', 1)] local i = 1 - while i <= #handlers[event] do - local handler = handlers[event][i] + while i <= #event_handlers do + local handler = event_handlers[i] local ok, result = pcall(handler, ...) if not ok then if not error_emitted then @@ -359,7 +362,7 @@ function M.emit(event, ...) end end if type(result) == 'boolean' then return result end - if handlers[event][i] == handler then i = i + 1 end -- unless M.disconnect() + if event_handlers[i] == handler then i = i + 1 end -- unless M.disconnect() end end diff --git a/core/file_io.lua b/core/file_io.lua index 97977b77..846b4a86 100644 --- a/core/file_io.lua +++ b/core/file_io.lua @@ -95,32 +95,31 @@ io.encodings = {'UTF-8', 'ASCII', 'CP1252', 'UTF-16'} -- @see _G.events -- @name open_file function io.open_file(filenames, encodings) + assert_type(encodings, 'string/table/nil', 2) + if not assert_type(filenames, 'string/table/nil', 1) then + filenames = ui.dialogs.fileselect{ + title = _L['Open File'], select_multiple = true, + with_directory = (buffer.filename or ''):match('^.+[/\\]') or + lfs.currentdir(), + width = CURSES and ui.size[1] - 2 or nil + } + if not filenames then return end + end if type(filenames) == 'string' then filenames = {filenames} end - if type(encodings or '') == 'string' then encodings = {encodings} end - filenames = filenames or ui.dialogs.fileselect{ - title = _L['Open File'], select_multiple = true, - with_directory = (buffer.filename or ''):match('^.+[/\\]') or - lfs.currentdir(), - width = CURSES and ui.size[1] - 2 or nil - } - if not filenames then return end + if type(encodings) ~= 'table' then encodings = {encodings} end for i = 1, #filenames do local filename = lfs.abspath((filenames[i]:gsub('^file://', ''))) - for j = 1, #_BUFFERS do - if filename == _BUFFERS[j].filename then - view:goto_buffer(_BUFFERS[j]) -- already open - goto continue - end + for _, buf in ipairs(_BUFFERS) do + if filename == buf.filename then view:goto_buffer(buf) goto continue end end local text = '' - local f, err = io.open(filename, 'rb') - if f then + if lfs.attributes(filename) then + local f, errmsg = io.open(filename, 'rb') + if not f then error(string.format('cannot open %s', errmsg), 2) end text = f:read('a') f:close() if not text then goto continue end -- filename exists, but cannot read it - elseif lfs.attributes(filename) then - error(err) end local buffer = buffer.new() if encodings[i] then @@ -128,11 +127,11 @@ function io.open_file(filenames, encodings) else -- Try to detect character encoding and convert to UTF-8. local has_zeroes = text:sub(1, 65536):find('\0') - for j = 1, #io.encodings do - if not has_zeroes or io.encodings[j]:find('^UTF%-[13][62]') then - local ok, conv = pcall(string.iconv, text, 'UTF-8', io.encodings[j]) + for _, encoding in ipairs(io.encodings) do + if not has_zeroes or encoding:find('^UTF%-[13][62]') then + local ok, conv = pcall(string.iconv, text, 'UTF-8', encoding) if ok then - buffer.encoding, text = io.encodings[j], conv + buffer.encoding, text = encoding, conv goto encoding_detected end end @@ -185,6 +184,7 @@ end -- LuaDoc is in core/.buffer.luadoc. local function set_encoding(buffer, encoding) + assert_type(encoding, 'string/nil', 1) local pos, first_visible_line = buffer.current_pos, buffer.first_visible_line local text = buffer:get_text() if buffer.encoding then @@ -229,12 +229,14 @@ end -- @name save_file_as function io.save_file_as(filename) local dir, name = (buffer.filename or ''):match('^(.-[/\\]?)([^/\\]*)$') - filename = filename or ui.dialogs.filesave{ - title = _L['Save File'], with_directory = dir, - with_file = name:iconv('UTF-8', _CHARSET), - width = CURSES and ui.size[1] - 2 or nil - } - if not filename then return end + if not assert_type(filename, 'string/nil', 1) then + filename = ui.dialogs.filesave{ + title = _L['Save File'], with_directory = dir, + with_file = name:iconv('UTF-8', _CHARSET), + width = CURSES and ui.size[1] - 2 or nil + } + if not filename then return end + end buffer.filename = filename io.save_file() events.emit(events.FILE_AFTER_SAVE, filename, true) @@ -246,9 +248,9 @@ end -- @name save_all_files function io.save_all_files() local current_buffer = buffer - for i = 1, #_BUFFERS do - if _BUFFERS[i].filename and _BUFFERS[i].modify then - view:goto_buffer(_BUFFERS[i]) + for _, buffer in ipairs(_BUFFERS) do + if buffer.filename and buffer.modify then + view:goto_buffer(buffer) io.save_file() end end @@ -358,11 +360,11 @@ local vcs = {'.bzr', '.git', '.hg', '.svn'} -- @return string root or nil -- @name get_project_root function io.get_project_root(path) - local lfs_attributes = lfs.attributes - local dir = path or (buffer.filename or lfs.currentdir()..'/'):match('^(.+)[/\\]') + local dir = assert_type(path, 'string/nil', 1) or + (buffer.filename or lfs.currentdir()..'/'):match('^(.+)[/\\]') while dir do for i = 1, #vcs do - if lfs_attributes(dir..'/'..vcs[i], 'mode') then return dir end + if lfs.attributes(dir..'/'..vcs[i], 'mode') then return dir end end dir = dir:match('^(.+)[/\\]') end @@ -414,8 +416,12 @@ io.quick_open_filters = {} -- @see ui.dialogs.filteredlist -- @name quick_open function io.quick_open(paths, filter, opts) - if not paths then paths = io.get_project_root() end - if not paths then return end + if not assert_type(paths, 'string/table/nil', 1) then + paths = io.get_project_root() + if not paths then return end + end + assert_type(filter, 'string/table/nil', 2) + assert_type(opts, 'table/nil', 3) if type(paths) == 'string' then if not filter then filter = io.quick_open_filters[paths] end paths = {paths} @@ -429,11 +435,12 @@ function io.quick_open(paths, filter, opts) end, filter or lfs.default_filter) end if #utf8_list >= io.quick_open_max then - local msg = string.format('%d %s %d', io.quick_open_max, - _L['files or more were found. Showing the first'], - io.quick_open_max) ui.dialogs.msgbox{ - title = _L['File Limit Exceeded'], text = msg, icon = 'gtk-dialog-info' + title = _L['File Limit Exceeded'], + text = string.format('%d %s %d', io.quick_open_max, + _L['files or more were found. Showing the first'], + io.quick_open_max), + icon = 'gtk-dialog-info' } end local options = { diff --git a/core/init.lua b/core/init.lua index f2d727d1..0f187e0a 100644 --- a/core/init.lua +++ b/core/init.lua @@ -6,6 +6,11 @@ _COPYRIGHT = 'Copyright © 2007-2020 Mitchell. See LICENSE.\n'.. package.path = _HOME..'/core/?.lua;'..package.path +--for i = 1, #arg do +-- if arg[i] == '-t' or arg[i] == '--test' then pcall(require, 'luacov') end +--end + +require('assert') _SCINTILLA = require('iface') events = require('events') args = require('args') @@ -14,22 +19,22 @@ require('file_io') require('lfs_ext') require('ui') keys = require('keys') - _M = {} -- language modules table + -- pdcurses compatibility. if CURSES and WIN32 then function os.spawn(argv, ...) local current_dir = lfs.currentdir() - local i = 1 - if type(select(i, ...) or nil) == 'string' then - lfs.chdir(select(i, ...)) -- cwd + local args, i = {...}, 1 + if type(args[i]) == 'string' then + lfs.chdir(args[i]) -- cwd i = i + 1 end - if type(select(i, ...) or nil) == 'table' then i = i + 1 end -- env (ignore) + if type(args[i]) == 'table' then i = i + 1 end -- env (ignore) local p = io.popen(argv..' 2>&1') - if select(i, ...) then select(i, ...)(p:read('a')) end -- stdout_cb + if type(args[i]) == 'function' then args[i](p:read('a')) end -- stdout_cb local status = select(3, p:close()) - if select(i + 2, ...) then select(i + 2, ...)(status) end -- exit_cb + if type(args[i + 2]) == 'function' then args[i + 2](status) end -- exit_cb lfs.chdir(current_dir) return p end @@ -39,6 +44,8 @@ end -- argument. -- Documentation is in core/.buffer.luadoc. local function text_range(buffer, start_pos, end_pos) + assert_type(start_pos, 'number', 2) + assert_type(end_pos, 'number', 3) local target_start, target_end = buffer.target_start, buffer.target_end if start_pos < 0 then start_pos = 0 end if end_pos > buffer.length then end_pos = buffer.length end diff --git a/core/keys.lua b/core/keys.lua index 35af9bc8..a6a9207a 100644 --- a/core/keys.lua +++ b/core/keys.lua @@ -88,9 +88,9 @@ local M = {} -- ## Key Chains -- -- Key chains are a powerful concept. They allow you to assign multiple key --- bindings to one key sequence. By default, the `Esc` (`⎋` on Mac OSX | `Esc` --- in curses) key cancels a key chain, but you can redefine it via --- [`keys.CLEAR`](). An example key chain looks like: +-- bindings to one key sequence. By default, the `Esc` key cancels a key chain, +-- but you can redefine it via [`keys.CLEAR`](). An example key chain looks +-- like: -- -- keys['aa'] = { -- a = function1, @@ -205,10 +205,13 @@ local function keypress(code, shift, control, alt, meta, caps_lock) local key = code < 256 and (not CURSES or (code ~= 7 and code ~= 13)) and string.char(code) or M.KEYSYMS[code] if not key then return end - shift = shift and (code >= 256 or code == 9) -- printable chars are uppercased - if OSX and alt and code < 256 then alt = false end -- composed key; ignore alt - local key_seq = (control and CTRL or '')..(alt and ALT or '').. - (meta and OSX and META or '')..(shift and SHIFT or '')..key + -- Since printable characters are uppercased, disable shift. + if shift and code < 256 and code ~= 9 then shift = false end + -- For composed keys on OSX, ignore alt. + if OSX and alt and code < 256 then alt = false end + local key_seq = string.format('%s%s%s%s%s', control and CTRL or '', + alt and ALT or '', meta and OSX and META or '', + shift and SHIFT or '', key) --print(key_seq) ui.statusbar_text = '' diff --git a/core/lfs_ext.lua b/core/lfs_ext.lua index c71c42bc..6d9fe2af 100644 --- a/core/lfs_ext.lua +++ b/core/lfs_ext.lua @@ -48,6 +48,10 @@ lfs.default_filter = { -- @see filter -- @name dir_foreach function lfs.dir_foreach(dir, f, filter, n, include_dirs, level) + assert_type(dir, 'string', 1) + assert_type(f, 'function', 2) + assert_type(filter, 'string/table/nil', 3) + assert_type(n, 'number/nil', 4) if not level then -- Convert filter to a table from nil or string arguments. if not filter then filter = lfs.default_filter end @@ -60,8 +64,7 @@ function lfs.dir_foreach(dir, f, filter, n, include_dirs, level) consider_any = true, exts = setmetatable({}, {__index = function() return true end}) } - for i = 1, #filter do - local patt = filter[i] + for _, patt in ipairs(filter) do patt = patt:gsub('^(!?)%%?%.([^.]+)$', '%1%%.%2$') -- '.lua' to '%.lua$' patt = patt:gsub('/([^\\])', '[/\\]%1') -- '/' to '[/\\]' local include = not patt:find('^!') @@ -76,10 +79,9 @@ function lfs.dir_foreach(dir, f, filter, n, include_dirs, level) end filter = processed_filter end - local dir_sep = not WIN32 and '/' or '\\' for basename in lfs.dir(dir) do if basename:find('^%.%.?$') then goto continue end -- ignore . and .. - local filename = dir..(dir ~= '/' and dir_sep or '')..basename + local filename = dir..(dir ~= '/' and '/' or '')..basename local mode = lfs.attributes(filename, 'mode') if mode ~= 'directory' and mode ~= 'file' then goto continue end local include @@ -90,22 +92,23 @@ function lfs.dir_foreach(dir, f, filter, n, include_dirs, level) elseif mode == 'directory' then include = filter.consider_any end - for i = 1, #filter do - local patt = filter[i] + for _, patt in ipairs(filter) do -- Treat exclusive patterns as logical AND. if patt:find('^!') and filename:find(patt:sub(2)) then goto continue end -- Treat inclusive patterns as logical OR. include = include or (not patt:find('^!') and filename:find(patt)) end + local dir_sep = not WIN32 and '/' or '\\' + local os_filename = not WIN32 and filename or filename:gsub('/', dir_sep) if include and mode == 'directory' then - if include_dirs and f(filename..dir_sep) == false then return end + if include_dirs and f(os_filename..dir_sep) == false then return end if not n or (level or 0) < n then local halt = lfs.dir_foreach(filename, f, filter, n, include_dirs, (level or 0) + 1) == false if halt then return false end end elseif include and mode == 'file' then - if f(filename) == false then return false end + if f(os_filename) == false then return false end end ::continue:: end @@ -120,6 +123,8 @@ end -- @return string absolute path -- @name abspath function lfs.abspath(filename, prefix) + assert_type(filename, 'string', 1) + assert_type(prefix, 'string/nil', 2) if WIN32 then filename = filename:gsub('/', '\\') end if not filename:find(not WIN32 and '^/' or '^%a:[/\\]') and not (WIN32 and filename:find('^\\\\')) then diff --git a/core/locale.lua b/core/locale.lua index 48de4af5..45a0f855 100644 --- a/core/locale.lua +++ b/core/locale.lua @@ -23,12 +23,12 @@ for line in f:lines() do -- comment. if not line:find('^%s*[^%w_%[]') then local id, str = line:match('^(.-)%s*=%s*(.+)$') - if id and str and assert(not M[id], 'duplicate locale key: '..id) then + if id and str and assert(not M[id], 'duplicate locale key "%s"', id) then M[id] = not CURSES and str or str:gsub('_', '') end end end f:close() -return setmetatable(M, - {__index = function(_, k) return 'No Localization:'..k end}) +setmetatable(M, {__index = function(_, k) return 'No Localization:'..k end}) +return M diff --git a/core/ui.lua b/core/ui.lua index b5c46114..65b1fb4d 100644 --- a/core/ui.lua +++ b/core/ui.lua @@ -41,11 +41,8 @@ ui.silent_print = false -- @see ui._print local function _print(buffer_type, ...) local print_buffer - for i = 1, #_BUFFERS do - if _BUFFERS[i]._type == buffer_type then - print_buffer = _BUFFERS[i] - break - end + for _, buffer in ipairs(_BUFFERS) do + if buffer._type == buffer_type then print_buffer = buffer break end end if not print_buffer then if not ui.tabs then view:split() end @@ -53,13 +50,10 @@ local function _print(buffer_type, ...) print_buffer._type = buffer_type events.emit(events.FILE_OPENED) elseif not ui.silent_print then - for i = 1, #_VIEWS do - local view = _VIEWS[i] + for _, view in ipairs(_VIEWS) do if view.buffer._type == buffer_type then ui.goto_view(view) break end end - if view.buffer._type ~= buffer_type then - view:goto_buffer(print_buffer) - end + if view.buffer._type ~= buffer_type then view:goto_buffer(print_buffer) end end local args, n = {...}, select('#', ...) for i = 1, n do args[i] = tostring(args[i]) end @@ -78,7 +72,9 @@ end -- @param ... Message strings. -- @usage ui._print(_L['[Message Buffer]'], message) -- @name _print -function ui._print(buffer_type, ...) _print(buffer_type, ...) end +function ui._print(buffer_type, ...) + _print(assert_type(buffer_type, 'string', 1), ...) +end --- -- Prints the given string messages to the message buffer. @@ -87,6 +83,16 @@ function ui._print(buffer_type, ...) _print(buffer_type, ...) end -- @name print function ui.print(...) ui._print(_L['[Message Buffer]'], ...) end +-- Returns 0xBBGGRR colors transformed into "#RRGGBB" for the colorselect +-- dialog. +-- @param value Number color to transform. +-- @return string or nil if the transform failed +local function torgb(value) + local bbggrr = string.format('%06X', value) + local b, g, r = bbggrr:match('^(%x%x)(%x%x)(%x%x)$') + return r and g and b and string.format('#%s%s%s', r, g, b) or nil +end + -- Documentation is in core/.ui.dialogs.luadoc. ui.dialogs = setmetatable({}, {__index = function(_, k) -- Wrapper for `ui.dialog(k)`, transforming the given table of arguments into @@ -106,22 +112,23 @@ ui.dialogs = setmetatable({}, {__index = function(_, k) -- Transform key-value pairs into command line arguments. local args = {} for option, value in pairs(options) do + assert_type(value, 'string/number/table/boolean', option) if value then args[#args + 1] = '--'..option:gsub('_', '-') - if option == 'color' or - option == 'palette' and type(value) ~= 'boolean' then - -- Transform 0xBBGGRR colors into "#RRGGBB" for color selector. - if type(value) ~= 'table' then value = {value} end - for i = 1, #value do - if type(value[i]) == 'number' then - local bbggrr = string.format('%06X', value[i]) - local b, g, r = bbggrr:match('^(%x%x)(%x%x)(%x%x)$') - if r and g and b then value[i] = '#'..r..g..b end + if type(value) == 'boolean' then goto continue end + if type(value) == 'table' then + for i, val in ipairs(value) do + assert_type(val, 'string/number', option..'['..i..']') + if option == 'palette' and type(val) == 'number' then + value[i] = torgb(val) -- nil return is okay end end + elseif option == 'color' and type(value) == 'number' then + value = torgb(value) end - if type(value) ~= 'boolean' then args[#args + 1] = value end + args[#args + 1] = value end + ::continue:: end -- Call gtdialog, stripping any trailing newline in the standard output. local result = ui.dialog(k:gsub('_', '-'), table.unpack(args)) @@ -153,7 +160,8 @@ ui.dialogs = setmetatable({}, {__index = function(_, k) elseif k == 'colorselect' then if options.string_output then return result ~= '' and result or nil end local r, g, b = result:match('^#(%x%x)(%x%x)(%x%x)$') - return r and g and b and tonumber('0x'..b..g..r) or nil + local bgr = r and g and b and string.format('0x%s%s%s', b, g, r) or nil + return tonumber(bgr) elseif k == 'fontselect' then return result ~= '' and result or nil elseif not options.string_output then @@ -246,8 +254,9 @@ end -- is `false`. -- @name goto_file function ui.goto_file(filename, split, preferred_view, sloppy) - local patt = '^'..filename..'$' -- TODO: escape filename properly - if sloppy then patt = filename:match('[^/\\]+$')..'$' end + assert_type(filename, 'string', 1) + local patt = not sloppy and '^'..filename..'$' or + filename:match('[^/\\]+$')..'$' -- TODO: escape filename properly if WIN32 then patt = patt:gsub('%a', function(letter) return string.format('[%s%s]', letter:upper(), letter:lower()) @@ -257,20 +266,15 @@ function ui.goto_file(filename, split, preferred_view, sloppy) view:split() else local other_view = _VIEWS[preferred_view] - for i = 1, #_VIEWS do - if (_VIEWS[i].buffer.filename or ''):find(patt) then - ui.goto_view(_VIEWS[i]) - return - end - if not other_view and _VIEWS[i] ~= view then other_view = _VIEWS[i] end + for _, view in ipairs(_VIEWS) do + local filename = view.buffer.filename or '' + if filename:find(patt) then ui.goto_view(view) return end + if not other_view and view ~= _G.view then other_view = view end end if other_view then ui.goto_view(other_view) end end - for i = 1, #_BUFFERS do - if (_BUFFERS[i].filename or ''):find(patt) then - view:goto_buffer(_BUFFERS[i]) - return - end + for _, buf in ipairs(_BUFFERS) do + if (buf.filename or ''):find(patt) then view:goto_buffer(buf) return end end io.open_file(filename) end @@ -418,13 +422,10 @@ events_connect(events.RESET_AFTER, -- Prompts for confirmation if any buffers are modified. events_connect(events.QUIT, function() local utf8_list = {} - for i = 1, #_BUFFERS do - if _BUFFERS[i].modify then - local filename = _BUFFERS[i].filename or _BUFFERS[i]._type or - _L['Untitled'] - if _BUFFERS[i].filename then - filename = filename:iconv('UTF-8', _CHARSET) - end + for _, buffer in ipairs(_BUFFERS) do + if buffer.modify then + local filename = buffer.filename or buffer._type or _L['Untitled'] + if buffer.filename then filename = filename:iconv('UTF-8', _CHARSET) end utf8_list[#utf8_list + 1] = filename end end |