aboutsummaryrefslogtreecommitdiff
path: root/modules/textadept/lsnippets.lua
diff options
context:
space:
mode:
authormitchell <70453897+667e-11@users.noreply.github.com>2010-06-10 23:42:30 -0400
committermitchell <70453897+667e-11@users.noreply.github.com>2010-06-10 23:42:30 -0400
commit2ddb3c692bebf8011207f8c961ea5c89fb8beabb (patch)
treec295fa70bd72596435ffbf8bedb6885c153b0abd /modules/textadept/lsnippets.lua
parentfbe7fcf467bd7f947da72cc7a098728d4a6bf364 (diff)
downloadtextadept-2ddb3c692bebf8011207f8c961ea5c89fb8beabb.tar.gz
textadept-2ddb3c692bebf8011207f8c961ea5c89fb8beabb.zip
Renamed _m.textadept.lsnippets to _m.textadept.snippets.
Diffstat (limited to 'modules/textadept/lsnippets.lua')
-rw-r--r--modules/textadept/lsnippets.lua480
1 files changed, 0 insertions, 480 deletions
diff --git a/modules/textadept/lsnippets.lua b/modules/textadept/lsnippets.lua
deleted file mode 100644
index 19f20e28..00000000
--- a/modules/textadept/lsnippets.lua
+++ /dev/null
@@ -1,480 +0,0 @@
--- Copyright 2007-2010 Mitchell mitchell<att>caladbolg.net. See LICENSE.
-
-local textadept = _G.textadept
-local locale = _G.locale
-
----
--- Provides Lua-style snippets for Textadept.
-module('_m.textadept.lsnippets', package.seeall)
-
--- Markdown:
--- ## Settings
---
--- * `MARK_SNIPPET`: The unique integer mark used to identify the line that
--- marks the end of a snippet.
--- * `MARK_SNIPPET_COLOR`: The [Scintilla color][scintilla_color] used for the
--- line that marks the end of the snippet.
---
--- [scintilla_color]: http://scintilla.org/ScintillaDoc.html#colour
---
--- ## Overview
---
--- Snippets are basically pieces of text inserted into a document, but can
--- execute code, contain placeholders you can enter dynamic text for, and
--- perform transformations on that text. This is much more powerful than
--- standard text templating.
---
--- Snippets are defined in the global table `snippets`. Each key-value pair in
--- `snippets` consist of either:
---
--- * A string snippet trigger word and its expanded text.
--- * A string language name and its associated `snippets`-like table.
--- * A string style name and its associated `snippets`-like table.
---
--- Language names are the names of the lexer files in `lexers/` such as `cpp`
--- and `lua`. Style names are different lexer styles, most of which are in
--- `lexers/lexer.lua`; examples are `whitespace`, `comment`, and `string`.
---
--- ## Snippet Precedence
---
--- When searching for a snippet to expand in the `snippets` table, snippets in
--- the current style have priority, followed by the ones in the current lexer,
--- and finally the ones in the global table.
---
--- ## Snippet Syntax
---
--- A snippet to insert may contain any of the following:
---
--- #### Plain Text
---
--- Any plain text characters may be used with the exception of `%` and &#96;.
--- These are special characters and must be "escaped" by prefixing one with a
--- `%`. As an example, `%%` inserts a single `%` in the snippet.
---
--- #### Lua and Shell Code
---
--- %(lua_code)
--- `shell_code`
---
--- The code is executed the moment the snippet is inserted.
---
--- For Lua code, the global Lua state is available as well as a `selected_text`
--- variable (containing the current selection in the buffer) for convenience.
--- Only the return value of the code execution is inserted, not standard out.
--- Therefore any `print()` statements are meaningless.
---
--- Shell code is run via Lua's [`io.popen()`][io_popen].
---
--- [io_popen]: http://www.lua.org/manual/5.1/manual.html#pdf-io.popen
---
--- #### Tab Stops and Mirrors
---
--- %num
---
--- These are visited in numeric order (1, 2, 3, etc.) with %0 being the final
--- position of the caret, or the end of the snippet if %0 is not specified. If
--- there is a placeholder (described below) with the specified `num`, its text
--- is mirrored here.
---
--- #### Placeholders
---
--- %num(text)
---
--- These are also visited in numeric order, but have precedence over tab stops,
--- and insert the specified `text` at the current position upon entry. `text`
--- can contain Lua code executed at run-time:
---
--- %num(#(lua_code))
---
--- The global Lua state is available as well as a `selected_text` variable
--- (containing the current selection in the buffer) for convenience.
---
--- `#`'s will have to be escaped with `%` for plain text. Any mis-matched `)`'s
--- must also be escaped, but balanced `()`'s need not be.
---
--- #### Transformations
---
--- %num(pattern|replacement)
---
--- These act like mirrors, but transform the text that would be inserted using
--- a given [Lua pattern][lua_pattern] and replacement. Like in placeholders,
--- `replacement` can contain Lua code executed at run-time as well as the
--- standard Lua capture sequences: `%n` where 1 <= `n` <= 9.
---
--- [lua_pattern]: http://www.lua.org/manual/5.1/manual.html#5.4.1
---
--- Any `|`'s after the first one do not need to be escaped.
---
--- ## Example
---
--- snippets = {
--- file = '%(buffer.filename)',
--- lua = {
--- f = 'function %1(name)(%2(args))\n %0\nend',
--- string = { [string-specific snippets here] }
--- }
--- }
---
--- The first snippet is global and runs the Lua code to determine the current
--- buffer's filename and inserts it. The other snippets apply only in the `lua`
--- lexer. Any snippets in the `string` table are available only when the current
--- style is `string` in the `lua` lexer.
-
--- settings
-MARK_SNIPPET = 4
-MARK_SNIPPET_COLOR = 0x4D9999
--- end settings
-
----
--- Global container that holds all snippet definitions.
--- @class table
--- @name _G.snippets
-_G.snippets = {}
-
-_G.snippets.file = "%(buffer.filename)"
-_G.snippets.path = "%((buffer.filename or ''):match('^.+/'))"
-_G.snippets.tab = "%%%1(1)(%2(default))"
-_G.snippets.key = "['%1'] = { %2(func)%3(, %4(arg)) }"
-
--- The current snippet.
-local snippet = {}
-
--- The stack of currently running snippets.
-local snippet_stack = {}
-
--- Replaces escaped characters with their octal equivalents in a given string.
--- @param s The string to handle escapes in.
--- @return string with escapes handled.
-local function handle_escapes(s)
- return s:gsub('%%([%%`%)|#])',
- function(char) return ("\\%03d"):format(char:byte()) end)
-end
-
--- Replaces octal characters with their escaped equivalents in a given string.
--- @param s The string to unhandle escapes in.
--- @return string with escapes unhandled.
-local function unhandle_escapes(s)
- local char = string.char
- return s:gsub('\\(%d%d%d)', function(byte) return '%'..char(byte) end)
-end
-
--- Replaces escaped characters with the actual characters in a given string.
--- This is used when escape sequences are no longer needed.
--- @param s The string to unescape escapes in.
--- @return string with escapes unescaped.
-local function unescape(s) return s:gsub('%%([%%`%)|#])', '%1') end
-
--- Gets the start position, end position, and text of the currently running
--- snippet.
--- @return start pos, end pos, and snippet text.
-local function snippet_info()
- local buffer = buffer
- local s = snippet.start_pos
- local e =
- buffer:position_from_line(
- buffer:marker_line_from_handle(snippet.end_marker)) - 1
- if e >= s then return s, e, buffer:text_range(s, e) end
-end
-
--- Runs the given Lua code.
--- @param code The Lua code to run.
--- @return string result from the code run.
-local function run_lua_code(code)
- code = unhandle_escapes(code)
- local env =
- setmetatable({ selected_text = buffer:get_sel_text() }, { __index = _G })
- local _, val = pcall(setfenv(loadstring('return '..code), env))
- return val or ''
-end
-
--- If previously at a placeholder or tab stop, attempts to mirror and/or
--- transform the entered text at all appropriate mirrors before moving on to
--- the next placeholder or tab stop.
--- @return false if no snippet was expanded; nil otherwise
-local function next_tab_stop()
- if not snippet.index then return false end -- no snippet active
- local buffer = buffer
- local s_start, s_end, s_text = snippet_info()
- if not s_text then
- cancel_current()
- return
- end
-
- local index = snippet.index
- snippet.snapshots[index] = s_text
- if index > 0 then
- buffer:begin_undo_action()
- local caret = math.max(buffer.anchor, buffer.current_pos)
- local ph_text = buffer:text_range(snippet.ph_pos, caret)
-
- -- Transform mirror.
- s_text =
- s_text:gsub('%%'..index..'(%b())',
- function(mirror)
- local pattern, replacement = mirror:match('^%(([^|]+)|(.+)%)$')
- if not pattern and not replacement then return ph_text end
- return ph_text:gsub(unhandle_escapes(pattern),
- function(...)
- local arg = {...}
- local repl = replacement:gsub('%%(%d+)',
- function(i) return arg[tonumber(i)] or '' end)
- return repl:gsub('#(%b())', run_lua_code)
- end, 1)
- end)
-
- -- Regular mirror.
- s_text = s_text:gsub('()%%'..index,
- function(pos)
- for mirror, e in s_text:gmatch('%%%d+(%b())()') do
- local s = mirror:find('|')
- if s and pos > s and pos < e then return nil end -- inside transform
- end
- return ph_text
- end)
-
- buffer:set_sel(s_start, s_end)
- buffer:replace_sel(s_text)
- s_start, s_end = snippet_info()
- buffer:end_undo_action()
- end
-
- buffer:begin_undo_action()
- index = index + 1
- if index <= snippet.max_index then
- -- Find the next tab stop.
- local s, e, next_item
- repeat -- ignore replacement mirrors
- s, e, next_item = s_text:find('%%'..index..'(%b())', e)
- until not s or next_item and not next_item:find('|')
- if next_item then -- placeholder
- buffer.target_start, buffer.target_end = s_start, buffer.length
- buffer.search_flags = 0
- buffer:search_in_target('%'..index..next_item)
- next_item = next_item:gsub('#(%b())', run_lua_code)
- next_item = unhandle_escapes(next_item:sub(2, -2))
- buffer:replace_target(next_item)
- buffer:set_sel(buffer.target_start, buffer.target_start + #next_item)
- snippet.ph_pos = buffer.target_start
- else
- repeat -- ignore placeholders
- local found = true
- s, e = (s_text..' '):find('%%'..index..'[^(]', e)
- if not s then
- snippet.index = index + 1
- next_tab_stop()
- return
- end
- for p_s, p_e in s_text:gmatch('%%%d+()%b()()') do
- if s > p_s and s < p_e then
- found = false
- break
- end
- end
- until found
- buffer:set_sel(s_start + s - 1, s_start + e - 1)
- buffer:replace_sel('') -- replace_target() doesn't place caret
- snippet.ph_pos = s_start + s - 1
- end
- -- Place additional carets at mirrors.
- local _, _, text = snippet_info()
- text = text:gsub('(%%%d+%b())',
- function(mirror)
- -- Lua code in replacement mirrors may contain '%' sequences; do not
- -- treat as mirrors
- if mirror:find('|') then return string.rep('_', #mirror) end
- end)
- for s, e in text:gmatch('()%%'..index..'()[^(]') do
- buffer:add_selection(s_start + s - 1, s_start + e - 1)
- end
- buffer.main_selection = 0 -- original placeholder/mirror
- -- Done.
- snippet.index = index
- else
- -- Finished. Find '%0' and place the caret there.
- s_text = unescape(unhandle_escapes(s_text))
- buffer:set_sel(s_start, s_end)
- buffer:replace_sel(s_text)
- s_start, s_end = snippet_info()
- if s_end then
- buffer:goto_pos(s_end + 1)
- buffer:delete_back()
- end
- local s, e = s_text:find('%%0')
- if s and e then
- buffer:set_sel(s_start + s - 1, s_start + e)
- buffer:replace_sel('')
- end
- buffer:marker_delete_handle(snippet.end_marker)
- snippet = #snippet_stack > 0 and table.remove(snippet_stack) or {}
- end
- buffer:end_undo_action()
-end
-
----
--- Begins expansion of a snippet.
--- The text inserted has escape sequences handled.
--- @param s_text Optional snippet to expand. If none is specified, the snippet
--- is determined from the trigger word (left of the caret), lexer, and style.
--- @return false if no snippet was expanded; true otherwise.
-function insert(s_text)
- local buffer = buffer
- local anchor, caret = buffer.anchor, buffer.current_pos
- local lexer, style, start, s_name
- if not s_text then
- lexer = buffer:get_lexer_language()
- style = buffer:get_style_name(buffer.style_at[caret])
- buffer:word_left_extend()
- start = buffer.current_pos
- s_name = buffer:get_sel_text()
- end
- if s_name then
- local function try_get_snippet(...)
- local table = _G.snippets
- for _, idx in ipairs{...} do table = table[idx] end
- return type(table) == 'string' and table or error()
- end
- local ret
- ret, s_text = pcall(try_get_snippet, lexer, style, s_name)
- if not ret then ret, s_text = pcall(try_get_snippet, lexer, s_name) end
- if not ret then ret, s_text = pcall(try_get_snippet, s_name) end
- if not ret then buffer:set_sel(anchor, caret) end -- restore caret
- end
-
- if s_text then
- buffer:begin_undo_action()
- s_text = handle_escapes(s_text)
-
- -- Take into account tab settings.
- if not buffer.use_tabs then
- s_text = s_text:gsub('\t', string.rep(' ', buffer.tab_width))
- end
-
- -- Execute Lua and shell code.
- s_text = s_text:gsub('%%(%b())', run_lua_code)
- s_text =
- s_text:gsub('`([^`]+)`',
- function(code)
- local p = io.popen(code)
- local out = p:read('*all'):sub(1, -2)
- p:close()
- return out
- end)
-
- -- Initialize the new snippet. If one is running, push it onto the stack.
- if snippet.index then snippet_stack[#snippet_stack + 1] = snippet end
- snippet = {}
- snippet.snapshots = {}
- snippet.start_pos = start or caret
- snippet.prev_sel_text = buffer:get_sel_text()
- snippet.index, snippet.max_index = 0, 0
- for i in s_text:gsub('(%%%d+)%b()', '%1'):gmatch('%%(%d+)') do
- -- placeholders may contain Lua code that has %n sequences that mess up
- -- this calculation; the above gsub accounts for this
- i = tonumber(i)
- if i > snippet.max_index then snippet.max_index = i end
- end
-
- -- Insert the snippet and set a mark defining the end of it.
- buffer:replace_sel(s_text)
- buffer:add_text('\n')
- local line = buffer:line_from_position(buffer.current_pos)
- snippet.end_marker = buffer:marker_add(line, MARK_SNIPPET)
- buffer:marker_set_back(MARK_SNIPPET, MARK_SNIPPET_COLOR)
-
- -- Indent all lines inserted.
- buffer.current_pos = snippet.start_pos
- local count = 0
- for _ in s_text:gmatch('\n') do count = count + 1 end
- if count > 0 then
- local ref_line = buffer:line_from_position(start)
- local isize, ibase = buffer.indent, buffer.line_indentation[ref_line]
- local inum = ibase / isize -- number of indents needed to match
- for i = 1, count do
- local linei = buffer.line_indentation[ref_line + i]
- buffer.line_indentation[ref_line + i] = linei + isize * inum
- end
- end
- buffer:end_undo_action()
- end
-
- return next_tab_stop() ~= false
-end
-
----
--- Goes back to the previous placeholder or tab stop, reverting changes made to
--- subsequent ones.
--- @return false if no snippet is active; nil otherwise
-function prev()
- if not snippet.index then return false end -- no snippet active
- local buffer = buffer
- local index = snippet.index
- if index > 1 then
- local s_start, s_end = snippet_info()
- local s_text = snippet.snapshots[index - 2]
- buffer:set_sel(s_start, s_end)
- buffer:replace_sel(s_text)
- snippet.index = index - 2
- next_tab_stop()
- else
- cancel_current()
- end
-end
-
----
--- Cancels the active snippet, reverting to the state before its activation,
--- and restores the previous running snippet (if any).
-function cancel_current()
- if not snippet.index then return end
- local buffer = buffer
- local s_start, s_end = snippet_info()
- buffer:begin_undo_action()
- if s_start and s_end then
- buffer:set_sel(s_start, s_end)
- buffer:replace_sel('')
- s_start, s_end = snippet_info()
- buffer:goto_pos(s_end + 1)
- buffer:delete_back()
- end
- if snippet.prev_sel_text then buffer:add_text(snippet.prev_sel_text) end
- buffer:end_undo_action()
- buffer:marker_delete_handle(snippet.end_marker)
- snippet = #snippet_stack > 0 and table.remove(snippet_stack) or {}
-end
-
----
--- Lists available snippets in an autocompletion list.
--- Global snippets and snippets in the current lexer and style are used.
-function list()
- local buffer = buffer
- local list = {}
- local function add_snippets(snippets)
- for s_name in pairs(snippets) do list[#list + 1] = s_name end
- end
- local snippets = _G.snippets
- add_snippets(snippets)
- local lexer = buffer:get_lexer_language()
- local style = buffer:get_style_name(buffer.style_at[buffer.current_pos])
- if snippets[lexer] and type(snippets[lexer]) == 'table' then
- add_snippets(snippets[lexer])
- if snippets[lexer][style] then add_snippets(snippets[lexer][style]) end
- end
- table.sort(list)
- local caret = buffer.current_pos
- buffer:auto_c_show(caret - buffer:word_start_position(caret, true),
- table.concat(list, string.char(buffer.auto_c_separator)))
-end
-
----
--- Shows the style at the current caret position in a call tip.
-function show_style()
- local buffer = buffer
- local lexer = buffer:get_lexer_language()
- local style_num = buffer.style_at[buffer.current_pos]
- local style = buffer:get_style_name(style_num)
- local text =
- string.format(locale.M_TEXTADEPT_SNIPPETS_SHOW_STYLE, lexer, style,
- style_num)
- buffer:call_tip_show(buffer.current_pos, text)
-end
-
-textadept.user_dofile('snippets.lua') -- load user snippets