aboutsummaryrefslogtreecommitdiff
path: root/modules/textadept/command_entry.lua
blob: fb8726cd57c2ed98804b0f19d0cc6a5b475076e1 (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
184
185
186
187
188
189
190
191
192
193
-- Copyright 2007-2016 Mitchell mitchell.att.foicica.com. See LICENSE.
-- Abbreviated environment and commands from Jay Gould.

local M = ui.command_entry

--[[ This comment is for LuaDoc.
---
-- Textadept's Command Entry.
--
-- ## Modes
--
-- The command entry supports multiple [modes](#keys.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 Textadept
-- uses for looking up key bindings. An example mode is "lua_command" mode for
-- executing Lua commands:
--
--     local function complete_lua() ... end
--     local function run_lua() ... end
--     keys['ce'] = {ui.command_entry.enter_mode, 'lua_command'}
--     keys.lua_command = {
--       ['\t'] = complete_lua,
--       ['\n'] = {ui.command_entry.finish_mode, run_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, Textadept pre-defines `Esc` 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. The command entry
-- handles all other editing and movement keys normally.
-- @field height (number)
--   The height in pixels of the command entry.
module('ui.command_entry')]]

---
-- A metatable with typical platform-specific key bindings for text entries.
-- This metatable may be used to add basic editing keys to command entry modes.
-- It is automatically added to command entry modes unless a metatable was
-- previously set.
-- @usage setmetatable(keys.my_mode, ui.command_entry.editing_keys)
-- @class table
-- @name editing_keys
M.editing_keys = {__index = {
  [not OSX and 'cx' or 'mx'] = {buffer.cut, M},
  [not OSX and 'cc' or 'mc'] = {buffer.copy, M},
  [not OSX and 'cv' or 'mv'] = {buffer.paste, M},
  [not OSX and not CURSES and 'ca' or 'ma'] = {buffer.select_all, M},
  [not OSX and 'cz' or 'mz'] = {buffer.undo, M},
  [not OSX and 'cZ' or 'mZ'] = {buffer.redo, M}, cy = {buffer.redo, M},
  -- Movement keys.
  [(OSX or CURSES) and 'cf' or '\0'] = {buffer.char_right, M},
  [(OSX or CURSES) and 'cb' or '\0'] = {buffer.char_left, M},
  [(OSX or CURSES) and 'ca' or '\0'] = {buffer.vc_home, M},
  [(OSX or CURSES) and 'ce' or '\0'] = {buffer.line_end, M},
  [(OSX or CURSES) and 'cd' or '\0'] = {buffer.clear, M}
}}

---
-- Opens the command entry in key mode *mode*, highlighting text with lexer name
-- *lexer*, and displaying *height* number of lines at a time.
-- Key bindings will be looked up in `keys[mode]` instead of `keys`. The `Esc`
-- 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.
-- @param lexer Optional string lexer name to use for command entry text. The
--   default value is `'text'`.
-- @param height Optional number of lines to display in the command entry. The
--   default value is `1`.
-- @usage keys['ce'] = {ui.command_entry.enter_mode, 'command_entry'}
-- @see _G.keys.MODE
-- @name enter_mode
function M.enter_mode(mode, lexer, height)
  if M:auto_c_active() then M:auto_c_cancel() end -- may happen in curses
  keys.MODE = mode
  if mode then
    local mkeys = keys[mode]
    if not mkeys['esc'] then mkeys['esc'] = M.enter_mode end
    if not getmetatable(mkeys) then setmetatable(mkeys, M.editing_keys) end
  end
  M:select_all()
  M.focus()
  M:set_lexer(lexer or 'text')
  M.height = M:text_height(0) * (height or 1)
end

---
-- Exits the current key mode, closes the command entry, and calls function *f*
-- (if given) with the command entry's 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'] = {ui.command_entry.finish_mode, ui.print}
-- @name finish_mode
function M.finish_mode(f)
  if M:auto_c_active() then return false end -- allow Enter to autocomplete
  M.enter_mode(nil)
  if f then f(M:get_text()) end
end

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

-- Executes string *code* as Lua code that is subject to an "abbreviated"
-- environment.
-- In this environment, the contents of the `buffer`, `view`, and `ui` tables
-- are also considered as global functions and fields.
-- Prints the results of '=' expressions like in the Lua prompt.
-- @param code The Lua code to execute.
local function run_lua(code)
  if code:find('^=') then code = 'return '..code:sub(2) end
  local result = assert(load(code, nil, 'bt', env))()
  if result ~= nil or code:find('^return ') then ui.print(result) end
  events.emit(events.UPDATE_UI)
end
args.register('-e', '--execute', 1, run_lua, 'Execute Lua code')

-- Shows a set of Lua code completions for the entry's text, subject to an
-- "abbreviated" environment where the `buffer`, `view`, and `ui` tables are
-- also considered as globals.
local function complete_lua()
  local line, pos = M:get_cur_line()
  local symbol, op, part = line:sub(1, pos):match('([%w_.]-)([%.:]?)([%w_]*)$')
  local ok, result = pcall((load('return ('..symbol..')', nil, 'bt', env)))
  if (not ok or type(result) ~= 'table') and symbol ~= '' then return end
  local cmpls = {}
  part = '^'..part
  if not ok or symbol == 'buffer' then
    local pool
    if not ok then
      -- Consider `buffer`, `view`, `ui` as globals too.
      pool = {buffer, view, ui, _G, _SCINTILLA.functions, _SCINTILLA.properties}
    else
      pool = op == ':' and {_SCINTILLA.functions} or
                           {_SCINTILLA.properties, _SCINTILLA.constants}
    end
    for i = 1, #pool do
      for k in pairs(pool[i]) do
        if type(k) == 'string' and k:find(part) then cmpls[#cmpls + 1] = k end
      end
    end
  end
  if ok then
    for k, v in pairs(result) do
      if type(k) == 'string' and k:find(part) and
         (op == '.' or type(v) == 'function') then
        cmpls[#cmpls + 1] = k
      end
    end
  end
  table.sort(cmpls)
  M:auto_c_show(#part - 1, table.concat(cmpls, ' '))
end

-- Define key mode for entering Lua commands.
keys.lua_command = {['\t'] = complete_lua, ['\n'] = {M.finish_mode, run_lua}}

-- Configure the command entry's default properties.
events.connect(events.INITIALIZED, function()
  M.h_scroll_bar, M.v_scroll_bar = false, false
  M.margin_width_n[0], M.margin_width_n[1], M.margin_width_n[2] = 0, 0, 0
  M.call_tip_position = true
end)

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

---
-- Opens the command entry.
-- @class function
-- @name focus
local focus
]]