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
|
-- Copyright 2007 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('_m.textadept.keys', package.seeall)
-- options
local SCOPES_ENABLED = true
local CTRL, SHIFT, ALT, ADD = 'c', 's', 'a', ''
-- end options
---
-- [Local table] Lookup table for key values higher than 255.
-- If a key value given to OnKey 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
---
-- Handles Textadept keypresses.
-- It is called every time a key is pressed and determines which commands to
-- execute or which new key in a chain to enter based on the current key
-- sequence, lexer, and scope.
-- @return keypress returns what the commands it executes return. If nothing is
-- returned, keypress returns true by default. A true return value will tell
-- Textadept not to handle the key afterwords.
function keypress(code, shift, control, alt)
local buffer, textadept = buffer, textadept
local keys = _G.keys
local key_seq = ''
if control then key_seq = key_seq..CTRL..ADD end
if shift then key_seq = key_seq..SHIFT..ADD end
if alt then key_seq = key_seq..ALT..ADD end
--print(code, string.char(code))
if code < 256 then
key_seq = key_seq..string.char(code):lower()
else
if not KEYSYMS[code] then return end
key_seq = key_seq..KEYSYMS[code]
end
if key_seq == keys.clear_sequence and #keychain > 0 then
clear_key_sequence()
return true
end
local lexer = buffer:get_lexer_language()
local style = buffer.style_at[buffer.current_pos]
local scope = buffer:get_style_name(style)
--print(key_seq, 'Lexer: '..lexer, 'Scope: '..scope)
keychain[#keychain + 1] = key_seq
local ret, func, args
if SCOPES_ENABLED then
ret, func, args = pcall(try_get_cmd1, keys, key_seq, lexer, scope)
end
if not ret and func ~= -1 then
ret, func, args = pcall(try_get_cmd2, keys, key_seq, lexer)
end
if not ret and func ~= -1 then
ret, func, args = pcall(try_get_cmd3, keys, key_seq)
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.handlers.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.handlers.add_handler_function('keypress', keypress, 1)
-- Note the following functions are called inside pcall so error handling or
-- checking if keys exist etc. is not necessary.
---
-- [Local function] Tries to get a key command based on the lexer and current
-- scope.
try_get_cmd1 = function(keys, key_seq, 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, key_seq, lexer)
return try_get_cmd( keys[lexer] )
end
---
-- [Local function] Tries to get a global key command.
try_get_cmd3 = function(keys, key_seq)
return try_get_cmd(keys)
end
---
-- [Local function] Helper function that gets commands associated with the
-- current keychain.
-- 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
|