aboutsummaryrefslogtreecommitdiffhomepage
path: root/modules
diff options
context:
space:
mode:
authorGravatar mitchell <70453897+orbitalquark@users.noreply.github.com>2020-10-20 15:29:03 -0400
committerGravatar mitchell <70453897+orbitalquark@users.noreply.github.com>2020-10-20 15:29:03 -0400
commit03c4016d07477781aa3adcc9edf340c0bec9c6c8 (patch)
treed3be089e9020807326a4e56562876ecb7bcf7892 /modules
parentb682fbd4a6e53185e2556686079532ad0e42be94 (diff)
Code cleanup.
Of note: * io.save_all_files() does not visit each buffer to save anymore. An unintended side-effect was checking for outside modification (but only if the file itself was modified), so outside changes will always be saved over now. * The menu clicked handler uses assert_type(), so the 'Unknown command' localization is no longer needed. * When printing to a new buffer type would split the view, use an existing split view when possible. * Prefer 'goto continue' construct in loops over nested 'if's. * Fixed clearing of ui.find.replace_entry_text on reset in the GUI version. * Fixed lack of statusbar updating when setting options like buffer EOL mode, indentation, and encoding. * Renamed internal new_snippet() to new() and put it in the snippet metatable.
Diffstat (limited to 'modules')
-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
17 files changed, 506 insertions, 567 deletions
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