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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
|
-- Copyright 2007-2008 Mitchell mitchell<att>caladbolg.net. See LICENSE.
---
-- Manages key commands in Textadept.
-- Default key commands should be defined in a separate file and loaded after
-- all modules.
-- There are several option variables used:
-- SCOPES_ENABLED: Flag indicating whether scopes/styles can be used for key
-- commands.
-- CTRL: The string representing the Control key.
-- SHIFT: The string representing the Shift key.
-- ALT: The string representing the Alt key.
-- ADD: The string representing used to join together a sequence of Control,
-- Shift, or Alt modifier keys.
--
module('textadept.keys', package.seeall)
-- Usage:
-- Keys are defined in the global table 'keys'. Keys in that table are key
-- sequences, and values are tables of Lua functions and arguments to execute.
-- The exceptions are language names, style names, and keychains (discussed
-- later). Language names have table values of either key commands or style keys
-- with table values of key commands. See /lexers/lexer.lua for some default
-- style names. Each lexer's 'add_style' function adds additional styles, the
-- string argument being the style's name. For example:
-- keys = {
-- ['ctrl+f'] = { 'char_right', 'buffer' },
-- ['ctrl+b'] = { 'char_left', 'buffer' },
-- lua = {
-- ['ctrl+c'] = { 'add_text', 'buffer', '-- ' },
-- whitespace = { function() print('whitespace') end }
-- }
-- }
-- Style and lexer insensitive key commands should be placed in the lexer and
-- keys tables respectively.
--
-- When searching for a key command to execute in the keys table, key commands
-- in the current style have priority, then ones in the current lexer, and
-- finally the ones in the global table.
--
-- As mentioned, key commands are key-value pairs, the key being the key
-- sequence compiled from the CTRL, SHIFT, ALT, and ADD options (discussed
-- below) as well as the key pressed and the value being a table of a function
-- to call and its arguments. For the table, the first item can be either a Lua
-- function or a string (representing a function name). If it is a function, all
-- table items after it are used as arguments. If the first item is a string,
-- the next string is checked to be either 'buffer' or 'view' and the current
-- buffer or view is used as the table with the function name as a field,
-- indexing a function. The current buffer or view is then used as the first
-- argument to that function, with all items after the second following as
-- additional ones. Basically in Lua: {buffer|view}:{first_item}(...)
--
-- As noted previously, key sequences can be compiled differently via the CTRL,
-- SHIFT, ALT, and ADD options. The first three indicate the text for each
-- respective modifier key and ADD is the text inserted between modifiers.
--
-- Key commands can be chained like in Emacs. Instead of a key sequence having
-- a table of a function and its arguments, it has a table of key commands (much
-- like lexer or style specific key commands). My default, the 'escape' key
-- cancels the current keychain, but it can be redefined by setting the
-- 'clear_sequence' key in the global keys table. It cannot be chained however.
--
-- Keys that have values higher than 255 cannot be represented by a string, but
-- can have a string representation defined in the KEYSYMS table.
--
-- Keep in mind that all Lua functions used in key commands must be defined
-- BEFORE the key command references it. Therefore the module containing key
-- commands should be loaded LAST, after all other modules have been loaded.
-- options
local SCOPES_ENABLED = true
local ADD = ''
local CTRL = 'c'..ADD
local SHIFT = 's'..ADD
local ALT = 'a'..ADD
-- end options
---
-- [Local table] Lookup table for key values higher than 255.
-- If a key value given to 'keypress' is higher than 255, this table is used to
-- return a string representation of the key if it exists.
-- @class table
-- @name KEYSYMS
local KEYSYMS = { -- from <gdk/gdkkeysyms.h>
[65288] = '\b',
[65289] = '\t',
[65293] = '\n',
[65307] = 'esc',
[65535] = 'del',
[65360] = 'home',
[65361] = 'left',
[65362] = 'up',
[65363] = 'right',
[65364] = 'down',
[65365] = 'pup',
[65366] = 'pdown',
[65367] = 'end',
[65379] = 'ins',
[65470] = 'f1', [65471] = 'f2', [65472] = 'f3', [65473] = 'f4',
[65474] = 'f5', [65475] = 'f6', [65476] = 'f7', [65477] = 'f8',
[65478] = 'f9', [65479] = 'f10', [65480] = 'f11', [65481] = 'f12',
}
--- [Local table] The current key sequence.
-- @class table
-- @name keychain
local keychain = {}
-- local functions
local try_get_cmd1, try_get_cmd2, try_get_cmd3, try_get_cmd
---
-- Clears the current key sequence.
function clear_key_sequence() keychain = {} textadept.statusbar_text = '' end
---
-- [Local function] Handles Textadept keypresses.
-- It is called every time a key is pressed, and based on lexer and scope,
-- executes a command. The command is looked up in the global 'keys' key
-- command table.
-- @return whatever the executed command returns, true by default. A true
-- return value will tell Textadept not to handle the key afterwords.
local function keypress(code, shift, control, alt)
local buffer, textadept = buffer, textadept
local string, pcall = string, pcall
local keys = _G.keys
local key
--print(code, string.char(code))
if code < 256 then
key = string.char(code):lower()
if MAC and not shift and not control and not alt then
local ch = string.char(code)
-- work around native GTK-OSX's handling of Alt key
if ch:match('[^A-Za-z ]') and #keychain == 0 then
buffer:add_text(ch)
textadept.events.handle('char_added', ch)
return true
end
end
else
if not KEYSYMS[code] then return end
key = KEYSYMS[code]
end
control = control and CTRL or ''
shift = shift and SHIFT or ''
alt = alt and ALT or ''
local key_seq = string.format('%s%s%s%s', control, shift, alt, key)
if #keychain > 0 and key_seq == keys.clear_sequence then
clear_key_sequence()
return true
end
local lexer = buffer:get_lexer_language()
keychain[#keychain + 1] = key_seq
local ret, func, args
if SCOPES_ENABLED then
local style = buffer.style_at[buffer.current_pos]
local scope = buffer:get_style_name(style)
--print(key_seq, 'Lexer: '..lexer, 'Scope: '..scope)
ret, func, args = pcall(try_get_cmd1, keys, lexer, scope)
end
if not ret and func ~= -1 then
ret, func, args = pcall(try_get_cmd2, keys, lexer)
end
if not ret and func ~= -1 then
ret, func, args = pcall(try_get_cmd3, keys)
end
if ret then
clear_key_sequence()
if type(func) == 'function' then
local ret, retval = pcall( func, unpack(args) )
if ret then
if type(retval) == 'boolean' then return retval end
else textadept.events.error(retval) end -- error
end
return true
else
-- Clear key sequence because it's not part of a chain.
-- (try_get_cmd throws error number -1.)
if func ~= -1 then
local size = #keychain - 1
clear_key_sequence()
if size > 0 then -- previously in a chain
textadept.statusbar_text = 'Invalid Sequence'
return true
end
else return true end
end
end
textadept.events.add_handler('keypress', keypress, 1)
---
-- [Local function] Tries to get a key command based on the lexer and current
-- scope.
try_get_cmd1 = function(keys, lexer, scope)
return try_get_cmd( keys[lexer][scope] )
end
---
-- [Local function] Tries to get a key command based on the lexer.
try_get_cmd2 = function(keys, lexer)
return try_get_cmd( keys[lexer] )
end
---
-- [Local function] Tries to get a global key command.
try_get_cmd3 = function(keys)
return try_get_cmd(keys)
end
---
-- [Local function] Helper function that gets commands associated with the
-- current keychain from 'keys'.
-- If the current item in the keychain is part of a chain, throw an error value
-- of -1. This way, pcall will return false and -1, where the -1 can easily and
-- efficiently be checked rather than using a string error message.
try_get_cmd = function(active_table)
local str_seq = ''
for _, key_seq in ipairs(keychain) do
str_seq = str_seq..key_seq..' '
active_table = active_table[key_seq]
end
if #active_table == 0 and next(active_table) then
textadept.statusbar_text = 'Keychain: '..str_seq
error(-1, 0)
else
local func = active_table[1]
if type(func) == 'function' then
return func, { unpack(active_table, 2) }
elseif type(func) == 'string' then
local object = active_table[2]
if object == 'buffer' then
return buffer[func], { buffer, unpack(active_table, 3) }
elseif object == 'view' then
return view[func], { view, unpack(active_table, 3) }
end
else
error( 'Unknown command: '..tostring(func) )
end
end
end
|