aboutsummaryrefslogtreecommitdiff
path: root/modules/textadept/command_entry.lua
blob: c3dfd963001e30767b29523e3d6ab7f92c61021f (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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
-- Copyright 2007-2013 Mitchell mitchell.att.foicica.com. See LICENSE.
-- Abbreviated environment and commands from Jay Gould.

local M = gui.command_entry

--[[ This comment is for LuaDoc.
---
-- Textadept's Command Entry.
--
-- ## Modes
--
-- The command entry supports multiple [modes][] that have their own sets of key
-- bindings stored in a separate table in `_G.keys` under a mode prefix key.
-- Mode names are arbitrary, but cannot conflict with lexer names or key
-- sequence strings (e.g. `'lua'` or `'send'`) due to the method used for
-- looking up key bindings. An example mode is "lua_command" mode for executing
-- Lua commands:
--
--     local gui_ce = gui.command_entry
--     keys['ce'] = {gui_ce.enter_mode, 'lua_command'}
--     keys.lua_command = {
--       ['\t'] = gui_ce.complete_lua,
--       ['\n'] = {gui_ce.finish_mode, gui_ce.execute_lua}
--     }
--
-- In this case, `Ctrl+E` opens the command entry and enters "lua_command" key
-- mode where the `Tab` and `Enter` keys have special, defined functionality.
-- (By default, `Esc` is pre-defined to exit any command entry mode.) `Tab`
-- shows a list of Lua completions for the entry text and `Enter` exits
-- "lua_command" key mode and executes the entered code. All other keys are
-- handled normally by the command entry.
--
-- It is important to note that while in any command entry key mode, all editor
-- key bindings are ignored -- even if the editor, not the command entry, has
-- focus. You must first exit the key mode by pressing `Esc` (or `Enter` in the
-- case of the above "lua_command" key mode).
--
-- [modes]: keys.html#Modes
-- @field entry_text (string)
--   The text in the entry.
module('gui.command_entry')]]

---
-- Opens the command entry in key mode *mode*.
-- Key bindings will be looked up in `keys[mode]` instead of `keys`. The `Esc`
-- (`⎋` on Mac OSX | `Esc` in curses) key exits the current mode, closes the
-- command entry, and restores normal key lookup.
-- This function is useful for binding keys to enter a command entry mode.
-- @param mode The key mode to enter into, or `nil` to exit the current mode.
-- @usage keys['ce'] = {gui.command_entry.enter_mode, 'command_entry'}
-- @see _G.keys.MODE
-- @name enter_mode
function M.enter_mode(mode)
  keys.MODE = mode
  if mode then keys[mode]['esc'] = M.enter_mode end
  -- In curses, M.focus() does not return immediately, so the key sequence that
  -- called M.focus() is still on the keychain. Clear it.
  if CURSES then keys.clear_key_sequence() end
  M.focus()
end

---
-- Exits the current key mode, closes the command entry, and calls function *f*
-- (if given) with the command entry text as an argument.
-- This is useful for binding keys to exit a command entry mode and perform an
-- action with the entered text.
-- @param f Optional function to call. It should accept the command entry text
--   as an argument.
-- @usage keys['\n'] = {gui.command_entry.finish_mode, gui.print}
-- @name finish_mode
function M.finish_mode(f)
  M.enter_mode(nil)
  if f then f(M.entry_text) end
  if CURSES then return false end -- propagate to exit CDK entry on Enter
end

-- Environment for abbreviated commands.
-- @class table
-- @name env
local env = setmetatable({}, {
  __index = function(t, k)
    local f = buffer[k]
    if f and type(f) == 'function' then
      f = function(...) buffer[k](buffer, ...) end
    elseif f == nil then
      f = view[k] or gui[k] or _G[k]
    end
    return f
  end,
  __newindex = function(t, k, v)
    for _, t2 in ipairs{buffer, view, gui} do
      if t2[k] ~= nil then t2[k] = v return end
    end
    rawset(t, k, v)
  end,
})

---
-- Executes string *code* as Lua code.
-- Code is subject to an "abbreviated" environment where the `buffer`, `view`,
-- and `gui` tables are also considered as globals.
-- @param code The Lua code to execute.
-- @name execute_lua
function M.execute_lua(code)
  local f, err = load(code, nil, 'bt', env)
  if err then error(err) end
  f()
  events.emit(events.UPDATE_UI)
end
args.register('-e', '--execute', 1, M.execute_lua, 'Execute Lua code')

---
-- Shows a set of Lua code completions for string *code* or `entry_text`.
-- Completions are subject to an "abbreviated" environment where the `buffer`,
-- `view`, and `gui` tables are also considered as globals.
-- @param code The Lua code to complete. The default value is the value of
--   `entry_text`.
-- @name complete_lua
function M.complete_lua(code)
  local substring = (code or M.entry_text):match('[%w_.:]+$') or ''
  local path, op, prefix = substring:match('^([%w_.:]-)([.:]?)([%w_]*)$')
  local f, err = load('return ('..path..')', nil, 'bt', env)
  local ok, tbl = pcall(f)
  local cmpls = {}
  prefix = '^'..prefix
  if not ok then -- shorthand notation
    for _, t in ipairs{buffer, view, gui, _G} do
      for k in pairs(t) do
        if type(k) == 'string' and k:find(prefix) then cmpls[#cmpls + 1] = k end
      end
    end
    for f in pairs(_SCINTILLA.functions) do
      if f:find(prefix) then cmpls[#cmpls + 1] = f end
    end
    for p in pairs(_SCINTILLA.properties) do
      if p:find(prefix) then cmpls[#cmpls + 1] = p end
    end
  else
    if type(tbl) ~= 'table' then return end
    for k in pairs(tbl) do
      if type(k) == 'string' and k:find(prefix) then cmpls[#cmpls + 1] = k end
    end
    if path == 'buffer' and op == ':' then
      for f in pairs(_SCINTILLA.functions) do
        if f:find(prefix) then cmpls[#cmpls + 1] = f end
      end
    elseif path == 'buffer' and op == '.' then
      for p in pairs(_SCINTILLA.properties) do
        if p:find(prefix) then cmpls[#cmpls + 1] = p end
      end
    end
  end
  table.sort(cmpls)
  M.show_completions(cmpls)
  return true
end

local events = events
-- Pass command entry keys to the default keypress handler.
-- Since the command entry is designed to be modal, command entry key bindings
-- should stay separate from editor key bindings.
events.connect(events.COMMAND_ENTRY_KEYPRESS, function(...)
  if keys.MODE then return events.emit(events.KEYPRESS, ...) end
end)

--[[ The function below is a Lua C function.

---
-- Focuses the command entry.
-- @class function
-- @name focus
local focus

---
-- Shows the completion list *completions* for the current word prefix.
-- Word prefix characters are alphanumerics and underscores. On selection, the
-- word prefix is replaced with the completion.
-- @param completions The table of completions to show. Non-string values are
--   ignored.
-- @class function
-- @name show_completions
local show_completions
]]