aboutsummaryrefslogtreecommitdiff
path: root/test.lua
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 /test.lua
parent1618f5017abb3c9bacc9ba346bf22a936ef5dd06 (diff)
downloadtextadept-fceb1a37df623649d191c3c1a881e5b0538b1391.tar.gz
textadept-fceb1a37df623649d191c3c1a881e5b0538b1391.zip
Added test suite and API type checking for more helpful error messages.
Diffstat (limited to 'test.lua')
-rw-r--r--test.lua2507
1 files changed, 2507 insertions, 0 deletions
diff --git a/test.lua b/test.lua
new file mode 100644
index 00000000..42b62461
--- /dev/null
+++ b/test.lua
@@ -0,0 +1,2507 @@
+-- Copyright 2020 Mitchell mitchell.att.foicica.com. See LICENSE.
+
+-- Scintilla uses 0-based indices as opposed to Lua's 1-based indices.
+local function LINE(i) return i - 1 end
+local function POS(i) return i - 1 end
+local function INDEX(i) return i - 1 end
+if CURSES then function update_ui() end end
+
+local _tostring = tostring
+-- Overloads tostring() to print more user-friendly output for `assert_equal()`.
+function tostring(value)
+ if type(value) == 'table' then
+ return string.format('{%s}', table.concat(value, ', '))
+ elseif type(value) == 'string' then
+ return string.format('%q', value)
+ else
+ return _tostring(value)
+ end
+end
+
+-- Asserts that values *v1* and *v2* are equal.
+-- Tables are compared by value, not by reference.
+function assert_equal(v1, v2)
+ if v1 == v2 then return end
+ if type(v1) == 'table' and type(v2) == 'table' then
+ if #v1 == #v2 then
+ for k, v in pairs(v1) do if v2[k] ~= v then goto continue end end
+ for k, v in pairs(v2) do if v1[k] ~= v then goto continue end end
+ return
+ end
+ ::continue::
+ end
+ error(string.format('%s ~= %s', tostring(v1), tostring(v2)), 2)
+end
+
+
+-- Asserts that function *f* raises an error whose error message contains string
+-- *expected_errmsg*.
+-- @param f Function to call.
+-- @param expected_errmsg String the error message should contain.
+function assert_raises(f, expected_errmsg)
+ local ok, errmsg = pcall(f)
+ if ok then error('error expected', 2) end
+ if expected_errmsg ~= errmsg and
+ not tostring(errmsg):find(expected_errmsg, 1, true) then
+ error(string.format(
+ 'error message %q expected, was %q', expected_errmsg, errmsg), 2)
+ end
+end
+
+local expected_failures = {}
+function expected_failure(f) expected_failures[f] = true end
+local unstable_tests = {}
+function unstable(f) unstable_tests[f] = true end
+
+--------------------------------------------------------------------------------
+
+function test_assert()
+ assert_equal(assert(true, 'okay'), true)
+ assert_raises(function() assert(false, 'not okay') end, 'not okay')
+ assert_raises(function() assert(false, 'not okay: %s', false) end, 'not okay: false')
+ assert_raises(function() assert(false, 'not okay: %s') end, 'no value')
+ assert_raises(function() assert(false, 1234) end, '1234')
+ assert_raises(function() assert(false) end, 'assertion failed!')
+end
+
+function test_assert_types()
+ function foo(bar, baz, quux)
+ assert_type(bar, 'string', 1)
+ assert_type(baz, 'boolean/nil', 2)
+ assert_type(quux, 'string/table/nil', 3)
+ return bar
+ end
+ assert_equal(foo('bar'), 'bar')
+ assert_raises(function() foo(1) end, "bad argument #1 to 'foo' (string expected, got number")
+ assert_raises(function() foo('bar', 'baz') end, "bad argument #2 to 'foo' (boolean/nil expected, got string")
+ assert_raises(function() foo('bar', true, 1) end, "bad argument #3 to 'foo' (string/table/nil expected, got number")
+
+ function foo(bar) assert_type(bar, string) end
+ assert_raises(function() foo(1) end, "bad argument #2 to 'assert_type' (string expected, got table")
+ function foo(bar) assert_type(bar, 'string') end
+ assert_raises(function() foo(1) end, "bad argument #3 to 'assert_type' (value expected, got nil")
+end
+
+function test_events_basic()
+ local emitted = false
+ local event, handler = 'test_basic', function() emitted = true end
+ events.connect(event, handler)
+ events.emit(event)
+ assert(emitted, 'event not emitted or handled')
+ emitted = false
+ events.disconnect(event, handler)
+ events.emit(event)
+ assert(not emitted, 'event still handled')
+
+ assert_raises(function() events.connect(nil) end, 'string expected')
+ assert_raises(function() events.connect(event, nil) end, 'function expected')
+ assert_raises(function() events.connect(event, function() end, 'bar') end, 'number/nil expected')
+ assert_raises(function() events.disconnect() end, 'expected, got nil')
+ assert_raises(function() events.disconnect(event, nil) end, 'function expected')
+ assert_raises(function() events.emit(nil) end, 'string expected')
+end
+
+function test_events_single_handle()
+ local count = 0
+ local event, handler = 'test_single_handle', function() count = count + 1 end
+ events.connect(event, handler)
+ events.connect(event, handler) -- should disconnect first
+ events.emit(event)
+ assert_equal(count, 1)
+end
+
+function test_events_insert()
+ local foo = {}
+ local event = 'test_insert'
+ events.connect(event, function() foo[#foo + 1] = 2 end)
+ events.connect(event, function() foo[#foo + 1] = 1 end, 1)
+ events.emit(event)
+ assert_equal({1, 2}, foo)
+end
+
+function test_events_short_circuit()
+ local emitted = false
+ local event = 'test_short_circuit'
+ events.connect(event, function() return true end)
+ events.connect(event, function() emitted = true end)
+ assert_equal(events.emit(event), true)
+ assert_equal(emitted, false)
+end
+
+function test_events_disconnect_during_handle()
+ local foo = {}
+ local event, handlers = 'test_disconnect_during_handle', {}
+ for i = 1, 3 do
+ handlers[i] = function()
+ foo[#foo + 1] = i
+ events.disconnect(event, handlers[i])
+ end
+ events.connect(event, handlers[i])
+ end
+ events.emit(event)
+ assert_equal({1, 2, 3}, foo)
+end
+
+function test_events_error()
+ local errmsg
+ local event, handler = 'test_error', function(message)
+ errmsg = message
+ return false -- halt propagation
+ end
+ events.connect(events.ERROR, handler, 1)
+ events.connect(event, function() error('foo') end)
+ events.emit(event)
+ events.disconnect(events.ERROR, handler)
+ assert(errmsg:find('foo'), 'error handler did not run')
+end
+
+local locales = {}
+-- Load localizations from *locale_conf* and return them in a table.
+-- @param locale_conf String path to a local file to load.
+local function load_locale(locale_conf)
+ if locales[locale_conf] then return locales[locale_conf] end
+ print(string.format('Loading locale "%s"', locale_conf))
+ local L = {}
+ for line in io.lines(locale_conf) do
+ if not line:find('^%s*[^%w_%[]') then
+ local id, str = line:match('^(.-)%s*=%s*(.+)$')
+ if id and str and assert(not L[id], 'duplicate locale id "%s"', id) then
+ L[id] = str
+ end
+ end
+ end
+ locales[locale_conf] = L
+ return L
+end
+
+-- Looks for use of localization in the given Lua file and verifies that each
+-- use is okay.
+-- @param filename String filename of the Lua file to check.
+-- @param L Table of localizations to read from.
+local function check_localizations(filename, L)
+ print(string.format('Processing file "%s"', filename:gsub(_HOME, '')))
+ local count = 0
+ for line in io.lines(filename) do
+ local id = line:match([=[_L%[['"]([^'"]+)['"]%]]=])
+ if id and assert(L[id], 'locale missing id "%s"', id) then
+ count = count + 1
+ end
+ end
+ print(string.format('Checked %d localizations.', count))
+end
+
+local loaded_extra = {}
+-- Records localization assignments in the given Lua file for use in subsequent
+-- checks.
+-- @param L Table of localizations to add to.
+local function load_extra_localizations(filename, L)
+ if loaded_extra[filename] then return end
+ print(string.format('Processing file "%s"', filename:gsub(_HOME, '')))
+ local count = 0
+ for line in io.lines(filename) do
+ if line:find('_L%b[]%s*=') then
+ local id = line:match([=[_L%[['"]([^'"]+)['"]%]%s*=]=])
+ if id and assert(not L[id], 'duplicate locale id "%s"', id) then
+ L[id], count = true, count + 1
+ end
+ end
+ end
+ loaded_extra[filename] = true
+ print(string.format('Added %d localizations.', count))
+end
+
+local LOCALE_CONF = _HOME .. '/core/locale.conf'
+local LOCALE_DIR = _HOME .. '/core/locales'
+
+function test_locale_load()
+ local L = load_locale(LOCALE_CONF)
+ lfs.dir_foreach(LOCALE_DIR, function(locale_conf)
+ local l = load_locale(locale_conf)
+ for id in pairs(L) do assert(l[id], 'locale missing id "%s"', id) end
+ for id in pairs(l) do assert(L[id], 'locale has extra id "%s"', id) end
+ end)
+end
+
+function test_locale_use_core()
+ local L = load_locale(LOCALE_CONF)
+ local ta_dirs = {'core', 'modules/ansi_c', 'modules/lua', 'modules/textadept'}
+ for _, dir in ipairs(ta_dirs) do
+ dir = _HOME .. '/' .. dir
+ lfs.dir_foreach(
+ dir, function(filename) check_localizations(filename, L) end, '.lua')
+ end
+ check_localizations(_HOME .. '/init.lua', L)
+end
+
+function test_locale_use_extra()
+ local L = load_locale(LOCALE_CONF)
+ lfs.dir_foreach(
+ _HOME, function(filename) load_extra_localizations(filename, L) end, '.lua')
+ lfs.dir_foreach(
+ _HOME, function(filename) check_localizations(filename, L) end, '.lua')
+end
+
+function test_locale_use_userhome()
+ local L = load_locale(LOCALE_CONF)
+ lfs.dir_foreach(
+ _HOME, function(filename) load_extra_localizations(filename, L) end, '.lua')
+ lfs.dir_foreach(_USERHOME, function(filename)
+ load_extra_localizations(filename, L)
+ end, '.lua')
+ L['%1'] = true -- snippet
+ lfs.dir_foreach(
+ _USERHOME, function(filename) check_localizations(filename, L) end, '.lua')
+end
+
+function test_file_io_open_file_detect_encoding()
+ io.recent_files = {} -- clear
+ local recent_files = {}
+ local files = {
+ [_HOME .. '/test/file_io/utf8'] = 'UTF-8',
+ [_HOME .. '/test/file_io/cp1252'] = 'CP1252',
+ [_HOME .. '/test/file_io/utf16'] = 'UTF-16',
+ [_HOME .. '/test/file_io/binary'] = '',
+ }
+ for filename, encoding in pairs(files) do
+ print(string.format('Opening file %s', filename))
+ io.open_file(filename)
+ assert_equal(buffer.filename, filename)
+ local f = io.open(filename, 'rb')
+ local contents = f:read('a')
+ f:close()
+ if encoding ~= '' then
+ --assert_equal(buffer:get_text():iconv(encoding, 'UTF-8'), contents)
+ assert_equal(buffer.encoding, encoding)
+ assert_equal(buffer.code_page, buffer.CP_UTF8)
+ else
+ assert_equal(buffer:get_text(), contents)
+ assert_equal(buffer.encoding, nil)
+ assert_equal(buffer.code_page, 0)
+ end
+ io.close_buffer()
+ table.insert(recent_files, 1, filename)
+ end
+ assert_equal(io.recent_files, recent_files)
+
+ assert_raises(function() io.open_file(1) end, 'string/table/nil expected, got number')
+ assert_raises(function() io.open_file('/tmp/foo', true) end, 'string/table/nil expected, got boolean')
+end
+
+function test_file_io_open_file_detect_newlines()
+ local files = {
+ [_HOME .. '/test/file_io/lf'] = buffer.EOL_LF,
+ [_HOME .. '/test/file_io/crlf'] = buffer.EOL_CRLF,
+ }
+ for filename, mode in pairs(files) do
+ io.open_file(filename)
+ assert_equal(buffer.eol_mode, mode)
+ io.close_buffer()
+ end
+end
+
+function test_file_io_open_file_with_encoding()
+ local num_buffers = #_BUFFERS
+ local files = {
+ _HOME .. '/test/file_io/utf8',
+ _HOME .. '/test/file_io/cp1252',
+ _HOME .. '/test/file_io/utf16'
+ }
+ local encodings = {nil, 'CP1252', 'UTF-16'}
+ io.open_file(files, encodings)
+ assert_equal(#_BUFFERS, num_buffers + #files)
+ for i = #files, 1, -1 do
+ view:goto_buffer(_BUFFERS[num_buffers + i])
+ assert_equal(buffer.filename, files[i])
+ if encodings[i] then assert_equal(buffer.encoding, encodings[i]) end
+ io.close_buffer()
+ end
+end
+
+function test_file_io_open_file_already_open()
+ local filename = _HOME .. '/test/file_io/utf8'
+ io.open_file(filename)
+ buffer.new()
+ local num_buffers = #_BUFFERS
+ io.open_file(filename)
+ assert_equal(buffer.filename, filename)
+ assert_equal(#_BUFFERS, num_buffers)
+ view:goto_buffer(1)
+ io.close_buffer() -- untitled
+ io.close_buffer() -- filename
+end
+
+function test_file_io_open_file_interactive()
+ local num_buffers = #_BUFFERS
+ io.open_file()
+ if #_BUFFERS > num_buffers then io.close_buffer() end
+end
+
+function test_file_io_open_file_errors()
+ if LINUX then
+ assert_raises(function() io.open_file('/etc/group-') end, 'cannot open /etc/group-: Permission denied')
+ end
+ -- TODO: find a case where the file can be opened, but not read
+end
+
+function test_file_io_reload_file()
+ io.open_file(_HOME .. '/test/file_io/utf8')
+ local pos = 10
+ buffer:goto_pos(pos)
+ local text = buffer:get_text()
+ buffer:append_text('foo')
+ assert(buffer:get_text() ~= text, 'buffer text is unchanged')
+ io.reload_file()
+ assert_equal(buffer:get_text(), text)
+ assert_equal(buffer.current_pos, pos)
+ io.close_buffer()
+end
+
+function test_file_io_set_encoding()
+ io.open_file(_HOME .. '/test/file_io/utf8')
+ local pos = 10
+ buffer:goto_pos(pos)
+ local text = buffer:get_text()
+ buffer:set_encoding('CP1252')
+ assert_equal(buffer.encoding, 'CP1252')
+ assert_equal(buffer.code_page, buffer.CP_UTF8)
+ assert_equal(buffer:get_text(), text) -- fundamentally the same
+ assert_equal(buffer.current_pos, pos)
+ io.reload_file()
+ io.close_buffer()
+
+ assert_raises(function() buffer:set_encoding(true) end, 'string/nil expected, got boolean')
+end
+
+function test_file_io_save_file()
+ buffer.new()
+ buffer:append_text('foo')
+ local filename = os.tmpname()
+ io.save_file_as(filename)
+ local f = assert(io.open(filename))
+ local contents = f:read('a')
+ f:close()
+ assert_equal(contents, buffer:get_text())
+ buffer:append_text('bar')
+ io.save_all_files()
+ f = assert(io.open(filename))
+ contents = f:read('a')
+ f:close()
+ assert_equal(contents, buffer:get_text())
+ io.close_buffer()
+ os.remove(filename)
+
+ assert_raises(function() io.save_file_as(1) end, 'string/nil expected, got number')
+end
+
+function test_file_io_file_detect_modified()
+ local modified = false
+ local handler = function()
+ modified = true
+ return false -- halt propagation
+ end
+ events.connect(events.FILE_CHANGED, handler, 1)
+ local filename = os.tmpname()
+ local f = assert(io.open(filename, 'w'))
+ f:write('foo\n'):flush()
+ io.open_file(filename)
+ assert_equal(buffer:get_text(), 'foo\n')
+ view:goto_buffer(-1)
+ os.execute('sleep 1') -- filesystem mod time has 1-second granularity
+ f:write('bar\n'):flush()
+ view:goto_buffer(1)
+ assert_equal(modified, true)
+ io.close_buffer()
+ f:close()
+ os.remove(filename)
+ events.disconnect(events.FILE_CHANGED, handler)
+end
+
+function test_file_io_file_detect_modified_interactive()
+ local filename = os.tmpname()
+ local f = assert(io.open(filename, 'w'))
+ f:write('foo\n'):flush()
+ io.open_file(filename)
+ assert_equal(buffer:get_text(), 'foo\n')
+ view:goto_buffer(-1)
+ os.execute('sleep 1') -- filesystem mod time has 1-second granularity
+ f:write('bar\n'):flush()
+ view:goto_buffer(1)
+ assert_equal(buffer:get_text(), 'foo\nbar\n')
+ io.close_buffer()
+ f:close()
+ os.remove(filename)
+end
+
+function test_file_io_recent_files()
+ io.recent_files = {} -- clear
+ local recent_files = {}
+ local files = {
+ _HOME .. '/test/file_io/utf8',
+ _HOME .. '/test/file_io/cp1252',
+ _HOME .. '/test/file_io/utf16',
+ _HOME .. '/test/file_io/binary'
+ }
+ for _, filename in ipairs(files) do
+ io.open_file(filename)
+ io.close_buffer()
+ table.insert(recent_files, 1, filename)
+ end
+ assert_equal(io.recent_files, recent_files)
+end
+
+function test_file_io_open_recent_interactive()
+ local filename = _HOME .. '/test/file_io/utf8'
+ io.open_file(filename)
+ io.close_buffer()
+ io.open_recent_file()
+ assert_equal(buffer.filename, filename)
+ io.close_buffer()
+end
+
+function test_file_io_get_project_root()
+ local cwd = lfs.currentdir()
+ lfs.chdir(_HOME)
+ assert_equal(io.get_project_root(), _HOME)
+ lfs.chdir(cwd)
+ assert_equal(io.get_project_root(_HOME), _HOME)
+ assert_equal(io.get_project_root(_HOME .. '/core'), _HOME)
+ assert_equal(io.get_project_root('/tmp'), nil)
+
+ assert_raises(function() io.get_project_root(1) end, 'string/nil expected, got number')
+end
+
+function test_file_io_quick_open_interactive()
+ local num_buffers = #_BUFFERS
+ local cwd = lfs.currentdir()
+ local dir = _HOME .. '/core'
+ lfs.chdir(dir)
+ io.quick_open_filters[dir] = '.lua'
+ io.quick_open(dir)
+ if #_BUFFERS > num_buffers then
+ assert(buffer.filename:find('%.lua$'), '.lua file filter did not work')
+ io.close_buffer()
+ end
+ io.quick_open_filters[_HOME] = '.lua'
+ io.quick_open()
+ if #_BUFFERS > num_buffers then
+ assert(buffer.filename:find('%.lua$'), '.lua file filter did not work')
+ io.close_buffer()
+ end
+ lfs.chdir(cwd)
+
+ assert_raises(function() io.quick_open(1) end, 'string/table/nil expected, got number')
+ assert_raises(function() io.quick_open(_HOME, true) end, 'string/table/nil expected, got boolean')
+ assert_raises(function() io.quick_open(_HOME, nil, 1) end, 'table/nil expected, got number')
+end
+
+function test_lfs_ext_dir_foreach()
+ local files, directories = 0, 0
+ lfs.dir_foreach(_HOME .. '/core', function(filename)
+ if not filename:find('/$') then
+ files = files + 1
+ else
+ directories = directories + 1
+ end
+ end, nil, nil, true)
+ assert(files > 0, 'no files found')
+ assert(directories > 0, 'no directories found')
+
+ assert_raises(function() lfs.dir_foreach() end, 'string expected, got nil')
+ assert_raises(function() lfs.dir_foreach(_HOME) end, 'function expected, got nil')
+ assert_raises(function() lfs.dir_foreach(_HOME, function() end, 1) end, 'string/table/nil expected, got number')
+ assert_raises(function() lfs.dir_foreach(_HOME, function() end, nil, true) end, 'number/nil expected, got boolean')
+end
+
+function test_lfs_ext_dir_foreach_filter_lua()
+ local count = 0
+ lfs.dir_foreach(_HOME .. '/core', function(filename)
+ assert(filename:find('%.lua$'), '"%s" not a Lua file', filename)
+ count = count + 1
+ end, '.lua')
+ assert(count > 0, 'no Lua files found')
+end
+
+function test_lfs_ext_dir_foreach_filter_exclusive()
+ local count = 0
+ lfs.dir_foreach(_HOME .. '/core', function(filename)
+ assert(not filename:find('%.lua$'), '"%s" is a Lua file', filename)
+ count = count + 1
+ end, '!.lua')
+ assert(count > 0, 'no non-Lua files found')
+end
+
+function test_lfs_ext_dir_foreach_filter_dir()
+ local count = 0
+ lfs.dir_foreach(_HOME, function(filename)
+ assert(filename:find('/core/'), '"%s" is not in core/', filename)
+ count = count + 1
+ end, '/core')
+ assert(count > 0, 'no core files found')
+end
+expected_failure(test_lfs_ext_dir_foreach_filter_dir)
+
+function test_lfs_ext_dir_foreach_filter_mixed()
+ local count = 0
+ lfs.dir_foreach(_HOME .. '/core', function(filename)
+ assert(not filename:find('/locales/') and filename:find('%.lua$'), '"%s" should not match', filename)
+ count = count + 1
+ end, {'!/locales', '.lua'})
+ assert(count > 0, 'no matching files found')
+end
+
+function test_lfs_ext_dir_foreach_max_depth()
+ local count = 0
+ lfs.dir_foreach(
+ _HOME, function(filename) count = count + 1 end, '.lua', 0)
+ assert_equal(count, 2) -- init.lua, test.lua
+end
+
+function test_lfs_ext_dir_foreach_win32()
+ local win32 = _G.WIN32
+ _G.WIN32 = true
+ local count = 0
+ lfs.dir_foreach(_HOME, function(filename)
+ assert(not filename:find('/'), '"%s" has /', filename)
+ if filename:find('\\core') then count = count + 1 end
+ end, {'/core'})
+ assert(count > 0, 'no core files found')
+ _G.WIN32 = win32 -- reset just in case
+end
+
+function test_lfs_ext_abs_path()
+ assert_equal(lfs.abspath('bar', '/foo'), '/foo/bar')
+ assert_equal(lfs.abspath('./bar', '/foo'), '/foo/bar')
+ assert_equal(lfs.abspath('../bar', '/foo'), '/bar')
+ assert_equal(lfs.abspath('/bar', '/foo'), '/bar')
+ assert_equal(lfs.abspath('../../././baz', '/foo/bar'), '/baz')
+ local win32 = _G.WIN32
+ _G.WIN32 = true
+ assert_equal(lfs.abspath('bar', 'C:\\foo'), 'C:\\foo\\bar')
+ assert_equal(lfs.abspath('.\\bar', 'C:\\foo'), 'C:\\foo\\bar')
+ assert_equal(lfs.abspath('..\\bar', 'C:\\foo'), 'C:\\bar')
+ assert_equal(lfs.abspath('C:\\bar', 'C:\\foo'), 'C:\\bar')
+ assert_equal(lfs.abspath('..\\../.\\./baz', 'C:\\foo\\bar'), 'C:\\baz')
+ _G.WIN32 = win32 -- reset just in case
+
+ assert_raises(function() lfs.abspath() end, 'string expected, got nil')
+ assert_raises(function() lfs.abspath('foo', 1) end, 'string/nil expected, got number')
+end
+
+function test_ui_print()
+ local tabs = ui.tabs
+ local silent_print = ui.silent_print
+
+ ui.tabs = true
+ ui.silent_print = false
+ ui.print('foo')
+ assert_equal(buffer._type, _L['[Message Buffer]'])
+ assert_equal(#_VIEWS, 1)
+ assert_equal(buffer:get_text(), 'foo\n')
+ assert(buffer:line_from_position(buffer.current_pos) > LINE(1), 'still on first line')
+ ui.print('bar', 'baz')
+ assert_equal(buffer:get_text(), 'foo\nbar\tbaz\n')
+ io.close_buffer()
+
+ ui.tabs = false
+ ui.print(1, 2, 3)
+ assert_equal(buffer._type, _L['[Message Buffer]'])
+ assert_equal(#_VIEWS, 2)
+ assert_equal(buffer:get_text(), '1\t2\t3\n')
+ ui.goto_view(-1) -- first view
+ assert(buffer._type ~= _L['[Message Buffer]'], 'still in message buffer')
+ ui.print(4, 5, 6) -- should jump to second view
+ assert_equal(buffer._type, _L['[Message Buffer]'])
+ assert_equal(buffer:get_text(), '1\t2\t3\n4\t5\t6\n')
+ ui.goto_view(-1) -- first view
+ assert(buffer._type ~= _L['[Message Buffer]'], 'still in message buffer')
+ ui.silent_print = true
+ ui.print(7, 8, 9) -- should stay in first view
+ assert(buffer._type ~= _L['[Message Buffer]'], 'switched to message buffer')
+ assert_equal(_BUFFERS[#_BUFFERS]:get_text(), '1\t2\t3\n4\t5\t6\n7\t8\t9\n')
+ ui.silent_print = false
+ ui.goto_view(1) -- second view
+ assert_equal(buffer._type, _L['[Message Buffer]'])
+ view:goto_buffer(-1)
+ assert(buffer._type ~= _L['[Message Buffer]'], 'message buffer still visible')
+ ui.print()
+ assert_equal(buffer._type, _L['[Message Buffer]'])
+ assert_equal(buffer:get_text(), '1\t2\t3\n4\t5\t6\n7\t8\t9\n\n')
+ view:unsplit()
+
+ io.close_buffer()
+ ui.tabs = tabs
+ ui.silent_print = silent_print
+end
+
+function test_ui_dialogs_colorselect_interactive()
+ local color = ui.dialogs.colorselect{title = 'Blue', color = 0xFF0000}
+ assert_equal(color, 0xFF0000)
+ color = ui.dialogs.colorselect{
+ title = 'Red', color = '#FF0000', palette = {'#FF0000', 0x00FF00},
+ string_output = true
+ }
+ assert_equal(color, '#FF0000')
+
+ assert_raises(function() ui.dialogs.colorselect{title = function() end} end, "bad argument #title to 'colorselect' (string/number/table/boolean expected, got function")
+ assert_raises(function() ui.dialogs.colorselect{palette = {true}} end, "bad argument #palette[1] to 'colorselect' (string/number expected, got boolean")
+end
+
+function test_ui_dialogs_dropdown_interactive()
+ local dropdowns = {'dropdown', 'standard_dropdown'}
+ for _, dropdown in ipairs(dropdowns) do
+ print('Running ' .. dropdown)
+ local button, i = ui.dialogs[dropdown]{items = {'foo', 'bar', 'baz'}}
+ assert_equal(type(button), 'number')
+ assert_equal(i, 1)
+ button, i = ui.dialogs[dropdown]{
+ text = 'foo', items = {'bar', 'baz', 'quux'}, select = 2,
+ no_cancel = true, width = 400, height = 400
+ }
+ assert_equal(i, 2)
+ end
+
+ assert_raises(function() ui.dialogs.dropdown{items = {'foo', 'bar', 'baz', true}} end, "bad argument #items[4] to 'dropdown' (string/number expected, got boolean")
+end
+
+function test_ui_dialogs_filesave_fileselect_interactive()
+ local test_filename = _HOME .. '/test/ui/empty'
+ local test_dir, test_file = test_filename:match('^(.+[/\\])([^/\\]+)$')
+ local filename = ui.dialogs.filesave{
+ with_directory = test_dir, with_file = test_file,
+ no_create_directories = true
+ }
+ assert_equal(filename, test_filename)
+ filename = ui.dialogs.fileselect{
+ with_directory = test_dir, with_file = test_file, select_multiple = true
+ }
+ assert_equal(filename, {test_filename})
+ filename = ui.dialogs.fileselect{
+ with_directory = test_dir, select_only_directories = true
+ }
+ assert_equal(filename, test_dir:match('^(.+)/$'))
+end
+
+function test_ui_dialogs_filteredlist_interactive()
+ local _, i = ui.dialogs.filteredlist{
+ informative_text = 'foo', columns = '1', items = {'bar', 'baz', 'quux'},
+ text = 'b z'
+ }
+ assert_equal(i, 2)
+ local _, text = ui.dialogs.filteredlist{
+ columns = {'1', '2'},
+ items = {'foo', 'foobar', 'bar', 'barbaz', 'baz', 'bazfoo'},
+ search_column = 2, text = 'baz', output_column = 2, string_output = true,
+ select_multiple = true, button1 = _L['OK'], button2 = _L['Cancel'],
+ button3 = 'Other'
+ }
+ assert_equal(text, {'barbaz'})
+end
+
+function test_ui_dialogs_fontselect_interactive()
+ local font = ui.dialogs.fontselect{
+ font_name = 'Monospace', font_size = 14, font_style = 'Bold'
+ }
+ assert_equal(font, 'Monospace Bold 14')
+end
+
+function test_ui_dialogs_inputbox_interactive()
+ local inputboxes = {
+ 'inputbox', 'secure_inputbox', 'standard_inputbox',
+ 'secure_standard_inputbox'
+ }
+ for _, inputbox in ipairs(inputboxes) do
+ print('Running ' .. inputbox)
+ local button, text = ui.dialogs[inputbox]{text = 'foo'}
+ assert_equal(type(button), 'number')
+ assert_equal(text, 'foo')
+ button, text = ui.dialogs[inputbox]{
+ text = 'foo', string_output = true, no_cancel = true
+ }
+ assert_equal(type(button), 'string')
+ assert_equal(text, 'foo')
+ end
+
+ local button, text = ui.dialogs.inputbox{
+ informative_text = {'info', 'foo', 'baz'}, text = {'bar', 'quux'}
+ }
+ assert_equal(type(button), 'number')
+ assert_equal(text, {'bar', 'quux'})
+ button = ui.dialogs.inputbox{
+ informative_text = {'info', 'foo', 'baz'}, text = {'bar', 'quux'},
+ string_output = true
+ }
+ assert_equal(type(button), 'string')
+end
+
+function test_ui_dialogs_msgbox_interactive()
+ local msgboxes = {'msgbox', 'ok_msgbox', 'yesno_msgbox'}
+ local icons = {'gtk-dialog-info', 'gtk-dialog-warning', 'gtk-dialog-question'}
+ for i, msgbox in ipairs(msgboxes) do
+ print('Running ' .. msgbox)
+ local button = ui.dialogs[msgbox]{icon = icons[i]}
+ assert_equal(type(button), 'number')
+ button = ui.dialogs[msgbox]{
+ icon_file = _HOME .. '/core/images/ta_32x32.png', string_output = true,
+ no_cancel = true
+ }
+ assert_equal(type(button), 'string')
+ end
+end
+
+function test_ui_dialogs_optionselect_interactive()
+ local _, selected = ui.dialogs.optionselect{items = 'foo', select = 1}
+ assert_equal(selected, {1})
+ _, selected = ui.dialogs.optionselect{
+ items = {'foo', 'bar', 'baz'}, select = {1, 3}, string_output = true
+ }
+ assert_equal(selected, {'foo', 'baz'})
+end
+
+function test_ui_dialogs_textbox_interactive()
+ ui.dialogs.textbox{
+ text = 'foo', editable = true, selected = true, monospaced_font = true
+ }
+ ui.dialogs.textbox{text_from_file = _HOME .. '/LICENSE', scroll_to = 'bottom'}
+end
+
+function test_ui_switch_buffer_interactive()
+ buffer.new()
+ buffer:append_text('foo')
+ buffer.new()
+ buffer:append_text('bar')
+ buffer:new()
+ buffer:append_text('baz')
+ ui.switch_buffer() -- back to [Test Output]
+ local text = buffer:get_text()
+ assert(text ~= 'foo' and text ~= 'bar' and text ~= 'baz')
+ for i = 1, 3 do view:goto_buffer(1) end -- cycle back to baz
+ ui.switch_buffer(true)
+ assert_equal(buffer:get_text(), 'bar')
+ for i = 1, 3 do
+ buffer:set_save_point()
+ io.close_buffer()
+ end
+end
+
+function test_ui_goto_file()
+ local dir1_file1 = _HOME .. '/core/ui/dir1/file1'
+ local dir1_file2 = _HOME .. '/core/ui/dir1/file2'
+ local dir2_file1 = _HOME .. '/core/ui/dir2/file1'
+ local dir2_file2 = _HOME .. '/core/ui/dir2/file2'
+ ui.goto_file(dir1_file1) -- current view
+ assert_equal(#_VIEWS, 1)
+ assert_equal(buffer.filename, dir1_file1)
+ ui.goto_file(dir1_file2, true) -- split view
+ assert_equal(#_VIEWS, 2)
+ assert_equal(buffer.filename, dir1_file2)
+ assert_equal(_VIEWS[1].buffer.filename, dir1_file1)
+ ui.goto_file(dir1_file1) -- should go back to first view
+ assert_equal(buffer.filename, dir1_file1)
+ assert_equal(_VIEWS[2].buffer.filename, dir1_file2)
+ ui.goto_file(dir2_file2, true, nil, true) -- should sloppily go back to second view
+ assert_equal(buffer.filename, dir1_file2) -- sloppy
+ assert_equal(_VIEWS[1].buffer.filename, dir1_file1)
+ ui.goto_file(dir2_file1) -- should go back to first view
+ assert_equal(buffer.filename, dir2_file1)
+ assert_equal(_VIEWS[2].buffer.filename, dir1_file2)
+ ui.goto_file(dir2_file2, false, _VIEWS[1]) -- should go to second view
+ assert_equal(#_VIEWS, 2)
+ assert_equal(buffer.filename, dir2_file2)
+ assert_equal(_VIEWS[1].buffer.filename, dir2_file1)
+ view:unsplit()
+ assert_equal(#_VIEWS, 1)
+ for i = 1, 4 do io.close_buffer() end
+end
+
+function test_ui_uri_drop()
+ local filename = _HOME .. '/test/ui/uri drop'
+ local uri = 'file://' .. _HOME .. '/test/ui/uri%20drop'
+ events.emit(events.URI_DROPPED, uri)
+ assert_equal(buffer.filename, filename)
+ io.close_buffer()
+ local buffer = buffer
+ events.emit(events.URI_DROPPED, 'file://' .. _HOME)
+ assert_equal(buffer, _G.buffer) -- do not open directory
+
+ -- TODO: WIN32
+ -- TODO: OSX
+end
+
+function test_ui_buffer_switch_save_restore_properties()
+ local filename = _HOME .. '/test/ui/test.lua'
+ io.open_file(filename)
+ buffer:goto_pos(10)
+ buffer:fold_line(
+ buffer:line_from_position(buffer.current_pos), buffer.FOLDACTION_CONTRACT)
+ buffer.view_eol = true
+ buffer.margin_width_n[INDEX(1)] = 0 -- hide line numbers
+ view:goto_buffer(-1)
+ assert(buffer.margin_width_n[INDEX(1)] > 0, 'line numbers are still hidden')
+ view:goto_buffer(1)
+ assert_equal(buffer.current_pos, 10)
+ assert_equal(buffer.fold_expanded[buffer:line_from_position(buffer.current_pos)], false)
+ assert_equal(buffer.view_eol, true)
+ assert_equal(buffer.margin_width_n[INDEX(1)], 0)
+ io.close_buffer()
+end
+
+if CURSES then
+ -- TODO: clipboard, mouse events, etc.
+end
+
+if WIN32 and CURSES then
+ function test_spawn()
+ -- TODO:
+ end
+end
+
+function test_buffer_text_range()
+ buffer.new()
+ buffer:set_text('foo\nbar\nbaz')
+ buffer:set_target_range(POS(5), POS(8))
+ assert_equal(buffer.target_text, 'bar')
+ assert_equal(buffer:text_range(POS(1), buffer.length), 'foo\nbar\nbaz')
+ assert_equal(buffer:text_range(-1, POS(4)), 'foo')
+ assert_equal(buffer:text_range(POS(9), POS(16)), 'baz')
+ assert_equal(buffer.target_text, 'bar') -- assert target range is unchanged
+ buffer:set_save_point()
+ io.close_buffer()
+
+ assert_raises(function() buffer:text_range() end, 'number expected, got nil')
+ assert_raises(function() buffer:text_range(POS(5)) end, 'number expected, got nil')
+end
+
+function test_bookmarks()
+ local function has_bookmark(line)
+ return buffer:marker_get(line) & 1 << textadept.bookmarks.MARK_BOOKMARK > 0
+ end
+
+ buffer.new()
+ buffer:new_line()
+ assert(buffer:line_from_position(buffer.current_pos) > LINE(1), 'still on first line')
+ textadept.bookmarks.toggle()
+ assert(has_bookmark(LINE(2)), 'no bookmark')
+ textadept.bookmarks.toggle()
+ assert(not has_bookmark(LINE(2)), 'bookmark still there')
+ textadept.bookmarks.toggle(true, LINE(1))
+ assert(has_bookmark(LINE(1)), 'no bookmark')
+ textadept.bookmarks.toggle(false, LINE(1))
+ assert(not has_bookmark(LINE(1)), 'bookmark still there')
+
+ textadept.bookmarks.toggle(true, LINE(1))
+ textadept.bookmarks.toggle(true, LINE(2))
+ textadept.bookmarks.goto_mark(true)
+ assert_equal(buffer:line_from_position(buffer.current_pos), LINE(1))
+ textadept.bookmarks.goto_mark(true)
+ assert_equal(buffer:line_from_position(buffer.current_pos), LINE(2))
+ textadept.bookmarks.goto_mark(false)
+ assert_equal(buffer:line_from_position(buffer.current_pos), LINE(1))
+ textadept.bookmarks.goto_mark(false)
+ assert_equal(buffer:line_from_position(buffer.current_pos), LINE(2))
+ textadept.bookmarks.clear()
+ assert(not has_bookmark(LINE(1)), 'bookmark still there')
+ assert(not has_bookmark(LINE(2)), 'bookmark still there')
+ buffer:set_save_point()
+ io.close_buffer()
+
+ assert_raises(function() textadept.bookmarks.toggle(true, 'foo') end, 'number/nil expected, got string')
+end
+
+function test_bookmarks_interactive()
+ buffer.new()
+ buffer:new_line()
+ textadept.bookmarks.toggle()
+ buffer:line_up()
+ assert_equal(buffer:line_from_position(buffer.current_pos), LINE(1))
+ textadept.bookmarks.goto_mark()
+ assert_equal(buffer:line_from_position(buffer.current_pos), LINE(2))
+ buffer:set_save_point()
+ io.close_buffer()
+end
+
+function test_command_entry_run()
+ local command_run, tab_pressed = false, false
+ ui.command_entry.run(function(command) command_run = command end, {
+ ['\t'] = function() tab_pressed = true end
+ }, nil, 2)
+ update_ui() -- redraw command entry
+ assert_equal(ui.command_entry:get_lexer(), 'text')
+ assert(ui.command_entry.height > ui.command_entry:text_height(0), 'height < 2 lines')
+ ui.command_entry:set_text('foo')
+ events.emit(events.KEYPRESS, string.byte('\t'))
+ events.emit(events.KEYPRESS, string.byte('\n'))
+ assert_equal(command_run, 'foo')
+ assert(tab_pressed, '\\t not registered')
+
+ assert_raises(function() ui.command_entry.run(function() end, 1) end, 'table/string/nil expected, got number')
+ assert_raises(function() ui.command_entry.run(function() end, {}, 1) end, 'string/nil expected, got number')
+ assert_raises(function() ui.command_entry.run(function() end, {}, 'lua', true) end, 'number/nil expected, got boolean')
+ assert_raises(function() ui.command_entry.run(function() end, 'lua', true) end, 'number/nil expected, got boolean')
+end
+
+local function run_lua_command(command)
+ ui.command_entry:set_text(command)
+ ui.command_entry.run()
+ assert_equal(ui.command_entry:get_lexer(), 'lua')
+ events.emit(events.KEYPRESS, string.byte('\n'))
+end
+
+function test_command_entry_run_lua()
+ run_lua_command('print(_HOME)')
+ assert_equal(buffer._type, _L['[Message Buffer]'])
+ assert_equal(buffer:get_text(), _HOME .. '\n')
+ run_lua_command('{key="value"}')
+ assert(buffer:get_text():find('{key = value}'), 'table not pretty-printed')
+ -- TODO: multi-line table pretty print.
+ if #_VIEWS > 1 then view:unsplit() end
+ io.close_buffer()
+end
+
+function test_command_entry_run_lua_abbreviated_env()
+ -- buffer get/set.
+ run_lua_command('length')
+ assert(buffer:get_text():find('%d+%s*$'), 'buffer.length result not a number')
+ run_lua_command('auto_c_active')
+ assert(buffer:get_text():find('false%s*$'), 'buffer:auto_c_active() result not false')
+ run_lua_command('view_eol=true')
+ assert_equal(buffer.view_eol, true)
+ -- view get/set.
+ if #_VIEWS > 1 then view:unsplit() end
+ run_lua_command('split')
+ assert_equal(#_VIEWS, 2)
+ run_lua_command('size=1')
+ assert_equal(view.size, 1)
+ run_lua_command('unsplit')
+ assert_equal(#_VIEWS, 1)
+ -- ui get/set.
+ run_lua_command('dialogs')
+ assert(buffer:get_text():find('%b{}%s*$'), 'ui.dialogs result not a table')
+ run_lua_command('statusbar_text="foo"')
+ -- _G get/set.
+ run_lua_command('foo="bar"')
+ run_lua_command('foo')
+ assert(buffer:get_text():find('bar%s*$'), 'foo result not "bar"')
+ io.close_buffer()
+end
+
+local function assert_lua_autocompletion(text, first_item)
+ ui.command_entry:set_text(text)
+ ui.command_entry:goto_pos(ui.command_entry.length)
+ events.emit(events.KEYPRESS, string.byte('\t'))
+ assert_equal(ui.command_entry:auto_c_active(), true)
+ assert_equal(ui.command_entry.auto_c_current_text, first_item)
+ ui.command_entry:auto_c_cancel()
+end
+
+function test_command_entry_complete_lua()
+ ui.command_entry.run()
+ assert_lua_autocompletion('string.', 'byte')
+ assert_lua_autocompletion('auto', 'auto_c_active')
+ assert_lua_autocompletion('buffer.auto', 'auto_c_auto_hide')
+ assert_lua_autocompletion('buffer:auto', 'auto_c_active')
+ assert_lua_autocompletion('goto', 'goto_buffer')
+ assert_lua_autocompletion('_', '_BUFFERS')
+ -- TODO: textadept.editing.show_documentation key binding.
+ ui.command_entry:focus() -- hide
+end
+
+function test_editing_auto_pair()
+ buffer.new()
+ -- Single selection.
+ buffer:add_text('foo(')
+ events.emit(events.CHAR_ADDED, string.byte('('))
+ assert_equal(buffer:get_text(), 'foo()')
+ events.emit(events.KEYPRESS, string.byte(')'))
+ assert_equal(buffer.current_pos, buffer.line_end_position[LINE(1)])
+ buffer:char_left()
+ -- Note: cannot check for brace highlighting; indicator search does not work.
+ events.emit(events.KEYPRESS, not CURSES and 0xFF08 or 263) -- \b
+ assert_equal(buffer:get_text(), 'foo')
+ -- Multi-selection.
+ buffer:set_text('foo(\nfoo(')
+ local pos1 = buffer.line_end_position[LINE(1)]
+ local pos2 = buffer.line_end_position[LINE(2)]
+ buffer:set_selection(pos1, pos1)
+ buffer:add_selection(pos2, pos2)
+ events.emit(events.CHAR_ADDED, string.byte('('))
+ assert_equal(buffer:get_text(), 'foo()\nfoo()')
+ assert_equal(buffer.selections, 2)
+ assert_equal(buffer.selection_n_start[INDEX(1)], buffer.selection_n_end[INDEX(1)])
+ assert_equal(buffer.selection_n_start[INDEX(1)], pos1)
+ assert_equal(buffer.selection_n_start[INDEX(2)], buffer.selection_n_end[INDEX(2)])
+ assert_equal(buffer.selection_n_start[INDEX(2)], pos2 + 1)
+ -- TODO: typeover.
+ events.emit(events.KEYPRESS, not CURSES and 0xFF08 or 263) -- \b
+ assert_equal(buffer:get_text(), 'foo\nfoo')
+ -- Verify atomic undo for multi-select.
+ buffer:undo() -- simulated backspace
+ buffer:undo() -- normal undo that a user would perform
+ assert_equal(buffer:get_text(), 'foo()\nfoo()')
+ buffer:undo()
+ assert_equal(buffer:get_text(), 'foo(\nfoo(')
+ buffer:set_save_point()
+ io.close_buffer()
+end
+
+function test_editing_auto_indent()
+ buffer.new()
+ buffer:add_text('foo')
+ buffer:new_line()
+ assert_equal(buffer.line_indentation[LINE(2)], 0)
+ buffer:tab()
+ buffer:add_text('bar')
+ buffer:new_line()
+ assert_equal(buffer.line_indentation[LINE(3)], buffer.tab_width)
+ assert_equal(buffer.current_pos, buffer.line_indent_position[LINE(3)])
+ buffer:new_line()
+ buffer:back_tab()
+ assert_equal(buffer.line_indentation[LINE(4)], 0)
+ assert_equal(buffer.current_pos, buffer:position_from_line(LINE(4)))
+ buffer:new_line() -- should indent since previous line is blank
+ assert_equal(buffer.line_indentation[LINE(5)], buffer.tab_width)
+ assert_equal(buffer.current_pos, buffer.line_indent_position[LINE(5)])
+ buffer:goto_pos(buffer:position_from_line(LINE(2))) -- "\tbar"
+ buffer:new_line() -- should not change indentation
+ assert_equal(buffer.line_indentation[LINE(3)], buffer.tab_width)
+ assert_equal(buffer.current_pos, buffer:position_from_line(LINE(3)))
+ buffer:set_save_point()
+ io.close_buffer()
+end
+
+function test_editing_strip_trailing_spaces()
+ local strip = textadept.editing.strip_trailing_spaces
+ textadept.editing.strip_trailing_spaces = true
+ buffer.new()
+ local text = table.concat({
+ 'foo ',
+ ' bar\t\r',
+ 'baz\t '
+ }, '\n')
+ buffer:set_text(text)
+ buffer:goto_pos(buffer.line_end_position[LINE(2)])
+ events.emit(events.FILE_BEFORE_SAVE)
+ assert_equal(buffer:get_text(), table.concat({
+ 'foo',
+ ' bar',
+ 'baz',
+ ''
+ }, '\n'))
+ assert_equal(buffer.current_pos, buffer.line_end_position[LINE(2)])
+ buffer:undo()
+ assert_equal(buffer:get_text(), text)
+ buffer:set_save_point()
+ io.close_buffer()
+ textadept.editing.strip_trailing_spaces = strip -- restore
+end
+
+function test_editing_paste_reindent_tabs_to_tabs()
+ ui.clipboard_text = table.concat({
+ '\tfoo',
+ '',
+ '\t\tbar',
+ '\tbaz'
+ }, '\n')
+ buffer.new()
+ buffer.use_tabs, buffer.eol_mode = true, buffer.EOL_CRLF
+ buffer:add_text('quux\r\n')
+ textadept.editing.paste_reindent()
+ assert_equal(buffer:get_text(), table.concat({
+ 'quux',
+ 'foo',
+ '',
+ '\tbar',
+ 'baz'
+ }, '\r\n'))
+ buffer:clear_all()
+ buffer:add_text('\t\tquux\r\n\r\n') -- no auto-indent
+ assert_equal(buffer.line_indentation[LINE(2)], 0)
+ assert_equal(buffer.line_indentation[LINE(3)], 0)
+ textadept.editing.paste_reindent()
+ assert_equal(buffer:get_text(), table.concat({
+ '\t\tquux',
+ '',
+ '\t\tfoo',
+ '\t\t',
+ '\t\t\tbar',
+ '\t\tbaz'
+ }, '\r\n'))
+ buffer:clear_all()
+ buffer:add_text('\t\tquux\r\n')
+ assert_equal(buffer.line_indentation[LINE(2)], 0)
+ buffer:new_line() -- auto-indent
+ assert_equal(buffer.line_indentation[LINE(3)], 2 * buffer.tab_width)
+ textadept.editing.paste_reindent()
+ assert_equal(buffer:get_text(), table.concat({
+ '\t\tquux',
+ '',
+ '\t\tfoo',
+ '\t\t',
+ '\t\t\tbar',
+ '\t\tbaz'
+ }, '\r\n'))
+ buffer:set_save_point()
+ io.close_buffer()
+end
+expected_failure(test_editing_paste_reindent_tabs_to_tabs)
+
+function test_editing_paste_reindent_spaces_to_spaces()
+ ui.clipboard_text = table.concat({
+ ' foo',
+ '',
+ ' bar',
+ ' baz',
+ ' quux'
+ }, '\n')
+ buffer.new()
+ buffer.use_tabs, buffer.tab_width = false, 2
+ buffer:add_text('foobar\n')
+ textadept.editing.paste_reindent()
+ assert_equal(buffer:get_text(), table.concat({
+ 'foobar',
+ 'foo',
+ '',
+ ' bar',
+ ' baz',
+ 'quux'
+ }, '\n'))
+ buffer:clear_all()
+ buffer:add_text(' foobar\n\n') -- no auto-indent
+ assert_equal(buffer.line_indentation[LINE(2)], 0)
+ assert_equal(buffer.line_indentation[LINE(3)], 0)
+ textadept.editing.paste_reindent()
+ assert_equal(buffer:get_text(), table.concat({
+ ' foobar',
+ '',
+ ' foo',
+ ' ',
+ ' bar',
+ ' baz',
+ ' quux'
+ }, '\n'))
+ buffer:clear_all()
+ buffer:add_text(' foobar\n')
+ assert_equal(buffer.line_indentation[LINE(2)], 0)
+ buffer:new_line() -- auto-indent
+ assert_equal(buffer.line_indentation[LINE(3)], 4)
+ textadept.editing.paste_reindent()
+ assert_equal(buffer:get_text(), table.concat({
+ ' foobar',
+ '',
+ ' foo',
+ ' ',
+ ' bar',
+ ' baz',
+ ' quux'
+ }, '\n'))
+ buffer:set_save_point()
+ io.close_buffer()
+end
+expected_failure(test_editing_paste_reindent_spaces_to_spaces)
+
+function test_editing_paste_reindent_spaces_to_tabs()
+ ui.clipboard_text = table.concat({
+ ' foo',
+ ' bar',
+ ' baz'
+ }, '\n')
+ buffer.new()
+ buffer.use_tabs, buffer.tab_width = true, 4
+ buffer:add_text('\tquux')
+ buffer:new_line()
+ textadept.editing.paste_reindent()
+ assert_equal(buffer:get_text(), table.concat({
+ '\tquux',
+ '\tfoo',
+ '\t\tbar',
+ '\tbaz'
+ }, '\n'))
+ buffer:set_save_point()
+ io.close_buffer()
+end
+
+function test_editing_paste_reindent_tabs_to_spaces()
+ ui.clipboard_text = table.concat({
+ '\tif foo and',
+ '\t bar then',
+ '\t\tbaz()',
+ '\tend',
+ ''
+ }, '\n')
+ buffer.new()
+ buffer.use_tabs, buffer.tab_width = false, 2
+ buffer:set_lexer('lua')
+ buffer:add_text('function quux()')
+ buffer:new_line()
+ buffer:insert_text(-1, 'end')
+ buffer:colourise(INDEX(1), -1) -- first line should be a fold header
+ textadept.editing.paste_reindent()
+ assert_equal(buffer:get_text(), table.concat({
+ 'function quux()',
+ ' if foo and',
+ ' bar then',
+ ' baz()',
+ ' end',
+ 'end'
+ }, '\n'))
+ buffer:set_save_point()
+ io.close_buffer()
+end
+expected_failure(test_editing_paste_reindent_tabs_to_spaces)
+
+function test_editing_block_comment_lines()
+ buffer.new()
+ buffer:add_text('foo')
+ textadept.editing.block_comment()
+ assert_equal(buffer:get_text(), 'foo')
+ buffer:set_lexer('lua')
+ local text = table.concat({
+ '',
+ 'local foo = "bar"',
+ ' local baz = "quux"',
+ ''
+ }, '\n')
+ buffer:set_text(text)
+ buffer:goto_pos(buffer:position_from_line(LINE(2)))
+ textadept.editing.block_comment()
+ assert_equal(buffer:get_text(), table.concat({
+ '',
+ '--local foo = "bar"',
+ ' local baz = "quux"',
+ ''
+ }, '\n'))
+ assert_equal(buffer.current_pos, buffer:position_from_line(LINE(2)) + 2)
+ textadept.editing.block_comment() -- uncomment
+ assert_equal(buffer:get_line(LINE(2)), 'local foo = "bar"\n')
+ assert_equal(buffer.current_pos, buffer:position_from_line(LINE(2)))
+ local offset = 5
+ buffer:set_sel(buffer:position_from_line(LINE(2)) + offset, buffer:position_from_line(LINE(4)) - offset)
+ textadept.editing.block_comment()
+ assert_equal(buffer:get_text(), table.concat({
+ '',
+ '--local foo = "bar"',
+ ' --local baz = "quux"',
+ ''
+ }, '\n'))
+ assert_equal(buffer.selection_start, buffer:position_from_line(LINE(2)) + offset + 2)
+ assert_equal(buffer.selection_end, buffer:position_from_line(LINE(4)) - offset)
+ textadept.editing.block_comment() -- uncomment
+ assert_equal(buffer:get_text(), table.concat({
+ '',
+ 'local foo = "bar"',
+ ' local baz = "quux"',
+ ''
+ }, '\n'))
+ assert_equal(buffer.selection_start, buffer:position_from_line(LINE(2)) + offset)
+ assert_equal(buffer.selection_end, buffer:position_from_line(LINE(4)) - offset)
+ buffer:undo() -- comment
+ buffer:undo() -- uncomment
+ assert_equal(buffer:get_text(), text) -- verify atomic undo
+ buffer:set_save_point()
+ io.close_buffer()
+end
+
+function test_editing_block_comment()
+ buffer.new()
+ buffer:set_lexer('ansi_c')
+ buffer:set_text(table.concat({
+ '',
+ 'const char *foo = "bar";',
+ ' const char *baz = "quux";',
+ ''
+ }, '\n'))
+ buffer:set_sel(buffer:position_from_line(LINE(2)), buffer:position_from_line(LINE(4)))
+ textadept.editing.block_comment()
+ assert_equal(buffer:get_text(), table.concat({
+ '',
+ '/*const char *foo = "bar";*/',
+ ' /*const char *baz = "quux";*/',
+ ''
+ }, '\n'))
+ assert_equal(buffer.selection_start, buffer:position_from_line(LINE(2)) + 2)
+ assert_equal(buffer.selection_end, buffer:position_from_line(LINE(4)))
+ textadept.editing.block_comment() -- uncomment
+ assert_equal(buffer:get_text(), table.concat({
+ '',
+ 'const char *foo = "bar";',
+ ' const char *baz = "quux";',
+ ''
+ }, '\n'))
+ assert_equal(buffer.selection_start, buffer:position_from_line(LINE(2)))
+ assert_equal(buffer.selection_end, buffer:position_from_line(LINE(4)))
+ buffer:set_save_point()
+ io.close_buffer()
+end
+
+function test_editing_goto_line()
+ buffer.new()
+ buffer:new_line()
+ textadept.editing.goto_line(LINE(1))
+ assert_equal(buffer:line_from_position(buffer.current_pos), LINE(1))
+ textadept.editing.goto_line(LINE(2))
+ assert_equal(buffer:line_from_position(buffer.current_pos), LINE(2))
+ buffer:set_save_point()
+ io.close_buffer()
+
+ assert_raises(function() textadept.editing.goto_line(true) end, 'number/nil expected, got boolean')
+end
+
+-- TODO: test_editing_goto_line_interactive
+
+function test_editing_transpose_chars()
+ buffer.new()
+ buffer:add_text('foobar')
+ textadept.editing.transpose_chars()
+ assert_equal(buffer:get_text(), 'foobra')
+ buffer:char_left()
+ textadept.editing.transpose_chars()
+ assert_equal(buffer:get_text(), 'foobar')
+ -- TODO: multiple selection?
+ buffer:set_save_point()
+ io.close_buffer()
+end
+
+function test_editing_join_lines()
+ buffer.new()
+ buffer:append_text('foo\nbar\n baz\nquux\n')
+ textadept.editing.join_lines()
+ assert_equal(buffer:get_text(), 'foo bar\n baz\nquux\n')
+ assert_equal(buffer.current_pos, POS(4))
+ buffer:set_sel(buffer:position_from_line(LINE(2)) + 5, buffer:position_from_line(LINE(4)) - 5)
+ textadept.editing.join_lines()
+ assert_equal(buffer:get_text(), 'foo bar\n baz quux\n')
+ buffer:set_save_point()
+ io.close_buffer()
+end
+
+function test_editing_enclose()
+ buffer.new()
+ buffer.add_text('foo bar')
+ textadept.editing.enclose('"', '"')
+ assert_equal(buffer:get_text(), 'foo "bar"')
+ buffer:undo()
+ buffer:select_all()
+ textadept.editing.enclose('(', ')')
+ assert_equal(buffer:get_text(), '(foo bar)')
+ buffer:undo()
+ buffer:append_text('\nfoo bar')
+ buffer:set_selection(buffer.line_end_position[LINE(1)], buffer.line_end_position[LINE(1)])
+ buffer:add_selection(buffer.line_end_position[LINE(2)], buffer.line_end_position[LINE(2)])
+ textadept.editing.enclose('<', '>')
+ assert_equal(buffer:get_text(), 'foo <bar>\nfoo <bar>')
+ buffer:undo()
+ assert_equal(buffer:get_text(), 'foo bar\nfoo bar') -- verify atomic undo
+ buffer:set_selection(buffer:position_from_line(LINE(1)), buffer.line_end_position[LINE(1)])
+ buffer:add_selection(buffer:position_from_line(LINE(2)), buffer.line_end_position[LINE(2)])
+ textadept.editing.enclose('-', '-')
+ assert_equal(buffer:get_text(), '-foo bar-\n-foo bar-')
+ buffer:set_save_point()
+ io.close_buffer()
+
+ assert_raises(function() textadept.editing.enclose() end, 'string expected, got nil')
+ assert_raises(function() textadept.editing.enclose('<', 1) end, 'string expected, got number')
+end
+
+function test_editing_select_enclosed()
+ buffer.new()
+ buffer:add_text('("foo bar")')
+ buffer:goto_pos(POS(6))
+ textadept.editing.select_enclosed()
+ assert_equal(buffer:get_sel_text(), 'foo bar')
+ textadept.editing.select_enclosed()
+ assert_equal(buffer:get_sel_text(), '"foo bar"')
+ textadept.editing.select_enclosed()
+ assert_equal(buffer:get_sel_text(), 'foo bar')
+ buffer:goto_pos(POS(6))
+ textadept.editing.select_enclosed('("', '")')
+ assert_equal(buffer:get_sel_text(), 'foo bar')
+ textadept.editing.select_enclosed('("', '")')
+ assert_equal(buffer:get_sel_text(), '("foo bar")')
+ textadept.editing.select_enclosed('("', '")')
+ assert_equal(buffer:get_sel_text(), 'foo bar')
+ buffer:append_text('"baz"')
+ buffer:goto_pos(POS(10)) -- last " on first line
+ textadept.editing.select_enclosed()
+ assert_equal(buffer:get_sel_text(), 'foo bar')
+ buffer:set_save_point()
+ io.close_buffer()
+
+ assert_raises(function() textadept.editing.select_enclosed('"') end, 'string expected, got nil')
+end
+expected_failure(test_editing_select_enclosed)
+
+function test_editing_select_word()
+ buffer.new()
+ buffer:append_text(table.concat({
+ 'foo',
+ 'foobar',
+ 'bar foo',
+ 'baz foo bar',
+ 'fooquux',
+ 'foo'
+ }, '\n'))
+ textadept.editing.select_word()
+ assert_equal(buffer:get_sel_text(), 'foo')
+ textadept.editing.select_word()
+ assert_equal(buffer.selections, 2)
+ assert_equal(buffer:get_sel_text(), 'foofoo') -- Scintilla stores it this way
+ textadept.editing.select_word(true)
+ assert_equal(buffer.selections, 4)
+ assert_equal(buffer:get_sel_text(), 'foofoofoofoo')
+ local lines = {}
+ for i = INDEX(1), INDEX(buffer.selections) do
+ lines[#lines + 1] = buffer:line_from_position(buffer.selection_n_start[i])
+ end
+ table.sort(lines)
+ assert_equal(lines, {LINE(1), LINE(3), LINE(4), LINE(6)})
+ buffer:set_save_point()
+ io.close_buffer()
+end
+
+function test_editing_select_line()
+ buffer.new()
+ buffer:add_text('foo\n bar')
+ textadept.editing.select_line()
+ assert_equal(buffer:get_sel_text(), ' bar')
+ buffer:set_save_point()
+ io.close_buffer()
+end
+
+function test_editing_select_paragraph()
+ buffer.new()
+ buffer:set_text(table.concat({
+ 'foo',
+ '',
+ 'bar',
+ 'baz',
+ '',
+ 'quux'
+ }, '\n'))
+ buffer:goto_pos(buffer:position_from_line(LINE(3)))
+ textadept.editing.select_paragraph()
+ assert_equal(buffer:get_sel_text(), 'bar\nbaz\n\n')
+ buffer:set_save_point()
+ io.close_buffer()
+end
+
+function test_editing_convert_indentation()
+ buffer.new()
+ local text = table.concat({
+ '\tfoo',
+ ' bar',
+ '\t baz',
+ ' \tquux'
+ }, '\n')
+ buffer:set_text(text)
+ buffer.use_tabs, buffer.tab_width = true, 4
+ textadept.editing.convert_indentation()
+ assert_equal(buffer:get_text(), table.concat({
+ '\tfoo',
+ ' bar',
+ '\t\tbaz',
+ '\t\tquux'
+ }, '\n'))
+ buffer:undo()
+ assert_equal(buffer:get_text(), text) -- verify atomic undo
+ buffer.use_tabs, buffer.tab_width = false, 2
+ textadept.editing.convert_indentation()
+ assert_equal(buffer:get_text(), table.concat({
+ ' foo',
+ ' bar',
+ ' baz',
+ ' quux'
+ }, '\n'))
+ buffer:set_save_point()
+ io.close_buffer()
+end
+
+function test_editing_highlight_word()
+ buffer.new()
+ buffer:append_text(table.concat({
+ 'foo',
+ 'foobar',
+ 'bar foo',
+ 'baz foo bar',
+ 'fooquux',
+ 'foo'
+ }, '\n'))
+ textadept.editing.highlight_word()
+ local indics = {
+ buffer:position_from_line(LINE(1)),
+ buffer:position_from_line(LINE(3)) + 4,
+ buffer:position_from_line(LINE(4)) + 4,
+ buffer:position_from_line(LINE(6))
+ }
+ local bit = 1 << textadept.editing.INDIC_HIGHLIGHT
+ for _, pos in ipairs(indics) do
+ local mask = buffer:indicator_all_on_for(pos)
+ assert(mask & bit > 0, 'no indicator on line %d', buffer:line_from_position(pos))
+ end
+ buffer:set_save_point()
+ io.close_buffer()
+end
+
+function test_editing_filter_through()
+ buffer.new()
+ buffer:set_text('3|baz\n1|foo\n5|foobar\n1|foo\n4|quux\n2|bar\n')
+ textadept.editing.filter_through('sort')
+ assert_equal(buffer:get_text(), '1|foo\n1|foo\n2|bar\n3|baz\n4|quux\n5|foobar\n')
+ buffer:undo()
+ textadept.editing.filter_through('sort | uniq|cut -d "|" -f2')
+ assert_equal(buffer:get_text(), 'foo\nbar\nbaz\nquux\nfoobar\n')
+ buffer:undo()
+ buffer:set_sel(buffer:position_from_line(LINE(2)) + 2, buffer.line_end_position[LINE(2)])
+ textadept.editing.filter_through('sed -e "s/o/O/g;"')
+ assert_equal(buffer:get_text(), '3|baz\n1|fOO\n5|foobar\n1|foo\n4|quux\n2|bar\n')
+ buffer:undo()
+ buffer:set_sel(buffer:position_from_line(LINE(2)), buffer:position_from_line(LINE(5)))
+ textadept.editing.filter_through('sort')
+ assert_equal(buffer:get_text(), '3|baz\n1|foo\n1|foo\n5|foobar\n4|quux\n2|bar\n')
+ buffer:undo()
+ buffer:set_sel(buffer:position_from_line(LINE(2)), buffer:position_from_line(LINE(5)) + 1)
+ textadept.editing.filter_through('sort')
+ assert_equal(buffer:get_text(), '3|baz\n1|foo\n1|foo\n4|quux\n5|foobar\n2|bar\n')
+ buffer:set_save_point()
+ io.close_buffer()
+
+ assert_raises(function() textadept.editing.filter_through() end, 'string expected, got nil')
+end
+unstable(test_editing_filter_through)
+
+function test_editing_autocomplete()
+ assert_raises(function() textadept.editing.autocomplete() end, 'string expected, got nil')
+end
+
+function test_editing_autocomplete_word()
+ local all_words = textadept.editing.autocomplete_all_words
+ textadept.editing.autocomplete_all_words = false
+ buffer.new()
+ buffer:add_text('foo f')
+ textadept.editing.autocomplete('word')
+ assert_equal(buffer:get_text(), 'foo foo')
+ buffer:add_text('bar f')
+ textadept.editing.autocomplete('word')
+ assert(buffer:auto_c_active(), 'autocomplete list not shown')
+ buffer:auto_c_select('foob')
+ buffer:auto_c_complete()
+ assert_equal(buffer:get_text(), 'foo foobar foobar')
+ buffer:set_save_point()
+ buffer.new()
+ buffer:add_text('foob')
+ textadept.editing.autocomplete_all_words = true
+ textadept.editing.autocomplete('word')
+ textadept.editing.autocomplete_all_words = all_words
+ assert_equal(buffer:get_text(), 'foobar')
+ buffer:set_save_point()
+ io.close_buffer()
+ io.close_buffer()
+end
+
+function test_editing_show_documentation()
+ buffer.new()
+ textadept.editing.api_files['text'] = {
+ _HOME .. '/test/modules/textadept/editing/api',
+ function() return _HOME .. '/test/modules/textadept/editing/api2' end
+ }
+ buffer:add_text('foo')
+ textadept.editing.show_documentation()
+ assert(buffer:call_tip_active(), 'documentation not found')
+ buffer:call_tip_cancel()
+ buffer:add_text('2')
+ textadept.editing.show_documentation()
+ assert(buffer:call_tip_active(), 'documentation not found')
+ buffer:call_tip_cancel()
+ buffer:add_text('bar')
+ textadept.editing.show_documentation()
+ assert(not buffer:call_tip_active(), 'documentation found')
+ buffer:clear_all()
+ buffer:add_text('FOO')
+ textadept.editing.show_documentation(nil, true)
+ assert(buffer:call_tip_active(), 'documentation not found')
+ buffer:call_tip_cancel()
+ buffer:add_text('(')
+ textadept.editing.show_documentation(nil, true)
+ assert(buffer:call_tip_active(), 'documentation not found')
+ buffer:call_tip_cancel()
+ buffer:add_text('bar')
+ textadept.editing.show_documentation(nil, true)
+ assert(buffer:call_tip_active(), 'documentation not found')
+ events.emit(events.CALL_TIP_CLICK, 1)
+ -- TODO: test calltip cycling.
+ buffer:set_save_point()
+ io.close_buffer()
+ textadept.editing.api_files['text'] = nil
+
+ assert_raises(function() textadept.editing.show_documentation(true) end, 'number/nil expected, got boolean')
+end
+
+function test_file_types_get_lexer()
+ buffer.new()
+ buffer:set_lexer('html')
+ buffer:set_text(table.concat({
+ '<html><head><style type="text/css">',
+ 'h1 {}',
+ '</style></head></html>'
+ }, '\n'))
+ buffer:colourise(POS(1), -1)
+ buffer:goto_pos(buffer:position_from_line(LINE(2)))
+ assert_equal(buffer:get_lexer(), 'html')
+ assert_equal(buffer:get_lexer(true), 'css')
+ assert_equal(buffer.style_name[buffer.style_at[buffer.current_pos]], 'identifier')
+ buffer:set_save_point()
+ io.close_buffer()
+
+ assert_raises(function() buffer.style_name[1] = 'foo' end, 'read-only property')
+end
+
+function test_file_types_set_lexer()
+ local lexer_loaded
+ local handler = function(lexer) lexer_loaded = lexer end
+ events.connect(events.LEXER_LOADED, handler)
+ buffer.new()
+ buffer.filename = 'foo.lua'
+ buffer:set_lexer()
+ assert_equal(buffer:get_lexer(), 'lua')
+ assert_equal(lexer_loaded, 'lua')
+ buffer.filename = 'foo'
+ buffer:set_text('#!/bin/sh')
+ buffer:set_lexer()
+ assert_equal(buffer:get_lexer(), 'bash')
+ buffer:undo()
+ buffer.filename = 'Makefile'
+ buffer:set_lexer()
+ assert_equal(buffer:get_lexer(), 'makefile')
+ -- Verify lexer after certain events.
+ buffer.filename = 'foo.c'
+ events.emit(events.FILE_AFTER_SAVE, nil, true)
+ assert_equal(buffer:get_lexer(), 'ansi_c')
+ buffer.filename = 'foo.cpp'
+ events.emit(events.FILE_OPENED)
+ assert_equal(buffer:get_lexer(), 'cpp')
+ view:goto_buffer(1)
+ view:goto_buffer(-1)
+ assert_equal(buffer:get_lexer(), 'cpp')
+ events.disconnect(events.LEXER_LOADED, handler)
+ buffer:set_save_point()
+ io.close_buffer()
+
+ assert_raises(function() buffer:set_lexer(true) end, 'string/nil expected, got boolean')
+end
+
+function test_file_types_select_lexer_interactive()
+ buffer.new()
+ local lexer = buffer:get_lexer()
+ textadept.file_types.select_lexer()
+ assert(buffer:get_lexer() ~= lexer, 'lexer unchanged')
+ io.close_buffer()
+end
+
+function test_ui_find_find_text()
+ local wrapped = false
+ local handler = function() wrapped = true end
+ buffer.new()
+ buffer:set_text(table.concat({
+ ' foo',
+ 'foofoo',
+ 'FOObar',
+ 'foo bar baz',
+ }, '\n'))
+ events.emit(events.FIND, 'foo', true)
+ assert_equal(buffer.selection_start, POS(1) + 1)
+ assert_equal(buffer.selection_end, buffer.selection_start + 3)
+ ui.find.whole_word = true
+ events.emit(events.FIND, 'foo', true)
+ assert_equal(buffer.selection_start, buffer:position_from_line(LINE(4)))
+ assert_equal(buffer.selection_end, buffer.selection_start + 3)
+ events.connect(events.FIND_WRAPPED, handler)
+ events.emit(events.FIND, 'foo', true)
+ assert(wrapped, 'search did not wrap')
+ events.disconnect(events.FIND_WRAPPED, handler)
+ assert_equal(buffer.selection_start, POS(1) + 1)
+ assert_equal(buffer.selection_end, buffer.selection_start + 3)
+ events.emit(events.FIND, 'foo', false)
+ assert_equal(buffer.selection_start, buffer:position_from_line(LINE(4)))
+ assert_equal(buffer.selection_end, buffer.selection_start + 3)
+ ui.find.match_case, ui.find.whole_word = true, false
+ events.emit(events.FIND, 'FOO', true)
+ assert_equal(buffer.selection_start, buffer:position_from_line(LINE(3)))
+ assert_equal(buffer.selection_end, buffer.selection_start + 3)
+ events.emit(events.FIND, 'FOO', true)
+ assert_equal(buffer.selection_start, buffer:position_from_line(LINE(3)))
+ assert_equal(buffer.selection_end, buffer.selection_start + 3)
+ ui.find.regex = true
+ events.emit(events.FIND, 'f(.)\\1', true)
+ assert_equal(buffer.selection_start, buffer:position_from_line(LINE(4)))
+ assert_equal(buffer.selection_end, buffer.selection_start + 3)
+ events.emit(events.FIND, 'quux', true)
+ assert_equal(buffer.selection_start, buffer.selection_end) -- no match
+ ui.find.match_case, ui.find.regex = false, false
+ buffer:set_save_point()
+ io.close_buffer()
+end
+
+function test_ui_find_incremental()
+ if not rawget(ui.find.find_incremental_keys, '\n') then
+ -- Overwritten in _USERHOME.
+ ui.find.find_incremental_keys['\n'] = function()
+ ui.find.find_entry_text = ui.command_entry:get_text() -- save
+ ui.find.find_incremental(ui.command_entry:get_text(), true, true)
+ end
+ end
+
+ buffer.new()
+ buffer:set_text(table.concat({
+ ' foo',
+ 'foobar',
+ 'FOObaz',
+ 'FOOquux'
+ }, '\n'))
+ assert_equal(buffer.current_pos, POS(1))
+ ui.find.find_incremental()
+ events.emit(events.KEYPRESS, string.byte('f'))
+ ui.command_entry:add_text('f') -- simulate keypress
+ assert_equal(buffer.selection_start, POS(1) + 1)
+ assert_equal(buffer.selection_end, buffer.selection_start + 1)
+ events.emit(events.KEYPRESS, string.byte('o'))
+ ui.command_entry:add_text('o') -- simulate keypress
+ events.emit(events.KEYPRESS, string.byte('o'))
+ ui.command_entry:add_text('o') -- simulate keypress
+ assert_equal(buffer.selection_start, POS(1) + 1)
+ assert_equal(buffer.selection_end, buffer.selection_start + 3)
+ events.emit(events.KEYPRESS, string.byte('\n'))
+ assert_equal(buffer.selection_start, buffer:position_from_line(LINE(2)))
+ assert_equal(buffer.selection_end, buffer.selection_start + 3)
+ events.emit(events.KEYPRESS, string.byte('q'))
+ ui.command_entry:add_text('q') -- simulate keypress
+ assert_equal(buffer.selection_start, buffer:position_from_line(LINE(4)))
+ assert_equal(buffer.selection_end, buffer.selection_start + 4)
+ events.emit(events.KEYPRESS, not CURSES and 0xFF08 or 263) -- \b
+ ui.command_entry:delete_back() -- simulate keypress
+ assert_equal(buffer.selection_start, buffer:position_from_line(LINE(2)))
+ assert_equal(buffer.selection_end, buffer.selection_start + 3)
+ events.emit(events.KEYPRESS, string.byte('\n'))
+ assert_equal(buffer.selection_start, buffer:position_from_line(LINE(3)))
+ assert_equal(buffer.selection_end, buffer.selection_start + 3)
+ ui.find.match_case = true
+ events.emit(events.KEYPRESS, string.byte('\n')) -- wrap
+ assert_equal(buffer.selection_start, POS(1) + 1)
+ assert_equal(buffer.selection_end, buffer.selection_start + 3)
+ ui.find.match_case = false
+ ui.find.find_entry_text = '' -- reset
+ buffer:set_save_point()
+ io.close_buffer()
+
+ assert_raises(function() ui.find.find_incremental(1) end, 'string/nil expected, got number')
+end
+
+function test_ui_find_find_in_files()
+ ui.find.find_entry_text = 'foo'
+ ui.find.match_case = true
+ ui.find.find_in_files(_HOME .. '/test')
+ assert_equal(buffer._type, _L['[Files Found Buffer]'])
+ if #_VIEWS > 1 then view:unsplit() end
+ local count = 0
+ for filename, line, text in buffer:get_text():gmatch('\n([^:]+):(%d+):([^\n]+)') do
+ assert(filename:find('^' .. _HOME .. '/test'), 'invalid filename "%s"', filename)
+ assert(text:find('foo'), '"foo" not found in "%s"', text)
+ count = count + 1
+ end
+ assert(count > 0, 'no files found')
+ local s = buffer:indicator_end(ui.find.INDIC_FIND, 0)
+ while true do
+ local e = buffer:indicator_end(ui.find.INDIC_FIND, s + 1)
+ if e == s then break end -- no more results
+ assert_equal(buffer:text_range(s, e), 'foo')
+ s = buffer:indicator_end(ui.find.INDIC_FIND, e + 1)
+ end
+ ui.find.goto_file_found(nil, true) -- wraps around
+ assert_equal(#_VIEWS, 2)
+ assert(buffer.filename, 'not in file found result')
+ ui.goto_view(1)
+ assert_equal(view.buffer._type, _L['[Files Found Buffer]'])
+ local filename, line_num = view.buffer:get_sel_text():match('^([^:]+):(%d+)')
+ ui.goto_view(-1)
+ assert_equal(buffer.filename, filename)
+ assert_equal(buffer:line_from_position(buffer.current_pos), LINE(tonumber(line_num)))
+ assert_equal(buffer:get_sel_text(), 'foo')
+ ui.goto_view(1) -- files found buffer
+ events.emit(events.KEYPRESS, not CURSES and 0xFF0D or 343) -- \n
+ assert_equal(buffer.filename, filename)
+ ui.goto_view(1) -- files found buffer
+ events.emit(events.DOUBLE_CLICK, nil, buffer:line_from_position(buffer.current_pos))
+ assert_equal(buffer.filename, filename)
+ io.close_buffer()
+ ui.goto_view(1) -- files found buffer
+ ui.find.goto_file_found(nil, false) -- wraps around
+ assert(buffer.filename and buffer.filename ~= filename, 'opened the same file')
+ io.close_buffer()
+ ui.goto_view(1) -- files found buffer
+ assert_raises(function() ui.find.goto_file_found(true) end, 'number/nil expected, got boolean')
+ ui.find.find_entry_text = ''
+ view:unsplit()
+ io.close_buffer()
+ -- TODO: ui.find.find_in_files() -- no param
+end
+
+function test_ui_find_replace()
+ buffer.new()
+ buffer:set_text('foofoo')
+ events.emit(events.FIND, 'foo', true)
+ events.emit(events.REPLACE, 'bar')
+ assert_equal(buffer.selection_start, POS(1))
+ assert_equal(buffer.selection_end, buffer.selection_start + 3)
+ assert_equal(buffer:get_sel_text(), 'bar')
+ assert_equal(buffer:get_text(), 'barfoo')
+ ui.find.regex = true
+ events.emit(events.FIND, 'f(.)\\1', true)
+ events.emit(events.REPLACE, 'b\\1\\1\\u1234')
+ assert_equal(buffer:get_text(), 'barbooሴ')
+ ui.find.regex = false
+ events.emit(events.FIND, 'quux', true)
+ events.emit(events.REPLACE, '')
+ assert_equal(buffer:get_text(), 'barbooሴ')
+ buffer:set_save_point()
+ io.close_buffer()
+end
+
+function test_ui_find_replace_all()
+ buffer.new()
+ local text = table.concat({
+ 'foo',
+ 'foobar',
+ 'foobaz',
+ 'foofoo'
+ }, '\n')
+ buffer:set_text(text)
+ events.emit(events.REPLACE_ALL, 'foo', 'bar')
+ assert_equal(buffer:get_text(), 'bar\nbarbar\nbarbaz\nbarbar')
+ buffer:undo()
+ assert_equal(buffer:get_text(), text) -- verify atomic undo
+ ui.find.regex = true
+ buffer:set_sel(buffer:position_from_line(LINE(2)), buffer:position_from_line(LINE(4)) + 3)
+ events.emit(events.REPLACE_ALL, 'f(.)\\1', 'b\\1\\1') -- replace in selection
+ assert_equal(buffer:get_text(), 'foo\nboobar\nboobaz\nboofoo')
+ ui.find.regex = false
+ buffer:undo()
+ events.emit(events.REPLACE_ALL, 'foo', '')
+ assert_equal(buffer:get_text(), '\nbar\nbaz\n')
+ events.emit(events.REPLACE_ALL, 'quux', '')
+ assert_equal(buffer:get_text(), '\nbar\nbaz\n')
+ buffer:set_save_point()
+ io.close_buffer()
+end
+
+function test_macro_record_play_save_load()
+ textadept.macros.save() -- should not do anything
+ textadept.macros.play() -- should not do anything
+ assert_equal(#_BUFFERS, 1)
+ assert(not buffer.modify, 'a macro was played')
+
+ textadept.macros.record()
+ events.emit(events.MENU_CLICKED, 1) -- File > New
+ buffer:add_text('f')
+ events.emit(events.CHAR_ADDED, string.byte('f'))
+ events.emit(events.FIND, 'f', true)
+ events.emit(events.REPLACE, 'b')
+ buffer:replace_sel('a') -- typing would do this
+ events.emit(events.CHAR_ADDED, string.byte('a'))
+ buffer:add_text('r')
+ events.emit(events.CHAR_ADDED, string.byte('r'))
+ events.emit(events.KEYPRESS, string.byte('t'), false, true) -- transpose
+ textadept.macros.play() -- should not do anything
+ textadept.macros.save() -- should not do anything
+ textadept.macros.load() -- should not do anything
+ textadept.macros.record() -- stop
+ assert_equal(#_BUFFERS, 2)
+ assert_equal(buffer:get_text(), 'ra')
+ buffer:set_save_point()
+ io.close_buffer()
+ textadept.macros.play()
+ assert_equal(#_BUFFERS, 2)
+ assert_equal(buffer:get_text(), 'ra')
+ buffer:set_save_point()
+ io.close_buffer()
+ local filename = os.tmpname()
+ textadept.macros.save(filename)
+ textadept.macros.record()
+ textadept.macros.record()
+ textadept.macros.load(filename)
+ textadept.macros.play()
+ assert_equal(#_BUFFERS, 2)
+ assert_equal(buffer:get_text(), 'ra')
+ buffer:set_save_point()
+ io.close_buffer()
+ os.remove(filename)
+
+ assert_raises(function() textadept.macros.save(1) end, 'string/nil expected, got number')
+ assert_raises(function() textadept.macros.load(1) end, 'string/nil expected, got number')
+end
+
+-- TODO: menu functions.
+
+function test_menu_select_command_interactive()
+ local num_buffers = #_BUFFERS
+ textadept.menu.select_command()
+ assert(#_BUFFERS > num_buffers, 'new buffer not created')
+ io.close_buffer()
+end
+
+function test_run_compile_run()
+ textadept.run.compile() -- should not do anything
+ textadept.run.run() -- should not do anything
+ assert_equal(#_BUFFERS, 1)
+ assert(not buffer.modify, 'a command was run')
+
+ local compile_file = _HOME .. '/test/modules/textadept/run/compile.lua'
+ textadept.run.compile(compile_file)
+ assert_equal(#_BUFFERS, 2)
+ assert_equal(buffer._type, _L['[Message Buffer]'])
+ update_ui() -- process output
+ assert(buffer:get_text():find("'end' expected"), 'no compile error')
+ assert(buffer:get_text():find('> exit status: 256'), 'no compile error')
+ if #_VIEWS > 1 then view:unsplit() end
+ textadept.run.goto_error(nil, true) -- wraps
+ assert_equal(#_VIEWS, 2)
+ assert_equal(buffer.filename, compile_file)
+ assert_equal(buffer:line_from_position(buffer.current_pos), LINE(3))
+ assert(buffer.annotation_text[LINE(3)]:find("'end' expected"), 'annotation not visible')
+ ui.goto_view(1) -- message buffer
+ assert_equal(buffer._type, _L['[Message Buffer]'])
+ assert(buffer:get_sel_text():find("'end' expected"), 'compile error not selected')
+ assert(buffer:marker_get(buffer:line_from_position(buffer.current_pos)) & 1 << textadept.run.MARK_ERROR > 0)
+ events.emit(events.KEYPRESS, not CURSES and 0xFF0D or 343) -- \n
+ assert_equal(buffer.filename, compile_file)
+ ui.goto_view(1) -- message buffer
+ events.emit(events.DOUBLE_CLICK, nil, buffer:line_from_position(buffer.current_pos))
+ assert_equal(buffer.filename, compile_file)
+ local compile_command = textadept.run.compile_commands.lua
+ textadept.run.compile() -- clears annotation
+ update_ui() -- process output
+ view:goto_buffer(1)
+ assert(not buffer.annotation_text[LINE(3)]:find("'end' expected"), 'annotation visible')
+ io.close_buffer() -- compile_file
+
+ local run_file = _HOME .. '/test/modules/textadept/run/run.lua'
+ textadept.run.run_commands[run_file] = function()
+ return textadept.run.run_commands.lua, run_file:match('^(.+[/\\])') -- intentional trailing '/'
+ end
+ io.open_file(run_file)
+ textadept.run.run()
+ assert_equal(buffer._type, _L['[Message Buffer]'])
+ update_ui() -- process output
+ assert(buffer:get_text():find('attempt to call a nil value'), 'no run error')
+ textadept.run.goto_error(nil, false)
+ assert_equal(buffer.filename, run_file)
+ assert_equal(buffer:line_from_position(buffer.current_pos), LINE(2))
+ textadept.run.goto_error(nil, false)
+ assert_equal(buffer.filename, run_file)
+ assert_equal(buffer:line_from_position(buffer.current_pos), LINE(1))
+ ui.goto_view(1)
+ assert(buffer:marker_get(buffer:line_from_position(buffer.current_pos)) & 1 << textadept.run.MARK_WARNING > 0)
+ ui.goto_view(-1)
+ textadept.run.goto_error(nil, false)
+ assert_equal(buffer.filename, compile_file)
+ if #_VIEWS > 1 then view:unsplit() end
+ io.close_buffer() -- compile_file
+ io.close_buffer() -- run_file
+ assert_raises(function() textadept.run.goto_error(true) end, 'number/nil expected, got boolean')
+ io.close_buffer() -- message buffer
+
+ assert_raises(function() textadept.run.compile({}) end, 'string/nil expected, got table')
+ assert_raises(function() textadept.run.run({}) end, 'string/nil expected, got table')
+end
+
+function test_run_build()
+ textadept.run.build_commands[_HOME] = function()
+ return 'lua modules/textadept/run/build.lua', _HOME .. '/test/' -- intentional trailing '/'
+ end
+ textadept.run.build(_HOME)
+ if #_VIEWS > 1 then view:unsplit() end
+ assert_equal(buffer._type, _L['[Message Buffer]'])
+ os.execute('sleep 0.1') -- ensure process is running
+ buffer:add_text('foo')
+ buffer:new_line() -- should send previous line as stdin
+ textadept.run.stop()
+ update_ui() -- process output
+ assert(buffer:get_text():find('> cd '), 'did not change directory')
+ assert(buffer:get_text():find('build%.lua'), 'did not run build command')
+ assert(buffer:get_text():find('read "foo"'), 'did not send stdin')
+ assert(buffer:get_text():find('> exit status: 9'), 'build not stopped')
+ io.close_buffer()
+ -- TODO: chdir(_HOME) and textadept.run.build() -- no param.
+ -- TODO: project whose makefile is autodetected.
+end
+
+-- TODO: test textadept.run.run_in_background
+
+function test_snippets_find_snippet()
+ snippets.foo = 'bar'
+ textadept.snippets.paths[1] = _HOME .. '/test/modules/textadept/snippets'
+
+ buffer.new()
+ buffer:add_text('foo')
+ assert(textadept.snippets.insert() == nil, 'snippet not inserted')
+ assert_equal(buffer:get_text(), 'bar') -- from snippets
+ textadept.snippets.insert()
+ assert_equal(buffer:get_text(), 'baz\n') -- from bar file
+ buffer:delete_back()
+ textadept.snippets.insert()
+ assert_equal(buffer:get_text(), 'quux\n') -- from baz.txt file
+ buffer:delete_back()
+ assert(not textadept.snippets.insert(), 'snippet inserted')
+ assert_equal(buffer:get_text(), 'quux')
+ buffer:clear_all()
+ buffer:set_lexer('lua') -- prefer lexer-specific snippets
+ snippets.lua = {foo = 'baz'} -- overwrite language module
+ buffer:add_text('foo')
+ textadept.snippets.insert()
+ assert_equal(buffer:get_text(), 'baz') -- from snippets.lua
+ textadept.snippets.insert()
+ assert_equal(buffer:get_text(), 'bar\n') -- from lua.baz.lua file
+ buffer:delete_back()
+ textadept.snippets.insert()
+ assert_equal(buffer:get_text(), 'quux\n') -- from lua.bar file
+ buffer:set_save_point()
+ io.close_buffer()
+
+ snippets.foo = nil
+ table.remove(textadept.snippets.paths, 1)
+end
+
+function test_snippets_match_indentation()
+ local snippet = '\t foo'
+ local multiline_snippet = table.concat({
+ 'foo',
+ '\tbar',
+ '\t baz',
+ 'quux'
+ }, '\n')
+ buffer.new()
+
+ buffer.use_tabs, buffer.tab_width, buffer.eol_mode = true, 4, buffer.EOL_CRLF
+ textadept.snippets.insert(snippet)
+ assert_equal(buffer:get_text(), '\t\tfoo')
+ buffer:clear_all()
+ buffer:add_text('\t')
+ textadept.snippets.insert(snippet)
+ assert_equal(buffer:get_text(), '\t\t\tfoo')
+ buffer:clear_all()
+ buffer:add_text('\t')
+ textadept.snippets.insert(multiline_snippet)
+ assert_equal(buffer:get_text(), table.concat({
+ '\tfoo',
+ '\t\tbar',
+ '\t\t\tbaz',
+ '\tquux'
+ }, '\r\n'))
+ buffer:clear_all()
+
+ buffer.use_tabs, buffer.tab_width, buffer.eol_mode = false, 2, buffer.EOL_LF
+ textadept.snippets.insert(snippet)
+ assert_equal(buffer:get_text(), ' foo')
+ buffer:clear_all()
+ buffer:add_text(' ')
+ textadept.snippets.insert(snippet)
+ assert_equal(buffer:get_text(), ' foo')
+ buffer:clear_all()
+ buffer:add_text(' ')
+ textadept.snippets.insert(multiline_snippet)
+ assert_equal(buffer:get_text(), table.concat({
+ ' foo',
+ ' bar',
+ ' baz',
+ ' quux'
+ }, '\n'))
+
+ buffer:set_save_point()
+ io.close_buffer()
+
+ assert_raises(function() textadept.snippets.insert(true) end, 'string/nil expected, got boolean')
+end
+
+function test_snippets_placeholders()
+ buffer.new()
+ local lua_date = os.date()
+ local p = io.popen('date')
+ local shell_date = p:read()
+ p:close()
+ textadept.snippets.insert(table.concat({
+ '%0placeholder: %1(foo) %2(bar)',
+ 'choice: %3{baz,quux}',
+ 'mirror: %2%3',
+ 'Lua: %<os.date()> %1<text:upper()>',
+ 'Shell: %[date] %1[echo %]',
+ 'escape: %%1 %4%( %4%{',
+ }, '\n'))
+ assert_equal(buffer.selections, 1)
+ assert_equal(buffer.selection_start, POS(1) + 14)
+ assert_equal(buffer.selection_end, buffer.selection_start + 3)
+ assert_equal(buffer:get_sel_text(), 'foo')
+ buffer:replace_sel('baz')
+ events.emit(events.UPDATE_UI, buffer.UPDATE_CONTENT + buffer.UPDATE_SELECTION) -- simulate typing
+ assert_equal(buffer:get_text(), string.format(table.concat({
+ ' placeholder: baz bar', -- placeholders to visit have 1 empty space
+ 'choice: ', -- placeholder choices are initially empty
+ 'mirror: ', -- placeholder mirrors are initially empty
+ 'Lua: %s BAZ', -- verify real-time transforms
+ 'Shell: %s baz', -- verify real-time transforms
+ 'escape: %%1 ( { ' -- trailing space for snippet sentinel
+ }, '\n'), lua_date, shell_date))
+ textadept.snippets.insert()
+ assert_equal(buffer.selections, 2)
+ assert_equal(buffer.selection_start, POS(1) + 18)
+ assert_equal(buffer.selection_end, buffer.selection_start + 3)
+ for i = INDEX(1), INDEX(buffer.selections) do
+ assert_equal(buffer.selection_n_end[i], buffer.selection_n_start[i] + 3)
+ assert_equal(buffer:text_range(buffer.selection_n_start[i], buffer.selection_n_end[i]), 'bar')
+ end
+ assert(buffer:get_text():find('mirror: bar'), 'mirror not updated')
+ textadept.snippets.insert()
+ assert_equal(buffer.selections, 2)
+ assert(buffer:auto_c_active(), 'no choice')
+ buffer:auto_c_select('quux')
+ buffer:auto_c_complete()
+ assert(buffer:get_text():find('\nmirror: barquux\n'), 'choice mirror not updated')
+ textadept.snippets.insert()
+ assert_equal(buffer.selection_start, buffer.selection_end) -- no default placeholder (escaped)
+ textadept.snippets.insert()
+ assert_equal(buffer:get_text(), string.format(table.concat({
+ 'placeholder: baz bar',
+ 'choice: quux',
+ 'mirror: barquux',
+ 'Lua: %s BAZ',
+ 'Shell: %s baz',
+ 'escape: %%1 ( {'
+ }, '\n'), lua_date, shell_date))
+ assert_equal(buffer.selection_start, POS(1))
+ assert_equal(buffer.selection_start, POS(1))
+ buffer:set_save_point()
+ io.close_buffer()
+end
+
+function test_snippets_irregular_placeholders()
+ buffer.new()
+ textadept.snippets.insert('%1(foo %2(bar))%5(quux)')
+ assert_equal(buffer:get_sel_text(), 'foo bar')
+ buffer:delete_back()
+ textadept.snippets.insert()
+ assert_equal(buffer:get_sel_text(), 'quux')
+ textadept.snippets.insert()
+ assert_equal(buffer:get_text(), 'quux')
+ buffer:set_save_point()
+ io.close_buffer()
+end
+
+function test_snippets_previous_cancel()
+ buffer.new()
+ textadept.snippets.insert('%1(foo) %2(bar) %3(baz)')
+ assert_equal(buffer:get_text(), 'foo bar baz ') -- trailing space for snippet sentinel
+ buffer:delete_back()
+ textadept.snippets.insert()
+ assert_equal(buffer:get_text(), ' bar baz ')
+ buffer:delete_back()
+ textadept.snippets.insert()
+ assert_equal(buffer:get_text(), ' baz ')
+ textadept.snippets.previous()
+ textadept.snippets.previous()
+ assert_equal(buffer:get_text(), 'foo bar baz ')
+ assert_equal(buffer:get_sel_text(), 'foo')
+ textadept.snippets.insert()
+ textadept.snippets.cancel_current()
+ assert_equal(buffer.length, 0)
+ buffer:set_save_point()
+ io.close_buffer()
+end
+
+function test_snippets_nested()
+ snippets.foo = '%1(foo)%2(bar)%3(baz)'
+ buffer.new()
+
+ buffer:add_text('foo')
+ textadept.snippets.insert()
+ buffer:char_right()
+ textadept.snippets.insert()
+ assert_equal(buffer:get_text(), 'foobarbaz barbaz ') -- trailing spaces for snippet sentinels
+ assert_equal(buffer:get_sel_text(), 'foo')
+ assert_equal(buffer.selection_start, POS(1))
+ assert_equal(buffer.selection_end, buffer.selection_start + 3)
+ buffer:replace_sel('quux')
+ textadept.snippets.insert()
+ assert_equal(buffer:get_sel_text(), 'bar')
+ assert_equal(buffer.selection_start, POS(1) + 4)
+ assert_equal(buffer.selection_end, buffer.selection_start + 3)
+ textadept.snippets.insert()
+ assert_equal(buffer:get_sel_text(), 'baz')
+ assert_equal(buffer.selection_start, POS(1) + 7)
+ assert_equal(buffer.selection_end, buffer.selection_start + 3)
+ textadept.snippets.insert()
+ assert_equal(buffer.current_pos, POS(1) + 10)
+ assert_equal(buffer.selection_start, buffer.selection_end)
+ assert_equal(buffer:get_text(), 'quuxbarbazbarbaz ')
+ textadept.snippets.insert()
+ assert_equal(buffer:get_sel_text(), 'bar')
+ assert_equal(buffer.selection_start, POS(1) + 10)
+ assert_equal(buffer.selection_end, buffer.selection_start + 3)
+ textadept.snippets.insert()
+ assert_equal(buffer:get_sel_text(), 'baz')
+ assert_equal(buffer.selection_start, POS(1) + 13)
+ assert_equal(buffer.selection_end, buffer.selection_start + 3)
+ textadept.snippets.insert()
+ assert_equal(buffer:get_text(), 'quuxbarbazbarbaz')
+ buffer:clear_all()
+
+ buffer:add_text('foo')
+ textadept.snippets.insert()
+ buffer:char_right()
+ textadept.snippets.insert()
+ textadept.snippets.cancel_current()
+ assert_equal(buffer.current_pos, POS(1) + 3)
+ assert_equal(buffer.selection_start, buffer.selection_end)
+ assert_equal(buffer:get_text(), 'foobarbaz ')
+ buffer:add_text('quux')
+ assert_equal(buffer:get_text(), 'fooquuxbarbaz ')
+ textadept.snippets.insert()
+ assert_equal(buffer:get_sel_text(), 'bar')
+ assert_equal(buffer.selection_start, POS(1) + 7)
+ assert_equal(buffer.selection_end, buffer.selection_start + 3)
+ textadept.snippets.insert()
+ assert_equal(buffer:get_sel_text(), 'baz')
+ assert_equal(buffer.selection_start, POS(1) + 10)
+ assert_equal(buffer.selection_end, buffer.selection_start + 3)
+ textadept.snippets.insert()
+ assert_equal(buffer.current_pos, buffer.line_end_position[LINE(1)])
+ assert_equal(buffer.selection_start, buffer.selection_end)
+ assert_equal(buffer:get_text(), 'fooquuxbarbaz')
+
+ buffer:set_save_point()
+ io.close_buffer()
+ snippets.foo = nil
+end
+
+function test_snippets_select_interactive()
+ snippets.foo = 'bar'
+ buffer.new()
+ textadept.snippets.select()
+ assert(buffer.length > 0, 'no snippet inserted')
+ buffer:set_save_point()
+ io.close_buffer()
+ snippets.foo = nil
+end
+
+function test_snippets_autocomplete()
+ snippets.bar = 'baz'
+ snippets.baz = 'quux'
+ buffer.new()
+ buffer:add_text('ba')
+ textadept.editing.autocomplete('snippet')
+ assert(buffer:auto_c_active(), 'snippet autocompletion list not shown')
+ buffer:auto_c_complete()
+ textadept.snippets.insert()
+ assert_equal(buffer:get_text(), 'baz')
+ buffer:set_save_point()
+ io.close_buffer()
+ snippets.bar = nil
+ snippets.baz = nil
+end
+
+function test_lua_autocomplete()
+ buffer.new()
+ buffer:set_lexer('lua')
+
+ buffer:add_text('raw')
+ textadept.editing.autocomplete('lua')
+ assert(buffer:auto_c_active(), 'no autocompletions')
+ assert_equal(buffer.auto_c_current_text, 'rawequal')
+ buffer:auto_c_cancel()
+ buffer:clear_all()
+
+ buffer:add_text('string.')
+ textadept.editing.autocomplete('lua')
+ assert(buffer:auto_c_active(), 'no autocompletions')
+ assert_equal(buffer.auto_c_current_text, 'byte')
+ buffer:auto_c_cancel()
+ buffer:clear_all()
+
+ buffer:add_text('s = "foo"\ns:')
+ textadept.editing.autocomplete('lua')
+ assert(buffer:auto_c_active(), 'no autocompletions')
+ assert_equal(buffer.auto_c_current_text, 'byte')
+ buffer:auto_c_cancel()
+ buffer:clear_all()
+
+ buffer:add_text('f = io.open("path")\nf:')
+ textadept.editing.autocomplete('lua')
+ assert(buffer:auto_c_active(), 'no autocompletions')
+ assert_equal(buffer.auto_c_current_text, 'close')
+ buffer:auto_c_cancel()
+ buffer:clear_all()
+
+ buffer:add_text('buffer:auto_c')
+ textadept.editing.autocomplete('lua')
+ assert(not buffer:auto_c_active(), 'autocompletions available')
+ buffer.filename = _HOME .. '/test/autocomplete_lua.lua'
+ textadept.editing.autocomplete('lua')
+ assert(buffer:auto_c_active(), 'no autocompletions')
+ assert_equal(buffer.auto_c_current_text, 'auto_c_active')
+ buffer:auto_c_cancel()
+ buffer:clear_all()
+
+ local autocomplete_snippets = _M.lua.autocomplete_snippets
+ _M.lua.autocomplete_snippets = false
+ buffer:add_text('for')
+ textadept.editing.autocomplete('lua')
+ assert(not buffer:auto_c_active(), 'autocompletions available')
+ _M.lua.autocomplete_snippets = true
+ textadept.editing.autocomplete('lua')
+ assert(buffer:auto_c_active(), 'no autocompletions')
+ buffer:auto_c_cancel()
+ buffer:clear_all()
+ _M.lua.autocomplete_snippets = autocomplete_snippets -- restore
+
+ buffer:set_save_point()
+ io.close_buffer()
+end
+
+function test_ansi_c_autocomplete()
+ buffer.new()
+ buffer:set_lexer('ansi_c')
+
+ buffer:add_text('str');
+ textadept.editing.autocomplete('ansi_c')
+ assert(buffer:auto_c_active(), 'no autocompletions')
+ assert_equal(buffer.auto_c_current_text, 'strcat')
+ buffer:auto_c_cancel()
+ buffer:clear_all()
+
+ buffer:add_text('div_t d;\nd->')
+ textadept.editing.autocomplete('ansi_c')
+ assert(buffer:auto_c_active(), 'no autocompletions')
+ assert_equal(buffer.auto_c_current_text, 'quot')
+ buffer:auto_c_cancel()
+ buffer:clear_all()
+
+ local autocomplete_snippets = _M.ansi_c.autocomplete_snippets
+ _M.ansi_c.autocomplete_snippets = false
+ buffer:add_text('for')
+ textadept.editing.autocomplete('ansi_c')
+ assert(not buffer:auto_c_active(), 'autocompletions available')
+ _M.ansi_c.autocomplete_snippets = true
+ textadept.editing.autocomplete('ansi_c')
+ assert(buffer:auto_c_active(), 'no autocompletions')
+ buffer:auto_c_cancel()
+ buffer:clear_all()
+ _M.ansi_c.autocomplete_snippets = autocomplete_snippets -- restore
+
+ -- TODO: typeref and rescan
+
+ buffer:set_save_point()
+ io.close_buffer()
+end
+
+--------------------------------------------------------------------------------
+
+assert(not WIN32 and not OSX, 'Test suite currently only runs on Linux')
+
+local TEST_OUTPUT_BUFFER = '[Test Output]'
+function print(...) ui._print(TEST_OUTPUT_BUFFER, ...) end
+-- Clean up after a previously failed test.
+local function cleanup()
+ while #_BUFFERS > 1 do
+ if buffer._type == TEST_OUTPUT_BUFFER then view:goto_buffer(1) end
+ buffer:set_save_point()
+ io.close_buffer()
+ end
+ while view:unsplit() do end
+end
+
+-- Determines whether or not to run the test whose name is string *name*.
+-- If no arg patterns are provided, returns true.
+-- If only inclusive arg patterns are provided, returns true if *name* matches
+-- at least one of those patterns.
+-- If only exclusive arg patterns are provided ('-' prefix), returns true if
+-- *name* does not match any of them.
+-- If both inclusive and exclusive arg patterns are provided, returns true if
+-- *name* matches at least one of the inclusive ones, but not any of the
+-- exclusive ones.
+-- @param name Name of the test to check for inclusion.
+-- @return true or false
+local function include_test(name)
+ if #arg == 0 then return true end
+ local include, includes, excludes = false, false, false
+ for _, patt in ipairs(arg) do
+ if patt:find('^%-') then
+ if name:find(patt:sub(2)) then return false end
+ excludes = true
+ else
+ if name:find(patt) then include = true end
+ includes = true
+ end
+ end
+ return include or not includes and excludes
+end
+
+local tests = {}
+for k in pairs(_ENV) do
+ if k:find('^test_') and include_test(k) then
+ tests[#tests + 1] = k
+ end
+end
+table.sort(tests)
+
+print('Starting test suite')
+
+local tests_run, tests_failed, tests_failed_expected = 0, 0, 0
+
+for i = 1, #tests do
+ cleanup()
+ assert_equal(#_BUFFERS, 1)
+ assert_equal(#_VIEWS, 1)
+
+ _ENV = setmetatable({}, {__index = _ENV})
+ local name, f, attempts = tests[i], _ENV[tests[i]], 1
+ ::retry::
+ print(string.format('Running %s', name))
+ update_ui()
+ local ok, errmsg = xpcall(f, function(errmsg)
+ local fail = not expected_failures[f] and 'Failed!' or 'Expected failure.'
+ return string.format('%s %s', fail, debug.traceback(errmsg, 3))
+ end)
+ update_ui()
+ if not ok and unstable_tests[f] and attempts < 3 then
+ cleanup()
+ print('Failed, but unstable. Trying again.')
+ attempts = attempts + 1
+ goto retry
+ elseif not errmsg then
+ if #_BUFFERS > 1 then
+ ok, errmsg = false, 'Failed! Test did not close the buffer(s) it created'
+ elseif #_VIEWS > 1 then
+ ok, errmsg = false, 'Failed! Test did not unsplit the view(s) it created'
+ elseif expected_failures[f] then
+ ok, errmsg = false, 'Failed! Test should have failed'
+ expected_failures[f] = nil
+ end
+ end
+ print(ok and 'Passed.' or errmsg)
+
+ tests_run = tests_run + 1
+ if not ok then
+ tests_failed = tests_failed + 1
+ if expected_failures[f] then
+ tests_failed_expected = tests_failed_expected + 1
+ end
+ end
+end
+
+print(string.format('%d tests run, %d unexpected failures, %d expected failures', tests_run, tests_failed - tests_failed_expected, tests_failed_expected))
+
+--if package.loaded['luacov'] then
+-- require('luacov').save_stats() -- TODO: this crashes every other run
+-- os.execute('luacov')
+-- local f = assert(io.open('luacov.report.out'))
+-- buffer:append_text(f:read('a'):match('\nSummary.+$'))
+-- f:close()
+--else
+-- buffer:new_line()
+-- buffer:append_text('No LuaCov coverage to report.')
+--end
+--buffer:set_save_point()