aboutsummaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
authormitchell <70453897+667e-11@users.noreply.github.com>2020-03-03 19:39:02 -0500
committermitchell <70453897+667e-11@users.noreply.github.com>2020-03-03 19:39:02 -0500
commitfceb1a37df623649d191c3c1a881e5b0538b1391 (patch)
tree87a34dfc2397dc4afdaa0c8ec189f037300f458e /core
parent1618f5017abb3c9bacc9ba346bf22a936ef5dd06 (diff)
downloadtextadept-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.lua45
-rw-r--r--core/assert.lua57
-rw-r--r--core/events.lua15
-rw-r--r--core/file_io.lua85
-rw-r--r--core/init.lua21
-rw-r--r--core/keys.lua17
-rw-r--r--core/lfs_ext.lua21
-rw-r--r--core/locale.lua6
-rw-r--r--core/ui.lua85
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