aboutsummaryrefslogtreecommitdiff
path: root/modules/textadept
diff options
context:
space:
mode:
authormitchell <70453897+667e-11@users.noreply.github.com>2020-03-03 19:39:02 -0500
committermitchell <70453897+667e-11@users.noreply.github.com>2020-03-03 19:39:02 -0500
commitfceb1a37df623649d191c3c1a881e5b0538b1391 (patch)
tree87a34dfc2397dc4afdaa0c8ec189f037300f458e /modules/textadept
parent1618f5017abb3c9bacc9ba346bf22a936ef5dd06 (diff)
downloadtextadept-fceb1a37df623649d191c3c1a881e5b0538b1391.tar.gz
textadept-fceb1a37df623649d191c3c1a881e5b0538b1391.zip
Added test suite and API type checking for more helpful error messages.
Diffstat (limited to 'modules/textadept')
-rw-r--r--modules/textadept/bookmarks.lua8
-rw-r--r--modules/textadept/command_entry.lua27
-rw-r--r--modules/textadept/editing.lua27
-rw-r--r--modules/textadept/file_types.lua1
-rw-r--r--modules/textadept/find.lua29
-rw-r--r--modules/textadept/macros.lua21
-rw-r--r--modules/textadept/menu.lua4
-rw-r--r--modules/textadept/run.lua24
-rw-r--r--modules/textadept/session.lua25
-rw-r--r--modules/textadept/snippets.lua16
10 files changed, 99 insertions, 83 deletions
diff --git a/modules/textadept/bookmarks.lua b/modules/textadept/bookmarks.lua
index 47cf751f..29a68c1c 100644
--- a/modules/textadept/bookmarks.lua
+++ b/modules/textadept/bookmarks.lua
@@ -21,6 +21,7 @@ M.MARK_BOOKMARK = _SCINTILLA.next_marker_number()
-- @param line Optional line number to add or remove a bookmark on.
-- @name toggle
function M.toggle(on, line)
+ assert_type(line, 'number/nil', 2)
if not line then line = buffer:line_from_position(buffer.current_pos) end
local f = on and buffer.marker_add or buffer.marker_delete
if on == nil then -- toggle
@@ -50,10 +51,9 @@ function M.goto_mark(next)
local utf8_list, buffers = {}, {}
-- List the current buffer's marks, and then all other buffers' marks.
for _, current_buffer_first in ipairs{true, false} do
- for i = 1, #_BUFFERS do
- if current_buffer_first and _BUFFERS[i] == buffer or
- not current_buffer_first and _BUFFERS[i] ~= buffer then
- local buffer = _BUFFERS[i]
+ for _, buffer in ipairs(_BUFFERS) do
+ if current_buffer_first and buffer == _G.buffer or
+ not current_buffer_first and buffer ~= _G.buffer then
local basename = (buffer.filename or ''):match('[^/\\]+$') or
buffer._type or _L['Untitled']
if buffer.filename then
diff --git a/modules/textadept/command_entry.lua b/modules/textadept/command_entry.lua
index b652e9d9..a668516f 100644
--- a/modules/textadept/command_entry.lua
+++ b/modules/textadept/command_entry.lua
@@ -44,20 +44,18 @@ M.editing_keys = {__index = {
-- @name env
local env = setmetatable({}, {
__index = function(_, k)
- local f = buffer[k]
- if f and type(f) == 'function' then
- f = function(...) return buffer[k](buffer, ...) end
- elseif f == nil and type(view[k]) == 'function' then
- f = function(...) view[k](view, ...) end -- do not return a value
- elseif f == nil then
- f = view[k] or ui[k] or _G[k]
+ if type(buffer[k]) == 'function' then
+ return function(...) return buffer[k](buffer, ...) end
+ elseif type(view[k]) == 'function' then
+ return function(...) view[k](view, ...) end -- do not return a value
end
- return f
+ return buffer[k] or view[k] or ui[k] or _G[k]
end,
__newindex = function(self, k, v)
local ok, value = pcall(function() return buffer[k] end)
if ok and value ~= nil or not ok and value:find('write-only property') then
- buffer[k] = v return
+ buffer[k] = v
+ return
end
if view[k] ~= nil then view[k] = v return end
if ui[k] ~= nil then ui[k] = v return end
@@ -165,12 +163,15 @@ local lua_mode_keys = {['\t'] = complete_lua}
-- @name run
function M.run(f, mode_keys, lexer, height)
if M:auto_c_active() then M:auto_c_cancel() end -- may happen in curses
- if not f and not mode_keys then
+ if not assert_type(f, 'function/nil', 1) and not mode_keys then
f, mode_keys, lexer = run_lua, lua_mode_keys, 'lua'
- elseif type(mode_keys) == 'string' then
- mode_keys, lexer, height = {}, mode_keys, lexer
- elseif not mode_keys then
+ elseif type(assert_type(mode_keys, 'table/string/nil', 2)) == 'string' then
+ lexer, height = mode_keys, assert_type(lexer, 'number/nil', 3)
mode_keys = {}
+ else
+ if not mode_keys then mode_keys = {} end
+ assert_type(lexer, 'string/nil', 3)
+ assert_type(height, 'number/nil', 4)
end
if not mode_keys['esc'] then mode_keys['esc'] = M.focus end -- hide
mode_keys['\n'] = mode_keys['\n'] or function()
diff --git a/modules/textadept/editing.lua b/modules/textadept/editing.lua
index 5ba6bdc6..1c9fbcb3 100644
--- a/modules/textadept/editing.lua
+++ b/modules/textadept/editing.lua
@@ -125,7 +125,8 @@ M.api_files = setmetatable({}, {__index = function(t, k)
return t[k]
end})
--- Matches characters specified in auto_pairs.
+-- Matches characters specified in auto_pairs, taking multiple selections into
+-- account.
events.connect(events.CHAR_ADDED, function(code)
if M.auto_pairs and M.auto_pairs[code] then
buffer:begin_undo_action()
@@ -138,7 +139,7 @@ events.connect(events.CHAR_ADDED, function(code)
end
end)
--- Removes matched chars on backspace.
+-- Removes matched chars on backspace, taking multiple selections into account.
events.connect(events.KEYPRESS, function(code)
if not M.auto_pairs or keys.KEYSYMS[code] ~= '\b' then return end
buffer:begin_undo_action()
@@ -163,7 +164,8 @@ events.connect(events.UPDATE_UI, function(updated)
f(pos, match)
end)
--- Moves over typeover characters when typed.
+-- Moves over typeover characters when typed, taking multiple selections into
+-- account.
events.connect(events.KEYPRESS, function(code)
if M.typeover_chars and M.typeover_chars[code] then
local handled = false
@@ -353,7 +355,7 @@ end
-- one.
-- @name goto_line
function M.goto_line(line)
- if not line then
+ if not assert_type(line, 'number/nil', 1) then
local button, value = ui.dialogs.inputbox{
title = _L['Go To'], informative_text = _L['Line Number:'],
button1 = _L['OK'], button2 = _L['Cancel']
@@ -409,6 +411,8 @@ end
-- @param right The right part of the enclosure.
-- @name enclose
function M.enclose(left, right)
+ assert_type(left, 'string', 1)
+ assert_type(right, 'string', 2)
buffer:begin_undo_action()
for i = 0, buffer.selections - 1 do
local s, e = buffer.selection_n_start[i], buffer.selection_n_end[i]
@@ -438,7 +442,7 @@ end
-- @name select_enclosed
function M.select_enclosed(left, right)
local s, e, anchor, pos = -1, -1, buffer.anchor, buffer.current_pos
- if left and right then
+ if assert_type(left, 'string/nil', 1) and assert_type(right, 'string', 2) then
if anchor ~= pos then buffer:goto_pos(pos - #right) end
buffer:search_anchor()
s, e = buffer:search_prev(0, left), buffer:search_next(0, right)
@@ -590,6 +594,7 @@ end
-- @name filter_through
function M.filter_through(command)
assert(not (WIN32 and CURSES), 'not implemented in this environment')
+ assert_type(command, 'string', 1)
local s, e = buffer.selection_start, buffer.selection_end
if s ~= e then
-- Use the selected lines as input.
@@ -637,7 +642,7 @@ end
-- @name autocomplete
-- @see autocompleters
function M.autocomplete(name)
- if not M.autocompleters[name] then return end
+ if not M.autocompleters[assert_type(name, 'string', 1)] then return end
local len_entered, list = M.autocompleters[name]()
if not len_entered or not list or #list == 0 then return end
buffer.auto_c_order = buffer.ORDER_PERFORMSORT
@@ -656,9 +661,8 @@ M.autocompleters.word = function()
local s = buffer:word_start_position(buffer.current_pos, true)
if s == buffer.current_pos then return end
local word = buffer:text_range(s, buffer.current_pos)
- for i = 1, #_BUFFERS do
- if _BUFFERS[i] == buffer or M.autocomplete_all_words then
- local buffer = _BUFFERS[i]
+ for _, buffer in ipairs(_BUFFERS) do
+ if buffer == _G.buffer or M.autocomplete_all_words then
buffer.search_flags = buffer.FIND_WORDSTART
if not buffer.auto_c_ignore_case then
buffer.search_flags = buffer.search_flags + buffer.FIND_MATCHCASE
@@ -696,7 +700,7 @@ function M.show_documentation(pos, case_insensitive)
if buffer:call_tip_active() then events.emit(events.CALL_TIP_CLICK) return end
local lang = buffer:get_lexer(true)
if not M.api_files[lang] then return end
- if not pos then pos = buffer.current_pos end
+ if not assert_type(pos, 'number/nil', 1) then pos = buffer.current_pos end
local s = buffer:word_start_position(pos, true)
local e = buffer:word_end_position(pos, true)
local symbol = buffer:text_range(s, e)
@@ -710,8 +714,7 @@ function M.show_documentation(pos, case_insensitive)
return string.format('[%s%s]', ch:upper(), ch:lower())
end)
end
- for i = 1, #M.api_files[lang] do
- local file = M.api_files[lang][i]
+ for _, file in ipairs(M.api_files[lang]) do
if type(file) == 'function' then file = file() end
if file and lfs.attributes(file) then
for line in io.lines(file) do
diff --git a/modules/textadept/file_types.lua b/modules/textadept/file_types.lua
index df35f861..f0ce5bdb 100644
--- a/modules/textadept/file_types.lua
+++ b/modules/textadept/file_types.lua
@@ -65,6 +65,7 @@ local SETLEXERLANGUAGE = _SCINTILLA.properties.lexer_language[2]
local GETERROR = _SCINTILLA.properties.status[1]
-- LuaDoc is in core/.buffer.luadoc.
local function set_lexer(buffer, lang)
+ assert_type(lang, 'string/nil', 2)
if not lang then lang = detect_language(buffer) end
buffer:private_lexer_call(SETDIRECTPOINTER, buffer.direct_pointer)
buffer:private_lexer_call(SETLEXERLANGUAGE, lang)
diff --git a/modules/textadept/find.lua b/modules/textadept/find.lua
index 70a3f7d7..48601ee3 100644
--- a/modules/textadept/find.lua
+++ b/modules/textadept/find.lua
@@ -124,6 +124,7 @@ end
-- internally, and should not be set otherwise.
-- @return position of the found text or `-1`
local function find(text, next, flags, no_wrap, wrapped)
+ -- Note: cannot use assert_type(), as event errors are handled silently.
if text == '' then return end
if not flags then flags = get_flags() end
if flags >= 0x1000000 then M.find_in_files() return end -- not performed here
@@ -193,7 +194,7 @@ end
-- caret position instead of the position where the incremental search began.
-- Only the `match_case` find option is recognized. Normal command entry
-- functionality is unavailable until the search is finished or by pressing
--- `Esc` (`⎋` on Mac OSX | `Esc` in curses).
+-- `Esc`.
-- @param text The text to incrementally search for, or `nil` to begin an
-- incremental search.
-- @param next Flag indicating whether or not the search direction is forward.
@@ -201,6 +202,7 @@ end
-- the caret position. The default value is `false`.
-- @name find_incremental
function M.find_incremental(text, next, anchor)
+ assert_type(text, 'string/nil', 1)
if text then find_incremental(text, next, anchor) return end
incremental_start = buffer.current_pos
ui.command_entry:set_text('')
@@ -251,17 +253,20 @@ end})
-- @see find_in_files_filters
-- @name find_in_files
function M.find_in_files(dir, filter)
- dir = dir or ui.dialogs.fileselect{
- title = _L['Select Directory'], select_only_directories = true,
- with_directory = io.get_project_root() or
- (buffer.filename or ''):match('^.+[/\\]') or
- lfs.currentdir()
- }
- if not dir then return end
+ if not assert_type(dir, 'string/nil', 1) then
+ dir = ui.dialogs.fileselect{
+ title = _L['Select Directory'], select_only_directories = true,
+ with_directory = io.get_project_root() or
+ (buffer.filename or ''):match('^.+[/\\]') or
+ lfs.currentdir()
+ }
+ if not dir then return end
+ end
if buffer._type ~= _L['[Files Found Buffer]'] then preferred_view = view end
ui.silent_print = false
- ui._print(_L['[Files Found Buffer]'], _L['Find:']..' '..M.find_entry_text)
+ ui._print(_L['[Files Found Buffer]'],
+ _L['Find:']:gsub('_', '')..' '..M.find_entry_text)
buffer.indicator_current = M.INDIC_FIND
local ff_buffer = buffer
@@ -273,8 +278,8 @@ function M.find_in_files(dir, filter)
buffer:clear_all()
buffer:empty_undo_buffer()
local f = io.open(filename, 'rb')
- while f:read(0) do buffer:append_text(f:read(1048576)) end
- --buffer:set_text(f:read('a'))
+ while f:read(0) do buffer:append_text(f:read(1048576)) end -- TODO: why?
+ --buffer:set_text(f:read('a')) -- TODO: why not?
f:close()
local binary = nil -- determine lazily for performance reasons
buffer:target_whole_document()
@@ -402,7 +407,7 @@ function M.goto_file_found(line_num, next)
if ff_view then ui.goto_view(ff_view) else view:goto_buffer(ff_buf) end
-- If no line was given, find the next search result.
- if not line_num and next ~= nil then
+ if not assert_type(line_num, 'number/nil', 1) and next ~= nil then
if next then buffer:line_end() else buffer:home() end
buffer:search_anchor()
local f = buffer['search_'..(next and 'next' or 'prev')]
diff --git a/modules/textadept/macros.lua b/modules/textadept/macros.lua
index 123c0392..a965b254 100644
--- a/modules/textadept/macros.lua
+++ b/modules/textadept/macros.lua
@@ -87,10 +87,12 @@ end
-- @name save
function M.save(filename)
if recording or not macro then return end
- filename = filename or ui.dialogs.filesave{
- title = _L['Save Macro'], with_directory = _USERHOME, with_extension = 'm'
- }
- if not filename then return end
+ if not assert_type(filename, 'string/nil', 1) then
+ filename = ui.dialogs.filesave{
+ title = _L['Save Macro'], with_directory = _USERHOME, with_extension = 'm'
+ }
+ if not filename then return end
+ end
local f = assert(io.open(filename, 'w'))
f:write('return {\n')
for i = 1, #macro do
@@ -113,10 +115,13 @@ end
-- @name load
function M.load(filename)
if recording then return end
- filename = filename or ui.dialogs.fileselect{
- title = _L['Load Macro'], with_directory = _USERHOME, with_extension = 'm'
- }
- if filename then macro = assert(loadfile(filename, 't', {}))() end
+ if not assert_type(filename, 'string/nil', 1) then
+ filename = ui.dialogs.fileselect{
+ title = _L['Load Macro'], with_directory = _USERHOME, with_extension = 'm'
+ }
+ if not filename then return end
+ end
+ macro = assert(loadfile(filename, 't', {}))()
end
return M
diff --git a/modules/textadept/menu.lua b/modules/textadept/menu.lua
index 25341bbe..9684516b 100644
--- a/modules/textadept/menu.lua
+++ b/modules/textadept/menu.lua
@@ -22,9 +22,7 @@ local menu_buffer_functions = {
'select_all', 'upper_case', 'lower_case', 'move_selected_lines_up',
'move_selected_lines_down', 'zoom_in', 'zoom_out'
}
-for i = 1, #menu_buffer_functions do
- buffer[menu_buffer_functions[i]] = buffer[menu_buffer_functions[i]]
-end
+for _, f in ipairs(menu_buffer_functions) do buffer[f] = buffer[f] end
-- Commonly used functions in menu commands.
local sel_enc = textadept.editing.select_enclosed
diff --git a/modules/textadept/run.lua b/modules/textadept/run.lua
index 52471ed7..f23babd7 100644
--- a/modules/textadept/run.lua
+++ b/modules/textadept/run.lua
@@ -169,7 +169,7 @@ local function compile_or_run(filename, commands)
-- Replace macros in the command.
local dirname, basename = '', filename
if filename:find('[/\\]') then
- dirname, basename = filename:match('^(.+[/\\])([^/\\]+)$')
+ dirname, basename = filename:match('^(.+)[/\\]([^/\\]+)$')
end
local basename_no_ext = basename:match('^(.+)%.')
command = command:gsub('%%([pdfe])', {
@@ -182,7 +182,7 @@ local function compile_or_run(filename, commands)
local ext_or_lexer = commands[ext] and ext or lexer
local function emit(output) events.emit(event, output, ext_or_lexer) end
-- Run the command.
- cwd = working_dir or dirname
+ cwd = (working_dir or dirname):gsub('[/\\]$', '')
if cwd ~= dirname then events.emit(event, '> cd '..cwd..'\n') end
events.emit(event, '> '..command:iconv('UTF-8', _CHARSET)..'\n')
proc = assert(os.spawn(command, cwd, emit, emit, function(status)
@@ -219,8 +219,9 @@ M.compile_commands = {actionscript='mxmlc "%f"',ada='gnatmake "%f"',ansi_c='gcc
-- @see _G.events
-- @name compile
function M.compile(filename)
- if not filename and not buffer.filename then return end
- compile_or_run(filename or buffer.filename, M.compile_commands)
+ if assert_type(filename, 'string/nil', 1) or buffer.filename then
+ compile_or_run(filename or buffer.filename, M.compile_commands)
+ end
end
events.connect(events.COMPILE_OUTPUT, print_output)
@@ -252,8 +253,9 @@ M.run_commands = {actionscript=WIN32 and 'start "" "%e.swf"' or OSX and 'open "f
-- @see _G.events
-- @name run
function M.run(filename)
- if not filename and not buffer.filename then return end
- compile_or_run(filename or buffer.filename, M.run_commands)
+ if assert_type(filename, 'string/nil', 1) or buffer.filename then
+ compile_or_run(filename or buffer.filename, M.run_commands)
+ end
end
events.connect(events.RUN_OUTPUT, print_output)
@@ -280,8 +282,10 @@ M.build_commands = {--[[Ant]]['build.xml']='ant',--[[Dockerfile]]Dockerfile='doc
-- @see _G.events
-- @name build
function M.build(root_directory)
- if not root_directory then root_directory = io.get_project_root() end
- if not root_directory then return end
+ if not assert_type(root_directory, 'string/nil', 1) then
+ root_directory = io.get_project_root()
+ if not root_directory then return end
+ end
for i = 1, #_BUFFERS do _BUFFERS[i]:annotation_clear_all() end
-- Determine command.
local command = M.build_commands[root_directory]
@@ -304,7 +308,7 @@ function M.build(root_directory)
preferred_view = view
local function emit(output) events.emit(events.BUILD_OUTPUT, output) end
-- Run the command.
- cwd = working_dir or root_directory
+ cwd = (working_dir or root_directory):gsub('[/\\]$', '')
events.emit(events.BUILD_OUTPUT, '> cd '..cwd..'\n')
events.emit(events.BUILD_OUTPUT, '> '..command:iconv('UTF-8', _CHARSET)..'\n')
proc = assert(os.spawn(command, cwd, emit, emit, function(status)
@@ -374,7 +378,7 @@ function M.goto_error(line, next)
if msg_view then ui.goto_view(msg_view) else view:goto_buffer(msg_buf) end
-- If no line was given, find the next warning or error marker.
- if not line and next ~= nil then
+ if not assert_type(line, 'number/nil', 1) and next ~= nil then
local f = buffer['marker_'..(next and 'next' or 'previous')]
line = buffer:line_from_position(buffer.current_pos)
local wrapped = false
diff --git a/modules/textadept/session.lua b/modules/textadept/session.lua
index ef85c9a2..972f3cbb 100644
--- a/modules/textadept/session.lua
+++ b/modules/textadept/session.lua
@@ -32,10 +32,12 @@ local session_file = _USERHOME..(not CURSES and '/session' or '/session_term')
-- @name load
function M.load(filename)
local dir, name = session_file:match('^(.-[/\\]?)([^/\\]+)$')
- filename = filename or ui.dialogs.fileselect{
- title = _L['Load Session'], with_directory = dir, with_file = name
- }
- if not filename then return end
+ if not assert_type(filename, 'string/nil', 1) then
+ filename = ui.dialogs.fileselect{
+ title = _L['Load Session'], with_directory = dir, with_file = name
+ }
+ if not filename then return end
+ end
local not_found = {}
local f = io.open(filename, 'rb')
if not f or not io.close_all_buffers() then return false end
@@ -121,18 +123,19 @@ events.connect(events.ARG_NONE, load_default_session)
-- @name save
function M.save(filename)
local dir, name = session_file:match('^(.-[/\\]?)([^/\\]+)$')
- filename = filename or ui.dialogs.filesave{
- title = _L['Save Session'], with_directory = dir,
- with_file = name:iconv('UTF-8', _CHARSET)
- }
- if not filename then return end
+ if not assert_type(filename, 'string/nil', 1) then
+ filename = ui.dialogs.filesave{
+ title = _L['Save Session'], with_directory = dir,
+ with_file = name:iconv('UTF-8', _CHARSET)
+ }
+ if not filename then return end
+ end
local session = {}
local buffer_line = 'buffer: %d %d %d %s' -- anchor, cursor, line, filename
local split_line = '%ssplit%d: %s %d' -- level, number, type, size
local view_line = '%sview%d: %d' -- level, number, doc index
-- Write out opened buffers.
- for i = 1, #_BUFFERS do
- local buffer = _BUFFERS[i]
+ for _, buffer in ipairs(_BUFFERS) do
local filename = buffer.filename or buffer._type
if filename then
local current = buffer == view.buffer
diff --git a/modules/textadept/snippets.lua b/modules/textadept/snippets.lua
index 7bdfab7e..e59bc223 100644
--- a/modules/textadept/snippets.lua
+++ b/modules/textadept/snippets.lua
@@ -309,12 +309,6 @@ local function new_snippet(text, trigger)
end
placeholder.position = #snapshot.text
if placeholder.default then
- -- Execute any embedded code first.
- placeholder.default = placeholder.default:gsub('%%%b<>', function(s)
- return snippet:execute_code{lua_code = s:sub(3, -2)}
- end):gsub('%%%b[]', function(s)
- return snippet:execute_code{sh_code = s:sub(3, -2)}
- end)
if placeholder.default:find('%%%d+') then
-- Parses out embedded placeholders, adding them to this snippet's
-- snapshot.
@@ -595,8 +589,11 @@ snippet_mt = {
-- @name insert
function M.insert(text)
local trigger
- if not text then trigger, text = find_snippet(trigger) end
- if type(text) == 'function' and not trigger:find('^_') then text = text() end
+ if not assert_type(text, 'string/nil', 1) then
+ trigger, text = find_snippet(trigger)
+ if type(text) == 'function' and not trigger:find('^_') then text = text() end
+ assert_type(text, 'string/nil', trigger or '?')
+ end
local snippet = type(text) == 'string' and new_snippet(text, trigger) or
snippet_stack[#snippet_stack]
if snippet then snippet:next() else return false end
@@ -633,8 +630,7 @@ function M.select()
all_snippets[#all_snippets + 1], all_snippets[trigger] = trigger, snippet
end
table.sort(all_snippets)
- for i = 1, #all_snippets do
- local trigger = all_snippets[i]
+ for _, trigger in ipairs(all_snippets) do
items[#items + 1], items[#items + 2] = trigger, all_snippets[trigger]
end
local button, i = ui.dialogs.filteredlist{