1-- Copyright 2020-2021 Mitchell. See LICENSE. 2 3local _tostring = tostring 4-- Overloads tostring() to print more user-friendly output for `assert_equal()`. 5function tostring(value) 6 if type(value) == 'table' then 7 return string.format('{%s}', table.concat(value, ', ')) 8 elseif type(value) == 'string' then 9 return string.format('%q', value) 10 else 11 return _tostring(value) 12 end 13end 14 15-- Asserts that values *v1* and *v2* are equal. 16-- Tables are compared by value, not by reference. 17function assert_equal(v1, v2) 18 if v1 == v2 then return end 19 if type(v1) == 'table' and type(v2) == 'table' then 20 if #v1 == #v2 then 21 for k, v in pairs(v1) do if v2[k] ~= v then goto continue end end 22 for k, v in pairs(v2) do if v1[k] ~= v then goto continue end end 23 return 24 end 25 ::continue:: 26 v1 = string.format('{%s}', table.concat(v1, ', ')) 27 v2 = string.format('{%s}', table.concat(v2, ', ')) 28 end 29 error(string.format('%s ~= %s', v1, v2), 2) 30end 31 32 33-- Asserts that function *f* raises an error whose error message contains string 34-- *expected_errmsg*. 35-- @param f Function to call. 36-- @param expected_errmsg String the error message should contain. 37function assert_raises(f, expected_errmsg) 38 local ok, errmsg = pcall(f) 39 if ok then error('error expected', 2) end 40 if expected_errmsg ~= errmsg and 41 not tostring(errmsg):find(expected_errmsg, 1, true) then 42 error(string.format( 43 'error message %q expected, was %q', expected_errmsg, errmsg), 2) 44 end 45end 46 47local expected_failures = {} 48function expected_failure(f) expected_failures[f] = true end 49 50-------------------------------------------------------------------------------- 51 52function test_assert() 53 assert_equal(assert(true, 'okay'), true) 54 assert_raises(function() assert(false, 'not okay') end, 'not okay') 55 assert_raises(function() assert(false, 'not okay: %s', false) end, 'not okay: false') 56 assert_raises(function() assert(false, 'not okay: %s') end, 'no value') 57 assert_raises(function() assert(false, 1234) end, '1234') 58 assert_raises(function() assert(false) end, 'assertion failed!') 59end 60 61function test_assert_types() 62 function foo(bar, baz, quux) 63 assert_type(bar, 'string', 1) 64 assert_type(baz, 'boolean/nil', 2) 65 assert_type(quux, 'string/table/nil', 3) 66 return bar 67 end 68 assert_equal(foo('bar'), 'bar') 69 assert_raises(function() foo(1) end, "bad argument #1 to 'foo' (string expected, got number") 70 assert_raises(function() foo('bar', 'baz') end, "bad argument #2 to 'foo' (boolean/nil expected, got string") 71 assert_raises(function() foo('bar', true, 1) end, "bad argument #3 to 'foo' (string/table/nil expected, got number") 72 73 function foo(bar) assert_type(bar, string) end 74 assert_raises(function() foo(1) end, "bad argument #2 to 'assert_type' (string expected, got table") 75 function foo(bar) assert_type(bar, 'string') end 76 assert_raises(function() foo(1) end, "bad argument #3 to 'assert_type' (value expected, got nil") 77end 78 79function test_events_basic() 80 local emitted = false 81 local event, handler = 'test_basic', function() emitted = true end 82 events.connect(event, handler) 83 events.emit(event) 84 assert(emitted, 'event not emitted or handled') 85 emitted = false 86 events.disconnect(event, handler) 87 events.emit(event) 88 assert(not emitted, 'event still handled') 89 90 assert_raises(function() events.connect(nil) end, 'string expected') 91 assert_raises(function() events.connect(event, nil) end, 'function expected') 92 assert_raises(function() events.connect(event, function() end, 'bar') end, 'number/nil expected') 93 assert_raises(function() events.disconnect() end, 'expected, got nil') 94 assert_raises(function() events.disconnect(event, nil) end, 'function expected') 95 assert_raises(function() events.emit(nil) end, 'string expected') 96end 97 98function test_events_single_handle() 99 local count = 0 100 local event, handler = 'test_single_handle', function() count = count + 1 end 101 events.connect(event, handler) 102 events.connect(event, handler) -- should disconnect first 103 events.emit(event) 104 assert_equal(count, 1) 105end 106 107function test_events_insert() 108 local foo = {} 109 local event = 'test_insert' 110 events.connect(event, function() foo[#foo + 1] = 2 end) 111 events.connect(event, function() foo[#foo + 1] = 1 end, 1) 112 events.emit(event) 113 assert_equal(foo, {1, 2}) 114end 115 116function test_events_short_circuit() 117 local emitted = false 118 local event = 'test_short_circuit' 119 events.connect(event, function() return true end) 120 events.connect(event, function() emitted = true end) 121 assert_equal(events.emit(event), true) 122 assert_equal(emitted, false) 123end 124 125function test_events_disconnect_during_handle() 126 local foo = {} 127 local event, handlers = 'test_disconnect_during_handle', {} 128 for i = 1, 3 do 129 handlers[i] = function() 130 foo[#foo + 1] = i 131 events.disconnect(event, handlers[i]) 132 end 133 events.connect(event, handlers[i]) 134 end 135 events.emit(event) 136 assert_equal(foo, {1, 2, 3}) 137end 138 139function test_events_error() 140 local errmsg 141 local event, handler = 'test_error', function(message) 142 errmsg = message 143 return false -- halt propagation 144 end 145 events.connect(events.ERROR, handler, 1) 146 events.connect(event, function() error('foo') end) 147 events.emit(event) 148 events.disconnect(events.ERROR, handler) 149 assert(errmsg:find('foo'), 'error handler did not run') 150end 151 152function test_events_value_passing() 153 local event = 'test_value_passing' 154 events.connect(event, function() return end) 155 events.connect(event, function() return {1, 2, 3} end) -- halts propagation 156 events.connect(event, function() return 'foo' end) 157 assert_equal(events.emit(event), {1, 2, 3}) 158end 159 160local locales = {} 161-- Load localizations from *locale_conf* and return them in a table. 162-- @param locale_conf String path to a local file to load. 163local function load_locale(locale_conf) 164 if locales[locale_conf] then return locales[locale_conf] end 165 print(string.format('Loading locale "%s"', locale_conf)) 166 local L = {} 167 for line in io.lines(locale_conf) do 168 if not line:find('^%s*[^%w_%[]') then 169 local id, str = line:match('^(.-)%s*=%s*(.+)$') 170 if id and str and assert(not L[id], 'duplicate locale id "%s"', id) then 171 L[id] = str 172 end 173 end 174 end 175 locales[locale_conf] = L 176 return L 177end 178 179-- Looks for use of localization in the given Lua file and verifies that each 180-- use is okay. 181-- @param filename String filename of the Lua file to check. 182-- @param L Table of localizations to read from. 183local function check_localizations(filename, L) 184 print(string.format('Processing file "%s"', filename:gsub(_HOME, ''))) 185 local count = 0 186 for line in io.lines(filename) do 187 for id in line:gmatch([=[_L%[['"]([^'"]+)['"]%]]=]) do 188 assert(L[id], 'locale missing id "%s"', id) 189 count = count + 1 190 end 191 end 192 print(string.format('Checked %d localizations.', count)) 193end 194 195local loaded_extra = {} 196-- Records localization assignments in the given Lua file for use in subsequent 197-- checks. 198-- @param L Table of localizations to add to. 199local function load_extra_localizations(filename, L) 200 if loaded_extra[filename] then return end 201 print(string.format('Processing file "%s"', filename:gsub(_HOME, ''))) 202 local count = 0 203 for line in io.lines(filename) do 204 if line:find('_L%b[]%s*=') then 205 for id in line:gmatch([=[_L%[['"]([^'"]+)['"]%]%s*=]=]) do 206 assert(not L[id], 'duplicate locale id "%s"', id) 207 L[id], count = true, count + 1 208 end 209 end 210 end 211 loaded_extra[filename] = true 212 print(string.format('Added %d localizations.', count)) 213end 214 215local LOCALE_CONF = _HOME .. '/core/locale.conf' 216local LOCALE_DIR = _HOME .. '/core/locales' 217 218function test_locale_load() 219 local L = load_locale(LOCALE_CONF) 220 for locale_conf in lfs.walk(LOCALE_DIR) do 221 local l = load_locale(locale_conf) 222 for id in pairs(L) do assert(l[id], 'locale missing id "%s"', id) end 223 for id in pairs(l) do assert(L[id], 'locale has extra id "%s"', id) end 224 end 225end 226 227function test_locale_use_core() 228 local L = load_locale(LOCALE_CONF) 229 local ta_dirs = {'core', 'modules/ansi_c', 'modules/lua', 'modules/textadept'} 230 for _, dir in ipairs(ta_dirs) do 231 dir = _HOME .. '/' .. dir 232 for filename in lfs.walk(dir, '.lua') do 233 check_localizations(filename, L) 234 end 235 end 236 check_localizations(_HOME .. '/init.lua', L) 237end 238 239function test_locale_use_extra() 240 local L = load_locale(LOCALE_CONF) 241 for filename in lfs.walk(_HOME, '.lua') do 242 load_extra_localizations(filename, L) 243 end 244 for filename in lfs.walk(_HOME, '.lua') do 245 check_localizations(filename, L) 246 end 247end 248 249function test_locale_use_userhome() 250 local L = load_locale(LOCALE_CONF) 251 for filename in lfs.walk(_HOME, '.lua') do 252 load_extra_localizations(filename, L) 253 end 254 for filename in lfs.walk(_USERHOME, '.lua') do 255 load_extra_localizations(filename, L) 256 end 257 L['%1'] = true -- snippet 258 for filename in lfs.walk(_USERHOME, '.lua') do 259 check_localizations(filename, L) 260 end 261end 262 263function test_file_io_open_file_detect_encoding() 264 io.recent_files = {} -- clear 265 local recent_files = {} 266 local files = { 267 [_HOME .. '/test/file_io/utf8'] = 'UTF-8', 268 [_HOME .. '/test/file_io/cp1252'] = 'CP1252', 269 [_HOME .. '/test/file_io/utf16'] = 'UTF-16', 270 [_HOME .. '/test/file_io/binary'] = '', 271 } 272 for filename, encoding in pairs(files) do 273 print(string.format('Opening file %s', filename)) 274 io.open_file(filename) 275 assert_equal(buffer.filename, filename) 276 local f = io.open(filename, 'rb') 277 local contents = f:read('a') 278 f:close() 279 if encoding ~= '' then 280 --assert_equal(buffer:get_text():iconv(encoding, 'UTF-8'), contents) 281 assert_equal(buffer.encoding, encoding) 282 assert_equal(buffer.code_page, buffer.CP_UTF8) 283 else 284 assert_equal(buffer:get_text(), contents) 285 assert_equal(buffer.encoding, nil) 286 assert_equal(buffer.code_page, 0) 287 end 288 buffer:close() 289 table.insert(recent_files, 1, filename) 290 end 291 assert_equal(io.recent_files, recent_files) 292 293 assert_raises(function() io.open_file(1) end, 'string/table/nil expected, got number') 294 assert_raises(function() io.open_file('/tmp/foo', true) end, 'string/table/nil expected, got boolean') 295 -- TODO: encoding failure 296end 297 298function test_file_io_open_file_detect_newlines() 299 local files = { 300 [_HOME .. '/test/file_io/lf'] = buffer.EOL_LF, 301 [_HOME .. '/test/file_io/crlf'] = buffer.EOL_CRLF, 302 } 303 for filename, mode in pairs(files) do 304 io.open_file(filename) 305 assert_equal(buffer.eol_mode, mode) 306 buffer:close() 307 end 308end 309 310function test_file_io_open_file_with_encoding() 311 local num_buffers = #_BUFFERS 312 local files = { 313 _HOME .. '/test/file_io/utf8', 314 _HOME .. '/test/file_io/cp1252', 315 _HOME .. '/test/file_io/utf16' 316 } 317 local encodings = {nil, 'CP1252', 'UTF-16'} 318 io.open_file(files, encodings) 319 assert_equal(#_BUFFERS, num_buffers + #files) 320 for i = #files, 1, -1 do 321 view:goto_buffer(_BUFFERS[num_buffers + i]) 322 assert_equal(buffer.filename, files[i]) 323 if encodings[i] then assert_equal(buffer.encoding, encodings[i]) end 324 buffer:close() 325 end 326end 327 328function test_file_io_open_file_already_open() 329 local filename = _HOME .. '/test/file_io/utf8' 330 io.open_file(filename) 331 buffer.new() 332 local num_buffers = #_BUFFERS 333 io.open_file(filename) 334 assert_equal(buffer.filename, filename) 335 assert_equal(#_BUFFERS, num_buffers) 336 view:goto_buffer(1) 337 buffer:close() -- untitled 338 buffer:close() -- filename 339end 340 341function test_file_io_open_file_interactive() 342 local num_buffers = #_BUFFERS 343 io.open_file() 344 if #_BUFFERS > num_buffers then buffer:close() end 345end 346 347function test_file_io_open_file_errors() 348 if LINUX then 349 assert_raises(function() io.open_file('/etc/group-') end, 'cannot open /etc/group-: Permission denied') 350 end 351 -- TODO: find a case where the file can be opened, but not read 352end 353 354function test_file_io_reload_file() 355 io.open_file(_HOME .. '/test/file_io/utf8') 356 local pos = 10 357 buffer:goto_pos(pos) 358 local text = buffer:get_text() 359 buffer:append_text('foo') 360 assert(buffer:get_text() ~= text, 'buffer text is unchanged') 361 buffer:reload() 362 assert_equal(buffer:get_text(), text) 363 assert_equal(buffer.current_pos, pos) 364 buffer:close() 365end 366 367function test_file_io_set_encoding() 368 io.open_file(_HOME .. '/test/file_io/utf8') 369 local pos = 10 370 buffer:goto_pos(pos) 371 local text = buffer:get_text() 372 buffer:set_encoding('CP1252') 373 assert_equal(buffer.encoding, 'CP1252') 374 assert_equal(buffer.code_page, buffer.CP_UTF8) 375 assert_equal(buffer:get_text(), text) -- fundamentally the same 376 assert_equal(buffer.current_pos, pos) 377 buffer:reload() 378 buffer:close() 379 380 assert_raises(function() buffer:set_encoding(true) end, 'string/nil expected, got boolean') 381end 382 383function test_file_io_save_file() 384 buffer.new() 385 buffer._type = '[Foo Buffer]' 386 buffer:append_text('foo') 387 local filename = os.tmpname() 388 buffer:save_as(filename) 389 local f = assert(io.open(filename)) 390 local contents = f:read('a') 391 f:close() 392 assert_equal(buffer:get_text(), contents) 393 assert(not buffer._type, 'still has a type') 394 buffer:append_text('bar') 395 io.save_all_files() 396 f = assert(io.open(filename)) 397 contents = f:read('a') 398 f:close() 399 assert_equal(buffer:get_text(), contents) 400 buffer:close() 401 os.remove(filename) 402 403 assert_raises(function() buffer:save_as(1) end, 'string/nil expected, got number') 404end 405 406function test_file_io_non_global_buffer_functions() 407 local filename = os.tmpname() 408 local buf = buffer.new() 409 buf:append_text('foo') 410 view:goto_buffer(-1) 411 assert(buffer ~= buf, 'still in untitled buffer') 412 assert_equal(buf:get_text(), 'foo') 413 assert(buffer ~= buf, 'jumped to untitled buffer') 414 buf:save_as(filename) 415 assert(buffer ~= buf, 'jumped to untitled buffer') 416 view:goto_buffer(1) 417 assert(buffer == buf, 'not in saved buffer') 418 assert_equal(buffer.filename, filename) 419 assert(not buffer.modify, 'saved buffer still marked modified') 420 local f = io.open(filename, 'rb') 421 local contents = f:read('a') 422 f:close() 423 assert_equal(buffer:get_text(), contents) 424 buffer:append_text('bar') 425 view:goto_buffer(-1) 426 assert(buffer ~= buf, 'still in saved buffer') 427 buf:save() 428 assert(buffer ~= buf, 'jumped to untitled buffer') 429 f = io.open(filename, 'rb') 430 contents = f:read('a') 431 f:close() 432 assert_equal(buf:get_text(), contents) 433 buf:append_text('baz') 434 assert_equal(buf:get_text(), contents .. 'baz') 435 assert(buf.modify, 'buffer not marked modified') 436 buf:reload() 437 assert_equal(buf:get_text(), contents) 438 assert(not buf.modify, 'buffer still marked modified') 439 buf:append_text('baz') 440 buf:close(true) 441 assert(buffer ~= buf, 'closed the wrong buffer') 442 os.remove(filename) 443end 444 445function test_file_io_file_detect_modified() 446 local modified = false 447 local handler = function(filename) 448 assert_type(filename, 'string', 1) 449 modified = true 450 return false -- halt propagation 451 end 452 events.connect(events.FILE_CHANGED, handler, 1) 453 local filename = os.tmpname() 454 local f = assert(io.open(filename, 'w')) 455 f:write('foo\n'):flush() 456 io.open_file(filename) 457 assert_equal(buffer:get_text(), 'foo\n') 458 view:goto_buffer(-1) 459 os.execute('sleep 1') -- filesystem mod time has 1-second granularity 460 f:write('bar\n'):flush() 461 view:goto_buffer(1) 462 assert_equal(modified, true) 463 buffer:close() 464 f:close() 465 os.remove(filename) 466 events.disconnect(events.FILE_CHANGED, handler) 467end 468 469function test_file_io_file_detect_modified_interactive() 470 local filename = os.tmpname() 471 local f = assert(io.open(filename, 'w')) 472 f:write('foo\n'):flush() 473 io.open_file(filename) 474 assert_equal(buffer:get_text(), 'foo\n') 475 view:goto_buffer(-1) 476 os.execute('sleep 1') -- filesystem mod time has 1-second granularity 477 f:write('bar\n'):flush() 478 view:goto_buffer(1) 479 assert_equal(buffer:get_text(), 'foo\nbar\n') 480 buffer:close() 481 f:close() 482 os.remove(filename) 483end 484 485function test_file_io_recent_files() 486 io.recent_files = {} -- clear 487 local recent_files = {} 488 local files = { 489 _HOME .. '/test/file_io/utf8', 490 _HOME .. '/test/file_io/cp1252', 491 _HOME .. '/test/file_io/utf16', 492 _HOME .. '/test/file_io/binary' 493 } 494 for _, filename in ipairs(files) do 495 io.open_file(filename) 496 buffer:close() 497 table.insert(recent_files, 1, filename) 498 end 499 assert_equal(io.recent_files, recent_files) 500end 501 502function test_file_io_open_recent_interactive() 503 local filename = _HOME .. '/test/file_io/utf8' 504 io.open_file(filename) 505 buffer:close() 506 local tmpfile = os.tmpname() 507 io.open_file(tmpfile) 508 buffer:close() 509 os.remove(tmpfile) 510 io.open_recent_file() 511 assert_equal(buffer.filename, filename) 512 buffer:close() 513end 514 515function test_file_io_get_project_root() 516 local cwd = lfs.currentdir() 517 lfs.chdir(_HOME) 518 assert_equal(io.get_project_root(), _HOME) 519 lfs.chdir(cwd) 520 assert_equal(io.get_project_root(_HOME), _HOME) 521 assert_equal(io.get_project_root(_HOME .. '/core'), _HOME) 522 assert_equal(io.get_project_root(_HOME .. '/core/init.lua'), _HOME) 523 assert_equal(io.get_project_root('/tmp'), nil) 524 lfs.chdir(cwd) 525 526 -- Test git submodules. 527 local dir = os.tmpname() 528 os.remove(dir) 529 lfs.mkdir(dir) 530 lfs.mkdir(dir .. '/.git') 531 lfs.mkdir(dir .. '/foo') 532 io.open(dir .. '/foo/.git', 'w'):write():close() -- simulate submodule 533 assert_equal(io.get_project_root(dir .. '/foo/bar.txt'), dir) 534 io.open_file(dir .. '/foo/bar.txt') 535 assert_equal(io.get_project_root(true), dir .. '/foo') 536 buffer:close() 537 os.execute('rm -r ' .. dir) 538 539 assert_raises(function() io.get_project_root(1) end, 'string/nil expected, got number') 540end 541 542function test_file_io_quick_open_interactive() 543 local num_buffers = #_BUFFERS 544 local cwd = lfs.currentdir() 545 local dir = _HOME .. '/core' 546 lfs.chdir(dir) 547 io.quick_open_filters[dir] = '.lua' 548 io.quick_open(dir) 549 if #_BUFFERS > num_buffers then 550 assert(buffer.filename:find('%.lua$'), '.lua file filter did not work') 551 buffer:close() 552 end 553 io.quick_open_filters[dir] = true 554 assert_raises(function() io.quick_open(dir) end, 'string/table/nil expected, got boolean') 555 io.quick_open_filters[_HOME] = '.lua' 556 io.quick_open() 557 if #_BUFFERS > num_buffers then 558 assert(buffer.filename:find('%.lua$'), '.lua file filter did not work') 559 buffer:close() 560 end 561 local quick_open_max = io.quick_open_max 562 io.quick_open_max = 10 563 io.quick_open(_HOME) 564 assert(#_BUFFERS > num_buffers, 'File limit exceeded notification did not occur') 565 buffer:close() 566 io.quick_open_max = quick_open_max -- restore 567 lfs.chdir(cwd) 568 569 assert_raises(function() io.quick_open(1) end, 'string/table/nil expected, got number') 570 assert_raises(function() io.quick_open(_HOME, true) end, 'string/table/nil expected, got boolean') 571 assert_raises(function() io.quick_open(_HOME, nil, 1) end, 'table/nil expected, got number') 572end 573 574function test_keys_keychain() 575 local ctrl_a = keys['ctrl+a'] 576 local foo = false 577 keys['ctrl+a'] = {a = function() foo = true end} 578 events.emit(events.KEYPRESS, string.byte('a')) 579 assert(not foo, 'foo set outside keychain') 580 events.emit(events.KEYPRESS, string.byte('a'), false, true) 581 assert_equal(#keys.keychain, 1) 582 assert_equal(keys.keychain[1], 'ctrl+a') 583 events.emit(events.KEYPRESS, not CURSES and 0xFF1B or 7) -- esc 584 assert_equal(#keys.keychain, 0, 'keychain not canceled') 585 events.emit(events.KEYPRESS, string.byte('a')) 586 assert(not foo, 'foo set outside keychain') 587 events.emit(events.KEYPRESS, string.byte('a'), false, true) 588 events.emit(events.KEYPRESS, string.byte('a')) 589 assert(foo, 'foo not set') 590 keys['ctrl+a'] = ctrl_a -- restore 591end 592 593function test_keys_propagation() 594 buffer:new() 595 local foo, bar, baz = false, false, false 596 keys.a = function() foo = true end 597 keys.b = function() bar = true end 598 keys.c = function() baz = true end 599 keys.cpp.a = function() end -- halt 600 keys.cpp.b = function() return false end -- propagate 601 keys.cpp.c = function() 602 keys.mode = 'test_mode' 603 return false -- propagate 604 end 605 buffer:set_lexer('cpp') 606 events.emit(events.KEYPRESS, string.byte('a')) 607 assert(not foo, 'foo set') 608 events.emit(events.KEYPRESS, string.byte('b')) 609 assert(bar, 'bar set') 610 events.emit(events.KEYPRESS, string.byte('c')) 611 assert(not baz, 'baz set') -- mode changed, so cannot propagate to keys.c 612 assert_equal(keys.mode, 'test_mode') 613 keys.mode = nil 614 keys.a, keys.b, keys.c, keys.cpp.a, keys.cpp.b, keys.cpp.c = nil, nil, nil, nil, nil, nil -- reset 615 buffer:close() 616end 617 618function test_keys_modes() 619 buffer.new() 620 local foo, bar = false, false 621 keys.a = function() foo = true end 622 keys.test_mode = {a = function() 623 bar = true 624 keys.mode = nil 625 return false -- propagate 626 end} 627 keys.cpp.a = function() keys.mode = 'test_mode' end 628 events.emit(events.KEYPRESS, string.byte('a')) 629 assert(foo, 'foo not set') 630 assert(not keys.mode, 'key mode entered') 631 assert(not bar, 'bar set outside mode') 632 foo = false 633 buffer:set_lexer('cpp') 634 events.emit(events.KEYPRESS, string.byte('a')) 635 assert_equal(keys.mode, 'test_mode') 636 assert(not foo, 'foo set outside mode') 637 assert(not bar, 'bar set outside mode') 638 events.emit(events.KEYPRESS, string.byte('a')) 639 assert(bar, 'bar not set') 640 assert(not keys.mode, 'key mode still active') 641 assert(not foo, 'foo set') -- TODO: should this propagate? 642 keys.a, keys.test_mode, keys.cpp.a = nil, nil, nil -- reset 643 buffer:close() 644end 645 646function test_lfs_ext_walk() 647 local files, directories = 0, 0 648 for filename in lfs.walk(_HOME .. '/core', nil, nil, true) do 649 if not filename:find('/$') then 650 files = files + 1 651 else 652 directories = directories + 1 653 end 654 end 655 assert(files > 0, 'no files found') 656 assert(directories > 0, 'no directories found') 657 658 assert_raises(function() lfs.walk() end, 'string expected, got nil') 659 assert_raises(function() lfs.walk(_HOME, 1) end, 'string/table/nil expected, got number') 660 assert_raises(function() lfs.walk(_HOME, nil, true) end, 'number/nil expected, got boolean') 661end 662 663function test_lfs_ext_walk_filter_lua() 664 local count = 0 665 for filename in lfs.walk(_HOME .. '/core', '.lua') do 666 assert(filename:find('%.lua$'), '"%s" not a Lua file', filename) 667 count = count + 1 668 end 669 assert(count > 0, 'no Lua files found') 670end 671 672function test_lfs_ext_walk_filter_exclusive() 673 local count = 0 674 for filename in lfs.walk(_HOME .. '/core', '!.lua') do 675 assert(not filename:find('%.lua$'), '"%s" is a Lua file', filename) 676 count = count + 1 677 end 678 assert(count > 0, 'no non-Lua files found') 679end 680 681function test_lfs_ext_walk_filter_dir() 682 local count = 0 683 for filename in lfs.walk(_HOME, '/core') do 684 assert(filename:find('/core/'), '"%s" is not in core/', filename) 685 count = count + 1 686 end 687 assert(count > 0, 'no core files found') 688end 689expected_failure(test_lfs_ext_walk_filter_dir) 690 691function test_lfs_ext_walk_filter_mixed() 692 local count = 0 693 for filename in lfs.walk(_HOME .. '/core', {'!/locales', '.lua'}) do 694 assert(not filename:find('/locales/') and filename:find('%.lua$'), '"%s" should not match', filename) 695 count = count + 1 696 end 697 assert(count > 0, 'no matching files found') 698end 699 700function test_lfs_ext_walk_max_depth() 701 local count = 0 702 for filename in lfs.walk(_HOME, '.lua', 0) do count = count + 1 end 703 assert_equal(count, 1) -- init.lua 704end 705 706function test_lfs_ext_walk_halt() 707 local count, count_at_halt = 0, 0 708 for filename in lfs.walk(_HOME .. '/core') do 709 count = count + 1 710 if filename:find('/locales/.') then 711 count_at_halt = count 712 break 713 end 714 end 715 assert_equal(count, count_at_halt) 716 717 for filename in lfs.walk(_HOME .. '/core', nil, nil, true) do 718 count = count + 1 719 if filename:find('[/\\]$') then 720 count_at_halt = count 721 break 722 end 723 end 724 assert_equal(count, count_at_halt) 725end 726 727function test_lfs_ext_walk_win32() 728 local win32 = _G.WIN32 729 _G.WIN32 = true 730 local count = 0 731 for filename in lfs.walk(_HOME, {'/core'}) do 732 assert(not filename:find('/'), '"%s" has /', filename) 733 if filename:find('\\core') then count = count + 1 end 734 end 735 assert(count > 0, 'no core files found') 736 _G.WIN32 = win32 -- reset just in case 737end 738 739function test_lfs_ext_walk_symlinks() 740 local dir = os.tmpname() 741 os.remove(dir) 742 lfs.mkdir(dir) 743 lfs.mkdir(dir .. '/1') 744 io.open(dir .. '/1/foo', 'w'):close() 745 lfs.mkdir(dir .. '/1/bar') 746 io.open(dir .. '/1/bar/baz', 'w'):close() 747 lfs.link(dir .. '/1/', dir .. '/1/bar/quux', true) -- trailing '/' on purpose 748 lfs.mkdir(dir .. '/2') 749 io.open(dir .. '/2/foobar', 'w'):close() 750 lfs.link(dir .. '/2/foobar', dir .. '/2/foobaz', true) 751 lfs.link(dir .. '/2', dir .. '/1/2', true) 752 local files = {} 753 for filename in lfs.walk(dir .. '/1/') do -- trailing '/' on purpose 754 files[#files + 1] = filename 755 end 756 table.sort(files) 757 local expected_files = {dir .. '/1/foo', dir .. '/1/bar/baz', dir .. '/1/2/foobar', dir .. '/1/2/foobaz'} 758 table.sort(expected_files) 759 assert_equal(files, expected_files) 760 os.execute('rm -r ' .. dir) 761 762 lfs.mkdir(dir) 763 io.open(dir .. '/foo', 'w'):close() 764 local cwd = lfs.currentdir() 765 lfs.chdir(dir) 766 lfs.link('.', 'bar', true) 767 lfs.mkdir(dir .. '/baz') 768 lfs.mkdir(dir .. '/baz/quux') 769 lfs.chdir(dir .. '/baz/quux') 770 lfs.link('../../baz/', 'foobar', true) 771 lfs.chdir(cwd) 772 local count = 0 773 for filename in lfs.walk(dir) do count = count + 1 end 774 assert_equal(count, 1) 775 os.execute('rm -r ' .. dir) 776end 777 778function test_lfs_ext_walk_root() 779 local filename = lfs.walk('/', nil, 0, true)() 780 assert(not filename:find('lfs_ext.lua:'), 'coroutine error') 781end 782 783function test_lfs_ext_abs_path() 784 assert_equal(lfs.abspath('bar', '/foo'), '/foo/bar') 785 assert_equal(lfs.abspath('./bar', '/foo'), '/foo/bar') 786 assert_equal(lfs.abspath('../bar', '/foo'), '/bar') 787 assert_equal(lfs.abspath('/bar', '/foo'), '/bar') 788 assert_equal(lfs.abspath('../../././baz', '/foo/bar'), '/baz') 789 local win32 = _G.WIN32 790 _G.WIN32 = true 791 assert_equal(lfs.abspath('bar', 'C:\\foo'), 'C:\\foo\\bar') 792 assert_equal(lfs.abspath('.\\bar', 'C:\\foo'), 'C:\\foo\\bar') 793 assert_equal(lfs.abspath('..\\bar', 'C:\\foo'), 'C:\\bar') 794 assert_equal(lfs.abspath('C:\\bar', 'C:\\foo'), 'C:\\bar') 795 assert_equal(lfs.abspath('c:\\bar', 'c:\\foo'), 'C:\\bar') 796 assert_equal(lfs.abspath('..\\../.\\./baz', 'C:\\foo\\bar'), 'C:\\baz') 797 _G.WIN32 = win32 -- reset just in case 798 799 assert_raises(function() lfs.abspath() end, 'string expected, got nil') 800 assert_raises(function() lfs.abspath('foo', 1) end, 'string/nil expected, got number') 801end 802 803function test_ui_print() 804 local tabs = ui.tabs 805 local silent_print = ui.silent_print 806 807 ui.tabs = true 808 ui.silent_print = false 809 ui.print('foo') 810 assert_equal(buffer._type, _L['[Message Buffer]']) 811 assert_equal(#_VIEWS, 1) 812 assert_equal(buffer:get_text(), 'foo\n') 813 assert(buffer:line_from_position(buffer.current_pos) > 1, 'still on first line') 814 ui.print('bar', 'baz') 815 assert_equal(buffer:get_text(), 'foo\nbar\tbaz\n') 816 buffer:close() 817 818 ui.tabs = false 819 ui.print(1, 2, 3) 820 assert_equal(buffer._type, _L['[Message Buffer]']) 821 assert_equal(#_VIEWS, 2) 822 assert_equal(buffer:get_text(), '1\t2\t3\n') 823 ui.goto_view(-1) -- first view 824 assert(buffer._type ~= _L['[Message Buffer]'], 'still in message buffer') 825 ui.print(4, 5, 6) -- should jump to second view 826 assert_equal(buffer._type, _L['[Message Buffer]']) 827 assert_equal(buffer:get_text(), '1\t2\t3\n4\t5\t6\n') 828 ui.goto_view(-1) -- first view 829 assert(buffer._type ~= _L['[Message Buffer]'], 'still in message buffer') 830 ui.silent_print = true 831 ui.print(7, 8, 9) -- should stay in first view 832 assert(buffer._type ~= _L['[Message Buffer]'], 'switched to message buffer') 833 assert_equal(_BUFFERS[#_BUFFERS]:get_text(), '1\t2\t3\n4\t5\t6\n7\t8\t9\n') 834 ui.silent_print = false 835 ui.goto_view(1) -- second view 836 assert_equal(buffer._type, _L['[Message Buffer]']) 837 view:goto_buffer(-1) 838 assert(buffer._type ~= _L['[Message Buffer]'], 'message buffer still visible') 839 ui.print() 840 assert_equal(buffer._type, _L['[Message Buffer]']) 841 assert_equal(buffer:get_text(), '1\t2\t3\n4\t5\t6\n7\t8\t9\n\n') 842 view:unsplit() 843 844 buffer:close() 845 ui.tabs = tabs 846 ui.silent_print = silent_print 847end 848 849function test_ui_print_to_other_view() 850 local silent_print = ui.silent_print 851 852 ui.silent_print = false 853 view:split() 854 ui.goto_view(-1) 855 assert_equal(_VIEWS[view], 1) 856 ui.print('foo') -- should print to other view, not split again 857 assert_equal(#_VIEWS, 2) 858 assert_equal(_VIEWS[view], 2) 859 buffer:close() 860 ui.goto_view(-1) 861 view:unsplit() 862 863 ui.silent_print = silent_print 864end 865 866function test_ui_dialogs_colorselect_interactive() 867 local color = ui.dialogs.colorselect{title = 'Blue', color = 0xFF0000} 868 assert_equal(color, 0xFF0000) 869 color = ui.dialogs.colorselect{ 870 title = 'Red', color = '#FF0000', palette = {'#FF0000', 0x00FF00}, 871 string_output = true 872 } 873 assert_equal(color, '#FF0000') 874 875 assert_raises(function() ui.dialogs.colorselect{title = function() end} end, "bad argument #title to 'colorselect' (string/number/table/boolean expected, got function") 876 assert_raises(function() ui.dialogs.colorselect{palette = {true}} end, "bad argument #palette[1] to 'colorselect' (string/number expected, got boolean") 877end 878 879function test_ui_dialogs_dropdown_interactive() 880 local dropdowns = {'dropdown', 'standard_dropdown'} 881 for _, dropdown in ipairs(dropdowns) do 882 print('Running ' .. dropdown) 883 local button, i = ui.dialogs[dropdown]{items = {'foo', 'bar', 'baz'}} 884 assert_equal(type(button), 'number') 885 assert_equal(i, 1) 886 button, i = ui.dialogs[dropdown]{ 887 text = 'foo', items = {'bar', 'baz', 'quux'}, select = 2, 888 no_cancel = true, width = 400, height = 400 889 } 890 assert_equal(i, 2) 891 end 892 893 assert_raises(function() ui.dialogs.dropdown{items = {'foo', 'bar', 'baz'}, select = true} end, "bad argument #select to 'dropdown' (number expected, got boolean") 894 assert_raises(function() ui.dialogs.dropdown{items = {'foo', 'bar', 'baz', true}} end, "bad argument #items[4] to 'dropdown' (string/number expected, got boolean") 895end 896 897function test_ui_dialogs_filesave_fileselect_interactive() 898 local test_filename = _HOME .. '/test/ui/empty' 899 local test_dir, test_file = test_filename:match('^(.+[/\\])([^/\\]+)$') 900 local filename = ui.dialogs.filesave{ 901 with_directory = test_dir, with_file = test_file, 902 no_create_directories = true 903 } 904 assert_equal(filename, test_filename) 905 filename = ui.dialogs.fileselect{ 906 with_directory = test_dir, with_file = test_file, select_multiple = true 907 } 908 assert_equal(filename, {test_filename}) 909 filename = ui.dialogs.fileselect{ 910 with_directory = test_dir, select_only_directories = true 911 } 912 assert_equal(filename, test_dir:match('^(.+)/$')) 913end 914 915function test_ui_dialogs_filteredlist_interactive() 916 local _, i = ui.dialogs.filteredlist{ 917 informative_text = 'foo', columns = '1', items = {'bar', 'baz', 'quux'}, 918 text = 'b z' 919 } 920 assert_equal(i, 2) 921 local _, text = ui.dialogs.filteredlist{ 922 columns = {'1', '2'}, 923 items = {'foo', 'foobar', 'bar', 'barbaz', 'baz', 'bazfoo'}, 924 search_column = 2, text = 'baz', output_column = 2, string_output = true, 925 select_multiple = true, button1 = _L['OK'], button2 = _L['Cancel'], 926 button3 = 'Other', width = ui.size[1] / 2 927 } 928 assert_equal(text, {'barbaz'}) 929end 930 931function test_ui_dialogs_fontselect_interactive() 932 local font = ui.dialogs.fontselect{ 933 font_name = 'Monospace', font_size = 14, font_style = 'Bold' 934 } 935 assert_equal(font, 'Monospace Bold 14') 936end 937 938function test_ui_dialogs_inputbox_interactive() 939 local inputboxes = { 940 'inputbox', 'secure_inputbox', 'standard_inputbox', 941 'secure_standard_inputbox' 942 } 943 for _, inputbox in ipairs(inputboxes) do 944 print('Running ' .. inputbox) 945 local button, text = ui.dialogs[inputbox]{text = 'foo'} 946 assert_equal(type(button), 'number') 947 assert_equal(text, 'foo') 948 button, text = ui.dialogs[inputbox]{ 949 text = 'foo', string_output = true, no_cancel = true 950 } 951 assert_equal(type(button), 'string') 952 assert_equal(text, 'foo') 953 end 954 955 local button, text = ui.dialogs.inputbox{ 956 informative_text = {'info', 'foo', 'baz'}, text = {'bar', 'quux'} 957 } 958 assert_equal(type(button), 'number') 959 assert_equal(text, {'bar', 'quux'}) 960 button = ui.dialogs.inputbox{ 961 informative_text = {'info', 'foo', 'baz'}, text = {'bar', 'quux'}, 962 string_output = true 963 } 964 assert_equal(type(button), 'string') 965end 966 967function test_ui_dialogs_msgbox_interactive() 968 local msgboxes = {'msgbox', 'ok_msgbox', 'yesno_msgbox'} 969 local icons = {'gtk-dialog-info', 'gtk-dialog-warning', 'gtk-dialog-question'} 970 for i, msgbox in ipairs(msgboxes) do 971 print('Running ' .. msgbox) 972 local button = ui.dialogs[msgbox]{icon = icons[i]} 973 assert_equal(type(button), 'number') 974 button = ui.dialogs[msgbox]{ 975 icon_file = _HOME .. '/core/images/ta_32x32.png', string_output = true, 976 no_cancel = true 977 } 978 assert_equal(type(button), 'string') 979 end 980end 981 982function test_ui_dialogs_optionselect_interactive() 983 local _, selected = ui.dialogs.optionselect{items = 'foo', select = 1} 984 assert_equal(selected, {1}) 985 _, selected = ui.dialogs.optionselect{ 986 items = {'foo', 'bar', 'baz'}, select = {1, 3}, string_output = true 987 } 988 assert_equal(selected, {'foo', 'baz'}) 989 990 assert_raises(function() ui.dialogs.optionselect{items = {'foo', 'bar', 'baz'}, select = {1, 'bar'}} end, "bad argument #select[2] to 'optionselect' (number expected, got string") 991end 992 993function test_ui_dialogs_progressbar_interactive() 994 local i = 0 995 ui.dialogs.progressbar({title = 'foo'}, function() 996 os.execute('sleep 0.1') 997 i = i + 10 998 if i > 100 then return nil end 999 return i, i .. '%' 1000 end) 1001 1002 local stopped = ui.dialogs.progressbar({ 1003 title = 'foo', indeterminite = true, stoppable = true 1004 }, function() 1005 os.execute('sleep 0.1') 1006 return 50 1007 end) 1008 assert(stopped, 'progressbar not stopped') 1009 1010 ui.update() -- allow GTK to remove callback for previous function 1011 i = 0 1012 ui.dialogs.progressbar({title = 'foo', stoppable = true}, function() 1013 os.execute('sleep 0.1') 1014 i = i + 10 1015 if i > 100 then return nil end 1016 return i, i <= 50 and "stop disable" or "stop enable" 1017 end) 1018 1019 local errmsg 1020 local handler = function(message) 1021 errmsg = message 1022 return false -- halt propagation 1023 end 1024 events.connect(events.ERROR, handler, 1) 1025 ui.dialogs.progressbar({}, function() error('foo') end) 1026 assert(errmsg:find('foo'), 'error handler did not run') 1027 ui.dialogs.progressbar({}, function() return true end) 1028 assert(errmsg:find('invalid return values'), 'error handler did not run') 1029 events.disconnect(events.ERROR, handler) 1030end 1031 1032function test_ui_dialogs_textbox_interactive() 1033 ui.dialogs.textbox{ 1034 text = 'foo', editable = true, selected = true, monospaced_font = true 1035 } 1036 ui.dialogs.textbox{text_from_file = _HOME .. '/LICENSE', scroll_to = 'bottom'} 1037end 1038 1039function test_ui_switch_buffer_interactive() 1040 buffer.new() 1041 buffer:append_text('foo') 1042 buffer.new() 1043 buffer:append_text('bar') 1044 buffer:new() 1045 buffer:append_text('baz') 1046 ui.switch_buffer() -- back to [Test Output] 1047 local text = buffer:get_text() 1048 assert(text ~= 'foo' and text ~= 'bar' and text ~= 'baz') 1049 for i = 1, 3 do view:goto_buffer(1) end -- cycle back to baz 1050 ui.switch_buffer(true) 1051 assert_equal(buffer:get_text(), 'bar') 1052 for i = 1, 3 do buffer:close(true) end 1053end 1054 1055function test_ui_goto_file() 1056 local dir1_file1 = _HOME .. '/core/ui/dir1/file1' 1057 local dir1_file2 = _HOME .. '/core/ui/dir1/file2' 1058 local dir2_file1 = _HOME .. '/core/ui/dir2/file1' 1059 local dir2_file2 = _HOME .. '/core/ui/dir2/file2' 1060 ui.goto_file(dir1_file1) -- current view 1061 assert_equal(#_VIEWS, 1) 1062 assert_equal(buffer.filename, dir1_file1) 1063 ui.goto_file(dir1_file2, true) -- split view 1064 assert_equal(#_VIEWS, 2) 1065 assert_equal(buffer.filename, dir1_file2) 1066 assert_equal(_VIEWS[1].buffer.filename, dir1_file1) 1067 ui.goto_file(dir1_file1) -- should go back to first view 1068 assert_equal(buffer.filename, dir1_file1) 1069 assert_equal(_VIEWS[2].buffer.filename, dir1_file2) 1070 ui.goto_file(dir2_file2, true, nil, true) -- should sloppily go back to second view 1071 assert_equal(buffer.filename, dir1_file2) -- sloppy 1072 assert_equal(_VIEWS[1].buffer.filename, dir1_file1) 1073 ui.goto_file(dir2_file1) -- should go back to first view 1074 assert_equal(buffer.filename, dir2_file1) 1075 assert_equal(_VIEWS[2].buffer.filename, dir1_file2) 1076 ui.goto_file(dir2_file2, false, _VIEWS[1]) -- should go to second view 1077 assert_equal(#_VIEWS, 2) 1078 assert_equal(buffer.filename, dir2_file2) 1079 assert_equal(_VIEWS[1].buffer.filename, dir2_file1) 1080 view:unsplit() 1081 assert_equal(#_VIEWS, 1) 1082 for i = 1, 4 do buffer:close() end 1083end 1084 1085function test_ui_uri_drop() 1086 local filename = _HOME .. '/test/ui/uri drop' 1087 local uri = 'file://' .. _HOME .. '/test/ui/uri%20drop' 1088 events.emit(events.URI_DROPPED, uri) 1089 assert_equal(buffer.filename, filename) 1090 buffer:close() 1091 local buffer = buffer 1092 events.emit(events.URI_DROPPED, 'file://' .. _HOME) 1093 assert_equal(buffer, _G.buffer) -- do not open directory 1094 1095 -- TODO: WIN32 1096 -- TODO: OSX 1097end 1098 1099function test_ui_buffer_switch_save_restore_properties() 1100 local filename = _HOME .. '/test/ui/test.lua' 1101 io.open_file(filename) 1102 buffer:goto_pos(10) 1103 view:fold_line( 1104 buffer:line_from_position(buffer.current_pos), view.FOLDACTION_CONTRACT) 1105 view.margin_width_n[1] = 0 -- hide line numbers 1106 view:goto_buffer(-1) 1107 assert(view.margin_width_n[1] > 0, 'line numbers are still hidden') 1108 view:goto_buffer(1) 1109 assert_equal(buffer.current_pos, 10) 1110 assert_equal(view.fold_expanded[buffer:line_from_position(buffer.current_pos)], false) 1111 assert_equal(view.margin_width_n[1], 0) 1112 buffer:close() 1113end 1114 1115if CURSES then 1116 -- TODO: clipboard, mouse events, etc. 1117end 1118 1119function test_spawn_cwd() 1120 assert_equal(os.spawn('pwd'):read('a'), lfs.currentdir() .. '\n') 1121 assert_equal(os.spawn('pwd', '/tmp'):read('a'), '/tmp\n') 1122end 1123 1124function test_spawn_env() 1125 assert(not os.spawn('env'):read('a'):find('^%s*$'), 'empty env') 1126 assert(os.spawn('env', {FOO = 'bar'}):read('a'):find('FOO=bar\n'), 'env not set') 1127 local output = os.spawn('env', {FOO = 'bar', 'BAR=baz', [true] = 'false'}):read('a') 1128 assert(output:find('FOO=bar\n'), 'env not set properly') 1129 assert(output:find('BAR=baz\n'), 'env not set properly') 1130 assert(not output:find('true=false\n'), 'env not set properly') 1131end 1132 1133function test_spawn_stdin() 1134 local p = os.spawn('lua -e "print(io.read())"') 1135 p:write('foo\n') 1136 p:close() 1137 assert_equal(p:read('l'), 'foo') 1138 assert_equal(p:read('a'), '') 1139end 1140 1141function test_spawn_callbacks() 1142 local exit_status = -1 1143 local p = os.spawn('echo foo', ui.print, nil, function(status) exit_status = status end) 1144 os.execute('sleep 0.1') 1145 ui.update() 1146 assert_equal(buffer._type, _L['[Message Buffer]']) 1147 assert(buffer:get_text():find('^foo'), 'no spawn stdout') 1148 assert_equal(exit_status, 0) 1149 buffer:close(true) 1150 view:unsplit() 1151 -- Verify stdout is not read as stderr. 1152 p = os.spawn('echo foo', nil, ui.print) 1153 os.execute('sleep 0.1') 1154 ui.update() 1155 assert_equal(#_BUFFERS, 1) 1156end 1157 1158function test_spawn_wait() 1159 local exit_status = -1 1160 local p = os.spawn('sleep 0.1', nil, nil, function(status) exit_status = status end) 1161 assert_equal(p:status(), "running") 1162 assert_equal(p:wait(), 0) 1163 assert_equal(exit_status, 0) 1164 assert_equal(p:status(), "terminated") 1165 -- Verify call to wait again returns previous exit status. 1166 assert_equal(p:wait(), exit_status) 1167end 1168 1169function test_spawn_kill() 1170 local p = os.spawn('sleep 1') 1171 p:kill() 1172 assert(p:wait() ~= 0) 1173 assert_equal(p:status(), "terminated") 1174end 1175 1176if WIN32 and CURSES then 1177 function test_spawn() 1178 -- TODO: 1179 end 1180end 1181 1182function test_buffer_text_range() 1183 buffer.new() 1184 buffer:set_text('foo\nbar\nbaz') 1185 buffer:set_target_range(5, 8) 1186 assert_equal(buffer.target_text, 'bar') 1187 assert_equal(buffer:text_range(1, buffer.length + 1), 'foo\nbar\nbaz') 1188 assert_equal(buffer:text_range(-1, 4), 'foo') 1189 assert_equal(buffer:text_range(9, 16), 'baz') 1190 assert_equal(buffer.target_text, 'bar') -- assert target range is unchanged 1191 buffer:close(true) 1192 1193 assert_raises(function() buffer:text_range() end, 'number expected, got nil') 1194 assert_raises(function() buffer:text_range(5) end, 'number expected, got nil') 1195end 1196 1197function test_bookmarks() 1198 local function has_bookmark(line) 1199 return buffer:marker_get(line) & 1 << textadept.bookmarks.MARK_BOOKMARK - 1 > 0 1200 end 1201 1202 buffer.new() 1203 buffer:new_line() 1204 assert(buffer:line_from_position(buffer.current_pos) > 1, 'still on first line') 1205 textadept.bookmarks.toggle() 1206 assert(has_bookmark(2), 'no bookmark') 1207 textadept.bookmarks.toggle() 1208 assert(not has_bookmark(2), 'bookmark still there') 1209 1210 buffer:goto_pos(buffer:position_from_line(1)) 1211 textadept.bookmarks.toggle() 1212 buffer:goto_pos(buffer:position_from_line(2)) 1213 textadept.bookmarks.toggle() 1214 textadept.bookmarks.goto_mark(true) 1215 assert_equal(buffer:line_from_position(buffer.current_pos), 1) 1216 textadept.bookmarks.goto_mark(true) 1217 assert_equal(buffer:line_from_position(buffer.current_pos), 2) 1218 textadept.bookmarks.goto_mark(false) 1219 assert_equal(buffer:line_from_position(buffer.current_pos), 1) 1220 textadept.bookmarks.goto_mark(false) 1221 assert_equal(buffer:line_from_position(buffer.current_pos), 2) 1222 textadept.bookmarks.clear() 1223 assert(not has_bookmark(1), 'bookmark still there') 1224 assert(not has_bookmark(2), 'bookmark still there') 1225 buffer:close(true) 1226end 1227 1228function test_bookmarks_interactive() 1229 buffer.new() 1230 buffer:new_line() 1231 textadept.bookmarks.toggle() 1232 buffer:line_up() 1233 assert_equal(buffer:line_from_position(buffer.current_pos), 1) 1234 textadept.bookmarks.goto_mark() 1235 assert_equal(buffer:line_from_position(buffer.current_pos), 2) 1236 buffer:close(true) 1237end 1238 1239function test_bookmarks_reload() 1240 local function has_bookmark(line) 1241 return buffer:marker_get(line) & 1 << textadept.bookmarks.MARK_BOOKMARK - 1 > 0 1242 end 1243 1244 io.open_file(_HOME .. '/test/modules/textadept/bookmarks/foo') 1245 buffer:line_down() 1246 textadept.bookmarks.toggle() 1247 buffer:line_down() 1248 buffer:line_down() 1249 textadept.bookmarks.toggle() 1250 assert(has_bookmark(2), 'line not bookmarked') 1251 assert(has_bookmark(4), 'line not bookmarked') 1252 buffer:reload() 1253 assert(has_bookmark(2), 'bookmark not restored') 1254 assert(has_bookmark(4), 'bookmark not restored') 1255 buffer:close(true) 1256end 1257 1258function test_command_entry_run() 1259 local command_run, tab_pressed = false, false 1260 ui.command_entry.run(function(command) command_run = command end, { 1261 ['\t'] = function() tab_pressed = true end 1262 }, nil, 2) 1263 ui.update() -- redraw command entry 1264 assert_equal(ui.command_entry.active, true) 1265 assert_equal(ui.command_entry:get_lexer(), 'text') 1266 assert(ui.command_entry.height > ui.command_entry:text_height(0), 'height < 2 lines') 1267 ui.command_entry:set_text('foo') 1268 events.emit(events.KEYPRESS, string.byte('\t')) 1269 events.emit(events.KEYPRESS, not CURSES and 0xFF0D or 343) -- \n 1270 assert_equal(command_run, 'foo') 1271 assert(tab_pressed, '\\t not registered') 1272 assert_equal(ui.command_entry.active, false) 1273 1274 assert_raises(function() ui.command_entry.run(function() end, 1) end, 'table/string/nil expected, got number') 1275 assert_raises(function() ui.command_entry.run(function() end, {}, 1) end, 'string/nil expected, got number') 1276 assert_raises(function() ui.command_entry.run(function() end, {}, 'lua', true) end, 'number/nil expected, got boolean') 1277 assert_raises(function() ui.command_entry.run(function() end, 'lua', true) end, 'number/nil expected, got boolean') 1278end 1279 1280local function run_lua_command(command) 1281 ui.command_entry.run() 1282 ui.command_entry:set_text(command) 1283 assert_equal(ui.command_entry:get_lexer(), 'lua') 1284 events.emit(events.KEYPRESS, not CURSES and 0xFF0D or 343) -- \n 1285end 1286 1287function test_command_entry_run_lua() 1288 run_lua_command('print(_HOME)') 1289 assert_equal(buffer._type, _L['[Message Buffer]']) 1290 assert_equal(buffer:get_text(), _HOME .. '\n') 1291 run_lua_command('{key="value"}') 1292 assert(buffer:get_text():find('{key = value}'), 'table not pretty-printed') 1293 -- TODO: multi-line table pretty print. 1294 if #_VIEWS > 1 then view:unsplit() end 1295 buffer:close() 1296end 1297 1298function test_command_entry_run_lua_abbreviated_env() 1299 -- buffer get/set. 1300 run_lua_command('length') 1301 assert(buffer:get_text():find('%d+%s*$'), 'buffer.length result not a number') 1302 run_lua_command('auto_c_active') 1303 assert(buffer:get_text():find('false%s*$'), 'buffer:auto_c_active() result not false') 1304 run_lua_command('view_eol=true') 1305 assert_equal(view.view_eol, true) 1306 -- view get/set. 1307 if #_VIEWS > 1 then view:unsplit() end 1308 run_lua_command('split') 1309 assert_equal(#_VIEWS, 2) 1310 run_lua_command('size=1') 1311 assert_equal(view.size, 1) 1312 run_lua_command('unsplit') 1313 assert_equal(#_VIEWS, 1) 1314 -- ui get/set. 1315 run_lua_command('dialogs') 1316 assert(buffer:get_text():find('%b{}%s*$'), 'ui.dialogs result not a table') 1317 run_lua_command('statusbar_text="foo"') 1318 -- _G get/set. 1319 run_lua_command('foo="bar"') 1320 run_lua_command('foo') 1321 assert(buffer:get_text():find('bar%s*$'), 'foo result not "bar"') 1322 -- textadept get/set. 1323 run_lua_command('editing') 1324 assert(buffer:get_text():find('%b{}%s*$'), 'textadept.editing result not a table') 1325 run_lua_command('editing.select_paragraph') 1326 assert(buffer.selection_start ~= buffer.selection_end, 'textadept.editing.select_paragraph() did not select paragraph') 1327 buffer:close() 1328end 1329 1330local function assert_lua_autocompletion(text, first_item) 1331 ui.command_entry:set_text(text) 1332 ui.command_entry:goto_pos(ui.command_entry.length + 1) 1333 events.emit(events.KEYPRESS, string.byte('\t')) 1334 assert_equal(ui.command_entry:auto_c_active(), true) 1335 assert_equal(ui.command_entry.auto_c_current_text, first_item) 1336 events.emit(events.KEYPRESS, not CURSES and 0xFF54 or 300) -- down 1337 events.emit(events.KEYPRESS, not CURSES and 0xFF52 or 301) -- up 1338 assert_equal(ui.command_entry:get_text(), text) -- no history cycling 1339 assert_equal(ui.command_entry:auto_c_active(), true) 1340 assert_equal(ui.command_entry.auto_c_current_text, first_item) 1341 ui.command_entry:auto_c_cancel() 1342end 1343 1344function test_command_entry_complete_lua() 1345 ui.command_entry.run() 1346 assert_lua_autocompletion('string.', 'byte') 1347 assert_lua_autocompletion('auto', 'auto_c_active') 1348 assert_lua_autocompletion('MARK', 'MARKER_MAX') 1349 assert_lua_autocompletion('buffer.auto', 'auto_c_auto_hide') 1350 assert_lua_autocompletion('buffer:auto', 'auto_c_active') 1351 assert_lua_autocompletion('caret', 'caret_fore') 1352 assert_lua_autocompletion('ANNO', 'ANNOTATION_BOXED') 1353 assert_lua_autocompletion('view.margin', 'margin_back_n') 1354 assert_lua_autocompletion('view:call', 'call_tip_active') 1355 assert_lua_autocompletion('goto', 'goto_buffer') 1356 assert_lua_autocompletion('_', '_BUFFERS') 1357 assert_lua_autocompletion('fi', 'file_types') 1358 -- TODO: textadept.editing.show_documentation key binding. 1359 ui.command_entry:focus() -- hide 1360end 1361 1362function test_command_entry_history() 1363 local one, two = function() end, function() end 1364 1365 ui.command_entry.run(one) 1366 events.emit(events.KEYPRESS, not CURSES and 0xFF52 or 301) -- up 1367 assert_equal(ui.command_entry:get_text(), '') -- no prior history 1368 events.emit(events.KEYPRESS, not CURSES and 0xFF54 or 300) -- down 1369 assert_equal(ui.command_entry:get_text(), '') -- no further history 1370 ui.command_entry:add_text('foo') 1371 events.emit(events.KEYPRESS, not CURSES and 0xFF0D or 343) -- \n 1372 1373 ui.command_entry.run(two) 1374 events.emit(events.KEYPRESS, not CURSES and 0xFF52 or 301) -- up 1375 assert_equal(ui.command_entry:get_text(), '') -- no prior history 1376 events.emit(events.KEYPRESS, not CURSES and 0xFF54 or 300) -- down 1377 assert_equal(ui.command_entry:get_text(), '') -- no further history 1378 ui.command_entry:add_text('bar') 1379 events.emit(events.KEYPRESS, not CURSES and 0xFF0D or 343) -- \n 1380 1381 ui.command_entry.run(one) 1382 assert_equal(ui.command_entry:get_text(), 'foo') 1383 assert_equal(ui.command_entry.selection_start, 1) 1384 assert_equal(ui.command_entry.selection_end, 4) 1385 events.emit(events.KEYPRESS, not CURSES and 0xFF52 or 301) -- up 1386 assert_equal(ui.command_entry:get_text(), 'foo') -- no prior history 1387 events.emit(events.KEYPRESS, not CURSES and 0xFF54 or 300) -- down 1388 assert_equal(ui.command_entry:get_text(), 'foo') -- no further history 1389 ui.command_entry:set_text('baz') 1390 events.emit(events.KEYPRESS, not CURSES and 0xFF0D or 343) -- \n 1391 1392 ui.command_entry.run(one) 1393 events.emit(events.KEYPRESS, not CURSES and 0xFF52 or 301) -- up 1394 assert_equal(ui.command_entry:get_text(), 'foo') 1395 events.emit(events.KEYPRESS, not CURSES and 0xFF54 or 300) -- down 1396 assert_equal(ui.command_entry:get_text(), 'baz') 1397 events.emit(events.KEYPRESS, not CURSES and 0xFF52 or 301) -- up, 'foo' 1398 events.emit(events.KEYPRESS, not CURSES and 0xFF0D or 343) -- \n 1399 1400 ui.command_entry.run(one) 1401 events.emit(events.KEYPRESS, not CURSES and 0xFF52 or 301) -- up 1402 assert_equal(ui.command_entry:get_text(), 'baz') 1403 events.emit(events.KEYPRESS, not CURSES and 0xFF52 or 301) -- up 1404 assert_equal(ui.command_entry:get_text(), 'foo') 1405 events.emit(events.KEYPRESS, not CURSES and 0xFF1B or 7) -- esc 1406 1407 ui.command_entry.run(two) 1408 assert_equal(ui.command_entry:get_text(), 'bar') 1409 events.emit(events.KEYPRESS, not CURSES and 0xFF1B or 7) -- esc 1410end 1411 1412function test_command_entry_history_append() 1413 local f, keys = function() end, {['\n'] = ui.command_entry.focus} 1414 1415 ui.command_entry.run(f, keys) 1416 ui.command_entry:set_text('foo') 1417 events.emit(events.KEYPRESS, not CURSES and 0xFF0D or 343) -- \n 1418 1419 ui.command_entry.run(f, keys) 1420 events.emit(events.KEYPRESS, not CURSES and 0xFF52 or 301) -- up 1421 assert_equal(ui.command_entry:get_text(), '') -- no prior history 1422 events.emit(events.KEYPRESS, not CURSES and 0xFF54 or 300) -- down 1423 assert_equal(ui.command_entry:get_text(), '') -- no further history 1424 events.emit(events.KEYPRESS, not CURSES and 0xFF0D or 343) -- \n 1425 ui.command_entry.append_history('bar') 1426 1427 ui.command_entry.run(f, keys) 1428 assert_equal(ui.command_entry:get_text(), 'bar') 1429 assert_equal(ui.command_entry.selection_start, 1) 1430 assert_equal(ui.command_entry.selection_end, 4) 1431 events.emit(events.KEYPRESS, not CURSES and 0xFF52 or 301) -- up 1432 assert_equal(ui.command_entry:get_text(), 'bar') -- no prior history 1433 events.emit(events.KEYPRESS, not CURSES and 0xFF54 or 300) -- down 1434 assert_equal(ui.command_entry:get_text(), 'bar') -- no further history 1435 events.emit(events.KEYPRESS, not CURSES and 0xFF0D or 343) -- \n 1436 1437 -- Verify no previous mode or history is needed for adding history. 1438 local f2 = function() end 1439 ui.command_entry.append_history(f2, 'baz') 1440 ui.command_entry.run(f2, keys) 1441 assert_equal(ui.command_entry:get_text(), 'baz') 1442 events.emit(events.KEYPRESS, not CURSES and 0xFF0D or 343) -- \n 1443 1444 assert_raises(function() ui.command_entry.append_history(1) end, 'string expected, got number') 1445 assert_raises(function() ui.command_entry:append_history('text') end, 'function expected, got table') 1446 assert_raises(function() ui.command_entry.append_history(function() end, true) end, 'string/nil expected, got boolean') 1447end 1448 1449function test_command_entry_mode_restore() 1450 local mode = 'test_mode' 1451 keys.mode = mode 1452 ui.command_entry.run(nil) 1453 assert(keys.mode ~= mode) 1454 events.emit(events.KEYPRESS, not CURSES and 0xFF0D or 343) -- \n 1455 assert_equal(keys.mode, mode) 1456 keys.mode = nil 1457end 1458 1459function test_command_entry_text_changed_event() 1460 local changed = false 1461 ui.command_entry.run() 1462 events.connect(events.COMMAND_TEXT_CHANGED, function() changed = true end) 1463 ui.command_entry:set_text('foo') 1464 assert(changed, 'changed event not emitted') 1465 changed = false 1466 ui.command_entry:undo() 1467 assert(changed, 'changed event not emitted') 1468 ui.command_entry:focus() -- hide 1469end 1470 1471function test_editing_auto_pair() 1472 buffer.new() 1473 -- Single selection. 1474 buffer:add_text('foo(') 1475 events.emit(events.CHAR_ADDED, string.byte('(')) 1476 assert_equal(buffer:get_text(), 'foo()') 1477 events.emit(events.KEYPRESS, string.byte(')')) 1478 assert_equal(buffer.current_pos, buffer.line_end_position[1]) 1479 buffer:char_left() 1480 -- Note: cannot check for brace highlighting; indicator search does not work. 1481 events.emit(events.KEYPRESS, not CURSES and 0xFF08 or 263) -- \b 1482 assert_equal(buffer:get_text(), 'foo') 1483 -- Multi-selection. 1484 buffer:set_text('foo(\nfoo(') 1485 local pos1 = buffer.line_end_position[1] 1486 local pos2 = buffer.line_end_position[2] 1487 buffer:set_selection(pos1, pos1) 1488 buffer:add_selection(pos2, pos2) 1489 events.emit(events.CHAR_ADDED, string.byte('(')) 1490 assert_equal(buffer:get_text(), 'foo()\nfoo()') 1491 assert_equal(buffer.selections, 2) 1492 assert_equal(buffer.selection_n_start[1], buffer.selection_n_end[1]) 1493 assert_equal(buffer.selection_n_start[1], pos1) 1494 assert_equal(buffer.selection_n_start[2], buffer.selection_n_end[2]) 1495 assert_equal(buffer.selection_n_start[2], pos2 + 1) 1496 -- TODO: typeover. 1497 events.emit(events.KEYPRESS, not CURSES and 0xFF08 or 263) -- \b 1498 assert_equal(buffer:get_text(), 'foo\nfoo') 1499 -- Verify atomic undo for multi-select. 1500 buffer:undo() -- simulated backspace 1501 buffer:undo() -- normal undo that a user would perform 1502 assert_equal(buffer:get_text(), 'foo()\nfoo()') 1503 buffer:undo() 1504 assert_equal(buffer:get_text(), 'foo(\nfoo(') 1505 buffer:close(true) 1506end 1507 1508function test_editing_auto_indent() 1509 buffer.new() 1510 buffer:add_text('foo') 1511 buffer:new_line() 1512 assert_equal(buffer.line_indentation[2], 0) 1513 buffer:tab() 1514 buffer:add_text('bar') 1515 buffer:new_line() 1516 assert_equal(buffer.line_indentation[3], buffer.tab_width) 1517 assert_equal(buffer.current_pos, buffer.line_indent_position[3]) 1518 buffer:new_line() 1519 buffer:back_tab() 1520 assert_equal(buffer.line_indentation[4], 0) 1521 assert_equal(buffer.current_pos, buffer:position_from_line(4)) 1522 buffer:new_line() -- should indent since previous line is blank 1523 assert_equal(buffer.line_indentation[5], buffer.tab_width) 1524 assert_equal(buffer.current_pos, buffer.line_indent_position[5]) 1525 buffer:goto_pos(buffer:position_from_line(2)) -- "\tbar" 1526 buffer:new_line() -- should not change indentation 1527 assert_equal(buffer.line_indentation[3], buffer.tab_width) 1528 assert_equal(buffer.current_pos, buffer:position_from_line(3)) 1529 buffer:close(true) 1530end 1531 1532function test_editing_strip_trailing_spaces() 1533 local strip = textadept.editing.strip_trailing_spaces 1534 textadept.editing.strip_trailing_spaces = true 1535 buffer.new() 1536 local text = table.concat({ 1537 'foo ', 1538 ' bar\t\r', 1539 'baz\t ' 1540 }, '\n') 1541 buffer:set_text(text) 1542 buffer:goto_pos(buffer.line_end_position[2]) 1543 events.emit(events.FILE_BEFORE_SAVE) 1544 assert_equal(buffer:get_text(), table.concat({ 1545 'foo', 1546 ' bar', 1547 'baz', 1548 '' 1549 }, '\n')) 1550 assert_equal(buffer.current_pos, buffer.line_end_position[2]) 1551 buffer:undo() 1552 assert_equal(buffer:get_text(), text) 1553 buffer.encoding = nil -- treat as a binary file 1554 events.emit(events.FILE_BEFORE_SAVE) 1555 assert_equal(buffer:get_text(), text) 1556 buffer:close(true) 1557 textadept.editing.strip_trailing_spaces = strip -- restore 1558end 1559 1560function test_editing_paste_reindent_tabs_to_tabs() 1561 ui.clipboard_text = table.concat({ 1562 '\tfoo', 1563 '', 1564 '\t\tbar', 1565 '\tbaz' 1566 }, '\n') 1567 buffer.new() 1568 buffer.use_tabs, buffer.eol_mode = true, buffer.EOL_CRLF 1569 buffer:add_text('quux\r\n') 1570 textadept.editing.paste_reindent() 1571 assert_equal(buffer:get_text(), table.concat({ 1572 'quux', 1573 'foo', 1574 '', 1575 '\tbar', 1576 'baz' 1577 }, '\r\n')) 1578 buffer:clear_all() 1579 buffer:add_text('\t\tquux\r\n\r\n') -- no auto-indent 1580 assert_equal(buffer.line_indentation[2], 0) 1581 assert_equal(buffer.line_indentation[3], 0) 1582 textadept.editing.paste_reindent() 1583 assert_equal(buffer:get_text(), table.concat({ 1584 '\t\tquux', 1585 '', 1586 '\t\tfoo', 1587 '\t\t', 1588 '\t\t\tbar', 1589 '\t\tbaz' 1590 }, '\r\n')) 1591 buffer:clear_all() 1592 buffer:add_text('\t\tquux\r\n') 1593 assert_equal(buffer.line_indentation[2], 0) 1594 buffer:new_line() -- auto-indent 1595 assert_equal(buffer.line_indentation[3], 2 * buffer.tab_width) 1596 textadept.editing.paste_reindent() 1597 assert_equal(buffer:get_text(), table.concat({ 1598 '\t\tquux', 1599 '', 1600 '\t\tfoo', 1601 '\t\t', 1602 '\t\t\tbar', 1603 '\t\tbaz' 1604 }, '\r\n')) 1605 buffer:close(true) 1606end 1607expected_failure(test_editing_paste_reindent_tabs_to_tabs) 1608 1609function test_editing_paste_reindent_spaces_to_spaces() 1610 ui.clipboard_text = table.concat({ 1611 ' foo', 1612 '', 1613 ' bar', 1614 ' baz', 1615 ' quux' 1616 }, '\n') 1617 buffer.new() 1618 buffer.use_tabs, buffer.tab_width = false, 2 1619 buffer:add_text('foobar\n') 1620 textadept.editing.paste_reindent() 1621 assert_equal(buffer:get_text(), table.concat({ 1622 'foobar', 1623 'foo', 1624 '', 1625 ' bar', 1626 ' baz', 1627 'quux' 1628 }, '\n')) 1629 buffer:clear_all() 1630 buffer:add_text(' foobar\n\n') -- no auto-indent 1631 assert_equal(buffer.line_indentation[2], 0) 1632 assert_equal(buffer.line_indentation[3], 0) 1633 textadept.editing.paste_reindent() 1634 assert_equal(buffer:get_text(), table.concat({ 1635 ' foobar', 1636 '', 1637 ' foo', 1638 ' ', 1639 ' bar', 1640 ' baz', 1641 ' quux' 1642 }, '\n')) 1643 buffer:clear_all() 1644 buffer:add_text(' foobar\n') 1645 assert_equal(buffer.line_indentation[2], 0) 1646 buffer:new_line() -- auto-indent 1647 assert_equal(buffer.line_indentation[3], 4) 1648 textadept.editing.paste_reindent() 1649 assert_equal(buffer:get_text(), table.concat({ 1650 ' foobar', 1651 '', 1652 ' foo', 1653 ' ', 1654 ' bar', 1655 ' baz', 1656 ' quux' 1657 }, '\n')) 1658 buffer:close(true) 1659end 1660expected_failure(test_editing_paste_reindent_spaces_to_spaces) 1661 1662function test_editing_paste_reindent_spaces_to_tabs() 1663 ui.clipboard_text = table.concat({ 1664 ' foo', 1665 ' bar', 1666 ' baz' 1667 }, '\n') 1668 buffer.new() 1669 buffer.use_tabs, buffer.tab_width = true, 4 1670 buffer:add_text('\tquux') 1671 buffer:new_line() 1672 textadept.editing.paste_reindent() 1673 assert_equal(buffer:get_text(), table.concat({ 1674 '\tquux', 1675 '\tfoo', 1676 '\t\tbar', 1677 '\tbaz' 1678 }, '\n')) 1679 buffer:close(true) 1680end 1681 1682function test_editing_paste_reindent_tabs_to_spaces() 1683 ui.clipboard_text = table.concat({ 1684 '\tif foo and', 1685 '\t bar then', 1686 '\t\tbaz()', 1687 '\tend', 1688 '' 1689 }, '\n') 1690 buffer.new() 1691 buffer.use_tabs, buffer.tab_width = false, 2 1692 buffer:set_lexer('lua') 1693 buffer:add_text('function quux()') 1694 buffer:new_line() 1695 buffer:insert_text(-1, 'end') 1696 buffer:colorize(1, -1) -- first line should be a fold header 1697 textadept.editing.paste_reindent() 1698 assert_equal(buffer:get_text(), table.concat({ 1699 'function quux()', 1700 ' if foo and', 1701 ' bar then', 1702 ' baz()', 1703 ' end', 1704 'end' 1705 }, '\n')) 1706 buffer:close(true) 1707end 1708expected_failure(test_editing_paste_reindent_tabs_to_spaces) 1709 1710function test_editing_toggle_comment_lines() 1711 buffer.new() 1712 buffer:add_text('foo') 1713 textadept.editing.toggle_comment() 1714 assert_equal(buffer:get_text(), 'foo') 1715 buffer:set_lexer('lua') 1716 local text = table.concat({ 1717 '', 1718 'local foo = "bar"', 1719 ' local baz = "quux"', 1720 '' 1721 }, '\n') 1722 buffer:set_text(text) 1723 buffer:goto_pos(buffer:position_from_line(2)) 1724 textadept.editing.toggle_comment() 1725 assert_equal(buffer:get_text(), table.concat({ 1726 '', 1727 '--local foo = "bar"', 1728 ' local baz = "quux"', 1729 '' 1730 }, '\n')) 1731 assert_equal(buffer.current_pos, buffer:position_from_line(2) + 2) 1732 textadept.editing.toggle_comment() -- uncomment 1733 assert_equal(buffer:get_line(2), 'local foo = "bar"\n') 1734 assert_equal(buffer.current_pos, buffer:position_from_line(2)) 1735 local offset = 5 1736 buffer:set_sel(buffer:position_from_line(2) + offset, buffer:position_from_line(4) - offset) 1737 textadept.editing.toggle_comment() 1738 assert_equal(buffer:get_text(), table.concat({ 1739 '', 1740 '--local foo = "bar"', 1741 '-- local baz = "quux"', 1742 '' 1743 }, '\n')) 1744 assert_equal(buffer.selection_start, buffer:position_from_line(2) + offset + 2) 1745 assert_equal(buffer.selection_end, buffer:position_from_line(4) - offset) 1746 textadept.editing.toggle_comment() -- uncomment 1747 assert_equal(buffer:get_text(), table.concat({ 1748 '', 1749 'local foo = "bar"', 1750 ' local baz = "quux"', 1751 '' 1752 }, '\n')) 1753 assert_equal(buffer.selection_start, buffer:position_from_line(2) + offset) 1754 assert_equal(buffer.selection_end, buffer:position_from_line(4) - offset) 1755 buffer:undo() -- comment 1756 buffer:undo() -- uncomment 1757 assert_equal(buffer:get_text(), text) -- verify atomic undo 1758 buffer:close(true) 1759end 1760 1761function test_editing_toggle_comment() 1762 buffer.new() 1763 buffer:set_lexer('ansi_c') 1764 buffer:set_text(table.concat({ 1765 '', 1766 ' const char *foo = "bar";', 1767 'const char *baz = "quux";', 1768 '' 1769 }, '\n')) 1770 buffer:set_sel(buffer:position_from_line(2), buffer:position_from_line(4)) 1771 textadept.editing.toggle_comment() 1772 assert_equal(buffer:get_text(), table.concat({ 1773 '', 1774 ' /*const char *foo = "bar";*/', 1775 '/*const char *baz = "quux";*/', 1776 '' 1777 }, '\n')) 1778 assert_equal(buffer.selection_start, buffer:position_from_line(2) + 2) 1779 assert_equal(buffer.selection_end, buffer:position_from_line(4)) 1780 textadept.editing.toggle_comment() -- uncomment 1781 assert_equal(buffer:get_text(), table.concat({ 1782 '', 1783 ' const char *foo = "bar";', 1784 'const char *baz = "quux";', 1785 '' 1786 }, '\n')) 1787 assert_equal(buffer.selection_start, buffer:position_from_line(2)) 1788 assert_equal(buffer.selection_end, buffer:position_from_line(4)) 1789 buffer:close(true) 1790end 1791 1792function test_editing_goto_line() 1793 buffer.new() 1794 buffer:new_line() 1795 textadept.editing.goto_line(1) 1796 assert_equal(buffer:line_from_position(buffer.current_pos), 1) 1797 textadept.editing.goto_line(2) 1798 assert_equal(buffer:line_from_position(buffer.current_pos), 2) 1799 buffer:close(true) 1800 1801 assert_raises(function() textadept.editing.goto_line(true) end, 'number/nil expected, got boolean') 1802end 1803 1804-- TODO: test_editing_goto_line_interactive 1805 1806function test_editing_transpose_chars() 1807 buffer.new() 1808 buffer:add_text('foobar') 1809 textadept.editing.transpose_chars() 1810 assert_equal(buffer:get_text(), 'foobra') 1811 buffer:char_left() 1812 textadept.editing.transpose_chars() 1813 assert_equal(buffer:get_text(), 'foobar') 1814 buffer:clear_all() 1815 buffer:add_text('⌘⇧⌥') 1816 textadept.editing.transpose_chars() 1817 assert_equal(buffer:get_text(), '⌘⌥⇧') 1818 buffer:char_left() 1819 textadept.editing.transpose_chars() 1820 assert_equal(buffer:get_text(), '⌘⇧⌥') 1821 buffer:clear_all() 1822 textadept.editing.transpose_chars() 1823 assert_equal(buffer:get_text(), '') 1824 buffer:add_text('a') 1825 textadept.editing.transpose_chars() 1826 assert_equal(buffer:get_text(), 'a') 1827 -- TODO: multiple selection? 1828 buffer:close(true) 1829end 1830 1831function test_editing_join_lines() 1832 buffer.new() 1833 buffer:append_text('foo\nbar\n baz\nquux\n') 1834 textadept.editing.join_lines() 1835 assert_equal(buffer:get_text(), 'foo bar\n baz\nquux\n') 1836 assert_equal(buffer.current_pos, 4) 1837 buffer:set_sel(buffer:position_from_line(2) + 5, buffer:position_from_line(4) - 5) 1838 textadept.editing.join_lines() 1839 assert_equal(buffer:get_text(), 'foo bar\n baz quux\n') 1840 buffer:close(true) 1841end 1842 1843function test_editing_enclose() 1844 buffer.new() 1845 buffer.add_text('foo bar') 1846 textadept.editing.enclose('"', '"') 1847 assert_equal(buffer:get_text(), 'foo "bar"') 1848 buffer:undo() 1849 buffer:select_all() 1850 textadept.editing.enclose('(', ')') 1851 assert_equal(buffer:get_text(), '(foo bar)') 1852 buffer:undo() 1853 buffer:append_text('\nfoo bar') 1854 buffer:set_selection(buffer.line_end_position[1], buffer.line_end_position[1]) 1855 buffer:add_selection(buffer.line_end_position[2], buffer.line_end_position[2]) 1856 textadept.editing.enclose('<', '>') 1857 assert_equal(buffer:get_text(), 'foo <bar>\nfoo <bar>') 1858 buffer:undo() 1859 assert_equal(buffer:get_text(), 'foo bar\nfoo bar') -- verify atomic undo 1860 buffer:set_selection(buffer:position_from_line(1), buffer.line_end_position[1]) 1861 buffer:add_selection(buffer:position_from_line(2), buffer.line_end_position[2]) 1862 textadept.editing.enclose('-', '-') 1863 assert_equal(buffer:get_text(), '-foo bar-\n-foo bar-') 1864 buffer:close(true) 1865 1866 assert_raises(function() textadept.editing.enclose() end, 'string expected, got nil') 1867 assert_raises(function() textadept.editing.enclose('<', 1) end, 'string expected, got number') 1868end 1869 1870function test_editing_auto_enclose() 1871 local auto_enclose = textadept.editing.auto_enclose 1872 buffer.new() 1873 buffer:add_text('foo bar') 1874 buffer:word_left_extend() 1875 textadept.editing.auto_enclose = false 1876 events.emit(events.KEYPRESS, string.byte('*')) -- simulate typing 1877 assert(buffer:get_text() ~= 'foo *bar*') 1878 textadept.editing.auto_enclose = true 1879 events.emit(events.KEYPRESS, string.byte('*')) -- simulate typing 1880 assert_equal(buffer:get_text(), 'foo *bar*') 1881 buffer:undo() 1882 buffer:select_all() 1883 events.emit(events.KEYPRESS, string.byte('(')) -- simulate typing 1884 assert_equal(buffer:get_text(), '(foo bar)') 1885 buffer:close(true) 1886 textadept.editing.auto_enclose = auto_enclose -- restore 1887end 1888 1889function test_editing_select_enclosed() 1890 buffer.new() 1891 buffer:add_text('("foo bar")') 1892 buffer:goto_pos(6) 1893 textadept.editing.select_enclosed() 1894 assert_equal(buffer:get_sel_text(), 'foo bar') 1895 textadept.editing.select_enclosed() 1896 assert_equal(buffer:get_sel_text(), '"foo bar"') 1897 textadept.editing.select_enclosed() 1898 assert_equal(buffer:get_sel_text(), 'foo bar') 1899 buffer:goto_pos(6) 1900 textadept.editing.select_enclosed('("', '")') 1901 assert_equal(buffer:get_sel_text(), 'foo bar') 1902 textadept.editing.select_enclosed('("', '")') 1903 assert_equal(buffer:get_sel_text(), '("foo bar")') 1904 textadept.editing.select_enclosed('("', '")') 1905 assert_equal(buffer:get_sel_text(), 'foo bar') 1906 buffer:append_text('"baz"') 1907 buffer:goto_pos(10) -- last " on first line 1908 textadept.editing.select_enclosed() 1909 assert_equal(buffer:get_sel_text(), 'foo bar') 1910 buffer:close(true) 1911 1912 assert_raises(function() textadept.editing.select_enclosed('"') end, 'string expected, got nil') 1913end 1914expected_failure(test_editing_select_enclosed) 1915 1916function test_editing_select_word() 1917 buffer.new() 1918 buffer:append_text(table.concat({ 1919 'foo', 1920 'foobar', 1921 'bar foo', 1922 'baz foo bar', 1923 'fooquux', 1924 'foo' 1925 }, '\n')) 1926 textadept.editing.select_word() 1927 assert_equal(buffer:get_sel_text(), 'foo') 1928 textadept.editing.select_word() 1929 assert_equal(buffer.selections, 2) 1930 assert_equal(buffer:get_sel_text(), 'foofoo') -- Scintilla stores it this way 1931 textadept.editing.select_word(true) 1932 assert_equal(buffer.selections, 4) 1933 assert_equal(buffer:get_sel_text(), 'foofoofoofoo') 1934 local lines = {} 1935 for i = 1, buffer.selections do 1936 lines[#lines + 1] = buffer:line_from_position(buffer.selection_n_start[i]) 1937 end 1938 table.sort(lines) 1939 assert_equal(lines, {1, 3, 4, 6}) 1940 buffer:close(true) 1941end 1942 1943function test_editing_select_line() 1944 buffer.new() 1945 buffer:add_text('foo\n bar') 1946 textadept.editing.select_line() 1947 assert_equal(buffer:get_sel_text(), ' bar') 1948 buffer:close(true) 1949end 1950 1951function test_editing_select_paragraph() 1952 buffer.new() 1953 buffer:set_text(table.concat({ 1954 'foo', 1955 '', 1956 'bar', 1957 'baz', 1958 '', 1959 'quux' 1960 }, '\n')) 1961 buffer:goto_pos(buffer:position_from_line(3)) 1962 textadept.editing.select_paragraph() 1963 assert_equal(buffer:get_sel_text(), 'bar\nbaz\n\n') 1964 buffer:close(true) 1965end 1966 1967function test_editing_convert_indentation() 1968 buffer.new() 1969 local text = table.concat({ 1970 '\tfoo', 1971 ' bar', 1972 '\t baz', 1973 ' \tquux' 1974 }, '\n') 1975 buffer:set_text(text) 1976 buffer.use_tabs, buffer.tab_width = true, 4 1977 textadept.editing.convert_indentation() 1978 assert_equal(buffer:get_text(), table.concat({ 1979 '\tfoo', 1980 ' bar', 1981 '\t\tbaz', 1982 '\t\tquux' 1983 }, '\n')) 1984 buffer:undo() 1985 assert_equal(buffer:get_text(), text) -- verify atomic undo 1986 buffer.use_tabs, buffer.tab_width = false, 2 1987 textadept.editing.convert_indentation() 1988 assert_equal(buffer:get_text(), table.concat({ 1989 ' foo', 1990 ' bar', 1991 ' baz', 1992 ' quux' 1993 }, '\n')) 1994 buffer:close(true) 1995end 1996 1997function test_editing_highlight_word() 1998 local function verify(indics) 1999 local bit = 1 << textadept.editing.INDIC_HIGHLIGHT - 1 2000 for _, pos in ipairs(indics) do 2001 local mask = buffer:indicator_all_on_for(pos) 2002 assert(mask & bit > 0, 'no indicator on line %d', buffer:line_from_position(pos)) 2003 end 2004 end 2005 local function update() 2006 ui.update() 2007 if CURSES then events.emit(events.UPDATE_UI, buffer.UPDATE_SELECTION) end 2008 end 2009 2010 local highlight = textadept.editing.highlight_words 2011 textadept.editing.highlight_words = textadept.editing.HIGHLIGHT_SELECTED 2012 buffer.new() 2013 buffer:append_text(table.concat({ 2014 'foo', 2015 'foobar', 2016 'bar foo', 2017 'baz foo bar', 2018 'fooquux', 2019 'foo' 2020 }, '\n')) 2021 local function verify_foo() 2022 verify{ 2023 buffer:position_from_line(1), 2024 buffer:position_from_line(3) + 5, 2025 buffer:position_from_line(4) + 4, 2026 buffer:position_from_line(6) 2027 } 2028 end 2029 textadept.editing.select_word() 2030 update() 2031 verify_foo() 2032 events.emit(events.KEYPRESS, not CURSES and 0xFF1B or 7) -- esc 2033 local pos = buffer:indicator_end(textadept.editing.INDIC_HIGHLIGHT, 1) 2034 assert_equal(pos, 1) -- highlights cleared 2035 -- Verify turning off word highlighting. 2036 textadept.editing.highlight_words = textadept.editing.HIGHLIGHT_NONE 2037 textadept.editing.select_word() 2038 update() 2039 pos = buffer:indicator_end(textadept.editing.INDIC_HIGHLIGHT, 2) 2040 assert_equal(pos, 1) -- no highlights 2041 textadept.editing.highlight_words = textadept.editing.HIGHLIGHT_SELECTED 2042 -- Verify partial word selections do not highlight words. 2043 buffer:set_sel(1, 3) 2044 pos = buffer:indicator_end(textadept.editing.INDIC_HIGHLIGHT, 2) 2045 assert_equal(pos, 1) -- no highlights 2046 -- Verify multi-word selections do not highlight words. 2047 buffer:set_sel(buffer:position_from_line(3), buffer.line_end_position[3]) 2048 assert(buffer:is_range_word(buffer.selection_start, buffer.selection_end)) 2049 pos = buffer:indicator_end(textadept.editing.INDIC_HIGHLIGHT, 2) 2050 assert_equal(pos, 1) -- no highlights 2051 -- Verify current word highlighting. 2052 textadept.editing.highlight_words = textadept.editing.HIGHLIGHT_CURRENT 2053 buffer:goto_pos(1) 2054 update() 2055 verify_foo() 2056 buffer:line_down() 2057 update() 2058 verify{buffer:position_from_line(2)} 2059 buffer:line_down() 2060 update() 2061 verify{buffer:position_from_line(3), buffer:position_from_line(4) + 9} 2062 buffer:word_right() 2063 update() 2064 verify_foo() 2065 buffer:char_left() 2066 update() 2067 pos = buffer:indicator_end(textadept.editing.INDIC_HIGHLIGHT, 2) 2068 assert_equal(pos, 1) -- no highlights 2069 buffer:close(true) 2070 textadept.editing.highlight_words = highlight -- reset 2071end 2072 2073function test_editing_filter_through() 2074 buffer.new() 2075 buffer:set_text('3|baz\n1|foo\n5|foobar\n1|foo\n4|quux\n2|bar\n') 2076 textadept.editing.filter_through('sort') 2077 assert_equal(buffer:get_text(), '1|foo\n1|foo\n2|bar\n3|baz\n4|quux\n5|foobar\n') 2078 buffer:undo() 2079 textadept.editing.filter_through('sort | uniq|cut -d "|" -f2') 2080 assert_equal(buffer:get_text(), 'foo\nbar\nbaz\nquux\nfoobar\n') 2081 buffer:undo() 2082 buffer:set_sel(buffer:position_from_line(2) + 2, buffer.line_end_position[2]) 2083 textadept.editing.filter_through('sed "s/o/O/g;"') 2084 assert_equal(buffer:get_text(), '3|baz\n1|fOO\n5|foobar\n1|foo\n4|quux\n2|bar\n') 2085 buffer:undo() 2086 buffer:set_sel(buffer:position_from_line(2), buffer:position_from_line(5)) 2087 textadept.editing.filter_through('sort') 2088 assert_equal(buffer:get_text(), '3|baz\n1|foo\n1|foo\n5|foobar\n4|quux\n2|bar\n') 2089 buffer:undo() 2090 buffer:set_sel(buffer:position_from_line(2), buffer:position_from_line(5) + 1) 2091 textadept.editing.filter_through('sort') 2092 assert_equal(buffer:get_text(), '3|baz\n1|foo\n1|foo\n4|quux\n5|foobar\n2|bar\n') 2093 buffer:close(true) 2094 2095 assert_raises(function() textadept.editing.filter_through() end, 'string expected, got nil') 2096end 2097 2098function test_editing_autocomplete() 2099 assert_raises(function() textadept.editing.autocomplete() end, 'string expected, got nil') 2100end 2101 2102function test_editing_autocomplete_word() 2103 local all_words = textadept.editing.autocomplete_all_words 2104 textadept.editing.autocomplete_all_words = false 2105 buffer.new() 2106 buffer:add_text('foo f') 2107 textadept.editing.autocomplete('word') 2108 assert_equal(buffer:get_text(), 'foo foo') 2109 buffer:add_text('bar f') 2110 textadept.editing.autocomplete('word') 2111 assert(buffer:auto_c_active(), 'autocomplete list not shown') 2112 buffer:auto_c_select('foob') 2113 buffer:auto_c_complete() 2114 assert_equal(buffer:get_text(), 'foo foobar foobar') 2115 local ignore_case = buffer.auto_c_ignore_case 2116 buffer.auto_c_ignore_case = false 2117 buffer:add_text(' Bar b') 2118 textadept.editing.autocomplete('word') 2119 assert_equal(buffer:get_text(), 'foo foobar foobar Bar b') 2120 buffer.auto_c_ignore_case = true 2121 textadept.editing.autocomplete('word') 2122 assert_equal(buffer:get_text(), 'foo foobar foobar Bar Bar') 2123 buffer.auto_c_ignore_case = ignore_case 2124 buffer.new() 2125 buffer:add_text('foob') 2126 textadept.editing.autocomplete_all_words = true 2127 textadept.editing.autocomplete('word') 2128 textadept.editing.autocomplete_all_words = all_words 2129 assert_equal(buffer:get_text(), 'foobar') 2130 buffer:close(true) 2131 buffer:close(true) 2132end 2133 2134function test_editing_show_documentation() 2135 buffer.new() 2136 textadept.editing.api_files['text'] = { 2137 _HOME .. '/test/modules/textadept/editing/api', 2138 function() return _HOME .. '/test/modules/textadept/editing/api2' end 2139 } 2140 buffer:add_text('foo') 2141 textadept.editing.show_documentation() 2142 assert(view:call_tip_active(), 'documentation not found') 2143 view:call_tip_cancel() 2144 buffer:add_text('2') 2145 textadept.editing.show_documentation() 2146 assert(view:call_tip_active(), 'documentation not found') 2147 view:call_tip_cancel() 2148 buffer:add_text('bar') 2149 textadept.editing.show_documentation() 2150 assert(not view:call_tip_active(), 'documentation found') 2151 buffer:clear_all() 2152 buffer:add_text('FOO') 2153 textadept.editing.show_documentation(nil, true) 2154 assert(view:call_tip_active(), 'documentation not found') 2155 view:call_tip_cancel() 2156 buffer:add_text('(') 2157 textadept.editing.show_documentation(nil, true) 2158 assert(view:call_tip_active(), 'documentation not found') 2159 view:call_tip_cancel() 2160 buffer:add_text('bar') 2161 textadept.editing.show_documentation(nil, true) 2162 assert(view:call_tip_active(), 'documentation not found') 2163 events.emit(events.CALL_TIP_CLICK, 1) 2164 -- TODO: test calltip cycling. 2165 buffer:close(true) 2166 textadept.editing.api_files['text'] = nil 2167 2168 assert_raises(function() textadept.editing.show_documentation(true) end, 'number/nil expected, got boolean') 2169end 2170 2171function test_file_types_get_lexer() 2172 buffer.new() 2173 buffer:set_lexer('html') 2174 buffer:set_text(table.concat({ 2175 '<html><head><style type="text/css">', 2176 'h1 {}', 2177 '</style></head></html>' 2178 }, '\n')) 2179 buffer:colorize(1, -1) 2180 buffer:goto_pos(buffer:position_from_line(2)) 2181 assert_equal(buffer:get_lexer(), 'html') 2182 assert_equal(buffer:get_lexer(true), 'css') 2183 assert_equal(buffer:name_of_style(buffer.style_at[buffer.current_pos]), 'identifier') 2184 buffer:close(true) 2185end 2186 2187function test_file_types_set_lexer() 2188 local lexer_loaded 2189 local handler = function(name) lexer_loaded = name end 2190 events.connect(events.LEXER_LOADED, handler) 2191 buffer.new() 2192 buffer.filename = 'foo.lua' 2193 buffer:set_lexer() 2194 assert_equal(buffer:get_lexer(), 'lua') 2195 assert_equal(lexer_loaded, 'lua') 2196 buffer.filename = 'foo' 2197 buffer:set_text('#!/bin/sh') 2198 buffer:set_lexer() 2199 assert_equal(buffer:get_lexer(), 'bash') 2200 buffer:undo() 2201 buffer.filename = 'Makefile' 2202 buffer:set_lexer() 2203 assert_equal(buffer:get_lexer(), 'makefile') 2204 -- Verify lexer after certain events. 2205 buffer.filename = 'foo.c' 2206 events.emit(events.FILE_AFTER_SAVE, nil, true) 2207 assert_equal(buffer:get_lexer(), 'ansi_c') 2208 buffer.filename = 'foo.cpp' 2209 events.emit(events.FILE_OPENED) 2210 assert_equal(buffer:get_lexer(), 'cpp') 2211 view:goto_buffer(1) 2212 view:goto_buffer(-1) 2213 assert_equal(buffer:get_lexer(), 'cpp') 2214 events.disconnect(events.LEXER_LOADED, handler) 2215 buffer:close(true) 2216 2217 assert_raises(function() buffer:set_lexer(true) end, 'string/nil expected, got boolean') 2218end 2219 2220function test_file_types_select_lexer_interactive() 2221 buffer.new() 2222 local name = buffer:get_lexer() 2223 textadept.file_types.select_lexer() 2224 assert(buffer:get_lexer() ~= name, 'lexer unchanged') 2225 buffer:close() 2226end 2227 2228function test_file_types_load_lexers() 2229 local lexers = {} 2230 for name in buffer:private_lexer_call(_SCINTILLA.functions.property_names[1]):gmatch('[^\n]+') do 2231 lexers[#lexers + 1] = name 2232 end 2233 print('Loading lexers...') 2234 if #_VIEWS > 1 then view:unsplit() end 2235 view:goto_buffer(-1) 2236 ui.silent_print = true 2237 buffer.new() 2238 for _, name in ipairs(lexers) do 2239 print('Loading lexer ' .. name) 2240 buffer:set_lexer(name) 2241 end 2242 buffer:close() 2243 ui.silent_print = false 2244end 2245 2246function test_ui_find_find_text() 2247 local wrapped = false 2248 local handler = function() wrapped = true end 2249 buffer.new() 2250 buffer:set_text(table.concat({ 2251 ' foo', 2252 'foofoo', 2253 'FOObar', 2254 'foo bar baz', 2255 }, '\n')) 2256 ui.find.find_entry_text = 'foo' 2257 ui.find.find_next() 2258 assert_equal(buffer.selection_start, 1 + 1) 2259 assert_equal(buffer.selection_end, buffer.selection_start + 3) 2260 ui.find.whole_word = true 2261 ui.find.find_next() 2262 assert_equal(buffer.selection_start, buffer:position_from_line(4)) 2263 assert_equal(buffer.selection_end, buffer.selection_start + 3) 2264 events.connect(events.FIND_WRAPPED, handler) 2265 ui.find.find_next() 2266 assert(wrapped, 'search did not wrap') 2267 events.disconnect(events.FIND_WRAPPED, handler) 2268 assert_equal(buffer.selection_start, 1 + 1) 2269 assert_equal(buffer.selection_end, buffer.selection_start + 3) 2270 ui.find.find_prev() 2271 assert_equal(buffer.selection_start, buffer:position_from_line(4)) 2272 assert_equal(buffer.selection_end, buffer.selection_start + 3) 2273 ui.find.match_case, ui.find.whole_word = true, false 2274 ui.find.find_entry_text = 'FOO' 2275 ui.find.find_next() 2276 assert_equal(buffer.selection_start, buffer:position_from_line(3)) 2277 assert_equal(buffer.selection_end, buffer.selection_start + 3) 2278 ui.find.find_next() 2279 assert_equal(buffer.selection_start, buffer:position_from_line(3)) 2280 assert_equal(buffer.selection_end, buffer.selection_start + 3) 2281 ui.find.regex = true 2282 ui.find.find_entry_text = 'f(.)\\1' 2283 ui.find.find_next() 2284 assert_equal(buffer.selection_start, buffer:position_from_line(4)) 2285 assert_equal(buffer.selection_end, buffer.selection_start + 3) 2286 ui.find.find_entry_text = 'quux' 2287 ui.find.find_next() 2288 assert_equal(buffer.selection_start, buffer.selection_end) -- no match 2289 assert_equal(events.emit(events.FIND, 'not found'), -1) -- simulate Find Next 2290 ui.find.match_case, ui.find.regex = false, false 2291 ui.find.find_entry_text = '' 2292 buffer:close(true) 2293end 2294 2295function test_ui_find_highlight_results() 2296 local function assert_indics(indics) 2297 local bit = 1 << ui.find.INDIC_FIND - 1 2298 for _, pos in ipairs(indics) do 2299 local mask = buffer:indicator_all_on_for(pos) 2300 assert(mask & bit > 0, 'no indicator on line %d', buffer:line_from_position(pos)) 2301 end 2302 end 2303 2304 local highlight_all_matches = ui.find.highlight_all_matches 2305 ui.find.highlight_all_matches = true 2306 buffer.new() 2307 buffer:append_text(table.concat({ 2308 'foo', 2309 'foobar', 2310 'bar foo', 2311 'baz foo bar', 2312 'fooquux', 2313 'foo' 2314 }, '\n')) 2315 -- Normal search. 2316 ui.find.find_entry_text = 'foo' 2317 ui.find.find_next() 2318 assert_indics{ 2319 buffer:position_from_line(1), 2320 buffer:position_from_line(3) + 4, 2321 buffer:position_from_line(4) + 4, 2322 buffer:position_from_line(6) 2323 } 2324 -- Regex search. 2325 ui.find.find_entry_text = 'ba.' 2326 ui.find.regex = true 2327 ui.find.find_next() 2328 assert_indics{ 2329 buffer:position_from_line(2) + 3, 2330 buffer:position_from_line(3), 2331 buffer:position_from_line(4), 2332 buffer:position_from_line(4) + 8, 2333 } 2334 ui.find.regex = false 2335 -- Do not highlight short searches (potential performance issue). 2336 ui.find.find_entry_text = 'f' 2337 ui.find.find_next() 2338 local pos = buffer:indicator_end(ui.find.INDIC_FIND, 2) 2339 assert_equal(pos, 1) 2340 -- Verify turning off match highlighting works. 2341 ui.find.highlight_all_matches = false 2342 ui.find.find_entry_text = 'foo' 2343 ui.find.find_next() 2344 pos = buffer:indicator_end(ui.find.INDIC_FIND, 2) 2345 assert_equal(pos, 1) 2346 ui.find.find_entry_text = '' 2347 ui.find.highlight_all_matches = highlight_all_matches -- reset 2348 buffer:close(true) 2349end 2350 2351function test_ui_find_incremental() 2352 buffer.new() 2353 buffer:set_text(table.concat({ 2354 ' foo', 2355 'foobar', 2356 'FOObaz', 2357 'FOOquux' 2358 }, '\n')) 2359 assert_equal(buffer.current_pos, 1) 2360 ui.find.incremental = true 2361 ui.find.find_entry_text = 'f' -- simulate 'f' keypress 2362 if CURSES then events.emit(events.FIND_TEXT_CHANGED) end -- simulate 2363 assert_equal(buffer.selection_start, 1 + 1) 2364 assert_equal(buffer.selection_end, buffer.selection_start + 1) 2365 ui.find.find_entry_text = 'fo' -- simulate 'o' keypress 2366 ui.find.find_entry_text = 'foo' -- simulate 'o' keypress 2367 if CURSES then events.emit(events.FIND_TEXT_CHANGED) end -- simulate 2368 assert_equal(buffer.selection_start, 1 + 1) 2369 assert_equal(buffer.selection_end, buffer.selection_start + 3) 2370 events.emit(events.FIND, ui.find.find_entry_text, true) -- simulate Find Next 2371 assert_equal(buffer.selection_start, buffer:position_from_line(2)) 2372 assert_equal(buffer.selection_end, buffer.selection_start + 3) 2373 ui.find.find_entry_text = 'fooq' -- simulate 'q' keypress 2374 if CURSES then events.emit(events.FIND_TEXT_CHANGED) end -- simulate 2375 assert_equal(buffer.selection_start, buffer:position_from_line(4)) 2376 assert_equal(buffer.selection_end, buffer.selection_start + 4) 2377 ui.find.find_entry_text = 'foo' -- simulate backspace 2378 if CURSES then events.emit(events.FIND_TEXT_CHANGED) end -- simulate 2379 assert_equal(buffer.selection_start, buffer:position_from_line(2)) 2380 assert_equal(buffer.selection_end, buffer.selection_start + 3) 2381 events.emit(events.FIND, ui.find.find_entry_text, true) -- simulate Find Next 2382 assert_equal(buffer.selection_start, buffer:position_from_line(3)) 2383 assert_equal(buffer.selection_end, buffer.selection_start + 3) 2384 ui.find.match_case = true 2385 events.emit(events.FIND, ui.find.find_entry_text, true) -- simulate Find Next, wrap 2386 assert_equal(buffer.selection_start, 1 + 1) 2387 assert_equal(buffer.selection_end, buffer.selection_start + 3) 2388 ui.find.match_case = false 2389 ui.find.whole_word = true 2390 ui.find.find_entry_text = 'foob' 2391 if CURSES then events.emit(events.FIND_TEXT_CHANGED) end -- simulate 2392 assert(buffer.selection_empty, 'no text should be found') 2393 ui.find.find_entry_text = 'foobar' 2394 if CURSES then events.emit(events.FIND_TEXT_CHANGED) end -- simulate 2395 assert_equal(buffer:get_sel_text(), 'foobar') 2396 ui.find.whole_word = false 2397 ui.find.find_entry_text = '' 2398 ui.find.incremental = false 2399 buffer:close(true) 2400end 2401 2402function test_ui_find_incremental_highlight() 2403 local highlight_all_matches = ui.find.highlight_all_matches 2404 ui.find.highlight_all_matches = true 2405 buffer.new() 2406 buffer:set_text(table.concat({ 2407 ' foo', 2408 'foobar', 2409 'FOObaz', 2410 'FOOquux' 2411 }, '\n')) 2412 ui.find.incremental = true 2413 ui.find.find_entry_text = 'f' -- simulate 'f' keypress 2414 if CURSES then events.emit(events.FIND_TEXT_CHANGED) end -- simulate 2415 local pos = buffer:indicator_end(ui.find.INDIC_FIND, 2) 2416 assert_equal(pos, 1) -- too short 2417 ui.find.find_entry_text = 'fo' -- simulate 'o' keypress 2418 if CURSES then events.emit(events.FIND_TEXT_CHANGED) end -- simulate 2419 local indics = { 2420 buffer:position_from_line(1) + 1, 2421 buffer:position_from_line(2), 2422 buffer:position_from_line(3), 2423 buffer:position_from_line(4) 2424 } 2425 local bit = 1 << ui.find.INDIC_FIND - 1 2426 for _, pos in ipairs(indics) do 2427 local mask = buffer:indicator_all_on_for(pos) 2428 assert(mask & bit > 0, 'no indicator on line %d', buffer:line_from_position(pos)) 2429 end 2430 ui.find.find_entry_text = '' 2431 ui.find.incremental = false 2432 ui.find.highlight_all_matches = highlight_all_matches 2433 buffer:close(true) 2434end 2435 2436function test_ui_find_incremental_not_found() 2437 buffer.new() 2438 buffer:set_text('foobar') 2439 ui.find.incremental = true 2440 ui.find.find_entry_text = 'b' 2441 if CURSES then events.emit(events.FIND_TEXT_CHANGED) end -- simulate 2442 assert_equal(buffer.current_pos, 4) 2443 ui.find.find_entry_text = 'bb' 2444 if CURSES then events.emit(events.FIND_TEXT_CHANGED) end -- simulate 2445 assert_equal(buffer.current_pos, 1) 2446 events.emit(events.FIND, ui.find.find_entry_text, true) -- simulate Find Next 2447 assert_equal(buffer.current_pos, 1) -- cursor did not advance 2448 ui.find.find_entry_text = '' 2449 ui.find.incremental = false 2450 buffer:close(true) 2451end 2452 2453function test_ui_find_find_in_files() 2454 ui.find.find_entry_text = 'foo' 2455 ui.find.match_case = true 2456 ui.find.find_in_files(_HOME .. '/test') 2457 assert_equal(buffer._type, _L['[Files Found Buffer]']) 2458 if #_VIEWS > 1 then view:unsplit() end 2459 local count = 0 2460 for filename, line, text in buffer:get_text():gmatch('\n([^:]+):(%d+):([^\n]+)') do 2461 assert(filename:find('^' .. _HOME .. '/test'), 'invalid filename "%s"', filename) 2462 assert(text:find('foo'), '"foo" not found in "%s"', text) 2463 count = count + 1 2464 end 2465 assert(count > 0, 'no files found') 2466 local s = buffer:indicator_end(ui.find.INDIC_FIND, 0) 2467 while true do 2468 local e = buffer:indicator_end(ui.find.INDIC_FIND, s) 2469 if e == s then break end -- no more results 2470 assert_equal(buffer:text_range(s, e), 'foo') 2471 s = buffer:indicator_end(ui.find.INDIC_FIND, e + 1) 2472 end 2473 ui.find.goto_file_found(true) -- wraps around 2474 assert_equal(#_VIEWS, 2) 2475 assert(buffer.filename, 'not in file found result') 2476 ui.goto_view(1) 2477 assert_equal(view.buffer._type, _L['[Files Found Buffer]']) 2478 local filename, line_num = view.buffer:get_sel_text():match('^([^:]+):(%d+)') 2479 ui.goto_view(-1) 2480 assert_equal(buffer.filename, filename) 2481 assert_equal(buffer:line_from_position(buffer.current_pos), tonumber(line_num)) 2482 assert_equal(buffer:get_sel_text(), 'foo') 2483 ui.goto_view(1) -- files found buffer 2484 events.emit(events.KEYPRESS, not CURSES and 0xFF0D or 343) -- \n 2485 assert_equal(buffer.filename, filename) 2486 ui.goto_view(1) -- files found buffer 2487 events.emit(events.DOUBLE_CLICK, nil, buffer:line_from_position(buffer.current_pos)) 2488 assert_equal(buffer.filename, filename) 2489 buffer:close() 2490 ui.goto_view(1) -- files found buffer 2491 ui.find.goto_file_found(nil, false) -- wraps around 2492 assert(buffer.filename and buffer.filename ~= filename, 'opened the same file') 2493 buffer:close() 2494 ui.goto_view(1) -- files found buffer 2495 ui.find.find_entry_text = '' 2496 view:unsplit() 2497 buffer:close() 2498 -- TODO: ui.find.find_in_files() -- no param 2499 2500 assert_raises(function() ui.find.find_in_files('', 1) end, 'string/table/nil expected, got number') 2501end 2502 2503function test_ui_find_find_in_files_interactive() 2504 local cwd = lfs.currentdir() 2505 lfs.chdir(_HOME) 2506 local filter = ui.find.find_in_files_filters[_HOME] 2507 ui.find.find_in_files_filters[_HOME] = nil -- ensure not set 2508 ui.find.find_entry_text = 'foo' 2509 ui.find.in_files = true 2510 ui.find.replace_entry_text = '/test' 2511 events.emit(events.FIND, ui.find.find_entry_text, true) 2512 local results = buffer:get_text() 2513 assert(results:find('Directory: '), 'directory not shown') 2514 assert(results:find('Filter: /test\n'), 'no filter defined') 2515 assert(results:find('src/foo.c'), 'foo.c not found') 2516 assert(results:find('include/foo.h'), 'foo.h not found') 2517 assert_equal(table.concat(ui.find.find_in_files_filters[_HOME], ','), '/test') 2518 buffer:clear_all() 2519 ui.find.replace_entry_text = '/test,.c' 2520 events.emit(events.FIND, ui.find.find_entry_text, true) 2521 results = buffer:get_text() 2522 assert(results:find('Filter: /test,.c\n'), 'no filter defined') 2523 assert(results:find('src/foo.c'), 'foo.c not found') 2524 assert(not results:find('include/foo.h'), 'foo.h found') 2525 assert_equal(table.concat(ui.find.find_in_files_filters[_HOME], ','), '/test,.c') 2526 if not CURSES then 2527 -- Verify save and restore of replacement text and directory filters. 2528 ui.find.focus{in_files = false} 2529 assert_equal(ui.find.in_files, false) 2530 ui.find.replace_entry_text = 'bar' 2531 ui.find.focus{in_files = true} 2532 assert_equal(ui.find.in_files, true) 2533 assert_equal(ui.find.replace_entry_text, '/test,.c') 2534 ui.find.focus{in_files = false} 2535 assert_equal(ui.find.replace_entry_text, 'bar') 2536 end 2537 ui.find.find_entry_text = '' 2538 ui.find.in_files = false 2539 buffer:close() 2540 ui.goto_view(1) 2541 view:unsplit() 2542 ui.find.find_in_files_filters[_HOME] = filter 2543 lfs.chdir(cwd) 2544end 2545 2546function test_ui_find_in_files_single_char() 2547 ui.find.find_entry_text = 'z' 2548 ui.find.find_in_files(_HOME .. '/test') 2549 ui.find.goto_file_found(true) 2550 assert_equal(buffer:get_sel_text(), 'z') 2551 ui.find.find_entry_text = '' 2552 buffer:close() 2553 ui.goto_view(1) 2554 view:unsplit() 2555 buffer:close() 2556end 2557 2558function test_ui_find_replace() 2559 buffer.new() 2560 buffer:set_text('foofoo') 2561 ui.find.find_entry_text = 'foo' 2562 ui.find.find_next() 2563 ui.find.replace_entry_text = 'bar' 2564 ui.find.replace() 2565 assert_equal(buffer.selection_start, 4) 2566 assert_equal(buffer.selection_end, buffer.selection_start + 3) 2567 assert_equal(buffer:get_sel_text(), 'foo') 2568 assert_equal(buffer:get_text(), 'barfoo') 2569 ui.find.regex = true 2570 ui.find.find_entry_text = 'f(.)\\1' 2571 ui.find.find_next() 2572 ui.find.replace_entry_text = 'b\\1\\1\\u1234' 2573 ui.find.replace() 2574 assert_equal(buffer:get_text(), 'barbooሴ') 2575 ui.find.regex = false 2576 ui.find.find_entry_text = 'quux' 2577 ui.find.find_next() 2578 ui.find.replace_entry_text = '' 2579 ui.find.replace() 2580 assert_equal(buffer:get_text(), 'barbooሴ') 2581 ui.find.find_entry_text, ui.find.replace_entry_text = '', '' 2582 buffer:close(true) 2583end 2584 2585function test_ui_find_replace_text_save_restore() 2586 if CURSES then return end -- there are focus issues in curses 2587 ui.find.focus() 2588 ui.find.find_entry_text = 'foo' 2589 ui.find.replace_entry_text = 'bar' 2590 ui.find.find_next() 2591 ui.find.focus() -- simulate activating "Find" 2592 assert_equal(ui.find.replace_entry_text, 'bar') 2593 ui.find.focus{in_files = true} -- simulate activating "Find in Files" 2594 assert(ui.find.replace_entry_text ~= 'bar', 'filter entry text not set') 2595 ui.find.focus{in_files = false} -- simulate activating "Find" 2596 assert_equal(ui.find.replace_entry_text, 'bar') 2597 ui.find.replace_entry_text = 'baz' 2598 ui.find.replace_all() 2599 ui.find.focus() -- simulate activating "Find" 2600 assert_equal(ui.find.replace_entry_text, 'baz') 2601end 2602 2603function test_ui_find_replace_all() 2604 buffer.new() 2605 local text = table.concat({ 2606 'foo', 2607 'foobar', 2608 'foobaz', 2609 'foofoo' 2610 }, '\n') 2611 buffer:set_text(text) 2612 ui.find.find_entry_text, ui.find.replace_entry_text = 'foo', 'bar' 2613 ui.find.replace_all() 2614 assert_equal(buffer:get_text(), 'bar\nbarbar\nbarbaz\nbarbar') 2615 buffer:undo() 2616 assert_equal(buffer:get_text(), text) -- verify atomic undo 2617 ui.find.regex = true 2618 buffer:set_sel(buffer:position_from_line(2), buffer:position_from_line(4) + 3) 2619 ui.find.find_entry_text, ui.find.replace_entry_text = 'f(.)\\1', 'b\\1\\1' 2620 ui.find.replace_all() -- replace in selection 2621 assert_equal(buffer:get_text(), 'foo\nboobar\nboobaz\nboofoo') 2622 ui.find.regex = false 2623 buffer:undo() 2624 ui.find.find_entry_text, ui.find.replace_entry_text = 'foo', '' 2625 ui.find.replace_all() 2626 assert_equal(buffer:get_text(), '\nbar\nbaz\n') 2627 ui.find.find_entry_text, ui.find.replace_entry_text = 'quux', '' 2628 ui.find.replace_all() 2629 assert_equal(buffer:get_text(), '\nbar\nbaz\n') 2630 ui.find.find_entry_text, ui.find.replace_entry_text = '', '' 2631 buffer:close(true) 2632end 2633 2634function test_find_replace_regex_transforms() 2635 buffer.new() 2636 buffer:set_text('foObaRbaz') 2637 ui.find.find_entry_text = 'f([oO]+)ba(..)' 2638 ui.find.regex = true 2639 local replacements = { 2640 ['f\\1ba\\2'] = 'foObaRbaz', 2641 ['f\\u\\1ba\\l\\2'] = 'fOObarbaz', 2642 ['f\\U\\1ba\\2'] = 'fOOBARBaz', 2643 ['f\\U\\1ba\\l\\2'] = 'fOOBArBaz', 2644 ['f\\U\\1\\Eba\\2'] = 'fOObaRbaz', 2645 ['f\\L\\1ba\\2'] = 'foobarbaz', 2646 ['f\\L\\1ba\\u\\2'] = 'foobaRbaz', 2647 ['f\\L\\1ba\\U\\2'] = 'foobaRBaz', 2648 ['f\\L\\1\\Eba\\2'] = 'foobaRbaz', 2649 ['f\\L\\u\\1ba\\2'] = 'fOobarbaz', 2650 ['f\\L\\u\\1ba\\U\\l\\2'] = 'fOobarBaz', 2651 ['f\\L\\u\\1\\Eba\\2'] = 'fOobaRbaz', 2652 ['f\\1ba\\U\\2'] = 'foObaRBaz', 2653 ['f\\1ba\\L\\2'] = 'foObarbaz', 2654 ['f\\1ba\\U\\l\\2'] = 'foObarBaz', 2655 [''] = 'az', 2656 ['\\0'] = 'foObaRbaz' 2657 } 2658 for regex, replacement in pairs(replacements) do 2659 ui.find.replace_entry_text = regex 2660 ui.find.find_next() 2661 ui.find.replace() 2662 assert_equal(buffer:get_text(), replacement) 2663 buffer:undo() 2664 ui.find.replace_all() 2665 assert_equal(buffer:get_text(), replacement) 2666 buffer:undo() 2667 end 2668 ui.find.find_entry_text, ui.find.replace_entry_text = '', '' 2669 ui.find.regex = false 2670 buffer:close(true) 2671end 2672 2673function test_ui_find_focus() 2674 buffer:new() 2675 buffer:append_text(' foo\n\n foo') 2676 ui.find.focus{incremental = true} 2677 ui.find.find_entry_text = 'foo' 2678 if CURSES then events.emit(events.FIND_TEXT_CHANGED) end -- simulate 2679 assert_equal(buffer:line_from_position(buffer.current_pos), 1) 2680 buffer:line_down() 2681 ui.find.focus() -- should turn off incremental find 2682 ui.find.find_entry_text = 'f' 2683 if CURSES then events.emit(events.FIND_TEXT_CHANGED) end -- simulate 2684 assert_equal(buffer:line_from_position(buffer.current_pos), 2) 2685 buffer:close(true) 2686 2687 assert_raises(function() ui.find.focus(1) end, 'table/nil expected, got number') 2688end 2689 2690function test_history() 2691 local filename1 = _HOME .. '/test/modules/textadept/history/1' 2692 io.open_file(filename1) 2693 textadept.history.clear() -- clear initial buffer switch record 2694 buffer:goto_line(5) 2695 textadept.history.back() -- should not do anything 2696 assert_equal(buffer.filename, filename1) 2697 assert_equal(buffer:line_from_position(buffer.current_pos), 5) 2698 buffer:add_text('foo') 2699 buffer:goto_line(5 + textadept.history.minimum_line_distance + 1) 2700 textadept.history.back() 2701 assert_equal(buffer.filename, filename1) 2702 assert_equal(buffer:line_from_position(buffer.current_pos), 5) 2703 assert_equal(buffer.current_pos, buffer.line_end_position[5]) 2704 textadept.history.forward() -- should stay put (no edits have been made since) 2705 assert_equal(buffer.filename, filename1) 2706 assert_equal(buffer:line_from_position(buffer.current_pos), 5) 2707 buffer:new_line() 2708 buffer:add_text('bar') -- close changes should update current history 2709 local filename2 = _HOME .. '/test/modules/textadept/history/2' 2710 io.open_file(filename2) 2711 buffer:goto_line(10) 2712 buffer:add_text('baz') 2713 textadept.history.back() -- should ignore initial file load and go back to file 1 2714 assert_equal(buffer.filename, filename1) 2715 assert_equal(buffer:line_from_position(buffer.current_pos), 6) 2716 textadept.history.back() -- should stay put (updated history from line 5 to line 6) 2717 assert_equal(buffer.filename, filename1) 2718 assert_equal(buffer:line_from_position(buffer.current_pos), 6) 2719 textadept.history.forward() 2720 assert_equal(buffer.filename, filename2) 2721 assert_equal(buffer:line_from_position(buffer.current_pos), 10) 2722 textadept.history.back() 2723 buffer:goto_line(15) 2724 buffer:clear() -- erases forward history to file 2 2725 textadept.history.forward() -- should not do anything 2726 assert_equal(buffer.filename, filename1) 2727 assert_equal(buffer:line_from_position(buffer.current_pos), 15) 2728 textadept.history.back() 2729 assert_equal(buffer.filename, filename1) 2730 assert_equal(buffer:line_from_position(buffer.current_pos), 6) 2731 textadept.history.forward() 2732 view:goto_buffer(1) 2733 assert_equal(buffer.filename, filename2) 2734 buffer:goto_line(20) 2735 buffer:add_text('quux') 2736 view:goto_buffer(-1) 2737 assert_equal(buffer.filename, filename1) 2738 buffer:undo() -- undo delete of '\n' 2739 buffer:undo() -- undo add of 'foo' 2740 buffer:redo() -- re-add 'foo' 2741 textadept.history.back() -- undo and redo should not affect history 2742 assert_equal(buffer.filename, filename2) 2743 assert_equal(buffer:line_from_position(buffer.current_pos), 20) 2744 textadept.history.back() 2745 assert_equal(buffer.filename, filename1) 2746 assert_equal(buffer:line_from_position(buffer.current_pos), 15) 2747 textadept.history.back() 2748 assert_equal(buffer.filename, filename1) 2749 assert_equal(buffer:line_from_position(buffer.current_pos), 6) 2750 buffer:target_whole_document() 2751 buffer:replace_target(string.rep('\n', buffer.line_count)) -- whole buffer replacements should not affect history (e.g. clang-format) 2752 textadept.history.forward() 2753 assert_equal(buffer.filename, filename1) 2754 assert_equal(buffer:line_from_position(buffer.current_pos), 15) 2755 view:goto_buffer(1) 2756 assert_equal(buffer.filename, filename2) 2757 buffer:close(true) 2758 textadept.history.back() -- should re-open file 2 2759 assert_equal(buffer.filename, filename2) 2760 assert_equal(buffer:line_from_position(buffer.current_pos), 20) 2761 buffer:close(true) 2762 buffer:close(true) 2763 2764 assert_raises(function() textadept.history.record(1) end, 'string/nil expected, got number') 2765 assert_raises(function() textadept.history.record('', true) end, 'number/nil expected, got boolean') 2766 assert_raises(function() textadept.history.record('', 1, '') end, 'number/nil expected, got string') 2767end 2768 2769function test_history_soft_records() 2770 local filename1 = _HOME .. '/test/modules/textadept/history/1' 2771 io.open_file(filename1) 2772 textadept.history.clear() -- clear initial buffer switch record 2773 buffer:goto_line(5) 2774 local filename2 = _HOME .. '/test/modules/textadept/history/2' 2775 io.open_file(filename2) 2776 buffer:goto_line(10) 2777 textadept.history.back() 2778 assert_equal(buffer.filename, filename1) 2779 assert_equal(buffer:line_from_position(buffer.current_pos), 5) 2780 buffer:goto_line(15) 2781 textadept.history.forward() -- should update soft record from line 5 to 15 2782 assert_equal(buffer.filename, filename2) 2783 assert_equal(buffer:line_from_position(buffer.current_pos), 10) 2784 buffer:goto_line(20) 2785 textadept.history.back() -- should update soft record from line 10 to 20 2786 assert_equal(buffer.filename, filename1) 2787 assert_equal(buffer:line_from_position(buffer.current_pos), 15) 2788 textadept.history.forward() 2789 assert_equal(buffer.filename, filename2) 2790 assert_equal(buffer:line_from_position(buffer.current_pos), 20) 2791 buffer:goto_line(10) 2792 buffer:add_text('foo') -- should update soft record from line 20 to 10 and make it hard 2793 textadept.history.back() 2794 assert_equal(buffer.filename, filename1) 2795 assert_equal(buffer:line_from_position(buffer.current_pos), 15) 2796 textadept.history.forward() 2797 assert_equal(buffer.filename, filename2) 2798 assert_equal(buffer:line_from_position(buffer.current_pos), 10) 2799 buffer:goto_line(20) 2800 buffer:add_text('bar') -- should create a new record 2801 textadept.history.back() 2802 assert_equal(buffer.filename, filename2) 2803 assert_equal(buffer:line_from_position(buffer.current_pos), 10) 2804 buffer:close(true) 2805 buffer:close(true) 2806end 2807 2808function test_history_per_view() 2809 local filename1 = _HOME .. '/test/modules/textadept/history/1' 2810 io.open_file(filename1) 2811 textadept.history.clear() -- clear initial buffer switch record 2812 buffer:goto_line(5) 2813 buffer:add_text('foo') 2814 buffer:goto_line(10) 2815 buffer:add_text('bar') 2816 view:split() 2817 textadept.history.back() -- no history for this view 2818 assert_equal(buffer.filename, filename1) 2819 assert_equal(buffer:line_from_position(buffer.current_pos), 10) 2820 local filename2 = _HOME .. '/test/modules/textadept/history/2' 2821 io.open_file(filename2) 2822 buffer:goto_line(15) 2823 buffer:add_text('baz') 2824 buffer:goto_line(20) 2825 textadept.history.back() 2826 assert_equal(buffer.filename, filename2) 2827 assert_equal(buffer:line_from_position(buffer.current_pos), 15) 2828 textadept.history.back() 2829 assert_equal(buffer.filename, filename1) 2830 assert_equal(buffer:line_from_position(buffer.current_pos), 10) 2831 textadept.history.back() -- no more history for this view 2832 assert_equal(buffer.filename, filename1) 2833 assert_equal(buffer:line_from_position(buffer.current_pos), 10) 2834 ui.goto_view(-1) 2835 textadept.history.back() 2836 assert_equal(buffer.filename, filename1) 2837 assert_equal(buffer:line_from_position(buffer.current_pos), 5) 2838 textadept.history.forward() 2839 assert_equal(buffer.filename, filename1) 2840 assert_equal(buffer:line_from_position(buffer.current_pos), 10) 2841 textadept.history.forward() -- no more history for this view 2842 assert_equal(buffer.filename, filename1) 2843 assert_equal(buffer:line_from_position(buffer.current_pos), 10) 2844 view:unsplit() 2845 view:goto_buffer(1) 2846 buffer:close(true) 2847 buffer:close(true) 2848end 2849 2850function test_history_print_buffer() 2851 local tabs = ui.tabs 2852 ui.tabs = true 2853 ui.print('foo') 2854 textadept.history.back() 2855 assert(buffer._type ~= _L['[Message Buffer]']) 2856 textadept.history.forward() 2857 assert_equal(buffer._type, _L['[Message Buffer]']) 2858 buffer:close() 2859 ui.tabs = tabs -- restore 2860end 2861 2862function test_macro_record_play_save_load() 2863 textadept.macros.save() -- should not do anything 2864 textadept.macros.play() -- should not do anything 2865 assert_equal(#_BUFFERS, 1) 2866 assert(not buffer.modify, 'a macro was played') 2867 2868 textadept.macros.record() 2869 events.emit(events.MENU_CLICKED, 1) -- File > New 2870 buffer:add_text('f') 2871 events.emit(events.CHAR_ADDED, string.byte('f')) 2872 events.emit(events.FIND, 'f', true) 2873 events.emit(events.REPLACE, 'b') 2874 buffer:replace_sel('a') -- typing would do this 2875 events.emit(events.CHAR_ADDED, string.byte('a')) 2876 buffer:add_text('r') 2877 events.emit(events.CHAR_ADDED, string.byte('r')) 2878 events.emit(events.KEYPRESS, string.byte('t'), false, true) -- transpose 2879 textadept.macros.play() -- should not do anything 2880 textadept.macros.save() -- should not do anything 2881 textadept.macros.load() -- should not do anything 2882 textadept.macros.record() -- stop 2883 assert_equal(#_BUFFERS, 2) 2884 assert_equal(buffer:get_text(), 'ra') 2885 buffer:close(true) 2886 textadept.macros.play() 2887 assert_equal(#_BUFFERS, 2) 2888 assert_equal(buffer:get_text(), 'ra') 2889 buffer:close(true) 2890 local filename = os.tmpname() 2891 textadept.macros.save(filename) 2892 textadept.macros.record() 2893 textadept.macros.record() 2894 textadept.macros.load(filename) 2895 textadept.macros.play() 2896 assert_equal(#_BUFFERS, 2) 2897 assert_equal(buffer:get_text(), 'ra') 2898 buffer:close(true) 2899 os.remove(filename) 2900 2901 assert_raises(function() textadept.macros.save(1) end, 'string/nil expected, got number') 2902 assert_raises(function() textadept.macros.load(1) end, 'string/nil expected, got number') 2903end 2904 2905function test_macro_record_play_with_keys_only() 2906 if keys.f9 ~= textadept.macros.record then 2907 print('Note: not running since F9 does not toggle macro recording') 2908 return 2909 end 2910 buffer.new() 2911 buffer:append_text('foo\nbar\nbaz\n') 2912 events.emit(events.KEYPRESS, 0xFFC6) -- f9; start recording 2913 events.emit(events.KEYPRESS, not CURSES and 0xFF57 or 305) -- end 2914 events.emit(events.KEYPRESS, not CURSES and 0xFF0D or 13) -- \n 2915 buffer:new_line() 2916 events.emit(events.KEYPRESS, not CURSES and 0xFF54 or 300) -- down 2917 events.emit(events.KEYPRESS, 0xFFC6) -- f9; stop recording 2918 assert_equal(buffer:get_text(), 'foo\n\nbar\nbaz\n') 2919 assert_equal(buffer.current_pos, buffer:position_from_line(3)) 2920 if not CURSES then 2921 events.emit(events.KEYPRESS, 0xFFC6, true) -- sf9; play 2922 else 2923 events.emit(events.KEYPRESS, 0xFFC7) -- f10; play 2924 end 2925 assert_equal(buffer:get_text(), 'foo\n\nbar\n\nbaz\n') 2926 assert_equal(buffer.current_pos, buffer:position_from_line(5)) 2927 if not CURSES then 2928 events.emit(events.KEYPRESS, 0xFFC6, true) -- sf9; play 2929 else 2930 events.emit(events.KEYPRESS, 0xFFC7) -- f10; play 2931 end 2932 assert_equal(buffer:get_text(), 'foo\n\nbar\n\nbaz\n\n') 2933 assert_equal(buffer.current_pos, buffer:position_from_line(7)) 2934 buffer:close(true) 2935end 2936 2937function test_menu_menu_functions() 2938 buffer.new() 2939 textadept.menu.menubar[_L['Buffer']][_L['Indentation']][_L['Tab width: 8']][2]() 2940 assert_equal(buffer.tab_width, 8) 2941 textadept.menu.menubar[_L['Buffer']][_L['EOL Mode']][_L['CRLF']][2]() 2942 assert_equal(buffer.eol_mode, buffer.EOL_CRLF) 2943 textadept.menu.menubar[_L['Buffer']][_L['Encoding']][_L['CP-1252 Encoding']][2]() 2944 assert_equal(buffer.encoding, 'CP1252') 2945 buffer:set_text('foo') 2946 textadept.menu.menubar[_L['Edit']][_L['Delete Word']][2]() 2947 assert_equal(buffer:get_text(), '') 2948 buffer:set_text('(foo)') 2949 textadept.menu.menubar[_L['Edit']][_L['Match Brace']][2]() 2950 assert_equal(buffer.char_at[buffer.current_pos], string.byte(')')) 2951 buffer:set_text('foo f') 2952 buffer:line_end() 2953 textadept.menu.menubar[_L['Edit']][_L['Complete Word']][2]() 2954 assert_equal(buffer:get_text(), 'foo foo') 2955 buffer:set_text('2\n1\n3\n') 2956 textadept.menu.menubar[_L['Edit']][_L['Filter Through']][2]() 2957 ui.command_entry:set_text('sort') 2958 events.emit(events.KEYPRESS, not CURSES and 0xFF0D or 343) -- \n 2959 assert_equal(buffer:get_text(), '1\n2\n3\n') 2960 buffer:set_text('foo') 2961 buffer:line_end() 2962 textadept.menu.menubar[_L['Edit']][_L['Selection']][_L['Enclose as XML Tags']][2]() 2963 assert_equal(buffer:get_text(), '<foo></foo>') 2964 assert_equal(buffer.current_pos, 6) 2965 buffer:undo() 2966 assert_equal(buffer:get_text(), 'foo') -- verify atomic undo 2967 textadept.menu.menubar[_L['Edit']][_L['Selection']][_L['Enclose as Single XML Tag']][2]() 2968 assert_equal(buffer:get_text(), '<foo />') 2969 assert_equal(buffer.current_pos, buffer.line_end_position[1]) 2970 if not CURSES then -- there are focus issues in curses 2971 textadept.menu.menubar[_L['Search']][_L['Find in Files']][2]() 2972 assert(ui.find.in_files, 'not finding in files') 2973 textadept.menu.menubar[_L['Search']][_L['Find']][2]() 2974 assert(not ui.find.in_files, 'finding in files') 2975 end 2976 buffer:clear_all() 2977 buffer:set_lexer('lua') 2978 buffer:add_text('string.') 2979 textadept.menu.menubar[_L['Tools']][_L['Complete Symbol']][2]() 2980 assert(buffer:auto_c_active(), 'no autocompletions') 2981 assert_equal(buffer.auto_c_current_text, 'byte') 2982 buffer:auto_c_cancel() 2983 buffer:char_left() 2984 textadept.menu.menubar[_L['Tools']][_L['Show Style']][2]() 2985 assert(view:call_tip_active(), 'style not shown') 2986 view:call_tip_cancel() 2987 local use_tabs = buffer.use_tabs 2988 textadept.menu.menubar[_L['Buffer']][_L['Indentation']][_L['Toggle Use Tabs']][2]() 2989 assert(buffer.use_tabs ~= use_tabs, 'use tabs not toggled') 2990 local wrap_mode = view.wrap_mode 2991 textadept.menu.menubar[_L['Buffer']][_L['Toggle Wrap Mode']][2]() 2992 assert(view.wrap_mode ~= wrap_mode, 'wrap mode not toggled') 2993 local view_whitespace = view.view_ws 2994 textadept.menu.menubar[_L['Buffer']][_L['Toggle View Whitespace']][2]() 2995 assert(view.view_ws ~= view_whitespace, 'view whitespace not toggled') 2996 view:split() 2997 ui.update() 2998 local size = view.size 2999 textadept.menu.menubar[_L['View']][_L['Grow View']][2]() 3000 assert(view.size > size, 'view shrunk') 3001 textadept.menu.menubar[_L['View']][_L['Shrink View']][2]() 3002 assert_equal(view.size, size) 3003 view:unsplit() 3004 buffer:set_text('if foo then\n bar\nend') 3005 buffer:colorize(1, -1) 3006 textadept.menu.menubar[_L['View']][_L['Toggle Current Fold']][2]() 3007 assert_equal(view.fold_expanded[buffer:line_from_position(buffer.current_pos)], false) 3008 local indentation_guides = view.indentation_guides 3009 textadept.menu.menubar[_L['View']][_L['Toggle Show Indent Guides']][2]() 3010 assert(view.indentation_guides ~= indentation_guides, 'indentation guides not toggled') 3011 local virtual_space = buffer.virtual_space_options 3012 textadept.menu.menubar[_L['View']][_L['Toggle Virtual Space']][2]() 3013 assert(buffer.virtual_space_options ~= virtual_space, 'virtual space not toggled') 3014 buffer:close(true) 3015end 3016 3017function test_menu_functions_interactive() 3018 buffer.new() 3019 textadept.menu.menubar[_L['Help']][_L['About']][2]() 3020 buffer:close(true) 3021end 3022 3023function test_menu_select_command_interactive() 3024 local num_buffers = #_BUFFERS 3025 textadept.menu.select_command() 3026 assert(#_BUFFERS > num_buffers, 'new buffer not created') 3027 buffer:close() 3028end 3029 3030function test_run_compile_run() 3031 textadept.run.compile() -- should not do anything 3032 textadept.run.run() -- should not do anything 3033 assert_equal(#_BUFFERS, 1) 3034 assert(not buffer.modify, 'a command was run') 3035 3036 local compile_file = _HOME .. '/test/modules/textadept/run/compile.lua' 3037 textadept.run.compile(compile_file) 3038 assert_equal(#_BUFFERS, 2) 3039 assert_equal(buffer._type, _L['[Message Buffer]']) 3040 ui.update() -- process output 3041 assert(buffer:get_text():find("'end' expected"), 'no compile error') 3042 assert(buffer:get_text():find('> exit status: 256'), 'no compile error') 3043 if #_VIEWS > 1 then view:unsplit() end 3044 textadept.run.goto_error(true) -- wraps 3045 assert_equal(#_VIEWS, 2) 3046 assert_equal(buffer.filename, compile_file) 3047 assert_equal(buffer:line_from_position(buffer.current_pos), 3) 3048 assert(buffer.annotation_text[3]:find("'end' expected"), 'annotation not visible') 3049 ui.goto_view(1) -- message buffer 3050 assert_equal(buffer._type, _L['[Message Buffer]']) 3051 assert(buffer:get_sel_text():find("'end' expected"), 'compile error not selected') 3052 assert(buffer:marker_get(buffer:line_from_position(buffer.current_pos)) & 1 << textadept.run.MARK_ERROR - 1 > 0) 3053 events.emit(events.KEYPRESS, not CURSES and 0xFF0D or 343) -- \n 3054 assert_equal(buffer.filename, compile_file) 3055 ui.goto_view(1) -- message buffer 3056 events.emit(events.DOUBLE_CLICK, nil, buffer:line_from_position(buffer.current_pos)) 3057 assert_equal(buffer.filename, compile_file) 3058 local compile_command = textadept.run.compile_commands.lua 3059 textadept.run.compile() -- clears annotation 3060 ui.update() -- process output 3061 view:goto_buffer(1) 3062 assert(not buffer.annotation_text[3]:find("'end' expected"), 'annotation visible') 3063 buffer:close() -- compile_file 3064 3065 local run_file = _HOME .. '/test/modules/textadept/run/run.lua' 3066 textadept.run.run_commands[run_file] = function() 3067 return textadept.run.run_commands.lua, run_file:match('^(.+[/\\])') -- intentional trailing '/' 3068 end 3069 io.open_file(run_file) 3070 textadept.run.run() 3071 assert_equal(buffer._type, _L['[Message Buffer]']) 3072 ui.update() -- process output 3073 assert(buffer:get_text():find('attempt to call a nil value'), 'no run error') 3074 textadept.run.goto_error(false) 3075 assert_equal(buffer.filename, run_file) 3076 assert_equal(buffer:line_from_position(buffer.current_pos), 2) 3077 textadept.run.goto_error(nil, false) 3078 assert_equal(buffer.filename, run_file) 3079 assert_equal(buffer:line_from_position(buffer.current_pos), 1) 3080 ui.goto_view(1) 3081 assert(buffer:marker_get(buffer:line_from_position(buffer.current_pos)) & 1 << textadept.run.MARK_WARNING - 1 > 0) 3082 ui.goto_view(-1) 3083 textadept.run.goto_error(false) 3084 assert_equal(buffer.filename, compile_file) 3085 if #_VIEWS > 1 then view:unsplit() end 3086 buffer:close() -- compile_file 3087 buffer:close() -- run_file 3088 buffer:close() -- message buffer 3089 3090 assert_raises(function() textadept.run.compile({}) end, 'string/nil expected, got table') 3091 assert_raises(function() textadept.run.run({}) end, 'string/nil expected, got table') 3092end 3093 3094function test_run_set_arguments() 3095 local lua_run_command = textadept.run.run_commands.lua 3096 local lua_compile_command = textadept.run.compile_commands.lua 3097 3098 buffer.new() 3099 buffer.filename = '/tmp/test.lua' 3100 textadept.run.set_arguments(nil, '-i', '-p') 3101 assert_equal(textadept.run.run_commands[buffer.filename], lua_run_command .. ' -i') 3102 assert_equal(textadept.run.compile_commands[buffer.filename], lua_compile_command .. ' -p') 3103 textadept.run.set_arguments(buffer.filename, '', '') 3104 assert_equal(textadept.run.run_commands[buffer.filename], lua_run_command .. ' ') 3105 assert_equal(textadept.run.compile_commands[buffer.filename], lua_compile_command .. ' ') 3106 buffer:close(true) 3107 3108 assert_raises(function() textadept.run.set_arguments(1) end, 'string/nil expected, got number') 3109 assert_raises(function() textadept.run.set_arguments('', true) end, 'string/nil expected, got boolean') 3110 assert_raises(function() textadept.run.set_arguments('', '', {}) end, 'string/nil expected, got table') 3111end 3112 3113function test_run_set_arguments_interactive() 3114 local lua_run_command = textadept.run.run_commands.lua 3115 local lua_compile_command = textadept.run.compile_commands.lua 3116 buffer.new() 3117 buffer.filename = '/tmp/test.lua' 3118 textadept.run.set_arguments(nil, '-i', '-p') 3119 textadept.run.set_arguments() 3120 assert_equal(textadept.run.run_commands[buffer.filename], lua_run_command .. ' -i') 3121 assert_equal(textadept.run.compile_commands[buffer.filename], lua_compile_command .. ' -p') 3122 buffer:close(true) 3123end 3124 3125function test_run_build() 3126 textadept.run.build_commands[_HOME] = function() 3127 return 'lua modules/textadept/run/build.lua', _HOME .. '/test/' -- intentional trailing '/' 3128 end 3129 textadept.run.stop() -- should not do anything 3130 textadept.run.build(_HOME) 3131 if #_VIEWS > 1 then view:unsplit() end 3132 assert_equal(buffer._type, _L['[Message Buffer]']) 3133 os.execute('sleep 0.1') -- ensure process is running 3134 buffer:add_text('foo') 3135 buffer:new_line() -- should send previous line as stdin 3136 os.execute('sleep 0.1') -- ensure process processed stdin 3137 textadept.run.stop() 3138 ui.update() -- process output 3139 assert(buffer:get_text():find('> cd '), 'did not change directory') 3140 assert(buffer:get_text():find('build%.lua'), 'did not run build command') 3141 assert(buffer:get_text():find('read "foo"'), 'did not send stdin') 3142 assert(buffer:get_text():find('> exit status: 9'), 'build not stopped') 3143 textadept.run.stop() -- should not do anything 3144 buffer:close() 3145 -- TODO: chdir(_HOME) and textadept.run.build() -- no param. 3146 -- TODO: project whose makefile is autodetected. 3147end 3148 3149function test_run_test() 3150 textadept.run.test_commands[_HOME] = function() 3151 return 'lua modules/textadept/run/test.lua', _HOME .. '/test/' -- intentional trailing '/' 3152 end 3153 textadept.run.test(_HOME) 3154 if #_VIEWS > 1 then view:unsplit() end 3155 ui.update() -- process output 3156 assert(buffer:get_text():find('test%.lua'), 'did not run test command') 3157 assert(buffer:get_text():find('assertion failed!'), 'assertion failure not detected') 3158 buffer:close() 3159end 3160 3161function test_run_goto_internal_lua_error() 3162 xpcall(error, function(message) events.emit(events.ERROR, debug.traceback(message)) end, 'internal error', 2) 3163 if #_VIEWS > 1 then view:unsplit() end 3164 textadept.run.goto_error(1) 3165 assert(buffer.filename:find('/test/test%.lua$'), 'did not detect internal Lua error') 3166 view:unsplit() 3167 buffer:close() 3168 buffer:close() 3169end 3170 3171function test_run_commands_function() 3172 local filename = os.tmpname() 3173 io.open_file(filename) 3174 textadept.run.run_commands.text = function() 3175 return [[lua -e 'print(os.getenv("FOO"))']], '/tmp', {FOO = 'bar'} 3176 end 3177 textadept.run.run() 3178 assert_equal(#_BUFFERS, 3) -- including [Test Output] 3179 assert_equal(buffer._type, _L['[Message Buffer]']) 3180 ui.update() -- process output 3181 assert(buffer:get_text():find('> cd /tmp'), 'cwd not set properly') 3182 assert(buffer:get_text():find('bar'), 'env not set properly') 3183 if #_VIEWS > 1 then view:unsplit() end 3184 buffer:close() 3185 buffer:close() 3186 textadept.run.run_commands.text = nil -- reset 3187 os.remove(filename) 3188end 3189 3190-- TODO: test textadept.run.run_in_background 3191 3192function test_session_save() 3193 local handler = function(session) 3194 session.baz = true 3195 session.quux = assert 3196 session.foobar = buffer.doc_pointer 3197 session.foobaz = coroutine.create(function() end) 3198 end 3199 events.connect(events.SESSION_SAVE, handler) 3200 buffer.new() 3201 buffer.filename = 'foo.lua' 3202 textadept.bookmarks.toggle() 3203 view:split() 3204 buffer.new() 3205 buffer.filename = 'bar.lua' 3206 local session_file = os.tmpname() 3207 textadept.session.save(session_file) 3208 local session = assert(loadfile(session_file, 't', {}))() 3209 assert_equal(session.buffers[#session.buffers - 1].filename, 'foo.lua') 3210 assert_equal(session.buffers[#session.buffers - 1].bookmarks, {1}) 3211 assert_equal(session.buffers[#session.buffers].filename, 'bar.lua') 3212 assert_equal(session.ui.maximized, false) 3213 assert_equal(type(session.views[1]), 'table') 3214 assert_equal(session.views[1][1], #_BUFFERS - 1) 3215 assert_equal(session.views[1][2], #_BUFFERS) 3216 assert(not session.views[1].vertical, 'split vertical') 3217 assert(session.views[1].size > 1, 'split size not set properly') 3218 assert_equal(session.views.current, #_VIEWS) 3219 assert_equal(session.baz, true) 3220 assert(not session.quux, 'function serialized') 3221 assert(not session.foobar, 'userdata serialized') 3222 assert(not session.foobaz, 'thread serialized') 3223 view:unsplit() 3224 buffer:close() 3225 buffer:close() 3226 os.remove(session_file) 3227 events.disconnect(events.SESSION_SAVE, handler) 3228end 3229 3230function test_session_save_before_load() 3231 local test_output_text = buffer:get_text() 3232 local foo = os.tmpname() 3233 local bar = os.tmpname() 3234 local baz = os.tmpname() 3235 buffer.new() 3236 buffer.filename = foo 3237 local session1 = os.tmpname() 3238 textadept.session.save(session1) 3239 buffer:close() 3240 buffer.new() 3241 buffer.filename = bar 3242 local session2 = os.tmpname() 3243 textadept.session.save(session2) 3244 buffer.new() 3245 buffer.filename = baz 3246 textadept.session.load(session1) -- should save baz to session 3247 assert_equal(#_BUFFERS, 1 + 1) -- test output buffer is open 3248 assert_equal(buffer.filename, foo) 3249 for i = 1, 2 do 3250 textadept.session.load(session2) -- when i == 2, reload; should not re-save 3251 assert_equal(#_BUFFERS, 2 + 1) -- test output buffer is open 3252 assert_equal(_BUFFERS[#_BUFFERS - 1].filename, bar) 3253 assert_equal(_BUFFERS[#_BUFFERS].filename, baz) 3254 buffer:close() 3255 buffer:close() 3256 end 3257 os.remove(foo) 3258 os.remove(bar) 3259 os.remove(baz) 3260 os.remove(session1) 3261 os.remove(session2) 3262 buffer:add_text(test_output_text) 3263end 3264 3265function test_snippets_find_snippet() 3266 snippets.foo = 'bar' 3267 textadept.snippets.paths[1] = _HOME .. '/test/modules/textadept/snippets' 3268 3269 buffer.new() 3270 buffer:add_text('foo') 3271 assert(textadept.snippets.insert() == nil, 'snippet not inserted') 3272 assert_equal(buffer:get_text(), 'bar') -- from snippets 3273 textadept.snippets.insert() 3274 assert_equal(buffer:get_text(), 'baz\n') -- from bar file 3275 buffer:delete_back() 3276 textadept.snippets.insert() 3277 assert_equal(buffer:get_text(), 'quux\n') -- from baz.txt file 3278 buffer:delete_back() 3279 assert(not textadept.snippets.insert(), 'snippet inserted') 3280 assert_equal(buffer:get_text(), 'quux') 3281 buffer:clear_all() 3282 buffer:set_lexer('lua') -- prefer lexer-specific snippets 3283 snippets.lua = {foo = 'baz'} -- overwrite language module 3284 buffer:add_text('foo') 3285 textadept.snippets.insert() 3286 assert_equal(buffer:get_text(), 'baz') -- from snippets.lua 3287 textadept.snippets.insert() 3288 assert_equal(buffer:get_text(), 'bar\n') -- from lua.baz.lua file 3289 buffer:delete_back() 3290 textadept.snippets.insert() 3291 assert_equal(buffer:get_text(), 'quux\n') -- from lua.bar file 3292 buffer:close(true) 3293 3294 snippets.foo = nil 3295 table.remove(textadept.snippets.paths, 1) 3296end 3297 3298function test_snippets_match_indentation() 3299 local snippet = '\t foo' 3300 local multiline_snippet = table.concat({ 3301 'foo', 3302 '\tbar', 3303 '\t baz', 3304 'quux' 3305 }, '\n') 3306 buffer.new() 3307 3308 buffer.use_tabs, buffer.tab_width, buffer.eol_mode = true, 4, buffer.EOL_CRLF 3309 textadept.snippets.insert(snippet) 3310 assert_equal(buffer:get_text(), '\t\tfoo') 3311 buffer:clear_all() 3312 buffer:add_text('\t') 3313 textadept.snippets.insert(snippet) 3314 assert_equal(buffer:get_text(), '\t\t\tfoo') 3315 buffer:clear_all() 3316 buffer:add_text('\t') 3317 textadept.snippets.insert(multiline_snippet) 3318 assert_equal(buffer:get_text(), table.concat({ 3319 '\tfoo', 3320 '\t\tbar', 3321 '\t\t\tbaz', 3322 '\tquux' 3323 }, '\r\n')) 3324 buffer:clear_all() 3325 3326 buffer.use_tabs, buffer.tab_width, buffer.eol_mode = false, 2, buffer.EOL_LF 3327 textadept.snippets.insert(snippet) 3328 assert_equal(buffer:get_text(), ' foo') 3329 buffer:clear_all() 3330 buffer:add_text(' ') 3331 textadept.snippets.insert(snippet) 3332 assert_equal(buffer:get_text(), ' foo') 3333 buffer:clear_all() 3334 buffer:add_text(' ') 3335 textadept.snippets.insert(multiline_snippet) 3336 assert_equal(buffer:get_text(), table.concat({ 3337 ' foo', 3338 ' bar', 3339 ' baz', 3340 ' quux' 3341 }, '\n')) 3342 buffer:close(true) 3343 3344 assert_raises(function() textadept.snippets.insert(true) end, 'string/nil expected, got boolean') 3345end 3346 3347function test_snippets_placeholders() 3348 buffer.new() 3349 local lua_date = os.date() 3350 local p = io.popen('date') 3351 local shell_date = p:read() 3352 p:close() 3353 textadept.snippets.insert(table.concat({ 3354 '%0placeholder: %1(foo) %2(bar)', 3355 'choice: %3{baz,quux}', 3356 'mirror: %2%3', 3357 'Lua: %<os.date()> %1<text:upper()>', 3358 'Shell: %[date] %1[echo %]', 3359 'escape: %%1 %4%( %4%{', 3360 }, '\n')) 3361 assert_equal(buffer.selections, 1) 3362 assert_equal(buffer.selection_start, 1 + 14) 3363 assert_equal(buffer.selection_end, buffer.selection_start + 3) 3364 assert_equal(buffer:get_sel_text(), 'foo') 3365 buffer:replace_sel('baz') 3366 events.emit(events.UPDATE_UI, buffer.UPDATE_CONTENT + buffer.UPDATE_SELECTION) -- simulate typing 3367 assert_equal(buffer:get_text(), string.format(table.concat({ 3368 ' placeholder: baz bar', -- placeholders to visit have 1 empty space 3369 'choice: ', -- placeholder choices are initially empty 3370 'mirror: ', -- placeholder mirrors are initially empty 3371 'Lua: %s BAZ', -- verify real-time transforms 3372 'Shell: %s baz', -- verify real-time transforms 3373 'escape: %%1 ( { ' -- trailing space for snippet sentinel 3374 }, '\n'), lua_date, shell_date)) 3375 textadept.snippets.insert() 3376 assert_equal(buffer.selections, 2) 3377 assert_equal(buffer.selection_start, 1 + 18) 3378 assert_equal(buffer.selection_end, buffer.selection_start + 3) 3379 for i = 1, buffer.selections do 3380 assert_equal(buffer.selection_n_end[i], buffer.selection_n_start[i] + 3) 3381 assert_equal(buffer:text_range(buffer.selection_n_start[i], buffer.selection_n_end[i]), 'bar') 3382 end 3383 assert(buffer:get_text():find('mirror: bar'), 'mirror not updated') 3384 textadept.snippets.insert() 3385 assert_equal(buffer.selections, 2) 3386 assert(buffer:auto_c_active(), 'no choice') 3387 buffer:auto_c_select('quux') 3388 buffer:auto_c_complete() 3389 assert(buffer:get_text():find('\nmirror: barquux\n'), 'choice mirror not updated') 3390 textadept.snippets.insert() 3391 assert_equal(buffer.selection_start, buffer.selection_end) -- no default placeholder (escaped) 3392 textadept.snippets.insert() 3393 assert_equal(buffer:get_text(), string.format(table.concat({ 3394 'placeholder: baz bar', 3395 'choice: quux', 3396 'mirror: barquux', 3397 'Lua: %s BAZ', 3398 'Shell: %s baz', 3399 'escape: %%1 ( {' 3400 }, '\n'), lua_date, shell_date)) 3401 assert_equal(buffer.selection_start, 1) 3402 assert_equal(buffer.selection_start, 1) 3403 buffer:close(true) 3404end 3405 3406function test_snippets_irregular_placeholders() 3407 buffer.new() 3408 textadept.snippets.insert('%1(foo %2(bar))%5(quux)') 3409 assert_equal(buffer:get_sel_text(), 'foo bar') 3410 buffer:delete_back() 3411 textadept.snippets.insert() 3412 assert_equal(buffer:get_sel_text(), 'quux') 3413 textadept.snippets.insert() 3414 assert_equal(buffer:get_text(), 'quux') 3415 buffer:close(true) 3416end 3417 3418function test_snippets_previous_cancel() 3419 buffer.new() 3420 textadept.snippets.insert('%1(foo) %2(bar) %3(baz)') 3421 assert_equal(buffer:get_text(), 'foo bar baz ') -- trailing space for snippet sentinel 3422 buffer:delete_back() 3423 textadept.snippets.insert() 3424 assert_equal(buffer:get_text(), ' bar baz ') 3425 buffer:delete_back() 3426 textadept.snippets.insert() 3427 assert_equal(buffer:get_text(), ' baz ') 3428 textadept.snippets.previous() 3429 textadept.snippets.previous() 3430 assert_equal(buffer:get_text(), 'foo bar baz ') 3431 assert_equal(buffer:get_sel_text(), 'foo') 3432 textadept.snippets.insert() 3433 textadept.snippets.cancel_current() 3434 assert_equal(buffer.length, 0) 3435 buffer:close(true) 3436end 3437 3438function test_snippets_nested() 3439 snippets.foo = '%1(foo)%2(bar)%3(baz)' 3440 buffer.new() 3441 3442 buffer:add_text('foo') 3443 textadept.snippets.insert() 3444 buffer:char_right() 3445 textadept.snippets.insert() 3446 assert_equal(buffer:get_text(), 'foobarbaz barbaz ') -- trailing spaces for snippet sentinels 3447 assert_equal(buffer:get_sel_text(), 'foo') 3448 assert_equal(buffer.selection_start, 1) 3449 assert_equal(buffer.selection_end, buffer.selection_start + 3) 3450 buffer:replace_sel('quux') 3451 textadept.snippets.insert() 3452 assert_equal(buffer:get_sel_text(), 'bar') 3453 assert_equal(buffer.selection_start, 1 + 4) 3454 assert_equal(buffer.selection_end, buffer.selection_start + 3) 3455 textadept.snippets.insert() 3456 assert_equal(buffer:get_sel_text(), 'baz') 3457 assert_equal(buffer.selection_start, 1 + 7) 3458 assert_equal(buffer.selection_end, buffer.selection_start + 3) 3459 textadept.snippets.insert() 3460 assert_equal(buffer.current_pos, 1 + 10) 3461 assert_equal(buffer.selection_start, buffer.selection_end) 3462 assert_equal(buffer:get_text(), 'quuxbarbazbarbaz ') 3463 textadept.snippets.insert() 3464 assert_equal(buffer:get_sel_text(), 'bar') 3465 assert_equal(buffer.selection_start, 1 + 10) 3466 assert_equal(buffer.selection_end, buffer.selection_start + 3) 3467 textadept.snippets.insert() 3468 assert_equal(buffer:get_sel_text(), 'baz') 3469 assert_equal(buffer.selection_start, 1 + 13) 3470 assert_equal(buffer.selection_end, buffer.selection_start + 3) 3471 textadept.snippets.insert() 3472 assert_equal(buffer:get_text(), 'quuxbarbazbarbaz') 3473 buffer:clear_all() 3474 3475 buffer:add_text('foo') 3476 textadept.snippets.insert() 3477 buffer:char_right() 3478 textadept.snippets.insert() 3479 textadept.snippets.cancel_current() 3480 assert_equal(buffer.current_pos, 1 + 3) 3481 assert_equal(buffer.selection_start, buffer.selection_end) 3482 assert_equal(buffer:get_text(), 'foobarbaz ') 3483 buffer:add_text('quux') 3484 assert_equal(buffer:get_text(), 'fooquuxbarbaz ') 3485 textadept.snippets.insert() 3486 assert_equal(buffer:get_sel_text(), 'bar') 3487 assert_equal(buffer.selection_start, 1 + 7) 3488 assert_equal(buffer.selection_end, buffer.selection_start + 3) 3489 textadept.snippets.insert() 3490 assert_equal(buffer:get_sel_text(), 'baz') 3491 assert_equal(buffer.selection_start, 1 + 10) 3492 assert_equal(buffer.selection_end, buffer.selection_start + 3) 3493 textadept.snippets.insert() 3494 assert_equal(buffer.current_pos, buffer.line_end_position[1]) 3495 assert_equal(buffer.selection_start, buffer.selection_end) 3496 assert_equal(buffer:get_text(), 'fooquuxbarbaz') 3497 3498 buffer:close(true) 3499 snippets.foo = nil 3500end 3501 3502function test_snippets_select_interactive() 3503 snippets.foo = 'bar' 3504 buffer.new() 3505 textadept.snippets.select() 3506 assert(buffer.length > 0, 'no snippet inserted') 3507 buffer:close(true) 3508 snippets.foo = nil 3509end 3510 3511function test_snippets_autocomplete() 3512 snippets.bar = 'baz' 3513 snippets.baz = 'quux' 3514 buffer.new() 3515 buffer:add_text('ba') 3516 textadept.editing.autocomplete('snippet') 3517 assert(buffer:auto_c_active(), 'snippet autocompletion list not shown') 3518 buffer:auto_c_complete() 3519 textadept.snippets.insert() 3520 assert_equal(buffer:get_text(), 'baz') 3521 buffer:close(true) 3522 snippets.bar = nil 3523 snippets.baz = nil 3524end 3525 3526function test_lua_autocomplete() 3527 buffer.new() 3528 buffer:set_lexer('lua') 3529 3530 buffer:add_text('raw') 3531 textadept.editing.autocomplete('lua') 3532 assert(buffer:auto_c_active(), 'no autocompletions') 3533 assert_equal(buffer.auto_c_current_text, 'rawequal') 3534 buffer:auto_c_cancel() 3535 buffer:clear_all() 3536 3537 buffer:add_text('string.') 3538 textadept.editing.autocomplete('lua') 3539 assert(buffer:auto_c_active(), 'no autocompletions') 3540 assert_equal(buffer.auto_c_current_text, 'byte') 3541 buffer:auto_c_cancel() 3542 buffer:clear_all() 3543 3544 buffer:add_text('s = "foo"\ns:') 3545 textadept.editing.autocomplete('lua') 3546 assert(buffer:auto_c_active(), 'no autocompletions') 3547 assert_equal(buffer.auto_c_current_text, 'byte') 3548 buffer:auto_c_cancel() 3549 buffer:clear_all() 3550 3551 buffer:add_text('f = io.open("path")\nf:') 3552 textadept.editing.autocomplete('lua') 3553 assert(buffer:auto_c_active(), 'no autocompletions') 3554 assert_equal(buffer.auto_c_current_text, 'close') 3555 buffer:auto_c_cancel() 3556 buffer:clear_all() 3557 3558 buffer:add_text('buffer:auto_c') 3559 textadept.editing.autocomplete('lua') 3560 assert(not buffer:auto_c_active(), 'autocompletions available') 3561 buffer.filename = _HOME .. '/test/autocomplete_lua.lua' 3562 textadept.editing.autocomplete('lua') 3563 assert(buffer:auto_c_active(), 'no autocompletions') 3564 assert_equal(buffer.auto_c_current_text, 'auto_c_active') 3565 buffer:auto_c_cancel() 3566 buffer:clear_all() 3567 3568 local autocomplete_snippets = _M.lua.autocomplete_snippets 3569 _M.lua.autocomplete_snippets = false 3570 buffer:add_text('for') 3571 textadept.editing.autocomplete('lua') 3572 assert(not buffer:auto_c_active(), 'autocompletions available') 3573 _M.lua.autocomplete_snippets = true 3574 textadept.editing.autocomplete('lua') 3575 assert(buffer:auto_c_active(), 'no autocompletions') 3576 buffer:auto_c_cancel() 3577 buffer:clear_all() 3578 _M.lua.autocomplete_snippets = autocomplete_snippets -- restore 3579 3580 buffer:close(true) 3581end 3582 3583function test_ansi_c_autocomplete() 3584 buffer.new() 3585 buffer:set_lexer('ansi_c') 3586 3587 buffer:add_text('str') 3588 textadept.editing.autocomplete('ansi_c') 3589 assert(buffer:auto_c_active(), 'no autocompletions') 3590 assert_equal(buffer.auto_c_current_text, 'strcat') 3591 buffer:auto_c_cancel() 3592 buffer:clear_all() 3593 3594 buffer:add_text('div_t d;\nd->') 3595 textadept.editing.autocomplete('ansi_c') 3596 assert(buffer:auto_c_active(), 'no autocompletions') 3597 assert_equal(buffer.auto_c_current_text, 'quot') 3598 buffer:auto_c_cancel() 3599 buffer:clear_all() 3600 3601 local autocomplete_snippets = _M.ansi_c.autocomplete_snippets 3602 _M.ansi_c.autocomplete_snippets = false 3603 buffer:add_text('for') 3604 textadept.editing.autocomplete('ansi_c') 3605 assert(not buffer:auto_c_active(), 'autocompletions available') 3606 _M.ansi_c.autocomplete_snippets = true 3607 textadept.editing.autocomplete('ansi_c') 3608 assert(buffer:auto_c_active(), 'no autocompletions') 3609 buffer:auto_c_cancel() 3610 buffer:clear_all() 3611 _M.ansi_c.autocomplete_snippets = autocomplete_snippets -- restore 3612 3613 -- TODO: typeref and rescan 3614 3615 buffer:close(true) 3616end 3617 3618function test_lexer_api() 3619 buffer.new() 3620 buffer.use_tabs, buffer.tab_width = true, 4 3621 buffer:set_text(table.concat({ 3622 'if foo then', 3623 '\tbar', 3624 '', 3625 'end', 3626 'baz' 3627 }, '\n')) 3628 buffer:set_lexer('lua') 3629 buffer:colorize(1, -1) 3630 local lexer = require('lexer') 3631 assert(lexer.fold_level[1] & lexer.FOLD_HEADER > 0, 'not a fold header') 3632 assert_equal(lexer.fold_level[2], lexer.fold_level[3]) 3633 assert(lexer.fold_level[4] > lexer.fold_level[5], 'incorrect fold levels') 3634 assert(lexer.indent_amount[1] < lexer.indent_amount[2], 'incorrect indent level') 3635 assert(lexer.indent_amount[2] > lexer.indent_amount[3], 'incorrect indent level') 3636 lexer.line_state[1] = 2 3637 assert_equal(lexer.line_state[1], 2) 3638 assert_equal(lexer.property['foo'], '') 3639 lexer.property['foo'] = 'bar' 3640 assert_equal(lexer.property['foo'], 'bar') 3641 lexer.property['bar'] = '$(foo),$(foo)' 3642 assert_equal(lexer.property_expanded['bar'], 'bar,bar') 3643 lexer.property['baz'] = '1' 3644 assert_equal(lexer.property_int['baz'], 1) 3645 lexer.property['baz'] = '' 3646 assert_equal(lexer.property_int['baz'], 0) 3647 assert_equal(lexer.property_int['quux'], 0) 3648 assert_equal(lexer.style_at[2], 'keyword') 3649 assert_equal(lexer.line_from_position(15), 2) 3650 buffer:close(true) 3651 3652 assert_raises(function() lexer.fold_level = nil end, 'read-only') 3653 assert_raises(function() lexer.fold_level[1] = 0 end, 'read-only') 3654 assert_raises(function() lexer.indent_amount = nil end, 'read-only') 3655 assert_raises(function() lexer.indent_amount[1] = 0 end, 'read-only') 3656 assert_raises(function() lexer.property = nil end, 'read-only') 3657 assert_raises(function() lexer.property_int = nil end, 'read-only') 3658 assert_raises(function() lexer.property_int['foo'] = 1 end, 'read-only') 3659 --TODO: assert_raises(function() lexer.property_expanded = nil end, 'read-only') 3660 assert_raises(function() lexer.property_expanded['foo'] = 'bar' end, 'read-only') 3661 assert_raises(function() lexer.style_at = nil end, 'read-only') 3662 assert_raises(function() lexer.style_at[1] = 0 end, 'read-only') 3663 assert_raises(function() lexer.line_state = nil end, 'read-only') 3664 assert_raises(function() lexer.line_from_position = nil end, 'read-only') 3665end 3666 3667function test_ui_size() 3668 local size = ui.size 3669 ui.size = {size[1] - 50, size[2] + 50} 3670 assert_equal(ui.size, size) 3671 ui.size = size 3672end 3673 3674function test_ui_maximized() 3675 local maximized = ui.maximized 3676 ui.maximized = not maximized 3677 local not_maximized = ui.maximized 3678 ui.maximized = maximized -- reset 3679 -- For some reason, the following fails, even though the window maximized 3680 -- status is toggled. `ui.update()` does not seem to help. 3681 assert_equal(not_maximized, not maximized) 3682end 3683expected_failure(test_ui_maximized) 3684 3685function test_ui_restore_view_state() 3686 buffer.new() -- 1 3687 view.view_ws = view.WS_VISIBLEALWAYS 3688 buffer.new() -- 2 3689 assert(view.view_ws ~= view.WS_VISIBLEALWAYS, 'view whitespace settings not reset') 3690 view:goto_buffer(-1) -- go back to 1 3691 assert_equal(view.view_ws, view.WS_VISIBLEALWAYS) 3692 view.view_ws = view.WS_INVISIBLE -- reset 3693 buffer.new() -- 3 3694 view.view_ws = view.WS_VISIBLEALWAYS 3695 buffer:close() -- switches back to 1 (after briefly switching to 2) 3696 assert_equal(view.view_ws, view.WS_INVISIBLE) 3697 view:goto_buffer(1) -- go back to 2 3698 assert_equal(view.view_ws, view.WS_INVISIBLE) 3699 buffer:close() 3700 buffer:close() 3701end 3702 3703function test_reset() 3704 local _persist 3705 _G.foo = 'bar' 3706 reset() 3707 assert(not _G.foo, 'Lua not reset') 3708 _G.foo = 'bar' 3709 events.connect(events.RESET_BEFORE, function(persist) 3710 persist.foo = _G.foo 3711 _persist = persist -- store 3712 end) 3713 reset() 3714 -- events.RESET_AFTER has already been run, but there was no opportunity to 3715 -- connect to it in this test, so connect and simulate the event again. 3716 events.connect(events.RESET_AFTER, function(persist) _G.foo = persist.foo end) 3717 events.emit(events.RESET_AFTER, _persist) 3718 assert_equal(_G.foo, 'bar') 3719end 3720 3721function test_timeout() 3722 if CURSES then 3723 assert_raises(function() timeout(1, function() end) end, 'not implemented') 3724 return 3725 end 3726 3727 local count = 0 3728 local function f() 3729 count = count + 1 3730 return count < 2 3731 end 3732 timeout(0.4, f) 3733 assert_equal(count, 0) 3734 os.execute('sleep 0.5') 3735 ui.update() 3736 assert_equal(count, 1) 3737 os.execute('sleep 0.5') 3738 ui.update() 3739 assert_equal(count, 2) 3740 os.execute('sleep 0.5') 3741 ui.update() 3742 assert_equal(count, 2) 3743end 3744 3745function test_view_split_resize_unsplit() 3746 view:split() 3747 local size = view.size 3748 view.size = view.size - 1 3749 assert_equal(view.size, size - 1) 3750 assert_equal(#_VIEWS, 2) 3751 view:split(true) 3752 size = view.size 3753 view.size = view.size + 1 3754 assert_equal(view.size, size + 1) 3755 assert_equal(#_VIEWS, 3) 3756 view:unsplit() 3757 assert_equal(#_VIEWS, 2) 3758 view:split(true) 3759 ui.goto_view(_VIEWS[1]) 3760 view:unsplit() -- unsplits split view, leaving single view 3761 assert_equal(#_VIEWS, 1) 3762end 3763 3764function test_view_split_refresh_styles() 3765 io.open_file(_HOME .. '/init.lua') 3766 local style = buffer:style_of_name('library') 3767 assert(style > 1, 'cannot retrieve number of library style') 3768 local color = view.style_fore[style] 3769 assert(color ~= view.style_fore[view.STYLE_DEFAULT], 'library style not set') 3770 view:split() 3771 for _, view in ipairs(_VIEWS) do 3772 local view_style = buffer:style_of_name('library') 3773 assert_equal(view_style, style) 3774 local view_color = view.style_fore[view_style] 3775 assert_equal(view_color, color) 3776 end 3777 view:unsplit() 3778 buffer:close(true) 3779end 3780 3781function test_buffer_read_write_only_properties() 3782 assert_raises(function() view.all_lines_visible = false end, 'read-only property') 3783 assert_raises(function() return buffer.auto_c_fill_ups end, 'write-only property') 3784 assert_raises(function() buffer.annotation_text = {} end, 'read-only property') 3785 assert_raises(function() buffer.char_at[1] = string.byte(' ') end, 'read-only property') 3786 assert_raises(function() return view.marker_alpha[1] end, 'write-only property') 3787end 3788 3789function test_set_theme() 3790 local current_theme = view.style_fore[view.STYLE_DEFAULT] 3791 view:split() 3792 io.open_file(_HOME .. '/init.lua') 3793 view:split(true) 3794 io.open_file(_HOME .. '/src/textadept.c') 3795 _VIEWS[2]:set_theme('dark') 3796 _VIEWS[3]:set_theme('light') 3797 assert(_VIEWS[2].style_fore[view.STYLE_DEFAULT] ~= _VIEWS[3].style_fore[view.STYLE_DEFAULT], 'same default styles') 3798 buffer:close(true) 3799 buffer:close(true) 3800 ui.goto_view(_VIEWS[1]) 3801 view:unsplit() 3802end 3803 3804function test_set_lexer_style() 3805 buffer.new() 3806 buffer:set_lexer('java') 3807 buffer:add_text('foo()') 3808 buffer:colorize(1, -1) 3809 local style = buffer:style_of_name('function') 3810 assert_equal(buffer.style_at[1], style) 3811 local default_fore = view.style_fore[view.STYLE_DEFAULT] 3812 assert(view.style_fore[style] ~= default_fore, 'function name style_fore same as default style_fore') 3813 view.style_fore[style] = view.style_fore[view.STYLE_DEFAULT] 3814 assert_equal(view.style_fore[style], default_fore) 3815 local color = lexer.colors[not CURSES and 'orange' or 'blue'] 3816 assert(color > 0 and color ~= default_fore) 3817 lexer.styles['function'] = {fore = color} 3818 assert_equal(view.style_fore[style], color) 3819 buffer:close(true) 3820 -- Defined in Lua lexer, which is not currently loaded. 3821 assert(buffer:style_of_name('library'), view.STYLE_DEFAULT) 3822 -- Emulate a theme setting to trigger an LPeg lexer style refresh, but without 3823 -- a token defined. 3824 view.property['style.library'] = view.property['style.library'] 3825end 3826 3827function test_lexer_fold_properties() 3828 lexer.property['fold.compact'] = '0' 3829 assert(not lexer.fold_compact, 'lexer.fold_compact not updated') 3830 lexer.fold_compact = true 3831 assert(lexer.fold_compact, 'lexer.fold_compact not updated') 3832 assert_equal(lexer.property['fold.compact'], '1') 3833 lexer.fold_compact = nil 3834 assert(not lexer.fold_compact) 3835 assert_equal(lexer.property['fold.compact'], '0') 3836 local truthy, falsy = {true, '1', 1}, {false, '0', 0} 3837 for i = 1, #truthy do 3838 lexer.fold_compact = truthy[i] 3839 assert(lexer.fold_compact, 'lexer.fold_compact not updated for "%s"', tostring(truthy[i])) 3840 lexer.fold_compact = falsy[i] 3841 assert(not lexer.fold_compact, 'lexer.fold_compact not updated for "%s"', tostring(falsy[i])) 3842 end 3843 -- Verify fold and folding properties are synchronized. 3844 lexer.property['fold'] = '0' 3845 assert(not lexer.folding) 3846 lexer.folding = true 3847 assert(lexer.property['fold'] == '1') 3848 -- Lexer fold properties and view fold properties do not mirror because 3849 -- Scintilla forwards view property settings to lexers, not vice-versa. 3850 view.property['fold'] = '0' 3851 assert(not lexer.folding) 3852 lexer.folding = true 3853 assert_equal(view.property['fold'], '0') 3854end 3855 3856function test_lexer_fold_line_groups() 3857 local fold_line_groups = lexer.fold_line_groups 3858 buffer.new() 3859 buffer:add_text[[ 3860 package foo; 3861 3862 import bar; 3863 import baz; 3864 import quux; 3865 // comment 3866 // comment 3867 // comment 3868 3869 public class Foo {} 3870 ]] 3871 buffer:set_lexer('java') 3872 lexer.fold_line_groups = false 3873 buffer:colorize(1, -1) 3874 assert(buffer.fold_level[3] & lexer.FOLD_HEADER == 0, 'import is a fold point') 3875 assert(buffer.fold_level[6] & lexer.FOLD_HEADER == 0, 'line comment is a fold point') 3876 lexer.fold_line_groups = true 3877 buffer:colorize(1, -1) 3878 assert(buffer.fold_level[3] & lexer.FOLD_HEADER > 0, 'import is not a fold point') 3879 assert(buffer.fold_level[6] & lexer.FOLD_HEADER > 0, 'line comment is not a fold point') 3880 view:toggle_fold(3) 3881 for i = 4, 5 do assert(not view.line_visible[i], 'line %i is visible', i) end 3882 view:toggle_fold(6) 3883 for i = 7, 8 do assert(not view.line_visible[i], 'line %i is visible', i) end 3884 buffer:close(true) 3885 lexer.fold_line_groups = fold_line_groups -- restore 3886end 3887 3888-- TODO: test init.lua's buffer settings 3889 3890function test_ctags() 3891 local ctags = require('ctags') 3892 3893 -- Setup project. 3894 local dir = os.tmpname() 3895 os.remove(dir) 3896 lfs.mkdir(dir) 3897 os.execute(string.format('cp -r %s/test/modules/ctags/c/* %s', _HOME, dir)) 3898 lfs.mkdir(dir .. '/.hg') -- simulate version control 3899 local foo_h, foo_c = dir .. '/include/foo.h', dir .. '/src/foo.c' 3900 3901 -- Generate tags and api. 3902 io.open_file(dir .. '/src/foo.c') 3903 textadept.menu.menubar[_L['Search']][_L['Ctags']][_L['Generate Project Tags and API']][2]() 3904 assert(lfs.attributes(dir .. '/tags'), 'tags file not generated') 3905 assert(lfs.attributes(dir .. '/api'), 'api file not generated') 3906 local f = io.open(dir .. '/api') 3907 local contents = f:read('a') 3908 f:close() 3909 assert(contents:find('main int main(int argc, char **argv) {', 1, true), 'did not properly generate api') 3910 3911 -- Test `ctags.goto_tag()`. 3912 ctags.goto_tag('main') 3913 assert_equal(buffer.filename, foo_c) 3914 assert(buffer:get_cur_line():find('^int main%('), 'not at "main" function') 3915 buffer:line_down() 3916 buffer:vc_home() 3917 ctags.goto_tag() -- foo(FOO) 3918 assert_equal(buffer.filename, foo_h) 3919 assert(buffer:get_cur_line():find('^void foo%('), 'not at "foo" function') 3920 view:goto_buffer(-1) -- back to src/foo.c 3921 assert_equal(buffer.filename, foo_c) 3922 buffer:word_right() 3923 buffer:word_right() 3924 ctags.goto_tag() -- FOO 3925 assert_equal(buffer.filename, foo_h) 3926 assert(buffer:get_cur_line():find('^#define FOO 1'), 'not at "FOO" definition') 3927 3928 -- Test tag autocompletion. 3929 buffer:line_end() 3930 buffer:new_line() 3931 buffer:add_text('m') 3932 textadept.editing.autocomplete('ctag') 3933 assert(buffer:get_cur_line():find('^main'), 'did not autocomplete "main" function') 3934 3935 -- Test `ctags.goto_tag()` with custom tags path. 3936 ctags.ctags_flags[dir] = '-R ' .. dir -- for writing absolute paths 3937 textadept.menu.menubar[_L['Search']][_L['Ctags']][_L['Generate Project Tags and API']][2]() 3938 os.execute(string.format('mv %s/tags %s/src', dir, dir)) 3939 assert(not lfs.attributes(dir .. '/tags') and lfs.attributes(dir .. '/src/tags'), 'did not move tags file') 3940 ctags[dir] = dir .. '/src/tags' 3941 ctags.goto_tag('main') 3942 assert_equal(buffer.filename, foo_c) 3943 assert(buffer:get_cur_line():find('^int main%('), 'not at "main" function') 3944 3945 -- Test `ctags.goto_tag()` with no tags file and using current file contents. 3946 os.remove(dir .. '/src/tags') 3947 assert(not lfs.attributes(dir .. '/src/tags'), 'did not remove tags file') 3948 buffer:line_down() 3949 buffer:line_down() 3950 buffer:vc_home() 3951 ctags.goto_tag() -- bar() 3952 assert_equal(buffer.filename, foo_c) 3953 assert(buffer:get_cur_line():find('^void bar%(')) 3954 3955 view:goto_buffer(1) 3956 buffer:close(true) 3957 buffer:close(true) 3958 os.execute('rm -r ' .. dir) 3959end 3960 3961function test_ctags_lua() 3962 local ctags = require('ctags') 3963 3964 -- Setup project. 3965 local dir = os.tmpname() 3966 os.remove(dir) 3967 lfs.mkdir(dir) 3968 os.execute(string.format('cp -r %s/test/modules/ctags/lua/* %s', _HOME, dir)) 3969 lfs.mkdir(dir .. '/.hg') -- simulate version control 3970 3971 -- Generate tags and api. 3972 io.open_file(dir .. '/foo.lua') 3973 ctags.ctags_flags[dir] = '-R ' .. ctags.LUA_FLAGS 3974 textadept.menu.menubar[_L['Search']][_L['Ctags']][_L['Generate Project Tags and API']][2]() 3975 assert(lfs.attributes(dir .. '/tags'), 'tags file not generated') 3976 assert(lfs.attributes(dir .. '/api'), 'api file not generated') 3977 3978 if not CURSES then -- TODO: cannot properly spawn with ctags.LUA_FLAGS on curses 3979 ctags.goto_tag('foo') 3980 assert(buffer:get_cur_line():find('^function foo%('), 'not at "foo" function') 3981 ctags.goto_tag('bar') 3982 assert(buffer:get_cur_line():find('^local function bar%('), 'not at "bar" function') 3983 ctags.goto_tag('baz') 3984 assert(buffer:get_cur_line():find('^baz = %{'), 'not at "baz" table') 3985 ctags.goto_tag('quux') 3986 assert(buffer:get_cur_line():find('^function baz:quux%('), 'not at "baz.quux" function') 3987 end 3988 3989 -- Test using Textadept's tags and api generator. 3990 ctags.ctags_flags[dir] = ctags.LUA_GENERATOR 3991 ctags.api_commands[dir] = ctags.LUA_GENERATOR 3992 textadept.menu.menubar[_L['Search']][_L['Ctags']][_L['Generate Project Tags and API']][2]() 3993 ctags.goto_tag('new') 3994 assert(buffer:get_cur_line():find('^function M%.new%('), 'not at "M.new" function') 3995 local f = io.open(dir .. '/api') 3996 local contents = f:read('a') 3997 f:close() 3998 assert(contents:find('new foo%.new%(%)\\nFoo'), 'did not properly generate api') 3999 4000 buffer:close(true) 4001 os.execute('rm -r ' .. dir) 4002end 4003 4004function test_debugger_ansi_c() 4005 local debugger = require('debugger') 4006 require('debugger.ansi_c').logging = true 4007 local function wait() 4008 os.spawn('sleep 0.2'):wait() 4009 ui.update() 4010 end 4011 local tabs = ui.tabs 4012 ui.tabs = false 4013 local dir = os.tmpname() 4014 os.remove(dir) 4015 lfs.mkdir(dir) 4016 local filename = dir .. '/foo.c' 4017 os.execute(string.format('cp %s/test/modules/debugger/ansi_c/foo.c %s', _HOME, filename)) 4018 io.open_file(filename) 4019 debugger.toggle_breakpoint(nil, 8) 4020 assert(buffer:marker_get(8) > 0, 'breakpoint marker not set') 4021 textadept.run.compile_commands[filename] = textadept.run.compile_commands.ansi_c .. ' -g' 4022 textadept.run.compile() 4023 wait() 4024 assert_equal(#_VIEWS, 2) 4025 local msg_buf = buffer 4026 assert(buffer:get_text():find('status: 0'), 'compile failed') 4027 ui.goto_view(-1) 4028 debugger.start(nil, dir .. '/foo') 4029 wait() 4030 debugger.continue() 4031 wait() 4032 assert_equal(buffer.filename, filename) 4033 assert_equal(buffer:line_from_position(buffer.current_pos), 8) 4034 assert(buffer:marker_number_from_line(8, 2) > 0, 'current line marker not set') 4035 assert(not msg_buf:get_text():find('^start\n'), 'not at breakpoint') 4036 debugger.restart() 4037 wait() 4038 assert_equal(buffer.filename, filename) 4039 assert_equal(buffer:line_from_position(buffer.current_pos), 8) 4040 assert(buffer:marker_number_from_line(8, 2) > 0, 'current line marker not set') 4041 assert(not msg_buf:get_text():find('^start\n'), 'not at breakpoint') 4042 debugger.stop() 4043 wait() 4044 assert_equal(buffer:marker_number_from_line(8, 2), -1, 'still debugging') 4045 debugger.start(nil, dir .. '/foo') 4046 wait() 4047 debugger.continue() 4048 wait() 4049 debugger.toggle_breakpoint() -- clear 4050 debugger.step_over() 4051 wait() 4052 assert_equal(buffer:line_from_position(buffer.current_pos), 9) 4053 assert(buffer:marker_get(9) > 0, 'current line marker not set') 4054 assert_equal(buffer:marker_get(8), 0) -- current line marker cleared 4055 -- TODO: gdb does not print program stdout to its stdout until the end when 4056 -- using the mi interface. 4057 --assert(msg_buf:get_text():find('^start\n'), 'process stdout not captured') 4058 debugger.step_over() 4059 wait() 4060 assert_equal(buffer:line_from_position(buffer.current_pos), 10) 4061 debugger.evaluate('i') 4062 wait() 4063 assert_equal(buffer.filename, filename) -- still in file being debugged 4064 assert(msg_buf:get_text():find('\n0\n'), 'evaluation of i failed') 4065 debugger.step_into() 4066 wait() 4067 assert_equal(buffer:line_from_position(buffer.current_pos), 4) 4068 debugger.set_frame(2) 4069 wait() 4070 assert_equal(buffer:line_from_position(buffer.current_pos), 10) 4071 debugger.set_frame(1) 4072 wait() 4073 assert_equal(buffer:line_from_position(buffer.current_pos), 4) 4074 buffer:search_anchor() 4075 local pos = buffer:search_next(buffer.FIND_MATCHCASE | buffer.FIND_WHOLEWORD, 'i') 4076 assert(pos > 0, "'i' not found") 4077 debugger.inspect(pos) 4078 wait() 4079 assert(buffer:call_tip_active(), 'no call tip active') 4080 debugger.step_out() 4081 wait() 4082 --assert(msg_buf:get_text():find('\nfoo 0\n'), 'process stdout not captured') 4083 assert_equal(buffer:line_from_position(buffer.current_pos), 9) 4084 debugger.set_watch('i') 4085 debugger.continue() 4086 wait() 4087 assert_equal(buffer:line_from_position(buffer.current_pos), 9) 4088 assert(not msg_buf:get_text():find('\nfoo 1\n'), 'watch point failed') 4089 debugger.remove_watch(1) 4090 debugger.step_over() 4091 wait() 4092 events.emit(events.MARGIN_CLICK, 2, buffer.current_pos, 0) -- simulate breakpoint margin click 4093 debugger.continue() 4094 wait() 4095 assert_equal(buffer:line_from_position(buffer.current_pos), 10) 4096 --assert(msg_buf:get_text():find('\nfoo 1\n'), 'set breakpoint failed') 4097 assert(not msg_buf:get_text():find('\nfoo 2\n'), 'set breakpoint failed') 4098 events.emit(events.MARGIN_CLICK, 2, buffer.current_pos, 0) -- simulate breakpoint margin click; clear 4099 debugger.continue() 4100 wait() 4101 --assert(msg_buf:get_text():find('\nfoo 2\n'), 'process stdout not captured') 4102 --assert(msg_buf:get_text():find('\nfoo 3\n'), 'process stdout not captured') 4103 --assert(msg_buf:get_text():find('\nend\n'), 'process stdout not captured') 4104 for i = 1, buffer.line_count do assert_equal(buffer:marker_get(i), 0) end 4105 ui.goto_view(1) 4106 buffer:close(true) 4107 view:unsplit() 4108 buffer:close(true) 4109 os.execute('rm -r ' .. dir) 4110 ui.tabs = tabs 4111end 4112 4113function test_debugger_lua() 4114 local debugger = require('debugger') 4115 local function wait() 4116 for i = 1, 10 do 4117 os.spawn('sleep 0.1'):wait() 4118 ui.update() 4119 end 4120 end 4121 local tabs = ui.tabs 4122 ui.tabs = false 4123 local filename = _HOME .. '/test/modules/debugger/lua/foo.lua' 4124 io.open_file(filename) 4125 debugger.toggle_breakpoint(nil, 5) 4126 assert(buffer:marker_get(5) > 0, 'breakpoint marker not set') 4127 debugger.continue() -- start 4128 wait() 4129 assert_equal(buffer.filename, filename) 4130 assert_equal(buffer:line_from_position(buffer.current_pos), 5) 4131 assert(buffer:marker_number_from_line(5, 2) > 0, 'current line marker not set') 4132 assert_equal(#_VIEWS, 1) 4133 debugger.restart() 4134 wait() 4135 assert_equal(buffer.filename, filename) 4136 assert_equal(buffer:line_from_position(buffer.current_pos), 3) -- for whatever reason 4137 assert(buffer:marker_get(3) > 0, 'current line marker not set') 4138 assert_equal(#_VIEWS, 1) 4139 debugger.stop() 4140 wait() 4141 assert_equal(buffer:marker_number_from_line(5, 2), -1, 'still debugging') 4142 debugger.continue() -- start 4143 wait() 4144 debugger.toggle_breakpoint() -- clear 4145 debugger.step_over() 4146 wait() 4147 assert_equal(#_VIEWS, 2) 4148 assert_equal(buffer.filename, filename) 4149 assert_equal(buffer:line_from_position(buffer.current_pos), 6) 4150 assert(buffer:marker_get(6) > 0, 'current line marker not set') 4151 assert_equal(buffer:marker_get(5), 0) -- current line marker cleared 4152 local msg_buf = _VIEWS[#_VIEWS].buffer 4153 assert(msg_buf:get_text():find('^"start"\n'), 'process stdout not captured') 4154 debugger.step_over() 4155 wait() 4156 assert_equal(buffer:line_from_position(buffer.current_pos), 7) 4157 debugger.evaluate("print('i', i)") 4158 wait() 4159 assert_equal(buffer.filename, filename) -- still in file being debugged 4160 assert(msg_buf:get_text():find('\n"i"%s1\n'), 'evaluation of i failed') 4161 debugger.step_into() 4162 wait() 4163 assert_equal(buffer:line_from_position(buffer.current_pos), 2) 4164 -- TODO: set_frame is not implemented in the Lua debugger. 4165 --debugger.set_frame(2) 4166 --wait() 4167 --assert_equal(buffer:line_from_position(buffer.current_pos), 7) 4168 --debugger.set_frame(1) 4169 --wait() 4170 --assert_equal(buffer:line_from_position(buffer.current_pos), 2) 4171 buffer:search_anchor() 4172 local pos = buffer:search_next(buffer.FIND_MATCHCASE | buffer.FIND_WHOLEWORD, 'i') 4173 assert(pos > 0, "'i' not found") 4174 debugger.inspect(pos) 4175 wait() 4176 assert(buffer:call_tip_active(), 'no call tip active') 4177 debugger.step_out() 4178 wait() 4179 assert(msg_buf:get_text():find('\n"foo"%s1\n'), 'process stdout not captured') 4180 assert_equal(buffer:line_from_position(buffer.current_pos), 6) 4181 debugger.set_watch('i') 4182 debugger.continue() 4183 wait() 4184 assert_equal(buffer:line_from_position(buffer.current_pos), 7) 4185 assert(not msg_buf:get_text():find('\n"foo"%s2\n'), 'watch point failed') 4186 debugger.remove_watch(1) 4187 events.emit(events.MARGIN_CLICK, 2, buffer.current_pos, 0) -- simulate breakpoint margin click 4188 debugger.continue() 4189 wait() 4190 assert_equal(buffer:line_from_position(buffer.current_pos), 7) 4191 assert(msg_buf:get_text():find('\n"foo"%s2\n'), 'set breakpoint failed') 4192 assert(not msg_buf:get_text():find('\n"foo"%s3\n'), 'set breakpoint failed') 4193 events.emit(events.MARGIN_CLICK, 2, buffer.current_pos, 0) -- simulate breakpoint margin click; clear 4194 debugger.continue() 4195 wait() 4196 assert(msg_buf:get_text():find('\n"foo"%s3\n'), 'process stdout not captured') 4197 assert(msg_buf:get_text():find('\n"foo"%s4\n'), 'process stdout not captured') 4198 assert(msg_buf:get_text():find('\n"end"\n'), 'process stdout not captured') 4199 for i = 1, buffer.line_count do assert_equal(buffer:marker_get(i), 0) end 4200 ui.goto_view(1) 4201 buffer:close(true) 4202 view:unsplit() 4203 buffer:close(true) 4204 ui.tabs = tabs 4205end 4206 4207function test_export_interactive() 4208 local export = require('export') 4209 buffer.new() 4210 buffer:add_text("_G.foo=table.concat{1,'bar',true,print}\nbar=[[<>& ]]") 4211 buffer:set_lexer('lua') 4212 local filename = os.tmpname() 4213 export.to_html(nil, filename) 4214 _G.timeout(0.5, function() os.remove(filename) end) 4215 buffer:close(true) 4216end 4217 4218function test_file_diff() 4219 local diff = require('file_diff') 4220 4221 local filename1 = _HOME .. '/test/modules/file_diff/1' 4222 local filename2 = _HOME .. '/test/modules/file_diff/2' 4223 io.open_file(filename1) 4224 io.open_file(filename2) 4225 view:split() 4226 ui.goto_view(-1) 4227 view:goto_buffer(-1) 4228 diff.start('-', '-') 4229 assert_equal(#_VIEWS, 2) 4230 assert_equal(view, _VIEWS[1]) 4231 local buffer1, buffer2 = _VIEWS[1].buffer, _VIEWS[2].buffer 4232 assert_equal(buffer1.filename, filename1) 4233 assert_equal(buffer2.filename, filename2) 4234 4235 local function verify(buffer, markers, indicators, annotations) 4236 for i = 1, buffer.line_count do 4237 if not markers[i] then 4238 assert(buffer:marker_get(i) == 0, 'unexpected marker on line %d', i) 4239 else 4240 assert(buffer:marker_get(i) & 1 << markers[i] - 1 > 0, 'incorrect marker on line %d', i) 4241 end 4242 if not annotations[i] then 4243 assert(buffer.annotation_text[i] == '', 'unexpected annotation on line %d', i) 4244 else 4245 assert(buffer.annotation_text[i] == annotations[i], 'incorrect annotation on line %d', i) 4246 end 4247 end 4248 for _, indic in ipairs{diff.INDIC_DELETION, diff.INDIC_ADDITION} do 4249 local s = buffer:indicator_end(indic, 1) 4250 local e = buffer:indicator_end(indic, s) 4251 while s < buffer.length and e > s do 4252 local text = buffer:text_range(s, e) 4253 assert(indicators[text] == indic, 'incorrect indicator for "%s"', text) 4254 s = buffer:indicator_end(indic, e) 4255 e = buffer:indicator_end(indic, s) 4256 end 4257 end 4258 end 4259 4260 -- Verify line markers. 4261 verify(buffer1, { 4262 [1] = diff.MARK_MODIFICATION, 4263 [2] = diff.MARK_MODIFICATION, 4264 [3] = diff.MARK_MODIFICATION, 4265 [4] = diff.MARK_MODIFICATION, 4266 [5] = diff.MARK_MODIFICATION, 4267 [6] = diff.MARK_MODIFICATION, 4268 [7] = diff.MARK_MODIFICATION, 4269 [12] = diff.MARK_MODIFICATION, 4270 [14] = diff.MARK_MODIFICATION, 4271 [15] = diff.MARK_MODIFICATION, 4272 [16] = diff.MARK_DELETION 4273 }, { 4274 ['is'] = diff.INDIC_DELETION, 4275 ['line\n'] = diff.INDIC_DELETION, 4276 [' '] = diff.INDIC_DELETION, 4277 ['+'] = diff.INDIC_DELETION, 4278 ['pl'] = diff.INDIC_DELETION, 4279 ['one'] = diff.INDIC_DELETION, 4280 ['wo'] = diff.INDIC_DELETION, 4281 ['three'] = diff.INDIC_DELETION, 4282 ['will'] = diff.INDIC_DELETION 4283 }, {[11] = ' \n'}) 4284 verify(buffer2, { 4285 [1] = diff.MARK_MODIFICATION, 4286 [2] = diff.MARK_MODIFICATION, 4287 [3] = diff.MARK_MODIFICATION, 4288 [4] = diff.MARK_MODIFICATION, 4289 [5] = diff.MARK_MODIFICATION, 4290 [6] = diff.MARK_MODIFICATION, 4291 [7] = diff.MARK_MODIFICATION, 4292 [12] = diff.MARK_ADDITION, 4293 [13] = diff.MARK_ADDITION, 4294 [14] = diff.MARK_MODIFICATION, 4295 [16] = diff.MARK_MODIFICATION, 4296 [17] = diff.MARK_MODIFICATION 4297 }, { 4298 ['at'] = diff.INDIC_ADDITION, 4299 ['paragraph\n '] = diff.INDIC_ADDITION, 4300 ['-'] = diff.INDIC_ADDITION, 4301 ['min'] = diff.INDIC_ADDITION, 4302 ['two'] = diff.INDIC_ADDITION, 4303 ['\t'] = diff.INDIC_ADDITION, 4304 ['hree'] = diff.INDIC_ADDITION, 4305 ['there are '] = diff.INDIC_ADDITION, 4306 ['four'] = diff.INDIC_ADDITION, 4307 ['have'] = diff.INDIC_ADDITION, 4308 ['d'] = diff.INDIC_ADDITION 4309 }, {[17] = ' '}) 4310 4311 -- Stop comparing, verify the buffers are restored to normal, and then start 4312 -- comparing again. 4313 textadept.menu.menubar[_L['Tools']][_L['Compare Files']][_L['Stop Comparing']][2]() 4314 verify(buffer1, {}, {}, {}) 4315 verify(buffer2, {}, {}, {}) 4316 textadept.menu.menubar[_L['Tools']][_L['Compare Files']][_L['Compare Buffers']][2]() 4317 4318 -- Test goto next/prev change. 4319 assert_equal(buffer1:line_from_position(buffer1.current_pos), 1) 4320 diff.goto_change(true) 4321 assert_equal(buffer1:line_from_position(buffer1.current_pos), 11) 4322 diff.goto_change(true) 4323 assert_equal(buffer1:line_from_position(buffer1.current_pos), 12) 4324 diff.goto_change(true) 4325 assert_equal(buffer1:line_from_position(buffer1.current_pos), 14) 4326 diff.goto_change(true) 4327 assert_equal(buffer1:line_from_position(buffer1.current_pos), 16) 4328 diff.goto_change(true) 4329 assert_equal(buffer1:line_from_position(buffer1.current_pos), 1) 4330 diff.goto_change() 4331 assert_equal(buffer1:line_from_position(buffer1.current_pos), 16) 4332 diff.goto_change() 4333 assert_equal(buffer1:line_from_position(buffer1.current_pos), 15) 4334 diff.goto_change() 4335 assert_equal(buffer1:line_from_position(buffer1.current_pos), 12) 4336 diff.goto_change() 4337 assert_equal(buffer1:line_from_position(buffer1.current_pos), 7) 4338 ui.goto_view(1) 4339 assert_equal(buffer2:line_from_position(buffer2.current_pos), 1) 4340 diff.goto_change(true) 4341 assert_equal(buffer2:line_from_position(buffer2.current_pos), 12) 4342 diff.goto_change(true) 4343 assert_equal(buffer2:line_from_position(buffer2.current_pos), 14) 4344 diff.goto_change(true) 4345 assert_equal(buffer2:line_from_position(buffer2.current_pos), 16) 4346 diff.goto_change(true) 4347 assert_equal(buffer2:line_from_position(buffer2.current_pos), 17) 4348 diff.goto_change(true) 4349 assert_equal(buffer2:line_from_position(buffer2.current_pos), 1) 4350 diff.goto_change() 4351 assert_equal(buffer2:line_from_position(buffer2.current_pos), 17) 4352 diff.goto_change() 4353 assert_equal(buffer2:line_from_position(buffer2.current_pos), 14) 4354 diff.goto_change() 4355 assert_equal(buffer2:line_from_position(buffer2.current_pos), 13) 4356 diff.goto_change() 4357 assert_equal(buffer2:line_from_position(buffer2.current_pos), 7) 4358 ui.goto_view(-1) 4359 buffer1:goto_line(1) 4360 4361 -- Merge first block right to left and verify. 4362 assert_equal(buffer1:line_from_position(buffer1.current_pos), 1) 4363 diff.merge(true) 4364 assert(buffer1:get_line(1):find('^that'), 'did not merge from right to left') 4365 local function verify_first_merge() 4366 for i = 1, 7 do assert_equal(buffer1:get_line(i), buffer2:get_line(i)) end 4367 verify(buffer1, { 4368 [12] = diff.MARK_MODIFICATION, 4369 [14] = diff.MARK_MODIFICATION, 4370 [15] = diff.MARK_MODIFICATION, 4371 [16] = diff.MARK_DELETION 4372 }, {['three'] = diff.INDIC_DELETION, ['will'] = diff.INDIC_DELETION}, {[11] = ' \n'}) 4373 verify(buffer2, { 4374 [12] = diff.MARK_ADDITION, 4375 [13] = diff.MARK_ADDITION, 4376 [14] = diff.MARK_MODIFICATION, 4377 [16] = diff.MARK_MODIFICATION, 4378 [17] = diff.MARK_MODIFICATION 4379 }, { 4380 ['four'] = diff.INDIC_ADDITION, 4381 ['have'] = diff.INDIC_ADDITION, 4382 ['d'] = diff.INDIC_ADDITION 4383 }, {[17] = ' '}) 4384 end 4385 verify_first_merge() 4386 -- Undo, merge left to right, and verify. 4387 buffer1:undo() 4388 buffer1:goto_line(1) 4389 assert_equal(buffer1:line_from_position(buffer1.current_pos), 1) 4390 diff.merge() 4391 assert(buffer2:get_line(1):find('^this'), 'did not merge from left to right') 4392 verify_first_merge() 4393 4394 if CURSES then goto curses_skip end do -- TODO: curses chokes trying to automate this 4395 4396 -- Go to next difference, merge second block right to left, and verify. 4397 diff.goto_change(true) 4398 assert_equal(buffer1:line_from_position(buffer1.current_pos), 11) 4399 ui.update() 4400 diff.merge(true) 4401 assert(buffer1:get_line(12):find('^%('), 'did not merge from right to left') 4402 for i = 12, 13 do assert_equal(buffer1:get_line(i), buffer2:get_line(i)) end 4403 verify(buffer1, { 4404 [14] = diff.MARK_MODIFICATION, 4405 [16] = diff.MARK_MODIFICATION, 4406 [17] = diff.MARK_MODIFICATION, 4407 [18] = diff.MARK_DELETION 4408 }, {['three'] = diff.INDIC_DELETION, ['will'] = diff.INDIC_DELETION}, {}) 4409 verify(buffer2, { 4410 [14] = diff.MARK_MODIFICATION, 4411 [16] = diff.MARK_MODIFICATION, 4412 [17] = diff.MARK_MODIFICATION 4413 }, { 4414 ['four'] = diff.INDIC_ADDITION, 4415 ['have'] = diff.INDIC_ADDITION, 4416 ['d'] = diff.INDIC_ADDITION 4417 }, {[17] = ' '}) 4418 -- Undo, merge left to right, and verify. 4419 buffer1:undo() 4420 buffer1:goto_line(11) 4421 assert_equal(buffer1:line_from_position(buffer1.current_pos), 11) 4422 diff.merge() 4423 assert(buffer2:get_line(12):find('^be changed'), 'did not merge from left to right') 4424 verify(buffer1, { 4425 [12] = diff.MARK_MODIFICATION, 4426 [14] = diff.MARK_MODIFICATION, 4427 [15] = diff.MARK_MODIFICATION, 4428 [16] = diff.MARK_DELETION 4429 }, {['three'] = diff.INDIC_DELETION, ['will'] = diff.INDIC_DELETION}, {}) 4430 verify(buffer2, { 4431 [12] = diff.MARK_MODIFICATION, 4432 [14] = diff.MARK_MODIFICATION, 4433 [15] = diff.MARK_MODIFICATION 4434 }, { 4435 ['four'] = diff.INDIC_ADDITION, 4436 ['have'] = diff.INDIC_ADDITION, 4437 ['d'] = diff.INDIC_ADDITION 4438 }, {[15] = ' '}) 4439 4440 -- Already on next difference; merge third block from right to left, and 4441 -- verify. 4442 assert_equal(buffer1:line_from_position(buffer1.current_pos), 12) 4443 diff.merge(true) 4444 assert(buffer1:get_line(12):find('into four'), 'did not merge from right to left') 4445 assert_equal(buffer1:get_line(12), buffer2:get_line(12)) 4446 local function verify_third_merge() 4447 verify(buffer1, { 4448 [14] = diff.MARK_MODIFICATION, 4449 [15] = diff.MARK_MODIFICATION, 4450 [16] = diff.MARK_DELETION 4451 }, {['will'] = diff.INDIC_DELETION}, {}) 4452 verify(buffer2, { 4453 [14] = diff.MARK_MODIFICATION, 4454 [15] = diff.MARK_MODIFICATION 4455 }, {['have'] = diff.INDIC_ADDITION, ['d'] = diff.INDIC_ADDITION}, {[15] = ' '}) 4456 end 4457 verify_third_merge() 4458 -- Undo, merge left to right, and verify. 4459 buffer1:undo() 4460 buffer1:goto_line(12) 4461 assert_equal(buffer1:line_from_position(buffer1.current_pos), 12) 4462 diff.merge() 4463 assert(buffer2:get_line(12):find('into three'), 'did not merge from left to right') 4464 verify_third_merge() 4465 4466 -- Go to next difference, merge fourth block from right to left, and verify. 4467 diff.goto_change(true) 4468 assert_equal(buffer1:line_from_position(buffer1.current_pos), 14) 4469 diff.merge(true) 4470 assert(buffer1:get_line(14):find('have'), 'did not merge from right to left') 4471 local function verify_fourth_merge() 4472 for i = 14, 15 do assert_equal(buffer1:get_line(i), buffer2:get_line(i)) end 4473 verify(buffer1, {[16] = diff.MARK_DELETION}, {}, {}) 4474 verify(buffer2, {}, {}, {[15] = ' '}) 4475 end 4476 verify_fourth_merge() 4477 -- Undo, merge left to right, and verify. 4478 buffer1:undo() 4479 buffer1:goto_line(14) 4480 assert_equal(buffer1:line_from_position(buffer1.current_pos), 14) 4481 diff.merge() 4482 assert(buffer2:get_line(14):find('will'), 'did not merge from left to right') 4483 verify_fourth_merge() 4484 4485 -- Go to next difference, merge fifth block from right to left, and verify. 4486 diff.goto_change(true) 4487 assert_equal(buffer1:line_from_position(buffer1.current_pos), 16) 4488 diff.merge(true) 4489 assert(buffer1:get_line(16):find('^\n'), 'did not merge from right to left') 4490 local function verify_fifth_merge() 4491 assert_equal(buffer1.length, buffer2.length) 4492 for i = 1, buffer1.length do 4493 assert_equal(buffer1:get_line(i), buffer2:get_line(i)) 4494 end 4495 verify(buffer1, {}, {}, {}) 4496 verify(buffer2, {}, {}, {}) 4497 end 4498 verify_fifth_merge() 4499 -- Undo, merge left to right, and verify. 4500 buffer1:undo() 4501 buffer1:goto_line(16) 4502 assert_equal(buffer1:line_from_position(buffer1.current_pos), 16) 4503 diff.merge() 4504 assert(buffer2:get_line(16):find('^%('), 'did not merge from left to right') 4505 verify_fifth_merge() 4506 4507 -- Test scroll synchronization. 4508 _VIEWS[1].x_offset = 50 4509 ui.update() 4510 assert_equal(_VIEWS[2].x_offset, _VIEWS[1].x_offset) 4511 _VIEWS[1].x_offset = 0 4512 -- TODO: test vertical synchronization 4513 4514 end ::curses_skip:: 4515 textadept.menu.menubar[_L['Tools']][_L['Compare Files']][_L['Stop Comparing']][2]() 4516 ui.goto_view(_VIEWS[#_VIEWS]) 4517 buffer:close(true) 4518 ui.goto_view(-1) 4519 view:unsplit() 4520 buffer:close(true) 4521 -- Make sure nothing bad happens. 4522 diff.goto_change() 4523 diff.merge() 4524end 4525 4526function test_file_diff_interactive() 4527 local diff = require('file_diff') 4528 diff.start(_HOME .. '/test/modules/file_diff/1') 4529 assert_equal(#_VIEWS, 2) 4530 textadept.menu.menubar[_L['Tools']][_L['Compare Files']][_L['Stop Comparing']][2]() 4531 local different_files = _VIEWS[1].buffer.filename ~= _VIEWS[2].buffer.filename 4532 ui.goto_view(1) 4533 buffer:close(true) 4534 view:unsplit() 4535 if different_files then buffer:close(true) end 4536end 4537 4538function test_spellcheck() 4539 local spellcheck = require('spellcheck') 4540 local SPELLING_ID = 1 -- not accessible 4541 buffer:new() 4542 buffer:add_text('-- foo bar\nbaz = "quux"') 4543 4544 -- Test background highlighting. 4545 spellcheck.check_spelling() 4546 local function get_misspellings() 4547 local misspellings = {} 4548 local s = buffer:indicator_end(spellcheck.INDIC_SPELLING, 1) 4549 local e = buffer:indicator_end(spellcheck.INDIC_SPELLING, s) 4550 while e > s do 4551 misspellings[#misspellings + 1] = buffer:text_range(s, e) 4552 s = buffer:indicator_end(spellcheck.INDIC_SPELLING, e) 4553 e = buffer:indicator_end(spellcheck.INDIC_SPELLING, s) 4554 end 4555 return misspellings 4556 end 4557 assert_equal(get_misspellings(), {'foo', 'baz', 'quux'}) 4558 buffer:set_lexer('lua') 4559 spellcheck.check_spelling() 4560 assert_equal(get_misspellings(), {'foo', 'quux'}) 4561 4562 -- Test interactive parts. 4563 spellcheck.check_spelling(true) 4564 assert(buffer:auto_c_active(), 'no misspellings') 4565 local s, e = buffer.current_pos, buffer:word_end_position(buffer.current_pos) 4566 assert_equal(buffer:text_range(s, e), 'foo') 4567 buffer:cancel() 4568 events.emit(events.USER_LIST_SELECTION, SPELLING_ID, 'goo', s) 4569 assert_equal(buffer:text_range(s, e), 'goo') 4570 ui.update() 4571 if CURSES then spellcheck.check_spelling() end -- not needed when interactive 4572 spellcheck.check_spelling(true) 4573 assert(buffer:auto_c_active(), 'spellchecker not active') 4574 s, e = buffer.current_pos, buffer:word_end_position(buffer.current_pos) 4575 assert_equal(buffer:text_range(s, e), 'quux') 4576 buffer:cancel() 4577 events.emit(events.INDICATOR_CLICK, s) 4578 assert(buffer:auto_c_active(), 'spellchecker not active') 4579 buffer:cancel() 4580 events.emit(events.USER_LIST_SELECTION, 1, '(Ignore)', s) 4581 assert_equal(get_misspellings(), {}) 4582 spellcheck.check_spelling(true) 4583 assert(not buffer:auto_c_active(), 'misspellings') 4584 4585 -- TODO: test add. 4586 4587 buffer:close(true) 4588end 4589 4590function test_spellcheck_encodings() 4591 local spellcheck = require('spellcheck') 4592 local SPELLING_ID = 1 -- not accessible 4593 buffer:new() 4594 4595 -- Test UTF-8 dictionary and caret placement. 4596 buffer:set_text(' multiumesc') 4597 spellcheck.load('ro_RO') 4598 spellcheck.check_spelling() 4599 events.emit(events.INDICATOR_CLICK, 8) 4600 assert_equal(buffer.auto_c_current_text, 'mulțumesc') 4601 ui.update() 4602 events.emit( 4603 events.USER_LIST_SELECTION, SPELLING_ID, buffer.auto_c_current_text, 4604 buffer.current_pos) 4605 assert_equal(buffer:get_text(), ' mulțumesc') 4606 assert_equal(buffer.current_pos, 9) 4607 4608 -- Test ISO8859-1 dictionary with different buffer encodings. 4609 for _, encoding in pairs{'UTF-8', 'ISO8859-1', 'CP1252'} do 4610 buffer:clear_all() 4611 buffer:set_encoding(encoding) 4612 buffer:set_text('schoen') 4613 ui.update() 4614 spellcheck.load('de_DE') 4615 spellcheck.check_spelling(true) 4616 assert_equal(buffer.auto_c_current_text, 'schön') 4617 events.emit( 4618 events.USER_LIST_SELECTION, SPELLING_ID, buffer.auto_c_current_text, 4619 buffer.current_pos) 4620 assert_equal(buffer:get_text():iconv(encoding, 'UTF-8'), string.iconv('schön', encoding, 'UTF-8')) 4621 ui.update() 4622 spellcheck.check_spelling() 4623 assert_equal(buffer:indicator_end(spellcheck.INDIC_SPELLING, 1), 1) 4624 end 4625 4626 buffer:close(true) 4627end 4628 4629function test_spellcheck_load_interactive() 4630 require('spellcheck') 4631 textadept.menu.menubar[_L['Tools']][_L['Spelling']][_L['Load Dictionary...']][2]() 4632end 4633 4634-- Load buffer and view API from their respective LuaDoc files. 4635local function load_buffer_view_props() 4636 local buffer_props, view_props = {}, {} 4637 for name, props in pairs{buffer = buffer_props, view = view_props} do 4638 for line in io.lines(string.format('%s/core/.%s.luadoc', _HOME, name)) do 4639 if line:find('@field') then 4640 props[line:match('@field ([%w_]+)')] = true 4641 elseif line:find('^function') then 4642 props[line:match('^function ([%w_]+)')] = true 4643 end 4644 end 4645 end 4646 return buffer_props, view_props 4647end 4648 4649local function check_property_usage(filename, buffer_props, view_props) 4650 print(string.format('Processing file "%s"', filename:gsub(_HOME, ''))) 4651 local line_num, count = 1, 0 4652 for line in io.lines(filename) do 4653 for pos, id, prop in line:gmatch('()([%w_]+)[.:]([%w_]+)') do 4654 if id == 'M' or id == 'f' or id == 'p' or id == 'lexer' or id == 'spawn_proc' then goto continue end 4655 if id == 'textadept' and prop == 'MARK_BOOKMARK' then goto continue end 4656 if (id == 'ui' or id == 'split') and prop == 'size' then goto continue end 4657 if id == 'keys' and prop == 'home' then goto continue end 4658 if id == 'Rout' and prop == 'save' then goto continue end 4659 if id == 'detail' and (prop == 'filename' or prop == 'column') then goto continue end 4660 if (id == 'placeholder' or id == 'ph') and prop == 'length' then goto continue end 4661 if id == 'client' and prop == 'close' then goto continue end 4662 if (id == 'Foo' or id == 'Array' or id == 'Server') and prop == 'new' then goto continue end 4663 if buffer_props[prop] then 4664 assert( 4665 id == 'buffer' or id == 'buf' or id == 'buffer1' or id == 'buffer2', 4666 'line %d:%d: "%s" should be a buffer property', line_num, pos, prop) 4667 count = count + 1 4668 elseif view_props[prop] then 4669 assert( 4670 id == 'view', 'line %d:%d: "%s" should be a view property', line_num, 4671 pos, prop) 4672 count = count + 1 4673 end 4674 ::continue:: 4675 end 4676 line_num = line_num + 1 4677 end 4678 print(string.format('Checked %d buffer/view property usages.', count)) 4679end 4680 4681function test_buffer_view_usage() 4682 local buffer_props, view_props = load_buffer_view_props() 4683 local filter = { 4684 '.lua', '.luadoc', '!/lexers', '!/modules/lsp/dkjson.lua', 4685 '!/modules/lua/lua.luadoc', '!/modules/debugger/lua/mobdebug.lua', 4686 '!/modules/yaml/lyaml.lua', '!/scripts', '!/src' 4687 } 4688 for filename in lfs.walk(_HOME, filter) do 4689 check_property_usage(filename, buffer_props, view_props) 4690 end 4691end 4692 4693-------------------------------------------------------------------------------- 4694 4695assert(not WIN32 and not OSX, 'Test suite currently only runs on Linux') 4696 4697local TEST_OUTPUT_BUFFER = '[Test Output]' 4698function print(...) ui._print(TEST_OUTPUT_BUFFER, ...) end 4699-- Clean up after a previously failed test. 4700local function cleanup() 4701 while #_BUFFERS > 1 do 4702 if buffer._type == TEST_OUTPUT_BUFFER then view:goto_buffer(1) end 4703 buffer:close(true) 4704 end 4705 while view:unsplit() do end 4706end 4707 4708-- Determines whether or not to run the test whose name is string *name*. 4709-- If no arg patterns are provided, returns true. 4710-- If only inclusive arg patterns are provided, returns true if *name* matches 4711-- at least one of those patterns. 4712-- If only exclusive arg patterns are provided ('-' prefix), returns true if 4713-- *name* does not match any of them. 4714-- If both inclusive and exclusive arg patterns are provided, returns true if 4715-- *name* matches at least one of the inclusive ones, but not any of the 4716-- exclusive ones. 4717-- @param name Name of the test to check for inclusion. 4718-- @return true or false 4719local function include_test(name) 4720 if #arg == 0 then return true end 4721 local include, includes, excludes = false, false, false 4722 for _, patt in ipairs(arg) do 4723 if patt:find('^%-') then 4724 if name:find(patt:sub(2)) then return false end 4725 excludes = true 4726 else 4727 if name:find(patt) then include = true end 4728 includes = true 4729 end 4730 end 4731 return include or not includes and excludes 4732end 4733 4734local tests = {} 4735for k in pairs(_ENV) do 4736 if k:find('^test_') and include_test(k) then 4737 tests[#tests + 1] = k 4738 end 4739end 4740table.sort(tests) 4741 4742print('Starting test suite') 4743 4744local tests_run, tests_failed, tests_failed_expected = 0, 0, 0 4745 4746for i = 1, #tests do 4747 cleanup() 4748 assert_equal(#_BUFFERS, 1) 4749 assert_equal(#_VIEWS, 1) 4750 4751 _ENV = setmetatable({}, {__index = _ENV}) 4752 local name, f, attempts = tests[i], _ENV[tests[i]], 1 4753 print(string.format('Running %s', name)) 4754 ui.update() 4755 local ok, errmsg = xpcall(f, function(errmsg) 4756 local fail = not expected_failures[f] and 'Failed!' or 'Expected failure.' 4757 return string.format('%s %s', fail, debug.traceback(errmsg, 3)) 4758 end) 4759 ui.update() 4760 if not errmsg then 4761 if #_BUFFERS > 1 then 4762 ok, errmsg = false, 'Failed! Test did not close the buffer(s) it created' 4763 elseif #_VIEWS > 1 then 4764 ok, errmsg = false, 'Failed! Test did not unsplit the view(s) it created' 4765 elseif expected_failures[f] then 4766 ok, errmsg = false, 'Failed! Test should have failed' 4767 expected_failures[f] = nil 4768 end 4769 end 4770 print(ok and 'Passed.' or errmsg) 4771 4772 tests_run = tests_run + 1 4773 if not ok then 4774 tests_failed = tests_failed + 1 4775 if expected_failures[f] then 4776 tests_failed_expected = tests_failed_expected + 1 4777 end 4778 end 4779end 4780 4781print(string.format('%d tests run, %d unexpected failures, %d expected failures', tests_run, tests_failed - tests_failed_expected, tests_failed_expected)) 4782 4783-- Note: stock luacov crashes on hook.lua lines 51 and 58 every other run. 4784-- `file.max` and `file.max_hits` are both `nil`, so change comparisons to be 4785-- `(file.max or 0)` and `(file.max_hits or 0)`, respectively. 4786if package.loaded['luacov'] then 4787 require('luacov').save_stats() 4788 os.execute('luacov') 4789 local f = assert(io.open('luacov.report.out')) 4790 buffer:append_text(f:read('a'):match('\nSummary.+$')) 4791 f:close() 4792else 4793 buffer:new_line() 4794 buffer:append_text('No LuaCov coverage to report.') 4795end 4796buffer:set_save_point() 4797