diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/args.lua | 30 | ||||
-rw-r--r-- | core/events.lua | 6 | ||||
-rw-r--r-- | core/file_io.lua | 71 | ||||
-rw-r--r-- | core/iface.lua | 2 | ||||
-rw-r--r-- | core/init.lua | 18 | ||||
-rw-r--r-- | core/keys.lua | 31 | ||||
-rw-r--r-- | core/lfs_ext.lua | 21 | ||||
-rw-r--r-- | core/locale.lua | 10 | ||||
-rw-r--r-- | core/ui.lua | 139 |
9 files changed, 163 insertions, 165 deletions
diff --git a/core/args.lua b/core/args.lua index 83c15917..6bf2b9e4 100644 --- a/core/args.lua +++ b/core/args.lua @@ -72,18 +72,18 @@ if not CURSES then print('Usage: textadept [args] [filenames]') local list = {} for name in pairs(switches) do list[#list + 1] = name end - table.sort(list, - function(a, b) return a:match('[^-]+') < b:match('[^-]+') end) - for i = 1, #list do - local switch = switches[list[i]] - print(string.format(' %s [%d args]: %s', list[i], switch.narg, - switch.description)) + table.sort( + list, function(a, b) return a:match('[^-]+') < b:match('[^-]+') end) + for _, name in ipairs(list) do + local switch = switches[name] + print(string.format( + ' %s [%d args]: %s', name, switch.narg, switch.description)) end os.exit() end, 'Shows this') -- Shows Textadept version and copyright on the command line. M.register('-v', '--version', 0, function() - print(_RELEASE..'\n'.._COPYRIGHT) + print(_RELEASE .. '\n' .. _COPYRIGHT) quit() end, 'Prints Textadept version and copyright') -- After Textadept finishes initializing and processes arguments, remove the @@ -99,21 +99,23 @@ end -- Set `_G._USERHOME`. -- This needs to be set as soon as possible since the processing of arguments is -- positional. -_USERHOME = os.getenv(not WIN32 and 'HOME' or 'USERPROFILE')..'/.textadept' -for i = 1, #arg do - if (arg[i] == '-u' or arg[i] == '--userhome') and arg[i + 1] then +_USERHOME = os.getenv(not WIN32 and 'HOME' or 'USERPROFILE') .. '/.textadept' +for i, switch in ipairs(arg) do + if (switch == '-u' or switch == '--userhome') and arg[i + 1] then _USERHOME = arg[i + 1] break end end local mode = lfs.attributes(_USERHOME, 'mode') assert(not mode or mode == 'directory', '"%s" is not a directory', _USERHOME) -if not mode then assert(lfs.mkdir(_USERHOME), 'cannot create %s', _USERHOME) end -local user_init = _USERHOME..'/init.lua' +if not mode then + assert(lfs.mkdir(_USERHOME), 'cannot create "%s"', _USERHOME) +end +local user_init = _USERHOME .. '/init.lua' mode = lfs.attributes(user_init, 'mode') -assert(not mode or mode == 'file', '"%s" is not a file', user_init) +assert(not mode or mode == 'file', '"%s" is not a file (%s)', user_init, mode) if not mode then - assert(io.open(user_init, 'w'), 'unable to create %s', user_init):close() + assert(io.open(user_init, 'w'), 'unable to create "%s"', user_init):close() end -- Placeholders. diff --git a/core/events.lua b/core/events.lua index 438bb5bf..0cbd8f2f 100644 --- a/core/events.lua +++ b/core/events.lua @@ -294,6 +294,10 @@ local M = {} -- Emitted by [`buffer.zoom_in()`]() and [`buffer.zoom_out()`](). module('events')]] +-- Map of event names to tables of handler functions. +-- Handler tables are auto-created as needed. +-- @class table +-- @name handlers local handlers = setmetatable({}, {__index = function(t, k) t[k] = {} return t[k] @@ -377,7 +381,7 @@ end) -- Set event constants. for _, v in pairs(_SCINTILLA.events) do M[v[1]:upper()] = v[1] end -local textadept_events = { +local textadept_events = { -- defined in C 'appleevent_odoc', 'buffer_after_switch', 'buffer_before_switch', 'buffer_deleted', 'buffer_new', 'csi', 'error', 'find', 'focus', 'initialized', 'keypress', 'menu_clicked', 'mouse', 'quit', 'replace', diff --git a/core/file_io.lua b/core/file_io.lua index 846b4a86..88db6907 100644 --- a/core/file_io.lua +++ b/core/file_io.lua @@ -99,8 +99,8 @@ function io.open_file(filenames, encodings) if not assert_type(filenames, 'string/table/nil', 1) then filenames = ui.dialogs.fileselect{ title = _L['Open File'], select_multiple = true, - with_directory = (buffer.filename or ''):match('^.+[/\\]') or - lfs.currentdir(), + with_directory = + (buffer.filename or ''):match('^.+[/\\]') or lfs.currentdir(), width = CURSES and ui.size[1] - 2 or nil } if not filenames then return end @@ -144,8 +144,7 @@ function io.open_file(filenames, encodings) -- Detect EOL mode. buffer.eol_mode = text:find('\r\n') and buffer.EOL_CRLF or buffer.EOL_LF -- Insert buffer text and set properties. - buffer:add_text(text, #text) - buffer:goto_pos(0) + buffer:append_text(text) buffer:empty_undo_buffer() buffer.mod_time = lfs.attributes(filename, 'modification') or os.time() buffer.filename = filename @@ -174,10 +173,9 @@ function io.reload_file() local text = f:read('a') f:close() if buffer.encoding then text = text:iconv('UTF-8', buffer.encoding) end - buffer:clear_all() - buffer:add_text(text, #text) - buffer:line_scroll(0, first_visible_line) + buffer:set_text(text) buffer:goto_pos(pos) + buffer.first_visible_line = first_visible_line buffer:set_save_point() buffer.mod_time = lfs.attributes(buffer.filename, 'modification') end @@ -192,10 +190,9 @@ local function set_encoding(buffer, encoding) if encoding then text = text:iconv(encoding, buffer.encoding) end end if encoding then text = text:iconv('UTF-8', encoding) end - buffer:clear_all() - buffer:add_text(text, #text) - buffer:line_scroll(0, first_visible_line) + buffer:set_text(text) buffer:goto_pos(pos) + buffer.first_visible_line = first_visible_line buffer.encoding = encoding buffer.code_page = buffer.encoding and buffer.CP_UTF8 or 0 end @@ -213,8 +210,7 @@ function io.save_file() events.emit(events.FILE_BEFORE_SAVE, buffer.filename) local text = buffer:get_text() if buffer.encoding then text = text:iconv(buffer.encoding, 'UTF-8') end - local f = assert(io.open(buffer.filename, 'wb')) - f:write(text):close() + assert(io.open(buffer.filename, 'wb')):write(text):close() buffer:set_save_point() buffer.mod_time = lfs.attributes(buffer.filename, 'modification') if buffer._type then buffer._type = nil end @@ -265,13 +261,15 @@ end function io.close_buffer() local filename = buffer.filename or buffer._type or _L['Untitled'] if buffer.filename then filename = filename:iconv('UTF-8', _CHARSET) end - local confirm = not buffer.modify or ui.dialogs.msgbox{ - title = _L['Close without saving?'], - text = _L['There are unsaved changes in'], informative_text = filename, - icon = 'gtk-dialog-question', button1 = _L['Cancel'], - button2 = _L['Close without saving'] - } == 2 - if not confirm then return nil end -- nil return won't propagate a key command + if buffer.modify then + local button = ui.dialogs.msgbox{ + title = _L['Close without saving?'], + text = _L['There are unsaved changes in'], informative_text = filename, + icon = 'gtk-dialog-question', button1 = _L['Cancel'], + button2 = _L['Close without saving'] + } + if button ~= 2 then return nil end -- do not propagate key command + end buffer:delete() return true end @@ -280,13 +278,13 @@ end -- Closes all open buffers, prompting the user to continue if there are unsaved -- buffers, and returns `true` if the user did not cancel. -- No buffers are saved automatically. They must be saved manually. --- @return `true` if user did not cancel. +-- @return `true` if user did not cancel; `nil` otherwise. -- @see io.close_buffer -- @name close_all_buffers function io.close_all_buffers() while #_BUFFERS > 1 do view:goto_buffer(_BUFFERS[#_BUFFERS]) - if not io.close_buffer() then return false end + if not io.close_buffer() then return nil end -- do not propagate key command end return io.close_buffer() -- the last one end @@ -299,7 +297,7 @@ local function update_modified_file() if not mod_time or not buffer.mod_time then return end if buffer.mod_time < mod_time then buffer.mod_time = mod_time - events.emit(events.FILE_CHANGED) + events.emit(events.FILE_CHANGED, buffer.filename) end end events_connect(events.BUFFER_AFTER_SWITCH, update_modified_file) @@ -309,14 +307,14 @@ events_connect(events.RESUME, update_modified_file) -- Prompts the user to reload the current file if it has been externally -- modified. -events_connect(events.FILE_CHANGED, function() +events_connect(events.FILE_CHANGED, function(filename) local button = ui.dialogs.msgbox{ title = _L['Reload?'], text = _L['Reload modified file?'], - informative_text = string.format('"%s"\n%s', - buffer.filename:iconv('UTF-8', _CHARSET), - _L['has been modified. Reload it?']), + informative_text = string.format( + '"%s"\n%s', filename:iconv('UTF-8', _CHARSET), + _L['has been modified. Reload it?']), icon = 'gtk-dialog-question', button1 = _L['Yes'], button2 = _L['No'], - width = CURSES and #buffer.filename > 40 and ui.size[1] - 2 or nil + width = CURSES and #filename > 40 and ui.size[1] - 2 or nil } if button == 1 then io.reload_file() end end) @@ -360,11 +358,13 @@ local vcs = {'.bzr', '.git', '.hg', '.svn'} -- @return string root or nil -- @name get_project_root function io.get_project_root(path) - local dir = assert_type(path, 'string/nil', 1) or - (buffer.filename or lfs.currentdir()..'/'):match('^(.+)[/\\]') + if not assert_type(path, 'string/nil', 1) then + path = buffer.filename or lfs.currentdir() + end + local dir = path:match('^(.+)[/\\]?') while dir do for i = 1, #vcs do - if lfs.attributes(dir..'/'..vcs[i], 'mode') then return dir end + if lfs.attributes(dir .. '/' .. vcs[i], 'mode') then return dir end end dir = dir:match('^(.+)[/\\]') end @@ -420,26 +420,25 @@ function io.quick_open(paths, filter, opts) paths = io.get_project_root() if not paths then return end end - assert_type(filter, 'string/table/nil', 2) - assert_type(opts, 'table/nil', 3) if type(paths) == 'string' then if not filter then filter = io.quick_open_filters[paths] end paths = {paths} end + assert_type(filter, 'string/table/nil', 2) + assert_type(opts, 'table/nil', 3) local utf8_list = {} for i = 1, #paths do lfs.dir_foreach(paths[i], function(filename) if #utf8_list >= io.quick_open_max then return false end - filename = filename:gsub('^%.[/\\]', '') utf8_list[#utf8_list + 1] = filename:iconv('UTF-8', _CHARSET) end, filter or lfs.default_filter) end if #utf8_list >= io.quick_open_max then ui.dialogs.msgbox{ title = _L['File Limit Exceeded'], - text = string.format('%d %s %d', io.quick_open_max, - _L['files or more were found. Showing the first'], - io.quick_open_max), + text = string.format( + '%d %s %d', io.quick_open_max, + _L['files or more were found. Showing the first'], io.quick_open_max), icon = 'gtk-dialog-info' } end diff --git a/core/iface.lua b/core/iface.lua index d6d9066e..f3f3847a 100644 --- a/core/iface.lua +++ b/core/iface.lua @@ -59,6 +59,7 @@ local marker_number, indic_number, list_type, image_type = -1, -1, 0, 0 -- @see buffer.marker_define -- @name next_marker_number function M.next_marker_number() + assert(marker_number < M.constants.MARKER_MAX, 'too many markers in use') marker_number = marker_number + 1 return marker_number end @@ -71,6 +72,7 @@ end -- @see buffer.indic_style -- @name next_indic_number function M.next_indic_number() + assert(indic_number < M.constants.INDICATOR_MAX, 'too many indicators in use') indic_number = indic_number + 1 return indic_number end diff --git a/core/init.lua b/core/init.lua index 0f187e0a..8b4f3d48 100644 --- a/core/init.lua +++ b/core/init.lua @@ -1,13 +1,13 @@ -- Copyright 2007-2020 Mitchell mitchell.att.foicica.com. See LICENSE. _RELEASE = 'Textadept 10.8' -_COPYRIGHT = 'Copyright © 2007-2020 Mitchell. See LICENSE.\n'.. - 'http://foicica.com/textadept' +_COPYRIGHT = + 'Copyright © 2007-2020 Mitchell. See LICENSE.\nhttp://foicica.com/textadept' -package.path = _HOME..'/core/?.lua;'..package.path +package.path = string.format('%s/core/?.lua;%s', _HOME, package.path) ---for i = 1, #arg do --- if arg[i] == '-t' or arg[i] == '--test' then pcall(require, 'luacov') end +--for _, arg in ipairs(arg) do +-- if arg == '-t' or arg == '--test' then pcall(require, 'luacov') end --end require('assert') @@ -24,18 +24,18 @@ _M = {} -- language modules table -- pdcurses compatibility. if CURSES and WIN32 then function os.spawn(argv, ...) - local current_dir = lfs.currentdir() + local cwd = lfs.currentdir() local args, i = {...}, 1 if type(args[i]) == 'string' then lfs.chdir(args[i]) -- cwd i = i + 1 end if type(args[i]) == 'table' then i = i + 1 end -- env (ignore) - local p = io.popen(argv..' 2>&1') + local p = io.popen(assert_type(argv, 'string', 1) .. ' 2>&1') if type(args[i]) == 'function' then args[i](p:read('a')) end -- stdout_cb local status = select(3, p:close()) if type(args[i + 2]) == 'function' then args[i + 2](status) end -- exit_cb - lfs.chdir(current_dir) + lfs.chdir(cwd) -- restore return p end end @@ -51,7 +51,7 @@ local function text_range(buffer, start_pos, end_pos) if end_pos > buffer.length then end_pos = buffer.length end buffer:set_target_range(start_pos, end_pos) local text = buffer.target_text - buffer:set_target_range(target_start, target_end) -- reset + buffer:set_target_range(target_start, target_end) -- restore return text end events.connect(events.BUFFER_NEW, function() buffer.text_range = text_range end) diff --git a/core/keys.lua b/core/keys.lua index a6a9207a..f5002ae5 100644 --- a/core/keys.lua +++ b/core/keys.lua @@ -118,11 +118,11 @@ M.CLEAR = 'esc' -- @class table -- @name KEYSYMS M.KEYSYMS = { - -- From Scintilla.h. - [7] = 'esc', [13] = '\n', + -- From Scintilla.h for CURSES. + [7] = 'esc', [8] = '\b', [9] = '\t', [13] = '\n', -- From curses.h. [263] = '\b', [343] = '\n', - -- From Scintilla.h. + -- From Scintilla.h for CURSES. [300] = 'down', [301] = 'up', [302] = 'left', [303] = 'right', [304] = 'home', [305] = 'end', [306] = 'pgup', [307] = 'pgdn', [308] = 'del', [309] = 'ins', -- From <gdk/gdkkeysyms.h>. @@ -181,7 +181,8 @@ local function key_command(prefix) end if type(key) ~= 'function' and type(key) ~= 'table' then return INVALID end if type(key) == 'table' then - ui.statusbar_text = _L['Keychain:']..' '..table.concat(keychain, ' ') + ui.statusbar_text = string.format( + '%s %s', _L['Keychain:'], table.concat(keychain, ' ')) return CHAIN end return select(2, xpcall(key, key_error)) == false and PROPAGATE or HALT @@ -202,26 +203,22 @@ local function keypress(code, shift, control, alt, meta, caps_lock) if caps_lock and (shift or control or alt or meta) and code < 256 then code = string[shift and 'upper' or 'lower'](string.char(code)):byte() end - local key = code < 256 and (not CURSES or (code ~= 7 and code ~= 13)) and - string.char(code) or M.KEYSYMS[code] + local key = code >= 32 and code < 256 and string.char(code) or M.KEYSYMS[code] if not key then return end -- Since printable characters are uppercased, disable shift. - if shift and code < 256 and code ~= 9 then shift = false end + if shift and code >= 32 and code < 256 then shift = false end -- For composed keys on OSX, ignore alt. if OSX and alt and code < 256 then alt = false end - local key_seq = string.format('%s%s%s%s%s', control and CTRL or '', - alt and ALT or '', meta and OSX and META or '', - shift and SHIFT or '', key) + local key_seq = string.format( + '%s%s%s%s%s', control and CTRL or '', alt and ALT or '', + meta and OSX and META or '', shift and SHIFT or '', key) --print(key_seq) ui.statusbar_text = '' --if CURSES then ui.statusbar_text = string.format('"%s"', key_seq) end - local keychain_size = #keychain - if keychain_size > 0 and key_seq == M.CLEAR then - clear_key_sequence() - return true - end - keychain[keychain_size + 1] = key_seq + local in_chain = #keychain > 0 + if in_chain and key_seq == M.CLEAR then clear_key_sequence() return true end + keychain[#keychain + 1] = key_seq local status = PROPAGATE if not M.MODE then @@ -232,7 +229,7 @@ local function keypress(code, shift, control, alt, meta, caps_lock) end if status ~= CHAIN then clear_key_sequence() end if status > PROPAGATE then return true end -- CHAIN or HALT - if status == INVALID and keychain_size > 0 then + if status == INVALID and in_chain then ui.statusbar_text = _L['Invalid sequence'] return true end diff --git a/core/lfs_ext.lua b/core/lfs_ext.lua index 6d9fe2af..ada0431e 100644 --- a/core/lfs_ext.lua +++ b/core/lfs_ext.lua @@ -81,7 +81,8 @@ function lfs.dir_foreach(dir, f, filter, n, include_dirs, level) end for basename in lfs.dir(dir) do if basename:find('^%.%.?$') then goto continue end -- ignore . and .. - local filename = dir..(dir ~= '/' and '/' or '')..basename + local filename = string.format( + '%s%s%s', dir, dir ~= '/' and '/' or '', basename) local mode = lfs.attributes(filename, 'mode') if mode ~= 'directory' and mode ~= 'file' then goto continue end local include @@ -98,15 +99,14 @@ function lfs.dir_foreach(dir, f, filter, n, include_dirs, level) -- Treat inclusive patterns as logical OR. include = include or (not patt:find('^!') and filename:find(patt)) end - local dir_sep = not WIN32 and '/' or '\\' - local os_filename = not WIN32 and filename or filename:gsub('/', dir_sep) + local sep = not WIN32 and '/' or '\\' + local os_filename = not WIN32 and filename or filename:gsub('/', sep) if include and mode == 'directory' then - if include_dirs and f(os_filename..dir_sep) == false then return end - if not n or (level or 0) < n then - local halt = lfs.dir_foreach(filename, f, filter, n, include_dirs, - (level or 0) + 1) == false - if halt then return false end - end + if include_dirs and f(os_filename .. sep) == false then return false end + if n and (level or 0) >= n then goto continue end + local halt = lfs.dir_foreach( + filename, f, filter, n, include_dirs, (level or 0) + 1) == false + if halt then return false end elseif include and mode == 'file' then if f(os_filename) == false then return false end end @@ -129,7 +129,8 @@ function lfs.abspath(filename, prefix) if not filename:find(not WIN32 and '^/' or '^%a:[/\\]') and not (WIN32 and filename:find('^\\\\')) then if not prefix then prefix = lfs.currentdir() end - filename = prefix..(not WIN32 and '/' or '\\')..filename + filename = string.format( + '%s%s%s', prefix, not WIN32 and '/' or '\\', filename) end filename = filename:gsub('%f[^/\\]%.[/\\]', '') -- clean up './' while filename:find('[^/\\]+[/\\]%.%.[/\\]') do diff --git a/core/locale.lua b/core/locale.lua index 45a0f855..f6bc32cb 100644 --- a/core/locale.lua +++ b/core/locale.lua @@ -11,12 +11,14 @@ local M = {} -- use. module('_L')]] -local f = io.open(_USERHOME..'/locale.conf', 'rb') +local f = io.open(_USERHOME .. '/locale.conf', 'rb') if not f then local lang = (os.getenv('LANG') or ''):match('^[^_.@]+') -- TODO: LC_MESSAGES? - if lang then f = io.open(_HOME..'/core/locales/locale.'..lang..'.conf') end + if lang then + f = io.open(string.format('%s/core/locales/locale.%s.conf', _HOME, lang)) + end end -if not f then f = io.open(_HOME..'/core/locale.conf', 'rb') end +if not f then f = io.open(_HOME .. '/core/locale.conf', 'rb') end assert(f, '"core/locale.conf" not found') for line in f:lines() do -- Any line that starts with a non-word character except '[' is considered a @@ -30,5 +32,5 @@ for line in f:lines() do end f:close() -setmetatable(M, {__index = function(_, k) return 'No Localization:'..k end}) +setmetatable(M, {__index = function(_, k) return 'No Localization:' .. k end}) return M diff --git a/core/ui.lua b/core/ui.lua index 65b1fb4d..a90bd9b6 100644 --- a/core/ui.lua +++ b/core/ui.lua @@ -101,42 +101,38 @@ ui.dialogs = setmetatable({}, {__index = function(_, k) -- @param options Table of key-value command line options for gtdialog. -- @return Lua objects depending on the dialog kind return function(options) - -- Set up dialog defaults and convert any 1-based indices to 0-based ones. if not options.button1 then options.button1 = _L['OK'] end - local select = options.select - if type(select) == 'number' then - options.select = select - 1 - elseif type(select) == 'table' then - for i = 1, #select do select[i] = select[i] - 1 end - end -- Transform key-value pairs into command line arguments. local args = {} for option, value in pairs(options) do assert_type(value, 'string/number/table/boolean', option) if value then - args[#args + 1] = '--'..option:gsub('_', '-') - if type(value) == 'boolean' then goto continue end + args[#args + 1] = '--' .. option:gsub('_', '-') if type(value) == 'table' then for i, val in ipairs(value) do - assert_type(val, 'string/number', option..'['..i..']') + local narg = string.format('%s[%d]', option, i) + assert_type(val, 'string/number', narg) if option == 'palette' and type(val) == 'number' then value[i] = torgb(val) -- nil return is okay + elseif option == 'select' and assert_type(val, 'number', narg) then + value[i] = val - 1 -- convert from 1-based index to 0-based index end end elseif option == 'color' and type(value) == 'number' then value = torgb(value) + elseif option == 'select' and assert_type(value, 'number', option) then + value = value - 1 -- convert from 1-based index to 0-based index end - args[#args + 1] = value + if type(value) ~= 'boolean' then args[#args + 1] = value end end - ::continue:: end -- Call gtdialog, stripping any trailing newline in the standard output. - local result = ui.dialog(k:gsub('_', '-'), table.unpack(args)) - result = result:match('^(.-)\n?$') + local result = ui.dialog( + k:gsub('_', '-'), table.unpack(args)):match('^(.-)\n?$') -- Depending on the dialog type, transform the result into Lua objects. if k == 'fileselect' or k == 'filesave' then if result == '' then return nil end - if not CURSES and WIN32 then result = result:iconv(_CHARSET, 'UTF-8') end + if WIN32 and not CURSES then result = result:iconv(_CHARSET, 'UTF-8') end if k == 'filesave' or not options.select_multiple then return result end local filenames = {} for filename in result:gmatch('[^\n]+') do @@ -153,7 +149,7 @@ ui.dialogs = setmetatable({}, {__index = function(_, k) options.string_output, options.select_multiple = true, true end local items, patt = {}, not k:find('input') and '[^\n]+' or '([^\n]*)\n' - for item in (value..'\n'):gmatch(patt) do + for item in (value .. '\n'):gmatch(patt) do items[#items + 1] = options.string_output and item or tonumber(item) + 1 end return button, options.select_multiple and items or items[1] @@ -180,8 +176,7 @@ local buffers_zorder = {} -- Adds new buffers to the z-order list. events.connect(events.BUFFER_NEW, function() - if buffer == ui.command_entry then return end -- ignore this buffer - table.insert(buffers_zorder, 1, buffer) + if buffer ~= ui.command_entry then table.insert(buffers_zorder, 1, buffer) end end) -- Updates the z-order list. @@ -200,10 +195,10 @@ events.connect(events.BUFFER_AFTER_SWITCH, update_zorder) events.connect(events.VIEW_AFTER_SWITCH, update_zorder) -- Saves and restores buffer zorder data during a reset. -events.connect(events.RESET_BEFORE, - function(persist) persist.buffers_zorder = buffers_zorder end) -events.connect(events.RESET_AFTER, - function(persist) buffers_zorder = persist.buffers_zorder end) +events.connect( + events.RESET_BEFORE, function(persist) persist.ui_zorder = buffers_zorder end) +events.connect( + events.RESET_AFTER, function(persist) buffers_zorder = persist.ui_zorder end) --- -- Prompts the user to select a buffer to switch to. @@ -221,7 +216,7 @@ function ui.switch_buffer(zorder) local filename = buffer.filename or buffer._type or _L['Untitled'] if buffer.filename then filename = filename:iconv('UTF-8', _CHARSET) end local basename = buffer.filename and filename:match('[^/\\]+$') or filename - utf8_list[#utf8_list + 1] = (buffer.modify and '*' or '')..basename + utf8_list[#utf8_list + 1] = (buffer.modify and '*' or '') .. basename utf8_list[#utf8_list + 1] = filename end local button, i = ui.dialogs.filteredlist{ @@ -255,8 +250,9 @@ end -- @name goto_file function ui.goto_file(filename, split, preferred_view, sloppy) assert_type(filename, 'string', 1) - local patt = not sloppy and '^'..filename..'$' or - filename:match('[^/\\]+$')..'$' -- TODO: escape filename properly + local patt = string.format( -- TODO: escape filename properly + '%s%s$', not sloppy and '^' or '', + not sloppy and filename or filename:match('[^/\\]+$')) if WIN32 then patt = patt:gsub('%a', function(letter) return string.format('[%s%s]', letter:upper(), letter:lower()) @@ -285,8 +281,8 @@ local events, events_connect = events, events.connect events_connect(events.VIEW_NEW, function() events.emit(events.UPDATE_UI) end) -- Switches between buffers when a tab is clicked. -events_connect(events.TAB_CLICKED, - function(index, button) view:goto_buffer(_BUFFERS[index]) end) +events_connect( + events.TAB_CLICKED, function(index) view:goto_buffer(_BUFFERS[index]) end) -- Sets the title of the Textadept window to the buffer's filename. local function set_title() @@ -295,9 +291,9 @@ local function set_title() filename = select(2, pcall(string.iconv, filename, 'UTF-8', _CHARSET)) end local basename = buffer.filename and filename:match('[^/\\]+$') or filename - ui.title = string.format('%s %s Textadept (%s)', basename, - buffer.modify and '*' or '-', filename) - buffer.tab_label = basename..(buffer.modify and '*' or '') + ui.title = string.format( + '%s %s Textadept (%s)', basename, buffer.modify and '*' or '-', filename) + buffer.tab_label = basename .. (buffer.modify and '*' or '') end -- Changes Textadept title to show the buffer as being "clean" or "dirty". @@ -306,40 +302,37 @@ events_connect(events.SAVE_POINT_LEFT, set_title) -- Open uri(s). events_connect(events.URI_DROPPED, function(utf8_uris) - for utf8_uri in utf8_uris:gmatch('[^\r\n]+') do - if utf8_uri:find('^file://') then - local path = utf8_uri:match('^file://([^\r\n]+)') - path = path:gsub('%%(%x%x)', function(hex) - return string.char(tonumber(hex, 16)) - end):iconv(_CHARSET, 'UTF-8') - -- In WIN32, ignore a leading '/', but not '//' (network path). - if WIN32 and not path:match('^//') then path = path:sub(2, -1) end - local mode = lfs.attributes(path, 'mode') - if mode and mode ~= 'directory' then io.open_file(path) end - end + for utf8_path in utf8_uris:gmatch('file://([^\r\n]+)') do + local path = utf8_path:gsub('%%(%x%x)', function(hex) + return string.char(tonumber(hex, 16)) + end):iconv(_CHARSET, 'UTF-8') + -- In WIN32, ignore a leading '/', but not '//' (network path). + if WIN32 and not path:match('^//') then path = path:sub(2, -1) end + local mode = lfs.attributes(path, 'mode') + if mode and mode ~= 'directory' then io.open_file(path) end end ui.goto_view(view) -- work around any view focus synchronization issues end) events_connect(events.APPLEEVENT_ODOC, function(uri) - return events.emit(events.URI_DROPPED, 'file://'..uri) + return events.emit(events.URI_DROPPED, 'file://' .. uri) end) -local GETLEXERLANGUAGE = _SCINTILLA.properties.lexer_language[1] -- Sets buffer statusbar text. events_connect(events.UPDATE_UI, function(updated) if updated and updated & 3 == 0 then return end -- ignore scrolling + local text = + not CURSES and '%s %d/%d %s %d %s %s %s %s' or + '%s %d/%d %s %d %s %s %s %s' local pos = buffer.selection_n_caret[buffer.main_selection] local line, max = buffer:line_from_position(pos) + 1, buffer.line_count local col = buffer.column[pos] + 1 - local lexer = buffer:private_lexer_call(GETLEXERLANGUAGE):match('^[^/]+') + local lexer = buffer:get_lexer() local eol = buffer.eol_mode == buffer.EOL_CRLF and _L['CRLF'] or _L['LF'] - local tabs = string.format('%s %d', buffer.use_tabs and _L['Tabs:'] or - _L['Spaces:'], buffer.tab_width) + local tabs = string.format( + '%s %d', buffer.use_tabs and _L['Tabs:'] or _L['Spaces:'], buffer.tab_width) local enc = buffer.encoding or '' - local text = not CURSES and '%s %d/%d %s %d %s %s %s %s' or - '%s %d/%d %s %d %s %s %s %s' - ui.bufstatusbar_text = string.format(text, _L['Line:'], line, max, _L['Col:'], - col, lexer, eol, tabs, enc) + ui.bufstatusbar_text = string.format( + text, _L['Line:'], line, max, _L['Col:'], col, lexer, eol, tabs, enc) end) -- Save buffer properties. @@ -353,11 +346,11 @@ events_connect(events.BUFFER_BEFORE_SWITCH, function() buffer._top_line = buffer:doc_line_from_visible(buffer.first_visible_line) buffer._x_offset = buffer.x_offset -- Save fold state. - buffer._folds = {} - local folds, i = buffer._folds, buffer:contracted_fold_next(0) - while i >= 0 do + local folds, i = {}, buffer:contracted_fold_next(0) + while i ~= -1 do folds[#folds + 1], i = i, buffer:contracted_fold_next(i + 1) end + buffer._folds = folds end) -- Restore buffer properties. @@ -368,20 +361,17 @@ events_connect(events.BUFFER_AFTER_SWITCH, function() for i = 1, #buffer._folds do buffer:toggle_fold(buffer._folds[i]) end -- Restore view state. buffer:set_sel(buffer._anchor, buffer._current_pos) - local n = buffer.main_selection - buffer.selection_n_anchor_virtual_space[n] = buffer._anchor_virtual_space - buffer.selection_n_caret_virtual_space[n] = buffer._caret_virtual_space + buffer.selection_n_anchor_virtual_space[0] = buffer._anchor_virtual_space + buffer.selection_n_caret_virtual_space[0] = buffer._caret_virtual_space buffer:choose_caret_x() - buffer:line_scroll(0, buffer:visible_from_doc_line(buffer._top_line) - - buffer.first_visible_line) + local _top_line, top_line = buffer._top_line, buffer.first_visible_line + buffer:line_scroll(0, buffer:visible_from_doc_line(_top_line) - top_line) buffer.x_offset = buffer._x_offset or 0 end) -- Updates titlebar and statusbar. local function update_bars() set_title() - local SETDIRECTPOINTER = _SCINTILLA.properties.doc_pointer[2] - buffer:private_lexer_call(SETDIRECTPOINTER, buffer.direct_pointer) events.emit(events.UPDATE_UI) end events_connect(events.BUFFER_NEW, update_bars) @@ -416,8 +406,8 @@ end events_connect(events.BUFFER_AFTER_SWITCH, restore_view_state) events_connect(events.VIEW_AFTER_SWITCH, restore_view_state) -events_connect(events.RESET_AFTER, - function() ui.statusbar_text = _L['Lua reset'] end) +events_connect( + events.RESET_AFTER, function() ui.statusbar_text = _L['Lua reset'] end) -- Prompts for confirmation if any buffers are modified. events_connect(events.QUIT, function() @@ -429,19 +419,20 @@ events_connect(events.QUIT, function() utf8_list[#utf8_list + 1] = filename end end - local cancel = #utf8_list > 0 and ui.dialogs.msgbox{ + if #utf8_list == 0 then return end + local button = ui.dialogs.msgbox{ title = _L['Quit without saving?'], text = _L['The following buffers are unsaved:'], informative_text = table.concat(utf8_list, '\n'), icon = 'gtk-dialog-question', button1 = _L['Cancel'], button2 = _L['Quit without saving'] - } ~= 2 - if cancel then return true end -- prevent quit + } + if button ~= 2 then return true end -- prevent quit end) -- Keeps track of and switches back to the previous buffer after buffer close. -events_connect(events.BUFFER_BEFORE_SWITCH, - function() view._prev_buffer = buffer end) +events_connect( + events.BUFFER_BEFORE_SWITCH, function() view._prev_buffer = buffer end) events_connect(events.BUFFER_DELETED, function() if _BUFFERS[view._prev_buffer] and buffer ~= view._prev_buffer then view:goto_buffer(view._prev_buffer) @@ -451,11 +442,11 @@ end) -- Properly handle clipboard text between views in curses, enables and disables -- mouse mode, and focuses and resizes views based on mouse events. if CURSES then - local clipboard_text - events.connect(events.VIEW_BEFORE_SWITCH, - function() clipboard_text = ui.clipboard_text end) - events.connect(events.VIEW_AFTER_SWITCH, - function() ui.clipboard_text = clipboard_text end) + local clip_text + events.connect( + events.VIEW_BEFORE_SWITCH, function() clip_text = ui.clipboard_text end) + events.connect( + events.VIEW_AFTER_SWITCH, function() ui.clipboard_text = clip_text end) if not WIN32 then local function enable_mouse() io.stdout:write("\x1b[?1002h"):flush() end @@ -477,8 +468,8 @@ if CURSES then return get_view(view[1], y, x) elseif vertical and x > size or not vertical and y > size then -- Zero y or x relative to the other view based on split orientation. - return get_view(view[2], vertical and y or y - size - 1, - vertical and x - size - 1 or x) + return get_view( + view[2], vertical and y or y - size - 1, vertical and x - size - 1 or x) else return view -- in-between views; return the split itself end |