aboutsummaryrefslogtreecommitdiffhomepage
path: root/modules/lua/init.lua
blob: 00508de33893ed05e3294c36df2dfad1fb22ad21 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
-- Copyright 2007-2021 Mitchell. See LICENSE.

local M = {}

--[[ This comment is for LuaDoc.
---
-- The lua module.
-- It provides utilities for editing Lua code.
-- @field autocomplete_snippets (boolean)
--   Whether or not to include snippets in autocompletion lists.
--   The default value is `false`.
module('_M.lua')]]

-- Autocompletion and documentation.

-- Returns a function that, when called from a Textadept Lua file, the Lua command entry, or
-- a special Lua buffer (e.g. a REPL), returns the given Textadept tags or API file for use in
-- autocompletion and documentation.
-- @param filename Textadept tags or api file to return.
local function ta_api(filename)
  local home = '^' .. _HOME:gsub('%p', '%%%0'):gsub('%%[/\\]', '[/\\]')
  local userhome = '^' .. _USERHOME:gsub('%p', '%%%0'):gsub('%%[/\\]', '[/\\]')
  return function()
    local ta_file = (buffer.filename or ''):find(home) or (buffer.filename or ''):find(userhome)
    return (ta_file or buffer == ui.command_entry or buffer._type) and filename
  end
end

---
-- List of "fake" ctags files (or functions that return such files) to use for autocompletion.
-- The kind 'm' is recognized as a module, 'f' as a function, 't' as a table and 'F' as a module
-- or table field.
-- The *modules/lua/tadoc.lua* script can generate *tags* and [*api*](#textadept.editing.api_files)
-- files for Lua modules via LuaDoc.
-- @class table
-- @name tags
M.tags = {
  _HOME .. '/modules/lua/tags', _USERHOME .. '/modules/lua/tags',
  ta_api(_HOME .. '/modules/lua/ta_tags')
}

---
-- Map of expression patterns to their types.
-- Used for type-hinting when showing autocompletions for variables. Expressions are expected
-- to match after the '=' sign of a statement.
-- @class table
-- @name expr_types
-- @usage _M.lua.expr_types['^spawn%b()%s*$'] = 'proc'
M.expr_types = {['^[\'"]'] = 'string', ['^io%.p?open%s*%b()%s*$'] = 'file'}

M.autocomplete_snippets = true

local XPM = textadept.editing.XPM_IMAGES
local xpms = {m = XPM.CLASS, f = XPM.METHOD, F = XPM.VARIABLE, t = XPM.TYPEDEF}

textadept.editing.autocompleters.lua = function()
  local list = {}
  -- Retrieve the symbol behind the caret.
  local line, pos = buffer:get_cur_line()
  local symbol, op, part = line:sub(1, pos - 1):match('([%w_%.]-)([%.:]?)([%w_]*)$')
  if symbol == '' and part == '' then return nil end -- nothing to complete
  if symbol == '' and M.autocomplete_snippets then
    local _, snippets = textadept.editing.autocompleters.snippet()
    for i = 1, #snippets do list[#list + 1] = snippets[i] end
  end
  symbol, part = symbol:gsub('^_G%.?', ''), part ~= '_G' and part or ''
  -- Attempt to identify string type and file type symbols.
  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 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
  local sep = string.char(buffer.auto_c_type_separator)
  for _, filename in ipairs(M.tags) do
    if type(filename) == 'function' then filename = filename() end
    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 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
  if #list == 1 and list[1]:find(name_patt .. '%?') then return nil end
  return #part, list
end

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'))

-- Snippets.

local snip = snippets.lua
snip.func = 'function %1(name)(%2(args))\n\t%0\nend'
snip['if'] = 'if %1 then\n\t%0\nend'
snip.eif = 'elseif %1 then\n\t'
snip['for'] = 'for %1(i) = %2(1), %3(10)%4(, %5(-1)) do\n\t%0\nend'
snip.forp = 'for %1(k), %2(v) in pairs(%3(t)) do\n\t%0\nend'
snip.fori = 'for %1(i), %2(v) in ipairs(%3(t)) do\n\t%0\nend'
snip['while'] = 'while %1 do\n\t%0\nend'
snip['repeat'] = 'repeat\n\t%0\nuntil %1'
snip['do'] = 'do\n\t%0\nend'

return M