aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--core/args.lua6
-rw-r--r--core/assert.lua2
-rw-r--r--core/events.lua7
-rw-r--r--core/file_io.lua66
-rw-r--r--core/init.lua14
-rw-r--r--core/keys.lua27
-rw-r--r--core/lfs_ext.lua62
-rw-r--r--core/locale.conf3
-rw-r--r--core/locale.lua14
-rw-r--r--core/locales/locale.ar.conf3
-rw-r--r--core/locales/locale.de.conf3
-rw-r--r--core/locales/locale.es.conf3
-rw-r--r--core/locales/locale.fr.conf3
-rw-r--r--core/locales/locale.it.conf3
-rw-r--r--core/locales/locale.pl.conf3
-rw-r--r--core/locales/locale.ru.conf3
-rw-r--r--core/locales/locale.sv.conf3
-rw-r--r--core/locales/locale.zh.conf3
-rw-r--r--core/ui.lua134
-rw-r--r--docs/api.md27
-rw-r--r--init.lua57
-rw-r--r--modules/ansi_c/init.lua13
-rw-r--r--modules/lua/init.lua35
-rw-r--r--modules/lua/ta_api5
-rw-r--r--modules/lua/ta_tags1
-rw-r--r--modules/textadept/bookmarks.lua10
-rw-r--r--modules/textadept/command_entry.lua30
-rw-r--r--modules/textadept/editing.lua62
-rw-r--r--modules/textadept/file_types.lua2
-rw-r--r--modules/textadept/find.lua71
-rw-r--r--modules/textadept/history.lua28
-rw-r--r--modules/textadept/init.lua20
-rw-r--r--modules/textadept/keys.lua167
-rw-r--r--modules/textadept/macros.lua4
-rw-r--r--modules/textadept/menu.lua43
-rw-r--r--modules/textadept/run.lua30
-rw-r--r--modules/textadept/session.lua16
-rw-r--r--modules/textadept/snippets.lua536
-rw-r--r--test/test.lua23
39 files changed, 729 insertions, 813 deletions
diff --git a/core/args.lua b/core/args.lua
index 3d249c9a..af605d36 100644
--- a/core/args.lua
+++ b/core/args.lua
@@ -11,7 +11,7 @@ module('args')]]
events.ARG_NONE = 'arg_none'
--- Contains registered command line options.
+-- Map of registered command line options.
-- @class table
-- @name options
local options = {}
@@ -44,6 +44,8 @@ end
-- Emits an `ARG_NONE` event when no arguments are present unless
-- *no_emit_arg_none* is `true`.
-- @param arg Argument table.
+-- @param no_emit_arg_none When `true`, do not emit `ARG_NONE` when no arguments
+-- are present. The default value is `false`.
-- @see register
-- @see _G.events
local function process(arg, no_emit_arg_none)
@@ -135,7 +137,7 @@ M.register('-t', '--test', 1, function(patterns)
local arg = {}
for patt in (patterns or ''):gmatch('[^,]+') do arg[#arg + 1] = patt end
local env = setmetatable({arg = arg}, {__index = _G})
- assert(loadfile(_HOME..'/test/test.lua', 't', env))()
+ assert(loadfile(_HOME .. '/test/test.lua', 't', env))()
end)
end, 'Runs unit tests indicated by comma-separated list of patterns (or all)')
diff --git a/core/assert.lua b/core/assert.lua
index baa08d5b..80e50a6e 100644
--- a/core/assert.lua
+++ b/core/assert.lua
@@ -31,7 +31,7 @@ end
-- This is intended to be used with API function arguments so users receive more
-- helpful error messages.
-- @param v Value to assert the type of.
--- @param expected_type String type to assert. It may be a punctuation-delimited
+-- @param expected_type String type to assert. It may be a non-letter-delimited
-- list of type options.
-- @param narg The positional argument number *v* is associated with. This is
-- not required to be a number.
diff --git a/core/events.lua b/core/events.lua
index 7282172d..1c146145 100644
--- a/core/events.lua
+++ b/core/events.lua
@@ -391,11 +391,10 @@ end
-- Handles Scintilla notifications.
M.connect('SCN', function(notification)
- local f = _SCINTILLA.events[notification.code]
- if not f then return end
+ local iface = _SCINTILLA.events[notification.code]
local args = {}
- for i = 2, #f do args[i - 1] = notification[f[i]] end
- return M.emit(f[1], table.unpack(args))
+ for i = 2, #iface do args[i - 1] = notification[iface[i]] end
+ return M.emit(iface[1], table.unpack(args))
end)
-- Set event constants.
diff --git a/core/file_io.lua b/core/file_io.lua
index 39d005b2..0426083a 100644
--- a/core/file_io.lua
+++ b/core/file_io.lua
@@ -42,13 +42,8 @@
module('io')]]
-- Events.
-local events, events_connect = events, events.connect
-events.FILE_OPENED = 'file_opened'
-events.FILE_BEFORE_RELOAD = 'file_before_reload'
-events.FILE_AFTER_RELOAD = 'file_after_reload'
-events.FILE_BEFORE_SAVE = 'file_before_save'
-events.FILE_AFTER_SAVE = 'file_after_save'
-events.FILE_CHANGED = 'file_changed'
+local file_io_events = {'file_opened','file_before_reload','file_after_reload','file_before_save','file_after_save','file_changed'}
+for _, v in ipairs(file_io_events) do events[v:upper()] = v end
io.quick_open_max = 1000
@@ -137,7 +132,7 @@ function io.open_file(filenames, encodings)
buffer.code_page = buffer.encoding and buffer.CP_UTF8 or 0
-- Detect EOL mode.
local s, e = text:find('\r?\n')
- if s then buffer.eol_mode = buffer[s < e and 'EOL_CRLF' or 'EOL_LF'] end
+ if s then buffer.eol_mode = buffer[s ~= e and 'EOL_CRLF' or 'EOL_LF'] end
-- Insert buffer text and set properties.
buffer:append_text(text)
buffer:empty_undo_buffer()
@@ -225,22 +220,17 @@ end
-- @see buffer.save
-- @name save_all_files
function io.save_all_files()
- local current_buffer = buffer
for _, buffer in ipairs(_BUFFERS) do
- if buffer.filename and buffer.modify then
- view:goto_buffer(buffer)
- buffer:save()
- end
+ if buffer.filename and buffer.modify then buffer:save() end
end
- view:goto_buffer(current_buffer)
end
-- LuaDoc is in core/.buffer.luadoc.
local function close(buffer, force)
if not buffer then buffer = _G.buffer end
- local filename = buffer.filename or buffer._type or _L['Untitled']
- if buffer.filename then filename = filename:iconv('UTF-8', _CHARSET) end
if buffer.modify and not force then
+ local filename = buffer.filename or buffer._type or _L['Untitled']
+ if buffer.filename then filename = filename:iconv('UTF-8', _CHARSET) end
local button = ui.dialogs.msgbox{
title = _L['Close without saving?'],
text = _L['There are unsaved changes in'], informative_text = filename,
@@ -262,15 +252,12 @@ end
-- @see buffer.close
-- @name close_all_buffers
function io.close_all_buffers()
- while #_BUFFERS > 1 do
- view:goto_buffer(_BUFFERS[#_BUFFERS])
- if not buffer:close() then return nil end -- do not propagate key command
- end
+ while #_BUFFERS > 1 do if not buffer:close() then return nil end end
return buffer:close() -- the last one
end
-- Sets buffer io methods and the default buffer encoding.
-events_connect(events.BUFFER_NEW, function()
+events.connect(events.BUFFER_NEW, function()
buffer.reload = reload
buffer.set_encoding, buffer.encoding = set_encoding, 'UTF-8'
buffer.save, buffer.save_as, buffer.close = save, save_as, close
@@ -286,20 +273,19 @@ io._reload, io._save, io._save_as, io._close = reload, save, save_as, close
local function update_modified_file()
if not buffer.filename then return end
local mod_time = lfs.attributes(buffer.filename, 'modification')
- if not mod_time or not buffer.mod_time then return end
- if buffer.mod_time < mod_time then
+ if mod_time and buffer.mod_time and buffer.mod_time < mod_time then
buffer.mod_time = mod_time
events.emit(events.FILE_CHANGED, buffer.filename)
end
end
-events_connect(events.BUFFER_AFTER_SWITCH, update_modified_file)
-events_connect(events.VIEW_AFTER_SWITCH, update_modified_file)
-events_connect(events.FOCUS, update_modified_file)
-events_connect(events.RESUME, update_modified_file)
+events.connect(events.BUFFER_AFTER_SWITCH, update_modified_file)
+events.connect(events.VIEW_AFTER_SWITCH, update_modified_file)
+events.connect(events.FOCUS, update_modified_file)
+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(filename)
+events.connect(events.FILE_CHANGED, function(filename)
local button = ui.dialogs.msgbox{
title = _L['Reload?'], text = _L['Reload modified file?'],
informative_text = string.format(
@@ -312,12 +298,10 @@ events_connect(events.FILE_CHANGED, function(filename)
end)
-- Closes the initial "Untitled" buffer when another buffer is opened.
-events_connect(events.FILE_OPENED, function()
- local buf = _BUFFERS[1]
- if #_BUFFERS == 2 and not (buf.filename or buf._type or buf.modify) then
- view:goto_buffer(_BUFFERS[1])
- buffer:close()
- end
+events.connect(events.FILE_OPENED, function()
+ if #_BUFFERS > 2 then return end
+ local first = _BUFFERS[1]
+ if not (first.filename or first._type or first.modify) then first:close() end
end)
---
@@ -392,8 +376,8 @@ io.quick_open_filters = {}
-- search. The default value is the current project's root directory, if
-- available.
-- @param filter Optional filter for files and directories to include and/or
--- exclude. The default value is `lfs.default_filter` unless *paths* is a
--- string and a filter for it is defined in `io.quick_open_filters`.
+-- exclude. The default value is `lfs.default_filter` unless a filter for
+-- *paths* is defined in `io.quick_open_filters`.
-- @param opts Optional table of additional options for
-- `ui.dialogs.filteredlist()`.
-- @usage io.quick_open(buffer.filename:match('^(.+)[/\\]')) -- list all files
@@ -412,15 +396,13 @@ function io.quick_open(paths, filter, opts)
paths = io.get_project_root()
if not paths then return end
end
- if type(paths) == 'string' then
- if not filter then filter = io.quick_open_filters[paths] end
- paths = {paths}
+ if not assert_type(filter, 'string/table/nil', 2) then
+ filter = io.quick_open_filters[paths] or lfs.default_filter
end
- assert_type(filter, 'string/table/nil', 2)
assert_type(opts, 'table/nil', 3)
local utf8_list = {}
- for i = 1, #paths do
- for filename in lfs.walk(paths[i], filter or lfs.default_filter) do
+ for _, path in ipairs(type(paths) == 'table' and paths or {paths}) do
+ for filename in lfs.walk(path, filter) do
if #utf8_list >= io.quick_open_max then break end
utf8_list[#utf8_list + 1] = filename:iconv('UTF-8', _CHARSET)
end
diff --git a/core/init.lua b/core/init.lua
index f71cd6be..e2674e48 100644
--- a/core/init.lua
+++ b/core/init.lua
@@ -44,22 +44,20 @@ end
-- argument.
-- Documentation is in core/.buffer.luadoc.
local function text_range(buffer, start_pos, end_pos)
- assert_type(start_pos, 'number', 2)
- assert_type(end_pos, 'number', 3)
local target_start, target_end = buffer.target_start, buffer.target_end
- if start_pos < 1 then start_pos = 1 end
- if end_pos > buffer.length + 1 then end_pos = buffer.length + 1 end
- buffer:set_target_range(start_pos, end_pos)
+ buffer:set_target_range(
+ math.max(1, assert_type(start_pos, 'number', 2)),
+ math.min(assert_type(end_pos, 'number', 3), buffer.length + 1))
local text = buffer.target_text
buffer:set_target_range(target_start, target_end) -- restore
return text
end
+local GETNAMEDSTYLE = _SCINTILLA.properties.named_styles[1]
-- Documentation is in core/.buffer.luadoc.
local function style_of_name(buffer, style_name)
- assert_type(style_name, 'string', 2)
- local GETNAMEDSTYLE = _SCINTILLA.properties.named_styles[1]
- return buffer:private_lexer_call(GETNAMEDSTYLE, style_name)
+ return buffer:private_lexer_call(
+ GETNAMEDSTYLE, assert_type(style_name, 'string', 2))
end
events.connect(events.BUFFER_NEW, function()
diff --git a/core/keys.lua b/core/keys.lua
index ac9523ab..2d4d8781 100644
--- a/core/keys.lua
+++ b/core/keys.lua
@@ -112,8 +112,8 @@ local M = {}
-- The default value is `nil`.
module('keys')]]
-local CTRL, ALT, SHIFT = 'ctrl+', not CURSES and 'alt+' or 'meta+', 'shift+'
-local CMD = 'cmd+'
+local CTRL, ALT, CMD, SHIFT = 'ctrl+', 'alt+', 'cmd+', 'shift+'
+if CURSES then ALT = 'meta+' end
M.CLEAR = 'esc'
---
@@ -151,7 +151,7 @@ local INVALID, PROPAGATE, CHAIN, HALT = -1, 0, 1, 2
-- Error handler for key commands that simply emits the error. This is needed
-- so `key_command()` can return `HALT` instead of never returning due to the
-- error.
-local function key_error(e) events.emit(events.ERROR, e) end
+local function key_error(errmsg) events.emit(events.ERROR, errmsg) end
-- Runs a key command associated with the current keychain.
-- @param prefix Optional prefix name for mode/lexer-specific commands.
@@ -171,19 +171,11 @@ local function key_command(prefix)
return select(2, xpcall(key, key_error)) == false and PROPAGATE or HALT
end
--- Handles Textadept keypresses.
--- It is called every time a key is pressed, and based on a mode or lexer,
--- executes a command. The command is looked up in the `_G.keys` table.
--- @param code The keycode.
--- @param shift Whether or not the Shift modifier is pressed.
--- @param control Whether or not the Control modifier is pressed.
--- @param alt Whether or not the Alt/option modifier is pressed.
--- @param cmd Whether or not the Command modifier on macOS is pressed.
--- @param caps_lock Whether or not Caps Lock is enabled.
--- @return `true` to stop handling the key; `nil` otherwise.
-local function keypress(code, shift, control, alt, cmd, caps_lock)
- --print(code, M.KEYSYMS[code], shift, control, alt, cmd, caps_lock)
- if caps_lock and (shift or control or alt or cmd) and code < 256 then
+-- Handles Textadept keypresses, executing commands based on a mode or lexer as
+-- necessary.
+events.connect(events.KEYPRESS, function(code, shift, control, alt, cmd, caps)
+ --print(code, M.KEYSYMS[code], shift, control, alt, cmd, caps)
+ if caps and (shift or control or alt or cmd) and code < 256 then
code = string[shift and 'upper' or 'lower'](string.char(code)):byte()
end
local key = code >= 32 and code < 256 and string.char(code) or M.KEYSYMS[code]
@@ -216,8 +208,7 @@ local function keypress(code, shift, control, alt, cmd, caps_lock)
return true
end
-- PROPAGATE otherwise.
-end
-events.connect(events.KEYPRESS, keypress)
+end)
--[[ This comment is for LuaDoc.
---
diff --git a/core/lfs_ext.lua b/core/lfs_ext.lua
index 86a0cada..c24ee2e9 100644
--- a/core/lfs_ext.lua
+++ b/core/lfs_ext.lua
@@ -22,33 +22,6 @@ lfs.default_filter = {--[[Extensions]]'!.a','!.bmp','!.bz2','!.class','!.dll','!
-- @param level Utility value indicating the directory level this function is
-- at.
local function walk(dir, filter, n, include_dirs, level)
- if not level then
- -- Convert filter to a table from nil or string arguments.
- if not filter then filter = lfs.default_filter end
- if type(filter) == 'string' then filter = {filter} end
- -- Process the given filter into something that can match files more easily
- -- and/or quickly. For example, convert '.ext' shorthand to '%.ext$',
- -- substitute '/' with '[/\\]', and enable hash lookup for file extensions
- -- to include or exclude.
- local processed_filter = {
- consider_any = true,
- exts = setmetatable({}, {__index = function() return true end})
- }
- for _, patt in ipairs(filter) do
- patt = patt:gsub('^(!?)%%?%.([^.]+)$', '%1%%.%2$') -- '.lua' to '%.lua$'
- patt = patt:gsub('/([^\\])', '[/\\]%1') -- '/' to '[/\\]'
- local include = not patt:find('^!')
- local ext = patt:match('^!?%%.([^.]+)%$$')
- if ext then
- processed_filter.exts[ext] = include
- if include then setmetatable(processed_filter.exts, nil) end
- else
- if include then processed_filter.consider_any = false end
- processed_filter[#processed_filter + 1] = patt
- end
- end
- filter = processed_filter
- end
for basename in lfs.dir(dir) do
if basename:find('^%.%.?$') then goto continue end -- ignore . and ..
local filename = dir .. (dir ~= '/' and '/' or '') .. basename
@@ -68,14 +41,15 @@ local function walk(dir, filter, n, include_dirs, level)
-- Treat inclusive patterns as logical OR.
include = include or (not patt:find('^!') and filename:find(patt))
end
+ if not include then goto continue end
local sep = not WIN32 and '/' or '\\'
local os_filename = not WIN32 and filename or filename:gsub('/', sep)
- if include and mode == 'directory' then
+ if mode == 'file' then
+ coroutine.yield(os_filename)
+ elseif mode == 'directory' then
if include_dirs then coroutine.yield(os_filename .. sep) end
if n and (level or 0) >= n then goto continue end
walk(filename, filter, n, include_dirs, (level or 0) + 1)
- elseif include and mode == 'file' then
- coroutine.yield(os_filename)
end
::continue::
end
@@ -104,9 +78,33 @@ end
-- @name walk
function lfs.walk(dir, filter, n, include_dirs)
assert_type(dir, 'string', 1)
- assert_type(filter, 'string/table/nil', 2)
+ if not assert_type(filter, 'string/table/nil', 2) then
+ filter = lfs.default_filter
+ end
assert_type(n, 'number/nil', 3)
- local co = coroutine.create(function() walk(dir, filter, n, include_dirs) end)
+ -- Process the given filter into something that can match files more easily
+ -- and/or quickly. For example, convert '.ext' shorthand to '%.ext$',
+ -- substitute '/' with '[/\\]', and enable hash lookup for file extensions
+ -- to include or exclude.
+ local processed_filter = {
+ consider_any = true,
+ exts = setmetatable({}, {__index = function() return true end})
+ }
+ for _, patt in ipairs(type(filter) == 'table' and filter or {filter}) do
+ patt = patt:gsub('^(!?)%%?%.([^.]+)$', '%1%%.%2$') -- '.lua' to '%.lua$'
+ patt = patt:gsub('/([^\\])', '[/\\]%1') -- '/' to '[/\\]'
+ local include = not patt:find('^!')
+ local ext = patt:match('^!?%%.([^.]+)%$$')
+ if ext then
+ processed_filter.exts[ext] = include
+ if include then setmetatable(processed_filter.exts, nil) end
+ else
+ if include then processed_filter.consider_any = false end
+ processed_filter[#processed_filter + 1] = patt
+ end
+ end
+ local co = coroutine.create(
+ function() walk(dir, processed_filter, n, include_dirs) end)
return function() return select(2, coroutine.resume(co)) end
end
diff --git a/core/locale.conf b/core/locale.conf
index d5f60436..18a62e2b 100644
--- a/core/locale.conf
+++ b/core/locale.conf
@@ -336,9 +336,6 @@ Help = _Help
Show Manual = Show _Manual
Show LuaDoc = Show _LuaDoc
About = _About
-# The error message displayed when activating a menu item associated with an
-# unknown command.
-Unknown command: = Unknown command:
# The text displayed in the dialog for running an arbitrary menu command. Any
# key binding associated with commands are also shown.
Run Command = Run Command
diff --git a/core/locale.lua b/core/locale.lua
index 48701116..3a661fae 100644
--- a/core/locale.lua
+++ b/core/locale.lua
@@ -23,14 +23,14 @@ 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
-- comment.
- if not line:find('^%s*[^%w_%[]') then
- local id, str = line:match('^(.-)%s*=%s*(.-)\r?$')
- if id and str and assert(not M[id], 'duplicate locale key "%s"', id) then
- M[id] = not CURSES and str or str:gsub('_', '')
- end
+ if not line:find('^%s*[%w_%[]') then goto continue end
+ local id, str = line:match('^(.-)%s*=%s*(.-)\r?$')
+ if id and str and assert(not M[id], 'duplicate locale key "%s"', id) then
+ M[id] = not CURSES and str or str:gsub('_', '')
end
+ ::continue::
end
f:close()
-setmetatable(M, {__index = function(_, k) return 'No Localization:' .. k end})
-return M
+return setmetatable(
+ M, {__index = function(_, k) return 'No Localization:' .. k end})
diff --git a/core/locales/locale.ar.conf b/core/locales/locale.ar.conf
index 953d3d38..e894445e 100644
--- a/core/locales/locale.ar.conf
+++ b/core/locales/locale.ar.conf
@@ -336,9 +336,6 @@ Help = م_ساعدة
Show Manual = عرض الد_ليل
Show LuaDoc = عرض و_ثائق لُوَ
About = ع_ن
-# The error message displayed when activating a menu item associated with an
-# unknown command.
-Unknown command: = أمر مجهول:
# The text displayed in the dialog for running an arbitrary menu command. Any
# key binding associated with commands are also shown.
Run Command = شغِّل الأمر
diff --git a/core/locales/locale.de.conf b/core/locales/locale.de.conf
index 22d46b5c..d73ca229 100644
--- a/core/locales/locale.de.conf
+++ b/core/locales/locale.de.conf
@@ -336,9 +336,6 @@ Help = _Hilfe
Show Manual = Handbuch anzeigen
Show LuaDoc = LuaDoc anzeigen
About = _Über Textadept
-# The error message displayed when activating a menu item associated with an
-# unknown command.
-Unknown command: = Unbekannter Befehl:
# The text displayed in the dialog for running an arbitrary menu command. Any
# key binding associated with commands are also shown.
Run Command = Befehl ausführen
diff --git a/core/locales/locale.es.conf b/core/locales/locale.es.conf
index 354a33fc..273b90ca 100644
--- a/core/locales/locale.es.conf
+++ b/core/locales/locale.es.conf
@@ -336,9 +336,6 @@ Help = A_yuda
Show Manual = Mostrar _manual
Show LuaDoc = Mostrar _LuaDoc
About = Acerca _de
-# The error message displayed when activating a menu item associated with an
-# unknown command.
-Unknown command: = Comando desconocido:
# The text displayed in the dialog for running an arbitrary menu command. Any
# key binding associated with commands are also shown.
Run Command = Ejecutar comando
diff --git a/core/locales/locale.fr.conf b/core/locales/locale.fr.conf
index b2fff92d..f3afccfa 100644
--- a/core/locales/locale.fr.conf
+++ b/core/locales/locale.fr.conf
@@ -337,9 +337,6 @@ Help = Aid_e
Show Manual = Voir _manuel
Show LuaDoc = Voir la documentation sur l’_API
About = À _propos
-# The error message displayed when activating a menu item associated with an
-# unknown command.
-Unknown command: = Commande inconnue:
# The text displayed in the dialog for running an arbitrary menu command. Any
# key binding associated with commands are also shown.
Run Command = Lancer une commande
diff --git a/core/locales/locale.it.conf b/core/locales/locale.it.conf
index 91d4958a..d9c508a1 100644
--- a/core/locales/locale.it.conf
+++ b/core/locales/locale.it.conf
@@ -336,9 +336,6 @@ Help = _Aiuto
Show Manual = _Manuale
Show LuaDoc = _Documentazione sull’API
About = _Informazioni
-# The error message displayed when activating a menu item associated with an
-# unknown command.
-Unknown command: = Comando sconosciuto:
# The text displayed in the dialog for running an arbitrary menu command. Any
# key binding associated with commands are also shown.
Run Command = Scegli un comando
diff --git a/core/locales/locale.pl.conf b/core/locales/locale.pl.conf
index bbe34282..a33b1d69 100644
--- a/core/locales/locale.pl.conf
+++ b/core/locales/locale.pl.conf
@@ -337,9 +337,6 @@ Help = P_omoc
Show Manual = Po_dręcznik użytkownika...
Show LuaDoc = _LuaDoc...
About = _O programie...
-# The error message displayed when activating a menu item associated with an
-# unknown command.
-Unknown command: = Nieznane polecenie:
# The text displayed in the dialog for running an arbitrary menu command. Any
# key binding associated with commands are also shown.
Run Command = Wykonaj polecenie
diff --git a/core/locales/locale.ru.conf b/core/locales/locale.ru.conf
index 3bfb7198..476d0f63 100644
--- a/core/locales/locale.ru.conf
+++ b/core/locales/locale.ru.conf
@@ -336,9 +336,6 @@ Help = _Справка
Show Manual = Показать _руководство
Show LuaDoc = Показать документацию по _lua
About = _О программе
-# The error message displayed when activating a menu item associated with an
-# unknown command.
-Unknown command: = Неизвестная команда:
# The text displayed in the dialog for running an arbitrary menu command. Any
# key binding associated with commands are also shown.
Run Command = Выполнить команду
diff --git a/core/locales/locale.sv.conf b/core/locales/locale.sv.conf
index d56d2456..f6f01d17 100644
--- a/core/locales/locale.sv.conf
+++ b/core/locales/locale.sv.conf
@@ -336,9 +336,6 @@ Help = _Hjälp
Show Manual = _Manual
Show LuaDoc = _LuaDoc
About = _Om
-# The error message displayed when activating a menu item associated with an
-# unknown command.
-Unknown command: = Okänt kommando:
# The text displayed in the dialog for running an arbitrary menu command. Any
# key binding associated with commands are also shown.
Run Command = Kör kommando
diff --git a/core/locales/locale.zh.conf b/core/locales/locale.zh.conf
index dbce6e55..54476596 100644
--- a/core/locales/locale.zh.conf
+++ b/core/locales/locale.zh.conf
@@ -336,9 +336,6 @@ Help = 帮助(_H)
Show Manual = 打开手册(_M)
Show LuaDoc = 打开_LuaDoc
About = 关于(_A)
-# The error message displayed when activating a menu item associated with an
-# unknown command.
-Unknown command: = 未知命令:
# The text displayed in the dialog for running an arbitrary menu command. Any
# key binding associated with commands are also shown.
Run Command = 运行命令
diff --git a/core/ui.lua b/core/ui.lua
index 343b4510..73616719 100644
--- a/core/ui.lua
+++ b/core/ui.lua
@@ -37,6 +37,12 @@ module('ui')]]
ui.silent_print = false
+-- Helper function for jumping to another view to print to, or creating a new
+-- view to print to (the latter depending on settings).
+local function prepare_view()
+ if #_VIEWS > 1 then ui.goto_view(1) elseif not ui.tabs then view:split() end
+end
+
-- Helper function for printing messages to buffers.
-- @see ui._print
local function _print(buffer_type, ...)
@@ -45,21 +51,19 @@ local function _print(buffer_type, ...)
if buf._type == buffer_type then buffer = buf break end
end
if not buffer then
- if not ui.tabs then view:split() end
+ prepare_view()
buffer = _G.buffer.new()
buffer._type = buffer_type
elseif not ui.silent_print then
for _, view in ipairs(_VIEWS) do
- if view.buffer._type == buffer_type then ui.goto_view(view) break end
- end
- if view.buffer._type ~= buffer_type then
- if #_VIEWS > 1 then
- ui.goto_view(1)
- elseif not ui.tabs then
- view:split()
+ if view.buffer._type == buffer_type then
+ ui.goto_view(view)
+ goto view_found
end
- view:goto_buffer(buffer)
end
+ prepare_view()
+ view:goto_buffer(buffer)
+ ::view_found::
end
local args, n = {...}, select('#', ...)
for i = 1, n do args[i] = tostring(args[i]) end
@@ -115,8 +119,7 @@ ui.dialogs = setmetatable({}, {__index = function(_, k)
-- Transform key-value pairs into command line arguments.
local args = {}
for option, value in pairs(options) do
- assert_type(value, 'string/number/table/boolean', option)
- if value then
+ if assert_type(value, 'string/number/table/boolean', option) then
args[#args + 1] = '--' .. option:gsub('_', '-')
if type(value) == 'table' then
for i, val in ipairs(value) do
@@ -139,7 +142,7 @@ ui.dialogs = setmetatable({}, {__index = function(_, k)
if k == 'progressbar' then
args[#args + 1] = assert_type(f, 'function', 2)
end
- -- Call gtdialog, stripping any trailing newline in the standard output.
+ -- Call gtdialog, stripping any trailing newline in the output.
local result = ui.dialog(
k:gsub('_', '-'), table.unpack(args)):match('^(.-)\n?$')
-- Depending on the dialog type, transform the result into Lua objects.
@@ -185,12 +188,10 @@ ui.dialogs = setmetatable({}, {__index = function(_, k)
end
end})
-local events, events_connect = events, events.connect
-
local buffers_zorder = {}
-- Adds new buffers to the z-order list.
-events_connect(events.BUFFER_NEW, function()
+events.connect(events.BUFFER_NEW, function()
if buffer ~= ui.command_entry then table.insert(buffers_zorder, 1, buffer) end
end)
@@ -206,13 +207,14 @@ local function update_zorder()
end
table.insert(buffers_zorder, 1, buffer)
end
-events_connect(events.BUFFER_AFTER_SWITCH, update_zorder)
-events_connect(events.VIEW_AFTER_SWITCH, update_zorder)
+events.connect(events.BUFFER_AFTER_SWITCH, update_zorder)
+events.connect(events.VIEW_AFTER_SWITCH, update_zorder)
+events.connect(events.BUFFER_DELETED, update_zorder)
-- Saves and restores buffer zorder data during a reset.
-events_connect(
+events.connect(
events.RESET_BEFORE, function(persist) persist.ui_zorder = buffers_zorder end)
-events_connect(
+events.connect(
events.RESET_AFTER, function(persist) buffers_zorder = persist.ui_zorder end)
---
@@ -237,9 +239,8 @@ function ui.switch_buffer(zorder)
local button, i = ui.dialogs.filteredlist{
title = _L['Switch Buffers'], columns = columns, items = utf8_list
}
- if button == 1 and i then
- view:goto_buffer(buffers[not zorder and i or i + 1])
- end
+ if button ~= 1 or not i then return end
+ view:goto_buffer(buffers[not zorder and i or i + 1])
end
---
@@ -290,10 +291,10 @@ function ui.goto_file(filename, split, preferred_view, sloppy)
end
-- Ensure title, statusbar, etc. are updated for new views.
-events_connect(events.VIEW_NEW, function() events.emit(events.UPDATE_UI, 3) end)
+events.connect(events.VIEW_NEW, function() events.emit(events.UPDATE_UI, 3) end)
-- Switches between buffers when a tab is clicked.
-events_connect(
+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.
@@ -309,11 +310,11 @@ local function set_title()
end
-- Changes Textadept title to show the buffer as being "clean" or "dirty".
-events_connect(events.SAVE_POINT_REACHED, set_title)
-events_connect(events.SAVE_POINT_LEFT, set_title)
+events.connect(events.SAVE_POINT_REACHED, set_title)
+events.connect(events.SAVE_POINT_LEFT, set_title)
-- Open uri(s).
-events_connect(events.URI_DROPPED, function(utf8_uris)
+events.connect(events.URI_DROPPED, function(utf8_uris)
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))
@@ -325,25 +326,25 @@ events_connect(events.URI_DROPPED, function(utf8_uris)
end
ui.goto_view(view) -- work around any view focus synchronization issues
end)
-events_connect(events.APPLEEVENT_ODOC, function(uri)
+events.connect(events.APPLEEVENT_ODOC, function(uri)
return events.emit(events.URI_DROPPED, 'file://' .. uri)
end)
-- Sets buffer statusbar text.
-events_connect(events.UPDATE_UI, function(updated)
+events.connect(events.UPDATE_UI, function(updated)
if 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 pos = buffer.current_pos
local line, max = buffer:line_from_position(pos), buffer.line_count
local col = buffer.column[pos]
local lang = 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 enc = buffer.encoding or ''
+ local encoding = buffer.encoding or ''
ui.buffer_statusbar_text = string.format(
- text, _L['Line:'], line, max, _L['Col:'], col, lang, eol, tabs, enc)
+ text, _L['Line:'], line, max, _L['Col:'], col, lang, eol, tabs, encoding)
end)
-- Save buffer properties.
@@ -357,19 +358,17 @@ local function save_buffer_state()
buffer._x_offset = view.x_offset
-- Save fold state.
local folds, i = {}, view:contracted_fold_next(1)
- while i >= 1 do
- folds[#folds + 1], i = i, view:contracted_fold_next(i + 1)
- end
+ while i >= 1 do folds[#folds + 1], i = i, view:contracted_fold_next(i + 1) end
buffer._folds = folds
end
-events_connect(events.BUFFER_BEFORE_SWITCH, save_buffer_state)
-events_connect(events.FILE_BEFORE_RELOAD, save_buffer_state)
+events.connect(events.BUFFER_BEFORE_SWITCH, save_buffer_state)
+events.connect(events.FILE_BEFORE_RELOAD, save_buffer_state)
-- Restore buffer properties.
local function restore_buffer_state()
if not buffer._folds then return end
-- Restore fold state.
- for i = 1, #buffer._folds do view:toggle_fold(buffer._folds[i]) end
+ for _, line in ipairs(buffer._folds) do view:toggle_fold(line) end
-- Restore view state.
buffer:set_sel(buffer._anchor, buffer._current_pos)
buffer.selection_n_anchor_virtual_space[1] = buffer._anchor_virtual_space
@@ -379,17 +378,17 @@ local function restore_buffer_state()
view:line_scroll(0, view:visible_from_doc_line(_top_line) - top_line)
view.x_offset = buffer._x_offset or 0
end
-events_connect(events.BUFFER_AFTER_SWITCH, restore_buffer_state)
-events_connect(events.FILE_AFTER_RELOAD, restore_buffer_state)
+events.connect(events.BUFFER_AFTER_SWITCH, restore_buffer_state)
+events.connect(events.FILE_AFTER_RELOAD, restore_buffer_state)
-- Updates titlebar and statusbar.
local function update_bars()
set_title()
events.emit(events.UPDATE_UI, 3)
end
-events_connect(events.BUFFER_NEW, update_bars)
-events_connect(events.BUFFER_AFTER_SWITCH, update_bars)
-events_connect(events.VIEW_AFTER_SWITCH, update_bars)
+events.connect(events.BUFFER_NEW, update_bars)
+events.connect(events.BUFFER_AFTER_SWITCH, update_bars)
+events.connect(events.VIEW_AFTER_SWITCH, update_bars)
-- Save view state.
local function save_view_state()
@@ -400,8 +399,8 @@ local function save_view_state()
buffer._margin_width_n[i] = view.margin_width_n[i]
end
end
-events_connect(events.BUFFER_BEFORE_SWITCH, save_view_state)
-events_connect(events.VIEW_BEFORE_SWITCH, save_view_state)
+events.connect(events.BUFFER_BEFORE_SWITCH, save_view_state)
+events.connect(events.VIEW_BEFORE_SWITCH, save_view_state)
-- Restore view state.
local function restore_view_state()
@@ -412,21 +411,21 @@ local function restore_view_state()
view.margin_width_n[i] = buffer._margin_width_n[i]
end
end
-events_connect(events.BUFFER_AFTER_SWITCH, restore_view_state)
-events_connect(events.VIEW_AFTER_SWITCH, restore_view_state)
+events.connect(events.BUFFER_AFTER_SWITCH, restore_view_state)
+events.connect(events.VIEW_AFTER_SWITCH, restore_view_state)
-events_connect(
+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()
+events.connect(events.QUIT, function()
local utf8_list = {}
for _, buffer in ipairs(_BUFFERS) do
- if buffer.modify then
- local filename = buffer.filename or buffer._type or _L['Untitled']
- if buffer.filename then filename = filename:iconv('UTF-8', _CHARSET) end
- utf8_list[#utf8_list + 1] = filename
- end
+ if not buffer.modify then goto continue end
+ local filename = buffer.filename or buffer._type or _L['Untitled']
+ if buffer.filename then filename = filename:iconv('UTF-8', _CHARSET) end
+ utf8_list[#utf8_list + 1] = filename
+ ::continue::
end
if #utf8_list == 0 then return end
local button = ui.dialogs.msgbox{
@@ -440,10 +439,10 @@ events_connect(events.QUIT, function()
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(
+-- 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_DELETED, function()
+events.connect(events.BUFFER_DELETED, function()
if _BUFFERS[view._prev_buffer] and buffer ~= view._prev_buffer then
view:goto_buffer(view._prev_buffer)
end
@@ -452,19 +451,20 @@ 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 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)
+ events.connect(events.VIEW_BEFORE_SWITCH, function()
+ ui._clipboard_text = ui.clipboard_text
+ end)
+ events.connect(events.VIEW_AFTER_SWITCH, function()
+ ui.clipboard_text = ui._clipboard_text
+ end)
if not WIN32 then
local function enable_mouse() io.stdout:write("\x1b[?1002h"):flush() end
local function disable_mouse() io.stdout:write("\x1b[?1002l"):flush() end
enable_mouse()
- events_connect(events.SUSPEND, disable_mouse)
- events_connect(events.RESUME, enable_mouse)
- events_connect(events.QUIT, disable_mouse)
+ events.connect(events.SUSPEND, disable_mouse)
+ events.connect(events.RESUME, enable_mouse)
+ events.connect(events.QUIT, disable_mouse)
end
-- Retrieves the view or split at the given terminal coordinates.
@@ -486,7 +486,7 @@ if CURSES then
end
local resize
- events_connect(events.MOUSE, function(event, button, y, x)
+ events.connect(events.MOUSE, function(event, button, y, x)
if event == view.MOUSE_RELEASE or button ~= 1 then return end
if event == view.MOUSE_PRESS then
local view = get_view(ui.get_split_table(), y - 1, x) -- title is at y = 1
@@ -511,7 +511,7 @@ events.connect(events.INITIALIZED, function()
-- Print internal Lua error messages as they are reported.
-- Attempt to mimic the Lua interpreter's error message format so tools that
-- look for it can recognize these errors too.
- events_connect(events.ERROR, function(text)
+ events.connect(events.ERROR, function(text)
if text and text:find(lua_error) then text = 'lua: ' .. text end
ui.print(text)
end)
diff --git a/docs/api.md b/docs/api.md
index 9a7bcdb2..5452dc23 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -610,7 +610,7 @@ helpful error messages.
Parameters:
* *`v`*: Value to assert the type of.
-* *`expected_type`*: String type to assert. It may be a punctuation-delimited
+* *`expected_type`*: String type to assert. It may be a non-letter-delimited
list of type options.
* *`narg`*: The positional argument number *v* is associated with. This is
not required to be a number.
@@ -4465,8 +4465,8 @@ Parameters:
search. The default value is the current project's root directory, if
available.
* *`filter`*: Optional filter for files and directories to include and/or
- exclude. The default value is `lfs.default_filter` unless *paths* is a
- string and a filter for it is defined in `io.quick_open_filters`.
+ exclude. The default value is `lfs.default_filter` unless a filter for
+ *paths* is defined in `io.quick_open_filters`.
* *`opts`*: Optional table of additional options for
`ui.dialogs.filteredlist()`.
@@ -7403,6 +7403,27 @@ See also:
* [`textadept.run.run_commands`](#textadept.run.run_commands)
* [`events`](#events)
+<a id="textadept.run.set_arguments"></a>
+#### `textadept.run.set_arguments`(*filename, run, compile*)
+
+Appends the command line argument strings *run* and *compile* to their
+respective run and compile commands for file *filename* or the current file.
+If either is `nil`, prompts the user for missing the arguments. Each filename
+has its own set of compile and run arguments.
+
+Parameters:
+
+* *`filename`*: Optional path to the file to set run/compile arguments for.
+* *`run`*: Optional string run arguments to set. If `nil`, the user is
+ prompted for them. Pass the empty string for no run arguments.
+* *`compile`*: Optional string compile arguments to set. If `nil`, the user
+ is prompted for them. Pass the empty string for no compile arguments.
+
+See also:
+
+* [`textadept.run.run_commands`](#textadept.run.run_commands)
+* [`textadept.run.compile_commands`](#textadept.run.compile_commands)
+
<a id="textadept.run.stop"></a>
#### `textadept.run.stop`()
diff --git a/init.lua b/init.lua
index e5bb39f1..07456f5d 100644
--- a/init.lua
+++ b/init.lua
@@ -12,8 +12,8 @@ package.cpath = table.concat({
-- Populate initial `_G.buffer` with temporarily exported io functions now that
-- it exists. This is needed for menus and key bindings.
-for _, name in ipairs{'reload', 'save', 'save_as', 'close'} do
- buffer[name], io['_' .. name] = io['_' .. name], nil
+for name, f in pairs(io) do
+ if name:find('^_') then buffer[name:sub(2)], io[name] = f, nil end
end
textadept = require('textadept')
@@ -51,11 +51,12 @@ buffer.set_theme=function(...)view:set_theme(select(2,...));events.connect(event
local function en_au_to_us()for au,us in pairs{CASEINSENSITIVEBEHAVIOUR_IGNORECASE=buffer.CASEINSENSITIVEBEHAVIOR_IGNORECASE,CASEINSENSITIVEBEHAVIOUR_RESPECTCASE=buffer.CASEINSENSITIVEBEHAVIOR_RESPECTCASE,INDIC_GRADIENTCENTRE=buffer.INDIC_GRADIENTCENTER,MARGIN_COLOUR=buffer.MARGIN_COLOR,auto_c_case_insensitive_behaviour=buffer.auto_c_case_insensitive_behavior,colourise=buffer.colorize,edge_colour=buffer.edge_color,set_fold_margin_colour=function()ui.dialogs.msgbox{title='Compatibility issue',text="Please update your theme's use of renamed buffer/view fields"};return buffer.set_fold_margin_color end,set_fold_margin_hi_colour=buffer.set_fold_margin_hi_color,vertical_centre_caret=buffer.vertical_center_caret}do buffer[au]=us;view[au]=us end end;events.connect(events.BUFFER_NEW,en_au_to_us);en_au_to_us()
events.connect(events.INITIALIZED,function()if _NOCOMPAT then return end;local update_keys={};local function translate_keys(keys,new_keys)for k,v in pairs(keys)do if type(k)=='string'and k:find('^[cmas]+.$')and not k:find('ctrl')and not k:find('cmd')and not k:find('alt')and not k:find('meta')and not k:find('shift')and k~='css'then update_keys[#update_keys+1]=k;k=k:gsub('^(c?m?a?)s(.)','%1shift+%2'):gsub('^(c?m?)a(.)','%1alt+%2'):gsub('^(c?)m(.)',string.format('%%1%s%%2',OSX and'cmd+'or'meta+')):gsub('^c(.)','ctrl+%1')end rawset(new_keys,k,type(v)=='table'and translate_keys(v,setmetatable({},getmetatable(v)))or v)end return new_keys end;for k,v in pairs(translate_keys(keys,{}))do keys[k]=v end;if#update_keys>0then ui.dialogs.msgbox{title='Compatibility issue',text='Please update your keys to use the new modifiers:\n'..table.concat(update_keys,'\n')..'\n\nSet _NOCOMPAT=true to disable this warning'}end end)
--- The remainder of this file defines default buffer properties and applies them
--- to subsequent buffers. Normally, a setting like `buffer.use_tabs = false`
--- only applies to the current (initial) buffer. However, temporarily tap into
--- buffer's metatable in order to capture these initial buffer settings (both
--- from Textadept's init.lua and from the user's init.lua).
+-- The remainder of this file defines default buffer and view properties and
+-- applies them to subsequent buffers and views. Normally, a setting like
+-- `buffer.use_tabs = false` only applies to the current (initial) buffer.
+-- However, temporarily tap into buffer and view's metatables in order to
+-- capture these initial settings (both from Textadept's init.lua and from the
+-- user's init.lua) so they can be applied to subsequent buffers and views.
local settings = {}
@@ -119,8 +120,7 @@ local styles = setmetatable({}, {__newindex = function(_, name, props)
end})
lexer = setmetatable({colors = colors, styles = styles}, {
__newindex = function(_, k, v)
- if k == 'folding' then k = 'fold' end
- property[k:gsub('_', '.')] = v and '1' or '0'
+ property[k ~= 'folding' and k:gsub('_', '.') or 'fold'] = v and '1' or '0'
end
})
@@ -130,8 +130,7 @@ local buffer, view = buffer, view
view:set_theme(not CURSES and 'light' or 'term')
-- Multiple Selection and Virtual Space
-buffer.multiple_selection = true
-buffer.additional_selection_typing = true
+buffer.multiple_selection, buffer.additional_selection_typing = true, true
buffer.multi_paste = buffer.MULTIPASTE_EACH
--buffer.virtual_space_options = buffer.VS_RECTANGULARSELECTION |
-- buffer.VS_USERACCESSIBLE
@@ -198,8 +197,7 @@ view.margin_mask_n[3] = view.MASK_FOLDERS
-- Other Margins.
for i = 2, view.margins do
view.margin_type_n[i] = view.MARGIN_SYMBOL
- view.margin_sensitive_n[i] = true
- view.margin_cursor_n[i] = view.CURSORARROW
+ view.margin_sensitive_n[i], view.margin_cursor_n[i] = true, view.CURSORARROW
if i > 3 then view.margin_width_n[i] = 0 end
end
@@ -215,11 +213,9 @@ buffer.buffered_draw = not CURSES and not OSX -- Quartz buffers drawing on macOS
-- Tabs and Indentation Guides.
-- Note: tab and indentation settings apply to individual buffers.
-buffer.tab_width = 2
-buffer.use_tabs = false
+buffer.tab_width, buffer.use_tabs = 2, false
--buffer.indent = 2
-buffer.tab_indents = true
-buffer.back_space_un_indents = true
+buffer.tab_indents, buffer.back_space_un_indents = true, true
view.indentation_guides = not CURSES and view.IV_LOOKBOTH or view.IV_NONE
-- Margin Markers.
@@ -265,16 +261,13 @@ view:marker_define(buffer.MARKNUM_FOLDERMIDTAIL, view.MARK_TCORNER)
-- Indicators.
view.indic_style[ui.find.INDIC_FIND] = view.INDIC_ROUNDBOX
-if not CURSES then view.indic_under[ui.find.INDIC_FIND] = true end
-local INDIC_BRACEMATCH = textadept.editing.INDIC_BRACEMATCH
-view.indic_style[INDIC_BRACEMATCH] = view.INDIC_BOX
-view:brace_highlight_indicator(not CURSES, INDIC_BRACEMATCH)
-local INDIC_HIGHLIGHT = textadept.editing.INDIC_HIGHLIGHT
-view.indic_style[INDIC_HIGHLIGHT] = view.INDIC_ROUNDBOX
-if not CURSES then view.indic_under[INDIC_HIGHLIGHT] = true end
-local INDIC_PLACEHOLDER = textadept.snippets.INDIC_PLACEHOLDER
-view.indic_style[INDIC_PLACEHOLDER] = not CURSES and view.INDIC_DOTBOX or
- view.INDIC_STRAIGHTBOX
+view.indic_under[ui.find.INDIC_FIND] = not CURSES
+view.indic_style[textadept.editing.INDIC_BRACEMATCH] = view.INDIC_BOX
+view:brace_highlight_indicator(not CURSES, textadept.editing.INDIC_BRACEMATCH)
+view.indic_style[textadept.editing.INDIC_HIGHLIGHT] = view.INDIC_ROUNDBOX
+view.indic_under[textadept.editing.INDIC_HIGHLIGHT] = not CURSES
+view.indic_style[textadept.snippets.INDIC_PLACEHOLDER] =
+ not CURSES and view.INDIC_DOTBOX or view.INDIC_STRAIGHTBOX
-- Autocompletion.
--buffer.auto_c_separator =
@@ -332,19 +325,19 @@ if lfs.attributes(user_init) then
end
-- Generate default buffer settings for subsequent buffers and remove temporary
--- buffer metatable listener.
+-- buffer and view metatable listeners.
local load_settings = load(table.concat(settings, '\n'))
for _, mt in ipairs{buffer_mt, view_mt} do
mt.__index, mt.__newindex = mt.__orig_index, mt.__orig_newindex
end
+local SETDIRECTFUNCTION = _SCINTILLA.properties.direct_function[1]
+local SETDIRECTPOINTER = _SCINTILLA.properties.doc_pointer[2]
+local SETLUASTATE = _SCINTILLA.functions.change_lexer_state[1]
+local LOADLEXERLIBRARY = _SCINTILLA.functions.load_lexer_library[1]
-- Sets default properties for a Scintilla document.
events.connect(events.BUFFER_NEW, function()
local buffer = _G.buffer
- local SETDIRECTFUNCTION = _SCINTILLA.properties.direct_function[1]
- local SETDIRECTPOINTER = _SCINTILLA.properties.doc_pointer[2]
- local SETLUASTATE = _SCINTILLA.functions.change_lexer_state[1]
- local LOADLEXERLIBRARY = _SCINTILLA.functions.load_lexer_library[1]
buffer:private_lexer_call(SETDIRECTFUNCTION, buffer.direct_function)
buffer:private_lexer_call(SETDIRECTPOINTER, buffer.direct_pointer)
buffer:private_lexer_call(SETLUASTATE, _LUA)
diff --git a/modules/ansi_c/init.lua b/modules/ansi_c/init.lua
index 10a73ff6..3f5288c4 100644
--- a/modules/ansi_c/init.lua
+++ b/modules/ansi_c/init.lua
@@ -26,11 +26,7 @@ M.tags = {
M.autocomplete_snippets = true
local XPM = textadept.editing.XPM_IMAGES
-local xpms = setmetatable({
- c = XPM.CLASS, d = XPM.SLOT, e = XPM.VARIABLE, f = XPM.METHOD,
- g = XPM.TYPEDEF, m = XPM.VARIABLE, s = XPM.STRUCT, t = XPM.TYPEDEF,
- v = XPM.VARIABLE
-}, {__index = function() return 0 end})
+local xpms = setmetatable({c=XPM.CLASS,d=XPM.SLOT,e=XPM.VARIABLE,f=XPM.METHOD,g=XPM.TYPEDEF,m=XPM.VARIABLE,s=XPM.STRUCT,t=XPM.TYPEDEF,v=XPM.VARIABLE},{__index=function()return 0 end})
textadept.editing.autocompleters.ansi_c = function()
-- Retrieve the symbol behind the caret.
@@ -85,10 +81,9 @@ textadept.editing.autocompleters.ansi_c = function()
return #part, list
end
-local api_files = textadept.editing.api_files
-api_files.ansi_c[#api_files.ansi_c + 1] = _HOME .. '/modules/ansi_c/api'
-api_files.ansi_c[#api_files.ansi_c + 1] = _HOME .. '/modules/ansi_c/lua_api'
-api_files.ansi_c[#api_files.ansi_c + 1] = _USERHOME .. '/modules/ansi_c/api'
+for _, tags in ipairs(M.tags) do
+ table.insert(textadept.editing.api_files.ansi_c, (tags:gsub('tags$', 'api')))
+end
-- Commands.
diff --git a/modules/lua/init.lua b/modules/lua/init.lua
index d3ef2419..abaa36aa 100644
--- a/modules/lua/init.lua
+++ b/modules/lua/init.lua
@@ -22,10 +22,8 @@ local function ta_api(filename)
local userhome = '^' .. _USERHOME:gsub('%p', '%%%0'):gsub('%%[/\\]', '[/\\]')
return function()
local buffer_filename = buffer.filename or ''
- if buffer_filename:find(home) or buffer_filename:find(userhome) or
- buffer == ui.command_entry or buffer._type then
- return filename
- end
+ return (buffer_filename:find(home) or buffer_filename:find(userhome) or
+ buffer == ui.command_entry or buffer._type) and filename
end
end
@@ -73,11 +71,11 @@ textadept.editing.autocompleters.lua = function()
local assignment = '%f[%w_]' .. symbol:gsub('(%p)', '%%%1') .. '%s*=%s*(.*)$'
for i = buffer:line_from_position(buffer.current_pos) - 1, 1, -1 do
local expr = buffer:get_line(i):match(assignment)
- if expr then
- for patt, type in pairs(M.expr_types) do
- if expr:find(patt) then symbol = type break end
- end
+ if not expr then goto continue end
+ for patt, type in pairs(M.expr_types) do
+ if expr:find(patt) then symbol = type break end
end
+ ::continue::
end
-- Search through ctags for completions for that symbol.
local name_patt = '^' .. part
@@ -87,14 +85,13 @@ textadept.editing.autocompleters.lua = function()
if not filename or not lfs.attributes(filename) then goto continue end
for tag_line in io.lines(filename) do
local name = tag_line:match('^%S+')
- if name:find(name_patt) and not list[name] then
- local fields = tag_line:match(';"\t(.*)$')
- local k, class = fields:sub(1, 1), fields:match('class:(%S+)') or ''
- if class == symbol and (op ~= ':' or k == 'f') then
- list[#list + 1] = name .. sep .. xpms[k]
- list[name] = true
- end
+ if not name:find(name_patt) or list[name] then goto continue end
+ local fields = tag_line:match(';"\t(.*)$')
+ local k, class = fields:sub(1, 1), fields:match('class:(%S+)') or ''
+ if class == symbol and (op ~= ':' or k == 'f') then
+ list[#list + 1], list[name] = name .. sep .. xpms[k], true
end
+ ::continue::
end
::continue::
end
@@ -102,10 +99,10 @@ textadept.editing.autocompleters.lua = function()
return #part, list
end
-local api_files = textadept.editing.api_files
-api_files.lua[#api_files.lua + 1] = _HOME .. '/modules/lua/api'
-api_files.lua[#api_files.lua + 1] = _USERHOME .. '/modules/lua/api'
-api_files.lua[#api_files.lua + 1] = ta_api(_HOME .. '/modules/lua/ta_api')
+local api_files = textadept.editing.api_files.lua
+table.insert(api_files, _HOME .. '/modules/lua/api')
+table.insert(api_files, _USERHOME .. '/modules/lua/api')
+table.insert(api_files, ta_api(_HOME .. '/modules/lua/ta_api'))
-- Commands.
diff --git a/modules/lua/ta_api b/modules/lua/ta_api
index c5e573e6..631fcb61 100644
--- a/modules/lua/ta_api
+++ b/modules/lua/ta_api
@@ -340,7 +340,7 @@ args _G.args (module)\nProcesses command line arguments for Textadept.
ascii lexer.ascii (pattern)\nA pattern that matches any ASCII character (codes 0 to 127).
assert _G.assert (module)\nExtends `_G` with formatted assertions and function argument type checks.
assert _G.assert(v, message, ...)\nAsserts that value *v* is not `false` or `nil` and returns *v*, or calls\n`error()` with *message* as the error message, defaulting to "assertion\nfailed!". If *message* is a format string, the remaining arguments are passed\nto `string.format()` and the resulting string becomes the error message.\n@param v Value to assert.\n@param message Optional error message to show on error. The default value is\n "assertion failed!".\n@param ... If *message* is a format string, these arguments are passed to\n `string.format()`.
-assert_type _G.assert_type(v, expected_type, narg)\nAsserts that value *v* has type string *expected_type* and returns *v*, or\ncalls `error()` with an error message that implicates function argument\nnumber *narg*.\nThis is intended to be used with API function arguments so users receive more\nhelpful error messages.\n@param v Value to assert the type of.\n@param expected_type String type to assert. It may be a punctuation-delimited\n list of type options.\n@param narg The positional argument number *v* is associated with. This is\n not required to be a number.\n@usage assert_type(filename, 'string/nil', 1)\n@usage assert_type(option.setting, 'number', 'setting') -- implicates key
+assert_type _G.assert_type(v, expected_type, narg)\nAsserts that value *v* has type string *expected_type* and returns *v*, or\ncalls `error()` with an error message that implicates function argument\nnumber *narg*.\nThis is intended to be used with API function arguments so users receive more\nhelpful error messages.\n@param v Value to assert the type of.\n@param expected_type String type to assert. It may be a non-letter-delimited\n list of type options.\n@param narg The positional argument number *v* is associated with. This is\n not required to be a number.\n@usage assert_type(filename, 'string/nil', 1)\n@usage assert_type(option.setting, 'number', 'setting') -- implicates key
auto_c_active buffer.auto_c_active(buffer)\nReturns whether or not an autocompletion or user list is visible.\n@param buffer A buffer.\n@return bool
auto_c_auto_hide buffer.auto_c_auto_hide (bool)\nAutomatically cancel an autocompletion or user list when no entries match\ntyped text.\nThe default value is `true`.
auto_c_cancel buffer.auto_c_cancel(buffer)\nCancels the displayed autocompletion or user list.\n@param buffer A buffer.
@@ -770,7 +770,7 @@ property_int lexer.property_int (table, Read-only)\nMap of key-value pairs with
property_int view.property_int (table, Read-only)\nMap of key-value pairs used by lexers with values interpreted as numbers,\nor `0` if not found.
punct lexer.punct (pattern)\nA pattern that matches any punctuation character ('!' to '/', ':' to '@',\n'[' to ''', '{' to '~').
punctuation_chars buffer.punctuation_chars (string)\nThe string set of characters recognized as punctuation characters.\nSet this only after setting `buffer.word_chars`.\nThe default value is a string that contains all non-word and non-whitespace\ncharacters.
-quick_open io.quick_open(paths, filter, opts)\nPrompts the user to select files to be opened from *paths*, a string\ndirectory path or list of directory paths, using a filtered list dialog.\nIf *paths* is `nil`, uses the current project's root directory, which is\nobtained from `io.get_project_root()`.\nString or list *filter* determines which files to show in the dialog, with\nthe default filter being `io.quick_open_filters[path]` (if it exists) or\n`lfs.default_filter`. A filter consists of Lua patterns that match file and\ndirectory paths to include or exclude. Patterns are inclusive by default.\nExclusive patterns begin with a '!'. If no inclusive patterns are given, any\npath is initially considered. As a convenience, file extensions can be\nspecified literally instead of as a Lua pattern (e.g. '.lua' vs. '%.lua$'),\nand '/' also matches the Windows directory separator ('[/\\]' is not needed).\nThe number of files in the list is capped at `quick_open_max`.\nIf *filter* is `nil` and *paths* is ultimately a string, the filter from the\n`io.quick_open_filters` table is used. If that filter does not exist,\n`lfs.default_filter` is used.\n*opts* is an optional table of additional options for\n`ui.dialogs.filteredlist()`.\n@param paths Optional string directory path or table of directory paths to\n search. The default value is the current project's root directory, if\n available.\n@param filter Optional filter for files and directories to include and/or\n exclude. The default value is `lfs.default_filter` unless *paths* is a\n string and a filter for it is defined in `io.quick_open_filters`.\n@param opts Optional table of additional options for\n `ui.dialogs.filteredlist()`.\n@usage io.quick_open(buffer.filename:match('^(.+)[/\\]')) -- list all files\n in the current file's directory, subject to the default filter\n@usage io.quick_open(io.get_current_project(), '.lua') -- list all Lua files\n in the current project\n@usage io.quick_open(io.get_current_project(), '!/build') -- list all files\n in the current project except those in the build directory\n@see io.quick_open_filters\n@see lfs.default_filter\n@see quick_open_max\n@see ui.dialogs.filteredlist
+quick_open io.quick_open(paths, filter, opts)\nPrompts the user to select files to be opened from *paths*, a string\ndirectory path or list of directory paths, using a filtered list dialog.\nIf *paths* is `nil`, uses the current project's root directory, which is\nobtained from `io.get_project_root()`.\nString or list *filter* determines which files to show in the dialog, with\nthe default filter being `io.quick_open_filters[path]` (if it exists) or\n`lfs.default_filter`. A filter consists of Lua patterns that match file and\ndirectory paths to include or exclude. Patterns are inclusive by default.\nExclusive patterns begin with a '!'. If no inclusive patterns are given, any\npath is initially considered. As a convenience, file extensions can be\nspecified literally instead of as a Lua pattern (e.g. '.lua' vs. '%.lua$'),\nand '/' also matches the Windows directory separator ('[/\\]' is not needed).\nThe number of files in the list is capped at `quick_open_max`.\nIf *filter* is `nil` and *paths* is ultimately a string, the filter from the\n`io.quick_open_filters` table is used. If that filter does not exist,\n`lfs.default_filter` is used.\n*opts* is an optional table of additional options for\n`ui.dialogs.filteredlist()`.\n@param paths Optional string directory path or table of directory paths to\n search. The default value is the current project's root directory, if\n available.\n@param filter Optional filter for files and directories to include and/or\n exclude. The default value is `lfs.default_filter` unless a filter for\n *paths* is defined in `io.quick_open_filters`.\n@param opts Optional table of additional options for\n `ui.dialogs.filteredlist()`.\n@usage io.quick_open(buffer.filename:match('^(.+)[/\\]')) -- list all files\n in the current file's directory, subject to the default filter\n@usage io.quick_open(io.get_current_project(), '.lua') -- list all Lua files\n in the current project\n@usage io.quick_open(io.get_current_project(), '!/build') -- list all files\n in the current project except those in the build directory\n@see io.quick_open_filters\n@see lfs.default_filter\n@see quick_open_max\n@see ui.dialogs.filteredlist
quick_open_filters io.quick_open_filters (table)\nMap of directory paths to filters used by `io.quick_open()`.\n@see quick_open
quick_open_max io.quick_open_max (number)\nThe maximum number of files listed in the quick open dialog.\nThe default value is `1000`.
quit _G.quit()\nEmits a `QUIT` event, and unless any handler returns `false`, quits\nTextadept.\n@see events.QUIT
@@ -857,6 +857,7 @@ selection_n_start_virtual_space buffer.selection_n_start_virtual_space (number,
selection_start buffer.selection_start (number)\nThe position of the beginning of the selected text.\nWhen set, becomes the anchor, but is not scrolled into view.
selections buffer.selections (number, Read-only)\nThe number of active selections. There is always at least one selection.
session textadept.session (module)\nSession support for Textadept.
+set_arguments textadept.run.set_arguments(filename, run, compile)\nAppends the command line argument strings *run* and *compile* to their\nrespective run and compile commands for file *filename* or the current file.\nIf either is `nil`, prompts the user for missing the arguments. Each filename\nhas its own set of compile and run arguments.\n@param filename Optional path to the file to set run/compile arguments for.\n@param run Optional string run arguments to set. If `nil`, the user is\n prompted for them. Pass the empty string for no run arguments.\n@param compile Optional string compile arguments to set. If `nil`, the user\n is prompted for them. Pass the empty string for no compile arguments.\n@see run_commands\n@see compile_commands
set_chars_default buffer.set_chars_default(buffer)\nResets `buffer.word_chars`, `buffer.whitespace_chars`, and\n`buffer.punctuation_chars` to their respective defaults.\n@param buffer A buffer.\n@see word_chars\n@see whitespace_chars\n@see punctuation_chars
set_default_fold_display_text view.set_default_fold_display_text(view, text)\nSets the default fold display text to string *text*.\n@param view A view.\n@param text The text to display by default next to folded lines.\n@see toggle_fold_show_text
set_empty_selection buffer.set_empty_selection(buffer, pos)\nMoves the caret to position *pos* without scrolling the view and removes any\nselections.\n@param buffer A buffer\n@param pos The position in *buffer* to move to.
diff --git a/modules/lua/ta_tags b/modules/lua/ta_tags
index a213f6ef..e186e8e3 100644
--- a/modules/lua/ta_tags
+++ b/modules/lua/ta_tags
@@ -859,6 +859,7 @@ selection_n_start_virtual_space _HOME/core/.buffer.luadoc /^module('buffer')$/;"
selection_start _HOME/core/.buffer.luadoc /^module('buffer')$/;" F class:buffer
selections _HOME/core/.buffer.luadoc /^module('buffer')$/;" F class:buffer
session _HOME/modules/textadept/session.lua /^module('textadept.session')]]$/;" m class:textadept
+set_arguments _HOME/modules/textadept/run.lua /^function M.set_arguments(filename, run, compile)$/;" f class:textadept.run
set_chars_default _HOME/core/.buffer.luadoc /^function set_chars_default(buffer) end$/;" f class:buffer
set_default_fold_display_text _HOME/core/.view.luadoc /^function set_default_fold_display_text(view, text) end$/;" f class:view
set_empty_selection _HOME/core/.buffer.luadoc /^function set_empty_selection(buffer, pos) end$/;" f class:buffer
diff --git a/modules/textadept/bookmarks.lua b/modules/textadept/bookmarks.lua
index c21f5dc4..de6f7470 100644
--- a/modules/textadept/bookmarks.lua
+++ b/modules/textadept/bookmarks.lua
@@ -28,10 +28,10 @@ function M.clear() buffer:marker_delete_all(M.MARK_BOOKMARK) end
-- Returns an iterator for all bookmarks in the given buffer.
local function bookmarks(buffer)
- return function(_, line)
+ return function(buffer, line)
line = buffer:marker_next(line + 1, 1 << M.MARK_BOOKMARK - 1)
return line >= 1 and line or nil
- end, nil, 0
+ end, buffer, 0
end
---
@@ -46,17 +46,17 @@ end
function M.goto_mark(next)
if next ~= nil then
local f = next and buffer.marker_next or buffer.marker_previous
- local current_line = buffer:line_from_position(buffer.current_pos)
+ local line = buffer:line_from_position(buffer.current_pos)
local BOOKMARK_BIT = 1 << M.MARK_BOOKMARK - 1
- local line = f(buffer, current_line + (next and 1 or -1), BOOKMARK_BIT)
+ line = f(buffer, line + (next and 1 or -1), BOOKMARK_BIT)
if line == -1 then
line = f(buffer, (next and 1 or buffer.line_count), BOOKMARK_BIT)
end
if line >= 1 then textadept.editing.goto_line(line) end
return
end
- local scan_this_buffer, utf8_list, buffers = true, {}, {}
-- List the current buffer's marks, and then all other buffers' marks.
+ local scan_this_buffer, utf8_list, buffers = true, {}, {}
::rescan::
for _, buffer in ipairs(_BUFFERS) do
if not (scan_this_buffer == (buffer == _G.buffer)) then goto continue end
diff --git a/modules/textadept/command_entry.lua b/modules/textadept/command_entry.lua
index 18f72e20..3e344f81 100644
--- a/modules/textadept/command_entry.lua
+++ b/modules/textadept/command_entry.lua
@@ -59,8 +59,7 @@ end
-- @name editing_keys
M.editing_keys = {__index = {}}
--- Fill in default platform-specific key bindings.
-local ekeys, plat = M.editing_keys.__index, CURSES and 3 or OSX and 2 or 1
+-- Fill in default key bindings for Linux/Win32, macOS, Terminal.
local bindings = {
-- Note: cannot use `M.cut`, `M.copy`, etc. since M is never considered the
-- global buffer.
@@ -81,8 +80,9 @@ local bindings = {
[function() M:line_end() end] = {nil, 'ctrl+e', 'ctrl+e'},
[function() M:clear() end] = {nil, 'ctrl+d', 'ctrl+d'}
}
+local plat = CURSES and 3 or OSX and 2 or 1
for f, plat_keys in pairs(bindings) do
- if plat_keys[plat] then ekeys[plat_keys[plat]] = f end
+ if plat_keys[plat] then M.editing_keys.__index[plat_keys[plat]] = f end
end
-- Environment for abbreviated Lua commands.
@@ -100,13 +100,13 @@ local env = setmetatable({}, {
__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
+ buffer[k] = v -- buffer and view are interchangeable in this case
return
end
if view[k] ~= nil then view[k] = v return end
if ui[k] ~= nil then ui[k] = v return end
rawset(self, k, v)
- end,
+ end
})
-- Executes string *code* as Lua code that is subject to an "abbreviated"
@@ -135,7 +135,7 @@ local function run_lua(code)
end
end
if result ~= nil or code:find('^return ') then ui.print(result) end
- events.emit(events.UPDATE_UI, 0)
+ events.emit(events.UPDATE_UI, 1) -- update UI if necessary (e.g. statusbar)
end
args.register('-e', '--execute', 1, run_lua, 'Execute Lua code')
@@ -151,8 +151,8 @@ local function complete_lua()
if (not ok or type(result) ~= 'table') and symbol ~= '' then return end
local cmpls = {}
part = '^' .. part
- local sep = string.char(M.auto_c_type_separator)
local XPM = textadept.editing.XPM_IMAGES
+ local sep = string.char(M.auto_c_type_separator)
if not ok or symbol == 'buffer' or symbol == 'view' then
local sci = _SCINTILLA
local global_envs = not ok and {
@@ -161,11 +161,11 @@ local function complete_lua()
} or op == ':' and {sci.functions} or {sci.properties, sci.constants}
for _, env in ipairs(global_envs) do
for k, v in pairs(env) do
- if type(k) == 'string' and k:find(part) then
- local xpm = (type(v) == 'function' or env == sci.functions) and
- XPM.METHOD or XPM.VARIABLE
- cmpls[#cmpls + 1] = k .. sep .. xpm
- end
+ if type(k) ~= 'string' or not k:find(part) then goto continue end
+ local xpm = (type(v) == 'function' or env == sci.functions) and
+ XPM.METHOD or XPM.VARIABLE
+ cmpls[#cmpls + 1] = k .. sep .. xpm
+ ::continue::
end
end
else
@@ -216,7 +216,6 @@ local prev_key_mode
-- @usage ui.command_entry.run(ui.print)
-- @name run
function M.run(f, keys, lang, height)
- if M:auto_c_active() then M:auto_c_cancel() end -- may happen in curses
if not assert_type(f, 'function/nil', 1) and not keys then
f, keys, lang = run_lua, lua_keys, 'lua'
elseif type(assert_type(keys, 'table/string/nil', 2)) == 'string' then
@@ -231,9 +230,8 @@ function M.run(f, keys, lang, height)
keys['\n'] = function()
if M:auto_c_active() then return false end -- allow Enter to autocomplete
M.focus() -- hide
- if not f then return end
M.append_history(M:get_text())
- f(M:get_text())
+ if f then f(M:get_text()) end
end
end
if not getmetatable(keys) then setmetatable(keys, M.editing_keys) end
@@ -242,7 +240,7 @@ function M.run(f, keys, lang, height)
local mode_history = history[history.mode]
M:set_text(mode_history and mode_history[mode_history.pos] or '')
M:select_all()
- prev_key_mode = _G.keys.mode
+ prev_key_mode = _G.keys.mode -- save before M.focus()
M.focus()
M:set_lexer(lang or 'text')
M.height = M:text_height(1) * (height or 1)
diff --git a/modules/textadept/editing.lua b/modules/textadept/editing.lua
index 6eda7a32..0c35942e 100644
--- a/modules/textadept/editing.lua
+++ b/modules/textadept/editing.lua
@@ -155,8 +155,7 @@ events.connect(events.KEYPRESS, function(code)
local pos = buffer.selection_n_caret[i]
local complement = M.auto_pairs[buffer.char_at[pos - 1]]
if complement and buffer.char_at[pos] == string.byte(complement) then
- buffer:set_target_range(pos, pos + 1)
- buffer:replace_target('')
+ buffer:delete_range(pos, 1)
end
end
buffer:end_undo_action()
@@ -166,14 +165,13 @@ end, 1) -- need index of 1 because default key handler halts propagation
-- Highlights matching braces.
events.connect(events.UPDATE_UI, function(updated)
if updated & 3 == 0 then return end -- ignore scrolling
- local pos = buffer.selection_n_caret[buffer.main_selection]
- if M.brace_matches[buffer.char_at[pos]] then
- local match = buffer:brace_match(pos, 0)
+ if M.brace_matches[buffer.char_at[buffer.current_pos]] then
+ local match = buffer:brace_match(buffer.current_pos, 0)
local f = match ~= -1 and view.brace_highlight or view.brace_bad_light
- f(buffer, pos, match)
- return
+ f(buffer, buffer.current_pos, match)
+ else
+ view:brace_bad_light(-1)
end
- view:brace_bad_light(-1)
end)
-- Clears highlighted word indicators.
@@ -221,10 +219,10 @@ events.connect(events.KEYPRESS, function(code)
local handled = false
for i = 1, buffer.selections do
local s, e = buffer.selection_n_start[i], buffer.selection_n_end[i]
- if s == e and buffer.char_at[s] == code then
- buffer.selection_n_start[i], buffer.selection_n_end[i] = s + 1, s + 1
- handled = true
- end
+ if s ~= e or buffer.char_at[s] ~= code then goto continue end
+ buffer.selection_n_start[i], buffer.selection_n_end[i] = s + 1, s + 1
+ handled = true
+ ::continue::
end
if handled then return true end -- prevent typing
end
@@ -414,9 +412,9 @@ function M.goto_line(line)
view:ensure_visible_enforce_policy(line)
buffer:goto_line(line)
end
-args.register(
- '-l', '--line', 1, function(line) M.goto_line(tonumber(line) or line) end,
- 'Go to line')
+args.register('-l', '--line', 1, function(line)
+ M.goto_line(tonumber(line) or line)
+end, 'Go to line')
---
-- Transposes characters intelligently.
@@ -424,7 +422,6 @@ args.register(
-- the caret. Otherwise, the characters to the left and right are.
-- @name transpose_chars
function M.transpose_chars()
- if buffer.current_pos == 1 then return end
local pos = buffer.current_pos
local line_end = buffer.line_end_position[buffer:line_from_position(pos)]
if pos == line_end then pos = buffer:position_before(pos) end
@@ -525,10 +522,9 @@ function M.select_enclosed(left, right)
s = s - 1
end
end
- if s >= 1 and e >= 1 then
- if s + #left == anchor and e == pos then s, e = s - #left, e + #right end
- buffer:set_sel(s + #left, e)
- end
+ if s == -1 or e == -1 then return end
+ if s + #left == anchor and e == pos then s, e = s - #left, e + #right end
+ buffer:set_sel(s + #left, e)
end
---
@@ -545,7 +541,7 @@ function M.select_word(all)
buffer.search_flags = buffer.FIND_MATCHCASE
if buffer.selection_empty or
buffer:is_range_word(buffer.selection_start, buffer.selection_end) then
- buffer.search_flags = buffer.search_flags + buffer.FIND_WHOLEWORD
+ buffer.search_flags = buffer.search_flags | buffer.FIND_WHOLEWORD
if all then buffer:multiple_select_add_next() end -- select word first
end
buffer['multiple_select_add_' .. (not all and 'next' or 'each')](buffer)
@@ -590,10 +586,10 @@ function M.convert_indentation()
else
new_indentation = string.rep(' ', indent)
end
- if current_indentation ~= new_indentation then
- buffer:set_target_range(s, e)
- buffer:replace_target(new_indentation)
- end
+ if current_indentation == new_indentation then goto continue end
+ buffer:set_target_range(s, e)
+ buffer:replace_target(new_indentation)
+ ::continue::
end
buffer:end_undo_action()
end
@@ -727,19 +723,19 @@ function M.show_documentation(pos, ignore_case)
if symbol ~= '' then
local symbol_patt = '^' .. symbol:gsub('(%p)', '%%%1')
if ignore_case then
- symbol_patt = symbol_patt:gsub('%a', function(ch)
- return string.format('[%s%s]', ch:upper(), ch:lower())
+ symbol_patt = symbol_patt:gsub('%a', function(letter)
+ return string.format('[%s%s]', letter:upper(), letter:lower())
end)
end
for _, file in ipairs(api_files) do
if type(file) == 'function' then file = file() end
- if file and lfs.attributes(file) then
- for line in io.lines(file) do
- if line:find(symbol_patt) then
- api_docs[#api_docs + 1] = line:match(symbol_patt .. '%s+(.+)$')
- end
- end
+ if not file or not lfs.attributes(file) then goto continue end
+ for line in io.lines(file) do
+ if not line:find(symbol_patt) then goto continue end
+ api_docs[#api_docs + 1] = line:match(symbol_patt .. '%s+(.+)$')
+ ::continue::
end
+ ::continue::
end
end
-- Search backwards for an open function call and show API documentation for
diff --git a/modules/textadept/file_types.lua b/modules/textadept/file_types.lua
index 4bf00465..37743fbd 100644
--- a/modules/textadept/file_types.lua
+++ b/modules/textadept/file_types.lua
@@ -93,13 +93,13 @@ events.connect(events.VIEW_AFTER_SWITCH, restore_lexer)
events.connect(events.VIEW_NEW, restore_lexer)
events.connect(events.RESET_AFTER, restore_lexer)
+local LEXERNAMES = _SCINTILLA.functions.property_names[1]
---
-- Prompts the user to select a lexer for the current buffer.
-- @see buffer.set_lexer
-- @name select_lexer
function M.select_lexer()
local lexers = {}
- local LEXERNAMES = _SCINTILLA.functions.property_names[1]
for name in buffer:private_lexer_call(LEXERNAMES):gmatch('[^\n]+') do
lexers[#lexers + 1] = name
end
diff --git a/modules/textadept/find.lua b/modules/textadept/find.lua
index 5590e356..7ea83e18 100644
--- a/modules/textadept/find.lua
+++ b/modules/textadept/find.lua
@@ -77,8 +77,7 @@ local M = ui.find
module('ui.find')]]
local _L = _L
-M.find_label_text = _L['Find:']
-M.replace_label_text = _L['Replace:']
+M.find_label_text, M.replace_label_text = _L['Find:'], _L['Replace:']
M.find_next_button_text = not CURSES and _L['Find Next'] or _L['[Next]']
M.find_prev_button_text = not CURSES and _L['Find Prev'] or _L['[Prev]']
M.replace_button_text = not CURSES and _L['Replace'] or _L['[Replace]']
@@ -92,7 +91,8 @@ M.highlight_all_matches = false
M.INDIC_FIND = _SCINTILLA.next_indic_number()
-- Events.
-events.FIND_RESULT_FOUND, events.FIND_WRAPPED = 'find_found', 'find_wrapped'
+local find_events = {'find_result_found', 'find_wrapped'}
+for _, v in ipairs(find_events) do events[v:upper()] = v end
-- When finding in files, note the current view since results are shown in a
-- split view. Jumping between results should be done in the original view.
@@ -113,7 +113,7 @@ M.find_in_files_filters = {}
-- text has changed, the user is still typing; if text is the same, the user
-- clicked "Find Next" or "Find Prev"). Keep track of repl_text for
-- non-"In files" in order to restore it from filter text as necessary.
-local find_text, found_text, repl_text = nil, nil, ''
+local find_text, found_text, repl_text = nil, nil, ui.find.replace_entry_text
-- Returns a reasonable initial directory for use with Find in Files.
local function ff_dir()
@@ -137,7 +137,7 @@ function M.focus(options)
local filter = M.find_in_files_filters[ff_dir()] or lfs.default_filter
M.replace_entry_text = type(filter) == 'string' and filter or
table.concat(filter, ',')
- elseif repl_text and M.replace_entry_text ~= repl_text then
+ elseif M.replace_entry_text ~= repl_text then
M.replace_entry_text = repl_text -- restore
end
orig_focus()
@@ -226,12 +226,10 @@ local function find(text, next, flags, no_wrap, wrapped)
view:line_scroll(0, first_visible_line - view.first_visible_line)
buffer:goto_pos(incremental_orig_pos or anchor)
end
- elseif not wrapped then
- ui.statusbar_text = ''
end
-- Count and optionally highlight all found occurrences.
- local count, current = 0
+ local count, current = 0, 1
clear_highlighted_matches()
if pos ~= -1 then
buffer.search_flags = flags
@@ -260,7 +258,7 @@ end
events.connect(events.FIND, find)
events.connect(events.FIND_TEXT_CHANGED, function()
if not M.incremental then return end
- return events.emit(events.FIND, M.find_entry_text, true) or true -- refresh
+ return events.emit(events.FIND, M.find_entry_text, true) -- refresh
end)
events.connect(
events.FIND_WRAPPED, function() ui.statusbar_text = _L['Search wrapped'] end)
@@ -341,24 +339,24 @@ function M.find_in_files(dir, filter)
found = true
if binary == nil then binary = buffer:text_range(1, 65536):find('\0') end
if binary then
- _G.buffer:append_text(string.format(
+ _G.buffer:add_text(string.format(
'%s:1:%s\n', utf8_filenames[i], _L['Binary file matches.']))
break
end
local line_num = buffer:line_from_position(buffer.target_start)
local line = buffer:get_line(line_num)
- _G.buffer:append_text(
+ _G.buffer:add_text(
string.format('%s:%d:%s', utf8_filenames[i], line_num, line))
- local pos = _G.buffer.length + 1 - #line +
+ local pos = _G.buffer.current_pos - #line +
buffer.target_start - buffer:position_from_line(line_num)
_G.buffer:indicator_fill_range(
pos, buffer.target_end - buffer.target_start)
- if not line:find('\n$') then _G.buffer:append_text('\n') end
+ if not line:find('\n$') then _G.buffer:add_text('\n') end
buffer:set_target_range(buffer.target_end, buffer.length + 1)
end
buffer:clear_all()
buffer:empty_undo_buffer()
- _G.buffer:goto_pos(_G.buffer.length + 1) -- [Files Found Buffer]
+ view:scroll_caret() -- [Files Found Buffer]
i = i + 1
if i > #filenames then return nil end
return i * 100 / #filenames, utf8_filenames[i]
@@ -370,44 +368,37 @@ function M.find_in_files(dir, filter)
not found and _L['No results found'] .. '\n' or '')
end
--- Unescapes \uXXXX sequences in the string *text* and returns the result.
--- Just like with \n, \t, etc., escape sequence interpretation only happens for
--- regex search and replace.
--- @param text String text to unescape.
--- @return unescaped text
-local function unescape(text)
- return M.regex and text:gsub('%f[\\]\\u(%x%x%x%x)', function(code)
- return utf8.char(tonumber(code, 16))
- end) or text
-end
-
-local P, V, upper, lower = lpeg.P, lpeg.V, string.upper, string.lower
+local P, V, C, upper, lower = lpeg.P, lpeg.V, lpeg.C, string.upper, string.lower
local re_patt = lpeg.Cs(P{
(V('text') + V('u') + V('l') + V('U') + V('L'))^1,
text = (1 - '\\' * lpeg.S('uUlLE'))^1,
- u = '\\u' * lpeg.C(1) / upper, l = '\\l' * lpeg.C(1) / lower,
+ u = '\\u' * C(1) / upper, l = '\\l' * C(1) / lower,
U = P('\\U') / '' * (V('text') / upper + V('u') + V('l'))^0 * V('E')^-1,
L = P('\\L') / '' * (V('text') / lower + V('u') + V('l'))^0 * V('E')^-1,
- E = P('\\E') / '',
+ E = P('\\E') / ''
})
--- Replaces the text in the target range with string *text* subject to:
+-- Returns string *text* with the following sequences unescaped:
+-- * "\uXXXX" sequences replaced with the equivalent UTF-8 character.
-- * "\d" sequences replaced with the text of capture number *d* from the
--- regular expression (or the entire match for *d* = 0)
+-- regular expression (or the entire match for *d* = 0).
-- * "\U" and "\L" sequences convert everything up to the next "\U", "\L", or
-- "\E" to uppercase and lowercase, respectively.
-- * "\u" and "\l" sequences convert the next character to uppercase and
-- lowercase, respectively. They may appear within "\U" and "\L" constructs.
-local function replace_target_re(buffer, rtext)
- rtext = rtext:gsub('\\0', buffer.target_text):gsub('\\(%d)', buffer.tag)
- buffer:replace_target(lpeg.match(re_patt, rtext) or rtext)
+-- @param text String text to unescape.
+-- @return unescaped text
+local function unescape(text)
+ text = text:gsub('%f[\\]\\u(%x%x%x%x)', function(code)
+ return utf8.char(tonumber(code, 16))
+ end):gsub('\\0', buffer.target_text):gsub('\\(%d)', buffer.tag)
+ return re_patt:match(text) or text
end
-- Replaces found (selected) text.
-events.connect(events.REPLACE, function(rtext)
+events.connect(events.REPLACE, function(text)
if buffer.selection_empty then return end
buffer:target_from_selection()
- local f = not M.regex and buffer.replace_target or replace_target_re
- f(buffer, unescape(rtext))
+ buffer:replace_target(not M.regex and text or unescape(text))
buffer:set_sel(buffer.target_start, buffer.target_end)
end)
@@ -426,8 +417,7 @@ events.connect(events.REPLACE_ALL, function(ftext, rtext)
buffer:indicator_fill_range(e, 1)
end
local EOF = replace_in_sel and e == buffer.length + 1 -- no indicator at EOF
- local f = not M.regex and buffer.replace_target or replace_target_re
- rtext, repl_text = unescape(rtext), rtext -- save for ui.find.focus()
+ repl_text = rtext -- save for ui.find.focus()
-- Perform the search and replace.
buffer:begin_undo_action()
@@ -436,7 +426,7 @@ events.connect(events.REPLACE_ALL, function(ftext, rtext)
while buffer:search_in_target(ftext) ~= -1 and (not replace_in_sel or
buffer.target_end <= buffer:indicator_end(INDIC_REPLACE, s) or EOF) do
if buffer.target_start == buffer.target_end then break end -- prevent loops
- f(buffer, rtext)
+ buffer:replace_target(not M.regex and rtext or unescape(rtext))
count = count + 1
buffer:set_target_range(buffer.target_end, buffer.length + 1)
end
@@ -493,8 +483,7 @@ function M.goto_file_found(line_num, next)
local line = buffer:get_cur_line()
local utf8_filename, pos
utf8_filename, line_num, pos = line:match('^(.+):(%d+):()')
- if not utf8_filename then return end
- line_num = tonumber(line_num)
+ if not utf8_filename then return else line_num = tonumber(line_num) end
textadept.editing.select_line()
pos = buffer.selection_start + pos - 1 -- absolute pos of result text on line
local s = buffer:indicator_end(M.INDIC_FIND, buffer.selection_start)
diff --git a/modules/textadept/history.lua b/modules/textadept/history.lua
index 4729322c..d619363f 100644
--- a/modules/textadept/history.lua
+++ b/modules/textadept/history.lua
@@ -34,21 +34,19 @@ local view_history = setmetatable({}, {__index = function(t, view)
end})
-- Listens for text insertion and deletion events and records their locations.
-events.connect(events.MODIFIED, function(position, mod_type, text, length)
+events.connect(events.MODIFIED, function(position, mod, text, length)
local buffer = buffer
-- Only interested in text insertion or deletion.
- if mod_type & buffer.MOD_INSERTTEXT > 0 then
+ if mod & buffer.MOD_INSERTTEXT > 0 then
if length == buffer.length then return end -- ignore file loading
position = position + length
- elseif mod_type & buffer.MOD_DELETETEXT > 0 then
+ elseif mod & buffer.MOD_DELETETEXT > 0 then
if buffer.length == 0 then return end -- ignore replacing buffer contents
else
return
end
-- Ignore undo/redo.
- if mod_type & (buffer.PERFORMED_UNDO | buffer.PERFORMED_REDO) > 0 then
- return
- end
+ if mod & (buffer.PERFORMED_UNDO | buffer.PERFORMED_REDO) > 0 then return end
M.record(nil, buffer:line_from_position(position), buffer.column[position])
end)
@@ -56,13 +54,11 @@ end)
-- forwards.
local jumping = false
--- Jumps to the current position in the current view's history after adjusting
--- that position backwards or forwards.
-local function goto_record()
+-- Jumps to the given record in the current view's history.
+-- @param record History record to jump to.
+local function jump(record)
jumping = true
- local history = view_history[view]
- local record = history[history.pos]
- local filename, line, column = record.filename, record.line, record.column
+ local filename = record.filename
if lfs.attributes(filename) then
io.open_file(filename)
else
@@ -75,7 +71,7 @@ local function goto_record()
end
end
end
- buffer:goto_pos(buffer:find_column(line, column))
+ buffer:goto_pos(buffer:find_column(record.line, record.column))
jumping = false
end
@@ -91,13 +87,13 @@ function M.back()
math.abs(record.line - line) > M.minimum_line_distance then
-- When navigated away from the most recent record, and if that record is
-- not a soft record, jump back to it first, then navigate backwards.
- if not record.soft then goto_record() return end
+ if not record.soft then jump(record) return end
-- Otherwise, update the soft record with the current position and
-- immediately navigate backwards.
M.record(record.filename, nil, nil, record.soft)
end
if history.pos > 1 then history.pos = history.pos - 1 end
- goto_record()
+ jump(history[history.pos])
end
---
@@ -109,7 +105,7 @@ function M.forward()
local record = history[history.pos]
if record.soft then M.record(record.filename, nil, nil, record.soft) end
history.pos = history.pos + 1
- goto_record()
+ jump(history[history.pos])
end
---
diff --git a/modules/textadept/init.lua b/modules/textadept/init.lua
index 435b34dd..0195ae31 100644
--- a/modules/textadept/init.lua
+++ b/modules/textadept/init.lua
@@ -9,19 +9,11 @@ textadept = M -- forward declaration
-- It provides utilities for editing text in Textadept.
module('textadept')]]
-M.bookmarks = require('textadept.bookmarks')
-require('textadept.command_entry')
-M.editing = require('textadept.editing')
-M.file_types = require('textadept.file_types')
-require('textadept.find')
-M.history = require('textadept.history')
-M.macros = require('textadept.macros')
-M.run = require('textadept.run')
-M.session = require('textadept.session')
-M.snippets = require('textadept.snippets')
-
--- These need to be loaded last.
-M.menu = require('textadept.menu')
-M.keys = require('textadept.keys')
+local modules = {
+ 'bookmarks', 'command_entry', 'editing', 'file_types', 'find', 'history',
+ 'macros', 'run', 'session', 'snippets', --[[need to be last]] 'menu', 'keys'
+}
+for _, name in ipairs(modules) do M[name] = require('textadept.' .. name) end
+M.command_entry, M.find = nil, nil -- ui.command_entry, ui.find
return M
diff --git a/modules/textadept/keys.lua b/modules/textadept/keys.lua
index 25db537b..eb5c9301 100644
--- a/modules/textadept/keys.lua
+++ b/modules/textadept/keys.lua
@@ -271,17 +271,16 @@ module('textadept.keys')]]
-- Control, Meta, and 'a' = 'ctrl+meta+a'
local _L = _L
-local m_edit = textadept.menu.menubar[_L['Edit']]
-local m_sel, m_seln = m_edit[_L['Select']], m_edit[_L['Selection']]
-local m_search = textadept.menu.menubar[_L['Search']]
-local m_tools = textadept.menu.menubar[_L['Tools']]
-local m_bookmark = m_tools[_L['Bookmarks']]
-local m_qopen = m_tools[_L['Quick Open']]
-local m_snippets = m_tools[_L['Snippets']]
-local m_buffer = textadept.menu.menubar[_L['Buffer']]
-local m_indentation = m_buffer[_L['Indentation']]
-local m_view = textadept.menu.menubar[_L['View']]
-local m_help = textadept.menu.menubar[_L['Help']]
+-- Returns the menu command associated with the '/'-separated string of menu
+-- labels.
+-- Labels are automatically localized.
+-- @param labels Path to the menu command.
+-- @usage m('Edit/Select/Select in XML Tag')
+local function m(labels)
+ local menu = textadept.menu.menubar
+ for label in labels:gmatch('[^/]+') do menu = menu[_L[label]] end
+ return menu[2]
+end
-- Bindings for Linux/Win32, macOS, Terminal.
local bindings = {
@@ -308,34 +307,33 @@ local bindings = {
[textadept.editing.paste_reindent] = {'ctrl+V', 'cmd+V', 'meta+v'},
[buffer.line_duplicate] = {'ctrl+d', 'cmd+d', nil},
[buffer.clear] = {'del', {'del', 'ctrl+d'}, {'del', 'ctrl+d'}},
- [m_edit[_L['Delete Word']][2]] =
- {'alt+del', 'ctrl+del', {'meta+del', 'meta+d'}},
+ [m('Edit/Delete Word')] = {'alt+del', 'ctrl+del', {'meta+del', 'meta+d'}},
[buffer.select_all] = {'ctrl+a', 'cmd+a', 'meta+a'},
- [m_edit[_L['Match Brace']][2]] = {'ctrl+m', 'ctrl+m', 'meta+m'},
- [m_edit[_L['Complete Word']][2]] =
+ [m('Edit/Match Brace')] = {'ctrl+m', 'ctrl+m', 'meta+m'},
+ [m('Edit/Complete Word')] =
{'ctrl+\n', 'ctrl+esc', {'ctrl+meta+j', 'ctrl+\n'}},
[textadept.editing.toggle_comment] = {'ctrl+/', 'ctrl+/', 'meta+/'},
[textadept.editing.transpose_chars] = {'ctrl+t', 'ctrl+t', 'ctrl+t'},
[textadept.editing.join_lines] = {'ctrl+J', 'ctrl+j', 'meta+j'},
- [m_edit[_L['Filter Through']][2]] = {'ctrl+|', 'cmd+|', 'ctrl+\\'},
+ [m('Edit/Filter Through')] = {'ctrl+|', 'cmd+|', 'ctrl+\\'},
-- Select.
- [m_sel[_L['Select between Matching Delimiters']][2]] =
+ [m('Edit/Select/Select between Matching Delimiters')] =
{'ctrl+M', 'ctrl+M', 'meta+M'},
- [m_sel[_L['Select between XML Tags']][2]] = {'ctrl+<', 'cmd+<', 'meta+<'},
- [m_sel[_L['Select in XML Tag']][2]] = {'ctrl+>', 'cmd+>', nil},
+ [m('Edit/Select/Select between XML Tags')] = {'ctrl+<', 'cmd+<', 'meta+<'},
+ [m('Edit/Select/Select in XML Tag')] = {'ctrl+>', 'cmd+>', nil},
[textadept.editing.select_word] = {'ctrl+D', 'cmd+D', 'meta+W'},
[textadept.editing.select_line] = {'ctrl+N', 'cmd+N', 'meta+N'},
[textadept.editing.select_paragraph] = {'ctrl+P', 'cmd+P', 'meta+P'},
-- Selection.
[buffer.upper_case] = {'ctrl+alt+u', 'ctrl+u', 'ctrl+meta+u'},
[buffer.lower_case] = {'ctrl+alt+U', 'ctrl+U', 'ctrl+meta+l'},
- [m_seln[_L['Enclose as XML Tags']][2]] = {'alt+<', 'ctrl+<', 'meta+>'},
- [m_seln[_L['Enclose as Single XML Tag']][2]] = {'alt+>', 'ctrl+>', nil},
- [m_seln[_L['Enclose in Single Quotes']][2]] = {"alt+'", "ctrl+'", nil},
- [m_seln[_L['Enclose in Double Quotes']][2]] = {'alt+"', 'ctrl+"', nil},
- [m_seln[_L['Enclose in Parentheses']][2]] = {'alt+(', 'ctrl+(', 'meta+)'},
- [m_seln[_L['Enclose in Brackets']][2]] = {'alt+[', 'ctrl+[', 'meta+]'},
- [m_seln[_L['Enclose in Braces']][2]] = {'alt+{', 'ctrl+{', 'meta+}'},
+ [m('Edit/Selection/Enclose as XML Tags')] = {'alt+<', 'ctrl+<', 'meta+>'},
+ [m('Edit/Selection/Enclose as Single XML Tag')] = {'alt+>', 'ctrl+>', nil},
+ [m('Edit/Selection/Enclose in Single Quotes')] = {"alt+'", "ctrl+'", nil},
+ [m('Edit/Selection/Enclose in Double Quotes')] = {'alt+"', 'ctrl+"', nil},
+ [m('Edit/Selection/Enclose in Parentheses')] = {'alt+(', 'ctrl+(', 'meta+)'},
+ [m('Edit/Selection/Enclose in Brackets')] = {'alt+[', 'ctrl+[', 'meta+]'},
+ [m('Edit/Selection/Enclose in Braces')] = {'alt+{', 'ctrl+{', 'meta+}'},
[buffer.move_selected_lines_up] =
{'ctrl+shift+up', 'ctrl+shift+up', 'ctrl+shift+up'},
[buffer.move_selected_lines_down] =
@@ -346,10 +344,10 @@ local bindings = {
-- TODO: textadept.history.record
-- TODO: textadept.history.clear
-- Preferences.
- [m_edit[_L['Preferences']][2]] = {'ctrl+p', 'cmd+,', 'meta+~'},
+ [m('Edit/Preferences')] = {'ctrl+p', 'cmd+,', 'meta+~'},
-- Search.
- [m_search[_L['Find']][2]] = {'ctrl+f', 'cmd+f', {'meta+f', 'meta+F'}},
+ [m('Search/Find')] = {'ctrl+f', 'cmd+f', {'meta+f', 'meta+F'}},
[ui.find.find_next] = {{'ctrl+g', 'f3'}, 'cmd+g', 'meta+g'},
[ui.find.find_prev] = {{'ctrl+G', 'shift+f3'}, 'cmd+G', 'meta+G'},
[ui.find.replace] = {'ctrl+alt+r', 'ctrl+r', 'meta+r'},
@@ -358,38 +356,37 @@ local bindings = {
-- Find Prev is ap when find pane is focused in GUI.
-- Replace is ar when find pane is focused in GUI.
-- Replace All is aa when find pane is focused in GUI.
- [m_search[_L['Find Incremental']][2]] =
- {'ctrl+alt+f', 'ctrl+cmd+f', 'ctrl+meta+f'},
- [m_search[_L['Find in Files']][2]] = {'ctrl+F', 'cmd+F', nil},
+ [m('Search/Find Incremental')] = {'ctrl+alt+f', 'ctrl+cmd+f', 'ctrl+meta+f'},
+ [m('Search/Find in Files')] = {'ctrl+F', 'cmd+F', nil},
-- Find in Files is ai when find pane is focused in GUI.
- [m_search[_L['Goto Next File Found']][2]] = {'ctrl+alt+g', 'ctrl+cmd+g', nil},
- [m_search[_L['Goto Previous File Found']][2]] =
- {'ctrl+alt+G', 'ctrl+cmd+G', nil},
+ [m('Search/Goto Next File Found')] = {'ctrl+alt+g', 'ctrl+cmd+g', nil},
+ [m('Search/Goto Previous File Found')] = {'ctrl+alt+G', 'ctrl+cmd+G', nil},
[textadept.editing.goto_line] = {'ctrl+j', 'cmd+j', 'ctrl+j'},
-- Tools.
- [m_tools[_L['Command Entry']][2]] = {'ctrl+e', 'cmd+e', 'meta+c'},
- [m_tools[_L['Select Command']][2]] = {'ctrl+E', 'cmd+E', 'meta+C'},
+ [m('Tools/Command Entry')] = {'ctrl+e', 'cmd+e', 'meta+c'},
+ [m('Tools/Select Command')] = {'ctrl+E', 'cmd+E', 'meta+C'},
[textadept.run.run] = {'ctrl+r', 'cmd+r', 'ctrl+r'},
[textadept.run.compile] = {'ctrl+R', 'cmd+R', 'ctrl+meta+r'},
- [m_tools[_L['Set Arguments...']][2]] = {'ctrl+A', 'cmd+A', nil},
+ [textadept.run.set_arguments] = {'ctrl+A', 'cmd+A', nil},
[textadept.run.build] = {'ctrl+B', 'cmd+B', 'ctrl+meta+b'},
[textadept.run.stop] = {'ctrl+X', 'cmd+X', 'ctrl+meta+x'},
- [m_tools[_L['Next Error']][2]] = {'ctrl+alt+e', 'ctrl+cmd+e', 'meta+x'},
- [m_tools[_L['Previous Error']][2]] = {'ctrl+alt+E', 'ctrl+cmd+E', 'meta+X'},
+ [m('Tools/Next Error')] = {'ctrl+alt+e', 'ctrl+cmd+e', 'meta+x'},
+ [m('Tools/Previous Error')] = {'ctrl+alt+E', 'ctrl+cmd+E', 'meta+X'},
-- Bookmark.
[textadept.bookmarks.toggle] = {'ctrl+f2', 'cmd+f2', 'f1'},
[textadept.bookmarks.clear] = {'ctrl+shift+f2', 'cmd+shift+f2', 'f6'},
- [m_bookmark[_L['Next Bookmark']][2]] = {'f2', 'f2', 'f2'},
- [m_bookmark[_L['Previous Bookmark']][2]] = {'shift+f2', 'shift+f2', 'f3'},
+ [m('Tools/Bookmarks/Next Bookmark')] = {'f2', 'f2', 'f2'},
+ [m('Tools/Bookmarks/Previous Bookmark')] = {'shift+f2', 'shift+f2', 'f3'},
[textadept.bookmarks.goto_mark] = {'alt+f2', 'alt+f2', 'f4'},
-- Macros.
[textadept.macros.record] = {'f9', 'f9', 'f9'},
[textadept.macros.play] = {'shift+f9', 'shift+f9', 'f10'},
-- Quick Open.
- [m_qopen[_L['Quickly Open User Home']][2]] = {'ctrl+u', 'cmd+u', 'ctrl+u'},
- -- TODO: m_qopen[_L['Quickly Open Textadept Home']][2]
- [m_qopen[_L['Quickly Open Current Directory']][2]] =
+ [m('Tools/Quick Open/Quickly Open User Home')] =
+ {'ctrl+u', 'cmd+u', 'ctrl+u'},
+ -- TODO: m('Tools/Quickly Open Textadept Home')
+ [m('Tools/Quick Open/Quickly Open Current Directory')] =
{'ctrl+alt+O', 'ctrl+cmd+O', 'meta+O'},
[io.quick_open] = {'ctrl+alt+P', 'ctrl+cmd+P', 'ctrl+meta+p'},
-- Snippets.
@@ -397,59 +394,58 @@ local bindings = {
[textadept.snippets.insert] = {'\t', '\t', '\t'},
[textadept.snippets.previous] = {'shift+\t', 'shift+\t', 'shift+\t'},
[textadept.snippets.cancel_current] = {'esc', 'esc', 'esc'},
- [m_snippets[_L['Complete Trigger Word']][2]] = {'ctrl+k', 'alt+\t', 'meta+k'},
+ [m('Tools/Snippets/Complete Trigger Word')] = {'ctrl+k', 'alt+\t', 'meta+k'},
-- Other.
- [m_tools[_L['Complete Symbol']][2]] = {'ctrl+ ', 'alt+esc', 'ctrl+ '},
+ [m('Tools/Complete Symbol')] = {'ctrl+ ', 'alt+esc', 'ctrl+ '},
[textadept.editing.show_documentation] =
{'ctrl+h', 'ctrl+h', {'meta+h', 'meta+H'}},
- [m_tools[_L['Show Style']][2]] = {'ctrl+i', 'cmd+i', 'meta+I'},
+ [m('Tools/Show Style')] = {'ctrl+i', 'cmd+i', 'meta+I'},
-- Buffer.
- [m_buffer[_L['Next Buffer']][2]] = {'ctrl+\t', 'ctrl+\t', 'meta+n'},
- [m_buffer[_L['Previous Buffer']][2]] =
- {'ctrl+shift+\t', 'ctrl+shift+\t', 'meta+p'},
+ [m('Buffer/Next Buffer')] = {'ctrl+\t', 'ctrl+\t', 'meta+n'},
+ [m('Buffer/Previous Buffer')] = {'ctrl+shift+\t', 'ctrl+shift+\t', 'meta+p'},
[ui.switch_buffer] = {'ctrl+b', 'cmd+b', {'meta+b', 'meta+B'}},
-- Indentation.
- -- TODO: m_indentation[_L['Tab width: 2']][2]
- -- TODO: m_indentation[_L['Tab width: 3']][2]
- -- TODO: m_indentation[_L['Tab width: 4']][2]
- -- TODO: m_indentation[_L['Tab width: 8']][2]
- [m_indentation[_L['Toggle Use Tabs']][2]] =
+ -- TODO: m('Buffer/Indentation/Tab width: 2')
+ -- TODO: m('Buffer/Indentation/Tab width: 3')
+ -- TODO: m('Buffer/Indentation/Tab width: 4')
+ -- TODO: m('Buffer/Indentation/Tab width: 8')
+ [m('Buffer/Indentation/Toggle Use Tabs')] =
{'ctrl+alt+T', 'ctrl+T', {'meta+t', 'meta+T'}},
[textadept.editing.convert_indentation] = {'ctrl+alt+i', 'ctrl+i', 'meta+i'},
-- EOL Mode.
- -- TODO: m_buffer[_L['EOL Mode']][_L['CRLF']][2]
- -- TODO: m_buffer[_L['EOL Mode']][_L['LF']][2]
+ -- TODO: m('Buffer/EOL Mode/CRLF')
+ -- TODO: m('Buffer/EOL Mode/LF')
-- Encoding.
- -- TODO: m_buffer[_L['Encoding']][_L['UTF-8 Encoding']][2]
- -- TODO: m_buffer[_L['Encoding']][_L['ASCII Encoding']][2]
- -- TODO: m_buffer[_L['Encoding']][_L['CP-1252 Encoding']][2]
- -- TODO: m_buffer[_L['Encoding']][_L['UTF-16 Encoding']][2]
- [m_buffer[_L['Toggle Wrap Mode']][2]] = {'ctrl+alt+\\', 'ctrl+\\', nil},
- [m_buffer[_L['Toggle View Whitespace']][2]] = {'ctrl+alt+S', 'ctrl+S', nil},
+ -- TODO: m('Buffer/Encoding/UTF-8 Encoding')
+ -- TODO: m('Buffer/Encoding/ASCII Encoding')
+ -- TODO: m('Buffer/Encoding/CP-1252 Encoding')
+ -- TODO: m('Buffer/Encoding/UTF-16 Encoding')
+ [m('Buffer/Toggle Wrap Mode')] = {'ctrl+alt+\\', 'ctrl+\\', nil},
+ [m('Buffer/Toggle View Whitespace')] = {'ctrl+alt+S', 'ctrl+S', nil},
[textadept.file_types.select_lexer] = {'ctrl+L', 'cmd+L', 'meta+L'},
-- View.
- [m_view[_L['Next View']][2]] = {'ctrl+alt+n', 'ctrl+alt+\t', nil},
- [m_view[_L['Previous View']][2]] = {'ctrl+alt+p', 'ctrl+alt+shift+\t', nil},
- [m_view[_L['Split View Horizontal']][2]] =
+ [m('View/Next View')] = {'ctrl+alt+n', 'ctrl+alt+\t', nil},
+ [m('View/Previous View')] = {'ctrl+alt+p', 'ctrl+alt+shift+\t', nil},
+ [m('View/Split View Horizontal')] =
{{'ctrl+alt+s', 'ctrl+alt+h'}, 'ctrl+s', nil},
- [m_view[_L['Split View Vertical']][2]] = {'ctrl+alt+v', 'ctrl+v', nil},
- [m_view[_L['Unsplit View']][2]] = {'ctrl+alt+w', 'ctrl+w', nil},
- [m_view[_L['Unsplit All Views']][2]] = {'ctrl+alt+W', 'ctrl+W', nil},
- [m_view[_L['Grow View']][2]] =
+ [m('View/Split View Vertical')] = {'ctrl+alt+v', 'ctrl+v', nil},
+ [m('View/Unsplit View')] = {'ctrl+alt+w', 'ctrl+w', nil},
+ [m('View/Unsplit All Views')] = {'ctrl+alt+W', 'ctrl+W', nil},
+ [m('View/Grow View')] =
{{'ctrl+alt++', 'ctrl+alt+='}, {'ctrl++', 'ctrl+='}, nil},
- [m_view[_L['Shrink View']][2]] = {'ctrl+alt+-', 'ctrl+-', nil},
- [m_view[_L['Toggle Current Fold']][2]] = {'ctrl+*', 'cmd+*', 'meta+*'},
- [m_view[_L['Toggle Show Indent Guides']][2]] = {'ctrl+alt+I', 'ctrl+I', nil},
- [m_view[_L['Toggle Virtual Space']][2]] = {'ctrl+alt+V', 'ctrl+V', nil},
+ [m('View/Shrink View')] = {'ctrl+alt+-', 'ctrl+-', nil},
+ [m('View/Toggle Current Fold')] = {'ctrl+*', 'cmd+*', 'meta+*'},
+ [m('View/Toggle Show Indent Guides')] = {'ctrl+alt+I', 'ctrl+I', nil},
+ [m('View/Toggle Virtual Space')] = {'ctrl+alt+V', 'ctrl+V', nil},
[view.zoom_in] = {'ctrl+=', 'cmd+=', nil},
[view.zoom_out] = {'ctrl+-', 'cmd+-', nil},
- [m_view[_L['Reset Zoom']][2]] = {'ctrl+0', 'cmd+0', nil},
+ [m('View/Reset Zoom')] = {'ctrl+0', 'cmd+0', nil},
-- Help.
- [m_help[_L['Show Manual']][2]] = {'f1', 'f1', nil},
- [m_help[_L['Show LuaDoc']][2]] = {'shift+f1', 'shift+f1', nil},
+ [m('Help/Show Manual')] = {'f1', 'f1', nil},
+ [m('Help/Show LuaDoc')] = {'shift+f1', 'shift+f1', nil},
-- Movement commands.
-- Unbound keys are handled by Scintilla, but when playing back a macro, this
@@ -486,7 +482,7 @@ local bindings = {
[buffer.document_start] = {nil, nil, 'ctrl+meta+a'},
[buffer.document_end] = {nil, nil, 'ctrl+meta+e'},
- [function(b)
+ [function()
buffer:line_end_extend()
if not buffer.selection_empty then buffer:cut() else buffer:clear() end
end] = {nil, 'ctrl+k', 'ctrl+k'},
@@ -519,16 +515,11 @@ end
if CURSES then
keys['ctrl+meta+v'] = {
- n = m_view[_L['Next View']][2],
- p = m_view[_L['Previous View']][2],
- s = m_view[_L['Split View Horizontal']][2],
- h = m_view[_L['Split View Horizontal']][2],
- v = m_view[_L['Split View Vertical']][2],
- w = m_view[_L['Unsplit View']][2],
- W = m_view[_L['Unsplit All Views']][2],
- ['+'] = m_view[_L['Grow View']][2],
- ['='] = m_view[_L['Grow View']][2],
- ['-'] = m_view[_L['Shrink View']][2]
+ n = m('View/Next View'), p = m('View/Previous View'),
+ s = m('View/Split View Horizontal'), h = m('View/Split View Horizontal'),
+ v = m('View/Split View Vertical'), w = m('View/Unsplit View'),
+ W = m('View/Unsplit All Views'), ['+'] = m('View/Grow View'),
+ ['='] = m('View/Grow View'), ['-'] = m('View/Shrink View')
}
end
diff --git a/modules/textadept/macros.lua b/modules/textadept/macros.lua
index 3428ef11..d1a3758f 100644
--- a/modules/textadept/macros.lua
+++ b/modules/textadept/macros.lua
@@ -11,8 +11,8 @@ local M = {}
local recording, macro
--- Commands bound to keys to ignore during macro recording, as the command(s)
--- ultimately executed will be recorded in some form.
+-- List of commands bound to keys to ignore during macro recording, as the
+-- command(s) ultimately executed will be recorded in some form.
local ignore
events.connect(events.INITIALIZED, function()
local m_tools = textadept.menu.menubar[_L['Tools']]
diff --git a/modules/textadept/menu.lua b/modules/textadept/menu.lua
index d684adc1..2dee4f60 100644
--- a/modules/textadept/menu.lua
+++ b/modules/textadept/menu.lua
@@ -12,8 +12,7 @@ local M = {}
-- assigned to string text.
module('textadept.menu')]]
-local _L = _L
-local SEPARATOR = {''}
+local _L, SEPARATOR = _L, {''}
-- The following buffer and view functions need to be made constant in order for
-- menu items to identify the key associated with the functions.
@@ -26,16 +25,16 @@ local sel_enc = textadept.editing.select_enclosed
local enc = textadept.editing.enclose
local function set_indentation(i)
buffer.tab_width = i
- events.emit(events.UPDATE_UI, 0) -- for updating statusbar
+ events.emit(events.UPDATE_UI, 1) -- for updating statusbar
end
local function set_eol_mode(mode)
buffer.eol_mode = mode
buffer:convert_eols(mode)
- events.emit(events.UPDATE_UI, 0) -- for updating statusbar
+ events.emit(events.UPDATE_UI, 1) -- for updating statusbar
end
local function set_encoding(encoding)
buffer:set_encoding(encoding)
- events.emit(events.UPDATE_UI, 0) -- for updating statusbar
+ events.emit(events.UPDATE_UI, 1) -- for updating statusbar
end
local function open_page(url)
local cmd = (WIN32 and 'start ""') or (OSX and 'open') or 'xdg-open'
@@ -205,9 +204,8 @@ local default_menubar = {
{_L['Quickly Open User Home'], function() io.quick_open(_USERHOME) end},
{_L['Quickly Open Textadept Home'], function() io.quick_open(_HOME) end},
{_L['Quickly Open Current Directory'], function()
- if buffer.filename then
- io.quick_open(buffer.filename:match('^(.+)[/\\]'))
- end
+ if not buffer.filename then return end
+ io.quick_open(buffer.filename:match('^(.+)[/\\]'))
end},
{_L['Quickly Open Current Project'], io.quick_open},
},
@@ -228,8 +226,8 @@ local default_menubar = {
end},
{_L['Show Documentation'], textadept.editing.show_documentation},
{_L['Show Style'], function()
- local char = buffer:text_range(buffer.current_pos,
- buffer:position_after(buffer.current_pos))
+ local char = buffer:text_range(
+ buffer.current_pos, buffer:position_after(buffer.current_pos))
if char == '' then return end -- end of buffer
local bytes = string.rep(' 0x%X', #char):format(char:byte(1, #char))
local style = buffer.style_at[buffer.current_pos]
@@ -255,7 +253,7 @@ local default_menubar = {
SEPARATOR,
{_L['Toggle Use Tabs'], function()
buffer.use_tabs = not buffer.use_tabs
- events.emit(events.UPDATE_UI, 0) -- for updating statusbar
+ events.emit(events.UPDATE_UI, 1) -- for updating statusbar
end},
{_L['Convert Indentation'], textadept.editing.convert_indentation}
},
@@ -306,12 +304,12 @@ local default_menubar = {
end},
SEPARATOR,
{_L['Toggle Show Indent Guides'], function()
- local off = view.indentation_guides == 0
- view.indentation_guides = off and view.IV_LOOKBOTH or 0
+ view.indentation_guides =
+ view.indentation_guides == 0 and view.IV_LOOKBOTH or 0
end},
{_L['Toggle Virtual Space'], function()
- local off = buffer.virtual_space_options == 0
- buffer.virtual_space_options = off and buffer.VS_USERACCESSIBLE or 0
+ buffer.virtual_space_options =
+ buffer.virtual_space_options == 0 and buffer.VS_USERACCESSIBLE or 0
end},
SEPARATOR,
{_L['Zoom In'], view.zoom_in},
@@ -399,8 +397,7 @@ end
-- @return GTK menu that can be passed to `ui.menu()`.
-- @see ui.menu
local function read_menu_table(menu, contextmenu)
- local gtkmenu = {}
- gtkmenu.title = menu.title
+ local gtkmenu = {title = menu.title}
for _, item in ipairs(menu) do
if item.title then
gtkmenu[#gtkmenu + 1] = read_menu_table(item, contextmenu)
@@ -465,8 +462,8 @@ local function set_menubar(menubar)
key_shortcuts, menu_items = {}, {} -- reset
for key, f in pairs(keys) do key_shortcuts[tostring(f)] = key end
local _menubar = {}
- for i = 1, #menubar do
- _menubar[#_menubar + 1] = ui.menu(read_menu_table(menubar[i]))
+ for _, menu in ipairs(menubar) do
+ _menubar[#_menubar + 1] = ui.menu(read_menu_table(menu))
end
ui.menubar = _menubar
proxies.menubar = proxy_menu(menubar, set_menubar)
@@ -513,11 +510,9 @@ proxies.tab_context_menu = proxy_menu(default_tab_context_menu, function() end)
-- Performs the appropriate action when clicking a menu item.
events.connect(events.MENU_CLICKED, function(menu_id)
- local menu_item = menu_id < 1000 and menu_items or contextmenu_items
- local action = menu_item[menu_id < 1000 and menu_id or menu_id - 1000][2]
- assert(type(action) == 'function', string.format(
- '%s %s', _L['Unknown command:'], action))
- action()
+ local items = menu_id < 1000 and menu_items or contextmenu_items
+ local f = items[menu_id < 1000 and menu_id or menu_id - 1000][2]
+ assert_type(f, 'function', 'command')()
end)
---
diff --git a/modules/textadept/run.lua b/modules/textadept/run.lua
index e2c54db8..9c30aa55 100644
--- a/modules/textadept/run.lua
+++ b/modules/textadept/run.lua
@@ -51,8 +51,8 @@ M.MARK_WARNING = _SCINTILLA.next_marker_number()
M.MARK_ERROR = _SCINTILLA.next_marker_number()
-- Events.
-events.COMPILE_OUTPUT, events.RUN_OUTPUT = 'compile_output', 'run_output'
-events.BUILD_OUTPUT = 'build_output'
+local run_events = {'compile_output', 'run_output', 'build_output'}
+for _, v in ipairs(run_events) do events[v:upper()] = v end
-- Keep track of: the last process spawned in order to kill it if requested; the
-- cwd of that process in order to jump to relative file paths in recognized
@@ -284,10 +284,12 @@ events.connect(events.RUN_OUTPUT, print_output)
-- @see compile_commands
-- @name set_arguments
function M.set_arguments(filename, run, compile)
- assert_type(filename, 'string/nil', 1)
+ if not assert_type(filename, 'string/nil', 1) then
+ filename = buffer.filename
+ if not filename then return end
+ end
assert_type(run, 'string/nil', 2)
assert_type(compile, 'string/nil', 3)
- if not filename then filename = buffer.filename end
local base_commands, utf8_args = {}, {}
for i, commands in ipairs{M.run_commands, M.compile_commands} do
-- Compare the base run/compile command with the one for the current
@@ -300,7 +302,8 @@ function M.set_arguments(filename, run, compile)
utf8_args[i] = args:iconv('UTF-8', _CHARSET)
end
if not run or not compile then
- local button, utf8_args = ui.dialogs.inputbox{
+ local button
+ button, utf8_args = ui.dialogs.inputbox{
title = _L['Set Arguments...']:gsub('_', ''), informative_text = {
_L['Command line arguments'], _L['For Run:'], _L['For Compile:']
}, text = utf8_args, width = not CURSES and 400 or nil
@@ -365,10 +368,13 @@ events.connect(events.BUILD_OUTPUT, print_output)
-- @name stop
function M.stop() if proc then proc:kill() end end
+-- Returns whether or not the given buffer is the message buffer.
+local function is_msg_buf(buf) return buf._type == _L['[Message Buffer]'] end
+
-- Send line as input to process stdin on return.
events.connect(events.CHAR_ADDED, function(code)
if code == string.byte('\n') and proc and proc:status() == 'running' and
- buffer._type == _L['[Message Buffer]'] then
+ is_msg_buf(buffer) then
local line_num = buffer:line_from_position(buffer.current_pos) - 1
proc:write(buffer:get_line(line_num))
end
@@ -392,8 +398,6 @@ M.error_patterns = {actionscript={'^(.-)%((%d+)%): col: (%d+) (.+)$'},ada={'^(.-
-- Note: ASP,CSS,Desktop,diff,django,gettext,Gtkrc,HTML,ini,JSON,JSP,Markdown,Postscript,Properties,R,RHTML,XML don't have parse-able errors.
-- Note: Batch,BibTeX,ConTeXt,Dockerfile,GLSL,Inform,Io,Lisp,MoonScript,Scheme,SQL,TeX cannot be parsed for one reason or another.
--- Returns whether or not the given buffer is a message buffer.
-local function is_msg_buf(buf) return buf._type == _L['[Message Buffer]'] end
---
-- Jumps to the source of the recognized compile/run warning or error on line
-- number *line_num* in the message buffer.
@@ -458,12 +462,10 @@ function M.goto_error(line_num, next)
if detail.column then
buffer:goto_pos(buffer:find_column(detail.line, detail.column))
end
- if detail.message then
- buffer.annotation_text[detail.line] = detail.message
- if not detail.warning then
- buffer.annotation_style[detail.line] = buffer:style_of_name('error')
- end
- end
+ if not detail.message then return end
+ buffer.annotation_text[detail.line] = detail.message
+ if detail.warning then return end
+ buffer.annotation_style[detail.line] = buffer:style_of_name('error')
end
events.connect(events.KEYPRESS, function(code)
if keys.KEYSYMS[code] == '\n' and is_msg_buf(buffer) and
diff --git a/modules/textadept/session.lua b/modules/textadept/session.lua
index 7083c991..55a33be8 100644
--- a/modules/textadept/session.lua
+++ b/modules/textadept/session.lua
@@ -29,7 +29,8 @@ module('textadept.session')]]
M.save_on_quit = true
-- Events.
-events.SESSION_SAVE, events.SESSION_LOAD = 'session_save', 'session_load'
+local session_events = {'session_save', 'session_load'}
+for _, v in ipairs(session_events) do events[v:upper()] = v end
local session_file = _USERHOME .. (not CURSES and '/session' or '/session_term')
@@ -51,7 +52,6 @@ function M.load(filename)
}
if not filename then return end
end
-
local f = loadfile(filename, 't', {})
if not f or not io.close_all_buffers() then return end -- fail silently
local session = f()
@@ -86,12 +86,12 @@ function M.load(filename)
local function unserialize_split(split)
if type(split) ~= 'table' then
view:goto_buffer(_BUFFERS[math.min(split, #_BUFFERS)])
- return
- end
- for i, view in ipairs{view:split(split.vertical)} do
- view.size = split.size
- ui.goto_view(view)
- unserialize_split(split[i])
+ else
+ for i, view in ipairs{view:split(split.vertical)} do
+ view.size = split.size
+ ui.goto_view(view)
+ unserialize_split(split[i])
+ end
end
end
unserialize_split(session.views[1])
diff --git a/modules/textadept/snippets.lua b/modules/textadept/snippets.lua
index e236bb4d..2f40d219 100644
--- a/modules/textadept/snippets.lua
+++ b/modules/textadept/snippets.lua
@@ -158,13 +158,13 @@ local function find_snippet(grep, no_trigger)
end
for _, snippets in ipairs(snippet_tables) do
if not grep and snippets[trigger] then return trigger, snippets[trigger] end
- if grep then
- for name, text in pairs(snippets) do
- if name:find(name_patt) and type(text) ~= 'table' then
- matching_snippets[name] = tostring(text)
- end
+ if not grep then goto continue end
+ for name, text in pairs(snippets) do
+ if name:find(name_patt) and type(text) ~= 'table' then
+ matching_snippets[name] = tostring(text)
end
end
+ ::continue::
end
-- Search in snippet files.
for i = 1, #M.paths do
@@ -188,59 +188,39 @@ local function find_snippet(grep, no_trigger)
if not grep then return nil, nil else return trigger, matching_snippets end
end
--- Metatable for a snippet object.
+-- A snippet object.
+-- @field trigger The word that triggered this snippet.
+-- @field original_sel_text The text originally selected when this snippet was
+-- inserted.
+-- @field start_pos This snippet's start position.
+-- @field end_pos This snippet's end position. This is a metafield that is
+-- computed based on the `INDIC_SNIPPET` sentinel.
+-- @field placeholder_pos The beginning of the current placeholder in this
+-- snippet. This is used by transforms to identify text to transform. This is
+-- a metafield that is computed based on `INDIC_CURRENTPLACEHOLDER`.
+-- @field index This snippet's current placeholder index.
+-- @field max_index The number of different placeholders in this snippet.
+-- @field snapshots A record of this snippet's text over time. The snapshot for
+-- a given placeholder index contains the state of the snippet with all
+-- placeholders of that index filled in (prior to moving to the next
+-- placeholder index). Snippet state consists of a `text` string field and a
+-- `placeholders` table field.
-- @class table
--- @name snippet_mt
-local snippet_mt -- defined later
+-- @name snippet
+local snippet = {}
-- The stack of currently running snippets.
-local snippet_stack = {}
+local stack = {}
--- Inserts a new snippet, adds it to the snippet stack, and returns the snippet.
+-- Inserts a new snippet and adds it to the snippet stack.
-- @param text The new snippet to insert.
-- @param trigger The trigger text used to expand the snippet, if any.
-local function new_snippet(text, trigger)
- -- An inserted snippet.
- -- @field trigger The word that triggered this snippet.
- -- @field original_sel_text The text originally selected when this snippet was
- -- inserted.
- -- @field start_pos This snippet's start position.
- -- @field end_pos This snippet's end position. This is a metafield that is
- -- computed based on the `INDIC_SNIPPET` sentinel.
- -- @field placeholder_pos The beginning of the current placeholder in this
- -- snippet. This is used by transforms to identify text to transform. This
- -- is a metafield that is computed based on `INDIC_CURRENTPLACEHOLDER`.
- -- @field index This snippet's current placeholder index.
- -- @field max_index The number of different placeholders in this snippet.
- -- @field snapshots A record of this snippet's text over time. The snapshot
- -- for a given placeholder index contains the state of the snippet with all
- -- placeholders of that index filled in (prior to moving to the next
- -- placeholder index). Snippet state consists of a `text` string field and a
- -- `placeholders` table field.
- -- @class table
- -- @name snippet
- local snippet = setmetatable({
+function snippet.new(text, trigger)
+ local snip = setmetatable({
trigger = trigger, original_sel_text = buffer:get_sel_text(),
- start_pos = buffer.selection_start - (trigger and #trigger or 0),
- index = 0, max_index = 0, snapshots = {},
- }, {__index = function(self, k)
- if k == 'end_pos' then
- local end_pos = buffer:indicator_end(INDIC_SNIPPET, self.start_pos)
- return end_pos > self.start_pos and end_pos or self.start_pos
- elseif k == 'placeholder_pos' then
- -- Normally the marker is one character behind the placeholder. However
- -- it will not exist at all if the placeholder is at the beginning of the
- -- snippet. Also account for the marker being at the beginning of the
- -- snippet. (If so, pos will point to the correct position.)
- local pos = buffer:indicator_end(INDIC_CURRENTPLACEHOLDER, self.start_pos)
- if pos == 1 then pos = self.start_pos end
- return buffer:indicator_all_on_for(pos) &
- 1 << INDIC_CURRENTPLACEHOLDER - 1 > 0 and pos + 1 or pos
- else
- return snippet_mt[k]
- end
- end})
- snippet_stack[#snippet_stack + 1] = snippet
+ start_pos = buffer.selection_start - (trigger and #trigger or 0), index = 0,
+ max_index = 0, snapshots = {}
+ }, snippet)
-- Convert and match indentation.
local lines = {}
@@ -301,7 +281,7 @@ local function new_snippet(text, trigger)
while placeholder do
if placeholder.index then
local i = placeholder.index
- if i > snippet.max_index then snippet.max_index = i end
+ if i > snip.max_index then snip.max_index = i end
placeholder.id = #snapshot.placeholders + 1
snapshot.placeholders[#snapshot.placeholders + 1] = placeholder
end
@@ -330,254 +310,265 @@ local function new_snippet(text, trigger)
default:sub(2, -2), position + #index + 2)
end
index = tonumber(index)
- if index > snippet.max_index then snippet.max_index = index end
+ if index > snip.max_index then snip.max_index = index end
snapshot.placeholders[#snapshot.placeholders + 1] = {
- id = #snapshot.placeholders + 1, index = index,
- default = default, simple = not default or nil,
- length = #(default or ' '),
- position = snippet.start_pos + position,
+ id = #snapshot.placeholders + 1, index = index, default = default,
+ simple = not default or nil, length = #(default or ' '),
+ position = snip.start_pos + position
}
return default or ' ' -- fill empty placeholder for display
end
- local ph_patt = P{
+ return lpeg.match(P{
lpeg.Cs((Cp() * '%' * C(R('09')^1) * C(V('parens'))^-1 / ph + 1)^0),
parens = '(' * (1 - S('()') + V('parens'))^0 * ')'
- }
- return ph_patt:match(s)
+ }, s)
end
placeholder.default = process_placeholders(
placeholder.default, placeholder.position)
end
snapshot.text = snapshot.text .. placeholder.default
elseif placeholder.transform and not placeholder.index then
- snapshot.text = snapshot.text .. snippet:execute_code(placeholder)
+ snapshot.text = snapshot.text .. snip:execute_code(placeholder)
else
snapshot.text = snapshot.text .. ' ' -- fill empty placeholder for display
end
placeholder.length = #snapshot.text - placeholder.position
- placeholder.position = snippet.start_pos + placeholder.position -- absolute
+ placeholder.position = snip.start_pos + placeholder.position -- absolute
text_part, placeholder, e = patt:match(text, e)
end
if text_part ~= '' then
snapshot.text = snapshot.text .. text_part:gsub('%%(%p)', '%1')
end
- snippet.snapshots[0] = snapshot
+ snip.snapshots[0] = snapshot
-- Insert the snippet into the buffer and mark its end position.
buffer:begin_undo_action()
- buffer:set_target_range(snippet.start_pos, buffer.selection_end)
+ buffer:set_target_range(snip.start_pos, buffer.selection_end)
buffer:replace_target(' ') -- placeholder for snippet text
buffer.indicator_current = INDIC_SNIPPET
- buffer:indicator_fill_range(snippet.start_pos + 1, 1)
- snippet:insert() -- insert into placeholder
+ buffer:indicator_fill_range(snip.start_pos + 1, 1)
+ snip:insert() -- insert into placeholder
buffer:end_undo_action()
- return snippet
+ stack[#stack + 1] = snip
end
-snippet_mt = {
- -- Inserts the current snapshot (based on `self.index`) of this snippet into
- -- the buffer and marks placeholders.
- insert = function(self)
- buffer:set_target_range(self.start_pos, self.end_pos)
- buffer:replace_target(self.snapshots[self.index].text)
- buffer.indicator_current = M.INDIC_PLACEHOLDER
- for id, placeholder in pairs(self.snapshots[self.index].placeholders) do
- buffer.indicator_value = id
- buffer:indicator_fill_range(placeholder.position, placeholder.length)
- end
- end,
-
- -- Jumps to the next placeholder in this snippet and adds additional carets
- -- at mirrors.
- next = function(self)
- if buffer:auto_c_active() then buffer:auto_c_complete() end
- -- Take a snapshot of the current state in order to restore it later if
- -- necessary.
- if self.index > 0 and self.start_pos < self.end_pos then
- local text = buffer:text_range(self.start_pos, self.end_pos)
- local placeholders = {}
- for pos, ph in self:each_placeholder() do
- -- Only the position of placeholders changes between snapshots; save it
- -- and keep all other existing properties.
- -- Note that nested placeholders will return the same placeholder id
- -- twice: once before a nested placeholder, and again after. (e.g.
- -- [foo[bar]baz] will will return the '[foo' and 'baz]' portions of the
- -- same placeholder.) Only process the first occurrence.
- if not placeholders[ph.id] then
- placeholders[ph.id] = setmetatable({position = pos}, {
- __index = self.snapshots[self.index - 1].placeholders[ph.id]
- })
- end
- end
- self.snapshots[self.index] = {text = text, placeholders = placeholders}
- end
- self.index = self.index < self.max_index and self.index + 1 or 0
-
- -- Find the default placeholder, which may be the first mirror.
- local ph = select(2, self:each_placeholder(self.index, 'default')()) or
- select(2, self:each_placeholder(self.index, 'choice')()) or
- select(2, self:each_placeholder(self.index, 'simple')()) or
- self.index == 0 and {position = self.end_pos, length = 0}
- if not ph then self:next() return end -- try next placeholder
-
- -- Mark the position of the placeholder so transforms can identify it.
- buffer.indicator_current = INDIC_CURRENTPLACEHOLDER
- buffer:indicator_clear_range(self.placeholder_pos - 1, 1)
- if ph.position > self.start_pos and self.index > 0 then
- -- Place it directly behind the placeholder so it will be preserved.
- buffer:indicator_fill_range(ph.position - 1, 1)
+-- Provides dynamic field values and methods for this snippet.
+function snippet:__index(k)
+ if k == 'end_pos' then
+ local end_pos = buffer:indicator_end(INDIC_SNIPPET, self.start_pos)
+ return end_pos > self.start_pos and end_pos or self.start_pos
+ elseif k == 'placeholder_pos' then
+ -- Normally the marker is one character behind the placeholder. However
+ -- it will not exist at all if the placeholder is at the beginning of the
+ -- snippet. Also account for the marker being at the beginning of the
+ -- snippet. (If so, pos will point to the correct position.)
+ local pos = buffer:indicator_end(INDIC_CURRENTPLACEHOLDER, self.start_pos)
+ if pos == 1 then pos = self.start_pos end
+ return buffer:indicator_all_on_for(pos) &
+ 1 << INDIC_CURRENTPLACEHOLDER - 1 > 0 and pos + 1 or pos
+ end
+ return getmetatable(self)[k]
+end
+
+-- Inserts the current snapshot (based on `self.index`) of this snippet into
+-- the buffer and marks placeholders.
+function snippet:insert()
+ buffer:set_target_range(self.start_pos, self.end_pos)
+ buffer:replace_target(self.snapshots[self.index].text)
+ buffer.indicator_current = M.INDIC_PLACEHOLDER
+ for id, placeholder in pairs(self.snapshots[self.index].placeholders) do
+ buffer.indicator_value = id
+ buffer:indicator_fill_range(placeholder.position, placeholder.length)
+ end
+end
+
+-- Jumps to the next placeholder in this snippet and adds additional carets
+-- at mirrors.
+function snippet:next()
+ if buffer:auto_c_active() then buffer:auto_c_complete() end
+ -- Take a snapshot of the current state in order to restore it later if
+ -- necessary.
+ if self.index > 0 and self.start_pos < self.end_pos then
+ local text = buffer:text_range(self.start_pos, self.end_pos)
+ local placeholders = {}
+ for pos, ph in self:each_placeholder() do
+ -- Only the position of placeholders changes between snapshots; save it
+ -- and keep all other existing properties.
+ -- Note that nested placeholders will return the same placeholder id
+ -- twice: once before a nested placeholder, and again after. (e.g.
+ -- [foo[bar]baz] will will return the '[foo' and 'baz]' portions of the
+ -- same placeholder.) Only process the first occurrence.
+ if placeholders[ph.id] then goto continue end
+ placeholders[ph.id] = setmetatable({position = pos}, {
+ __index = self.snapshots[self.index - 1].placeholders[ph.id]
+ })
+ ::continue::
end
+ self.snapshots[self.index] = {text = text, placeholders = placeholders}
+ end
+ self.index = self.index < self.max_index and self.index + 1 or 0
+
+ -- Find the default placeholder, which may be the first mirror.
+ local ph = select(2, self:each_placeholder(self.index, 'default')()) or
+ select(2, self:each_placeholder(self.index, 'choice')()) or
+ select(2, self:each_placeholder(self.index, 'simple')()) or
+ self.index == 0 and {position = self.end_pos, length = 0}
+ if not ph then self:next() return end -- try next placeholder
+
+ -- Mark the position of the placeholder so transforms can identify it.
+ buffer.indicator_current = INDIC_CURRENTPLACEHOLDER
+ buffer:indicator_clear_range(self.placeholder_pos - 1, 1)
+ if ph.position > self.start_pos and self.index > 0 then
+ -- Place it directly behind the placeholder so it will be preserved.
+ buffer:indicator_fill_range(ph.position - 1, 1)
+ end
+
+ buffer:begin_undo_action()
+
+ -- Jump to the default placeholder and clear its marker.
+ buffer:set_sel(ph.position, ph.position + ph.length)
+ local e = buffer:indicator_end(M.INDIC_PLACEHOLDER, ph.position)
+ buffer.indicator_current = M.INDIC_PLACEHOLDER
+ buffer:indicator_clear_range(ph.position, e - ph.position)
+ if not ph.default then buffer:replace_sel('') end -- delete filler ' '
+ if ph.choice then
+ local sep = buffer.auto_c_separator
+ buffer.auto_c_separator = string.byte(',')
+ buffer.auto_c_order = buffer.ORDER_CUSTOM
+ buffer:auto_c_show(0, ph.choice)
+ buffer.auto_c_separator = sep -- restore
+ end
+
+ -- Add additional carets at mirrors and clear their markers.
+ local text = ph.default or ''
+ ::redo::
+ for pos in self:each_placeholder(self.index, 'simple') do
+ local e = buffer:indicator_end(M.INDIC_PLACEHOLDER, pos)
+ buffer:indicator_clear_range(pos, e - pos)
+ buffer:set_target_range(pos, pos + 1)
+ buffer:replace_target(text)
+ buffer:add_selection(pos, pos + #text)
+ goto redo -- indicator positions have changed
+ end
+ buffer.main_selection = 1
+
+ -- Update transforms.
+ self:update_transforms()
- buffer:begin_undo_action()
+ buffer:end_undo_action()
+
+ if self.index == 0 then self:finish() end
+end
- -- Jump to the default placeholder and clear its marker.
- buffer:set_sel(ph.position, ph.position + ph.length)
- local e = buffer:indicator_end(M.INDIC_PLACEHOLDER, ph.position)
+-- Jumps to the previous placeholder in this snippet and restores the state
+-- associated with that placeholder.
+function snippet:previous()
+ if self.index < 2 then self:finish(true) return end
+ self.index = self.index - 2
+ self:insert()
+ self:next()
+end
+
+-- Finishes or cancels this snippet depending on boolean *canceling*.
+-- The snippet cleans up after itself regardless.
+-- @param canceling Whether or not to cancel inserting this snippet. When
+-- `true`, the buffer is restored to its state prior to snippet expansion.
+function snippet:finish(canceling)
+ local s, e = self.start_pos, self.end_pos
+ if e ~= s then buffer:delete_range(e, 1) end -- clear initial padding space
+ if not canceling then
buffer.indicator_current = M.INDIC_PLACEHOLDER
- buffer:indicator_clear_range(ph.position, e - ph.position)
- if not ph.default then buffer:replace_sel('') end -- delete filler ' '
- if ph.choice then
- local sep = buffer.auto_c_separator
- buffer.auto_c_separator = string.byte(',')
- buffer.auto_c_order = buffer.ORDER_CUSTOM
- buffer:auto_c_show(0, ph.choice)
- buffer.auto_c_separator = sep -- restore
- end
+ buffer:indicator_clear_range(s, e - s)
+ else
+ buffer:set_sel(s, e)
+ buffer:replace_sel(self.trigger or self.original_sel_text)
+ end
+ stack[#stack] = nil
+end
- -- Add additional carets at mirrors and clear their markers.
- local text = ph.default or ''
- ::redo::
- for pos in self:each_placeholder(self.index, 'simple') do
- local e = buffer:indicator_end(M.INDIC_PLACEHOLDER, pos)
- buffer:indicator_clear_range(pos, e - pos)
- buffer:set_target_range(pos, pos + 1)
- buffer:replace_target(text)
- buffer:add_selection(pos, pos + #text)
- goto redo -- indicator positions have changed
- end
- buffer.main_selection = 1
-
- -- Update transforms.
- self:update_transforms()
-
- buffer:end_undo_action()
-
- if self.index == 0 then self:finish() end
- end,
-
- -- Jumps to the previous placeholder in this snippet and restores the state
- -- associated with that placeholder.
- previous = function(self)
- if self.index < 2 then self:finish(true) return end
- self.index = self.index - 2
- self:insert()
- self:next()
- end,
-
- -- Finishes or cancels this snippet depending on boolean *canceling*.
- -- The snippet cleans up after itself regardless.
- -- @param canceling Whether or not to cancel inserting this snippet. When
- -- `true`, the buffer is restored to its state prior to snippet expansion.
- finish = function(self, canceling)
- local s, e = self.start_pos, self.end_pos
- if e ~= s then buffer:delete_range(e, 1) end -- clear initial padding space
- if not canceling then
- buffer.indicator_current = M.INDIC_PLACEHOLDER
- buffer:indicator_clear_range(s, e - s)
- else
- buffer:set_sel(s, e)
- buffer:replace_sel(self.trigger or self.original_sel_text)
- end
- snippet_stack[#snippet_stack] = nil
- end,
-
- -- Returns a generator that returns each placeholder's position and state for
- -- all placeholders in this snippet.
- -- DO NOT modify the buffer while this generator is running. Doing so will
- -- affect the generator's state and cause errors. Re-run the generator each
- -- time a buffer edit is made (e.g. via `goto`).
- -- @param index Optional placeholder index to constrain results to.
- -- @param type Optional placeholder type to constrain results to.
- each_placeholder = function(self, index, type)
- local snapshot =
- self.snapshots[self.index > 0 and self.index - 1 or #self.snapshots]
- local i = self.start_pos
- local PLACEHOLDER_BIT = 1 << M.INDIC_PLACEHOLDER - 1
- return function()
- local s = buffer:indicator_end(M.INDIC_PLACEHOLDER, i)
- while s > 1 and s <= self.end_pos do
- if buffer:indicator_all_on_for(i) & PLACEHOLDER_BIT > 0 then
- -- This next indicator comes directly after the previous one; adjust
- -- start and end positions to compensate.
- s, i = buffer:indicator_start(M.INDIC_PLACEHOLDER, i), s
- else
- i = buffer:indicator_end(M.INDIC_PLACEHOLDER, s)
- end
- local id = buffer:indicator_value_at(M.INDIC_PLACEHOLDER, s)
- local ph = snapshot.placeholders[id]
- if ph and (not index or ph.index == index) and
- (not type or ph[type]) then
- return s, ph
- end
- s = buffer:indicator_end(M.INDIC_PLACEHOLDER, i)
+-- Returns a generator that returns each placeholder's position and state for
+-- all placeholders in this snippet.
+-- DO NOT modify the buffer while this generator is running. Doing so will
+-- affect the generator's state and cause errors. Re-run the generator each
+-- time a buffer edit is made (e.g. via `goto`).
+-- @param index Optional placeholder index to constrain results to.
+-- @param type Optional placeholder type to constrain results to.
+function snippet:each_placeholder(index, type)
+ local snapshot =
+ self.snapshots[self.index > 0 and self.index - 1 or #self.snapshots]
+ local i = self.start_pos
+ return function()
+ local s = buffer:indicator_end(M.INDIC_PLACEHOLDER, i)
+ while s > 1 and s <= self.end_pos do
+ if buffer:indicator_all_on_for(i) & 1 << M.INDIC_PLACEHOLDER - 1 > 0 then
+ -- This next indicator comes directly after the previous one; adjust
+ -- start and end positions to compensate.
+ s, i = buffer:indicator_start(M.INDIC_PLACEHOLDER, i), s
+ else
+ i = buffer:indicator_end(M.INDIC_PLACEHOLDER, s)
end
+ local id = buffer:indicator_value_at(M.INDIC_PLACEHOLDER, s)
+ local ph = snapshot.placeholders[id]
+ if ph and (not index or ph.index == index) and (not type or ph[type]) then
+ return s, ph
+ end
+ s = buffer:indicator_end(M.INDIC_PLACEHOLDER, i)
end
- end,
-
- -- Returns the result of executing Lua or Shell code, in placeholder table
- -- *placeholder*, in the context of this snippet.
- -- @param placeholder The placeholder that contains code to execute.
- execute_code = function(self, placeholder)
- local s, e = self.placeholder_pos, buffer.selection_end
- if s > e then s, e = e, s end
- local text = self.index and buffer:text_range(s, e) or '' -- %<...>, %[...]
- if placeholder.lua_code then
- local env = setmetatable(
- {text = text, selected_text = self.original_sel_text}, {__index = _G})
- local f, result = load('return ' .. placeholder.lua_code, nil, 't', env)
- return f and select(2, pcall(f)) or result or ''
- elseif placeholder.sh_code then
- -- Note: cannot use spawn since $env variables are not expanded.
- local command = placeholder.sh_code:gsub('%f[%%]%%%f[^%%]', text)
- local p = io.popen(command)
- local result = p:read('a'):sub(1, -2) -- chop '\n'
- p:close()
- return result
- end
- end,
+ end
+end
- -- Updates transforms in place based on the current placeholder's text.
- update_transforms = function(self)
- buffer.indicator_current = M.INDIC_PLACEHOLDER
- local processed = {}
- ::redo::
- for s, ph in self:each_placeholder(nil, 'transform') do
- if ph.index == self.index and not processed[ph] then
- -- Execute the code and replace any existing transform text.
- local result = self:execute_code(ph)
- if result == '' then result = ' ' end -- fill for display
- local id = buffer:indicator_value_at(M.INDIC_PLACEHOLDER, s)
- buffer:set_target_range(s, buffer:indicator_end(M.INDIC_PLACEHOLDER, s))
- buffer:replace_target(result)
- buffer.indicator_value = id
- buffer:indicator_fill_range(s, #result) -- re-mark
- processed[ph] = true
- goto redo -- indicator positions have changed
- elseif ph.index < self.index or self.index == 0 then
- -- Clear obsolete transforms, deleting filler text if necessary.
- local e = buffer:indicator_end(M.INDIC_PLACEHOLDER, s)
- buffer:indicator_clear_range(s, e - s)
- if buffer:text_range(s, e) == ' ' then
- buffer:set_target_range(s, e)
- buffer:replace_target('') -- delete filler ' '
- goto redo
- end
+-- Returns the result of executing Lua or Shell code, in placeholder table
+-- *placeholder*, in the context of this snippet.
+-- @param placeholder The placeholder that contains code to execute.
+function snippet:execute_code(placeholder)
+ local s, e = self.placeholder_pos, buffer.selection_end
+ if s > e then s, e = e, s end
+ local text = self.index and buffer:text_range(s, e) or '' -- %<...>, %[...]
+ if placeholder.lua_code then
+ local env = setmetatable(
+ {text = text, selected_text = self.original_sel_text}, {__index = _G})
+ local f, result = load('return ' .. placeholder.lua_code, nil, 't', env)
+ return f and select(2, pcall(f)) or result or ''
+ elseif placeholder.sh_code then
+ -- Note: cannot use spawn since $env variables are not expanded.
+ local command = placeholder.sh_code:gsub('%f[%%]%%%f[^%%]', text)
+ local p = io.popen(command)
+ local result = p:read('a'):sub(1, -2) -- chop '\n'
+ p:close()
+ return result
+ end
+end
+
+-- Updates transforms in place based on the current placeholder's text.
+function snippet:update_transforms()
+ buffer.indicator_current = M.INDIC_PLACEHOLDER
+ local processed = {}
+ ::redo::
+ for s, ph in self:each_placeholder(nil, 'transform') do
+ if ph.index == self.index and not processed[ph] then
+ -- Execute the code and replace any existing transform text.
+ local result = self:execute_code(ph)
+ if result == '' then result = ' ' end -- fill for display
+ local id = buffer:indicator_value_at(M.INDIC_PLACEHOLDER, s)
+ buffer:set_target_range(s, buffer:indicator_end(M.INDIC_PLACEHOLDER, s))
+ buffer:replace_target(result)
+ buffer.indicator_value = id
+ buffer:indicator_fill_range(s, #result) -- re-mark
+ processed[ph] = true
+ goto redo -- indicator positions have changed
+ elseif ph.index < self.index or self.index == 0 then
+ -- Clear obsolete transforms, deleting filler text if necessary.
+ local e = buffer:indicator_end(M.INDIC_PLACEHOLDER, s)
+ buffer:indicator_clear_range(s, e - s)
+ if buffer:text_range(s, e) == ' ' then
+ buffer:delete_range(s, e - s) -- delete filler ' '
+ goto redo
end
- -- TODO: insert initial transform for ph.index > self.index
end
- end,
-}
+ -- TODO: insert initial transform for ph.index > self.index
+ end
+end
---
-- Inserts snippet text *text* or the snippet assigned to the trigger word
@@ -593,13 +584,12 @@ snippet_mt = {
function M.insert(text)
local trigger
if not assert_type(text, 'string/nil', 1) then
- trigger, text = find_snippet(trigger)
+ trigger, text = find_snippet()
if type(text) == 'function' 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
+ if text then snippet.new(text, trigger) end
+ if #stack > 0 then stack[#stack]:next() else return false end
end
---
@@ -609,8 +599,7 @@ end
-- @return `false` if no snippet is active; `nil` otherwise.
-- @name previous
function M.previous()
- if #snippet_stack == 0 then return false end
- snippet_stack[#snippet_stack]:previous()
+ if #stack > 0 then stack[#stack]:previous() else return false end
end
---
@@ -619,8 +608,7 @@ end
-- @return `false` if no snippet is active; `nil` otherwise.
-- @name cancel_current
function M.cancel_current()
- if #snippet_stack == 0 then return false end
- snippet_stack[#snippet_stack]:finish(true)
+ if #stack > 0 then stack[#stack]:finish(true) else return false end
end
---
@@ -645,12 +633,11 @@ end
-- Update snippet transforms when text is added or deleted.
events.connect(events.UPDATE_UI, function(updated)
- if #snippet_stack > 0 then
- if updated & buffer.UPDATE_CONTENT > 0 then
- snippet_stack[#snippet_stack]:update_transforms()
- end
- if #keys.keychain == 0 then ui.statusbar_text = _L['Snippet active'] end
+ if #stack == 0 then return end
+ if updated & buffer.UPDATE_CONTENT > 0 then
+ stack[#stack]:update_transforms()
end
+ if #keys.keychain == 0 then ui.statusbar_text = _L['Snippet active'] end
end)
events.connect(events.VIEW_NEW, function()
@@ -662,8 +649,7 @@ end)
-- completions.
-- @see textadept.editing.autocomplete
textadept.editing.autocompleters.snippet = function()
- local list = {}
- local trigger, snippets = find_snippet(true)
+ local list, trigger, snippets = {}, find_snippet(true)
local sep = string.char(buffer.auto_c_type_separator)
local xpm = textadept.editing.XPM_IMAGES.NAMESPACE
for name in pairs(snippets) do list[#list + 1] = name .. sep .. xpm end
diff --git a/test/test.lua b/test/test.lua
index 1505f2c2..047f6601 100644
--- a/test/test.lua
+++ b/test/test.lua
@@ -786,6 +786,23 @@ function test_ui_print()
ui.silent_print = silent_print
end
+function test_ui_print_to_other_view()
+ local silent_print = ui.silent_print
+
+ ui.silent_print = false
+ view:split()
+ ui.goto_view(-1)
+ assert_equal(_VIEWS[view], 1)
+ ui.print('foo') -- should print to other view, not split again
+ assert_equal(#_VIEWS, 2)
+ assert_equal(_VIEWS[view], 2)
+ buffer:close()
+ ui.goto_view(-1)
+ view:unsplit()
+
+ ui.silent_print = silent_print
+end
+
function test_ui_dialogs_colorselect_interactive()
local color = ui.dialogs.colorselect{title = 'Blue', color = 0xFF0000}
assert_equal(color, 0xFF0000)
@@ -1729,6 +1746,12 @@ function test_editing_transpose_chars()
buffer:char_left()
textadept.editing.transpose_chars()
assert_equal(buffer:get_text(), '⌘⇧⌥')
+ buffer:clear_all()
+ textadept.editing.transpose_chars()
+ assert_equal(buffer:get_text(), '')
+ buffer:add_text('a')
+ textadept.editing.transpose_chars()
+ assert_equal(buffer:get_text(), 'a')
-- TODO: multiple selection?
buffer:close(true)
end