diff options
-rw-r--r-- | core/init.lua | 1 | ||||
-rw-r--r-- | core/keys.lua | 277 | ||||
-rw-r--r-- | modules/textadept/editing.lua | 5 | ||||
-rw-r--r-- | modules/textadept/find.lua | 7 | ||||
-rw-r--r-- | modules/textadept/keys.lua | 285 |
5 files changed, 287 insertions, 288 deletions
diff --git a/core/init.lua b/core/init.lua index 439593e7..8242176f 100644 --- a/core/init.lua +++ b/core/init.lua @@ -11,6 +11,7 @@ require 'locale' require 'events' require 'file_io' require 'gui' +require 'keys' _LEXERPATH = _USERHOME..'/lexers/?.lua;'.._HOME..'/lexers' diff --git a/core/keys.lua b/core/keys.lua new file mode 100644 index 00000000..2b233007 --- /dev/null +++ b/core/keys.lua @@ -0,0 +1,277 @@ +-- Copyright 2007-2010 Mitchell mitchell<att>caladbolg.net. See LICENSE. + +local L = _G.locale.localize + +--- +-- Manages key commands in Textadept. +module('keys', package.seeall) + +-- Markdown: +-- ## Overview +-- +-- Key commands are defined in the global table `keys`. Each key-value pair in +-- `keys` consists of either: +-- +-- * A string representing a key command and an associated action table. +-- * A string language name and its associated `keys`-like table. +-- * A string style name and its associated `keys`-like table. +-- * A string representing a key command and its associated `keys`-like table. +-- (This is a keychain sequence.) +-- +-- A key command string is built from a combination of the `CTRL`, `SHIFT`, +-- `ALT`, and `ADD` constants as well as the pressed key itself. The value of +-- `ADD` is inserted between each of `CTRL`, `SHIFT`, `ALT`, and the key. +-- For example: +-- +-- -- keys.lua: +-- CTRL = 'Ctrl' +-- SHIFT = 'Shift' +-- ALT = 'Alt' +-- ADD = '+' +-- -- pressing control, shift, alt and 'a' yields: 'Ctrl+Shift+Alt+A' +-- +-- For key values less than 255, Lua's [`string.char()`][string_char] is used to +-- determine the key's string representation. Otherwise, the +-- [`KEYSYMS`][keysyms] lookup table is used. +-- +-- [string_char]: http://www.lua.org/manual/5.1/manual.html#pdf-string.char +-- [keysyms]: ../modules/_m.textadept.keys.html#KEYSYMS +-- +-- An action table is a table consisting of either: +-- +-- * A Lua function followed by a list of arguments to pass to that function. +-- * A string representing a [buffer][buffer] or [view][view] function followed +-- by its respective `'buffer'` or `'view'` string and then any arguments to +-- pass to the resulting function. +-- +-- `buffer.`_`function`_ by itself cannot be used because at the time of +-- evaluation, `buffer.`_`function`_ would apply only to the current +-- buffer, not for all buffers. By using this string reference system, the +-- correct `buffer.`_`function`_ will be evaluated every time. The same +-- applies to `view`. +-- +-- [buffer]: ../modules/buffer.html +-- [view]: ../modules/view.html +-- +-- Language names are the names of the lexer files in `lexers/` such as `cpp` +-- and `lua`. Style names are different lexer styles, most of which are in +-- `lexers/lexer.lua`; examples are `whitespace`, `comment`, and `string`. +-- +-- Key commands can be chained like in Emacs using keychain sequences. By +-- default, the `Esc` key cancels the current keychain, but it can be redefined +-- by setting the `keys.clear_sequence` field. Naturally, the clear sequence +-- cannot be chained. +-- +-- ## Settings +-- +-- * `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 (the Apple key on Mac OSX). +-- * `ADD`: The string representing used to join together a sequence of Control, +-- Shift, or Alt modifier keys. +-- +-- ## Key Command Precedence +-- +-- When searching for a key command to execute in the `keys` table, key commands +-- in the current style have priority, followed by the ones in the current +-- lexer, and finally the ones in the global table. +-- +-- ## Example +-- +-- keys = { +-- ['ctrl+f'] = { 'char_right', 'buffer' }, +-- ['ctrl+b'] = { 'char_left', 'buffer' }, +-- lua = { +-- ['ctrl+c'] = { 'add_text', 'buffer', '-- ' }, +-- comment = { +-- ['ctrl+f'] = { function() print('comment') end } +-- } +-- } +-- } +-- +-- The first two key commands are global and call `buffer:char_right()` and +-- `buffer:char_left()` respectively. The last two commands apply only in the +-- Lua lexer with the very last one only being available in Lua's `comment` +-- style. If `ctrl+f` is pressed when the current style is `comment` in the +-- `lua` lexer, the global key command with the same shortcut is overridden and +-- `comment` is printed to standard out. +-- +-- ## Problems +-- +-- All Lua functions must be defined BEFORE they are reference in key commands. +-- Therefore, any module containing key commands should be loaded after all +-- other modules, whose functions are being referenced, have been loaded. +-- +-- ## Events +-- +-- The following is a list of all key events generated in +-- `event_name(arguments)` format: +-- +-- * **keypress** (code, shift, control, alt)<br /> +-- Called when a key is pressed. +-- - code: the key code (according to `<gdk/gdkkeysyms.h>`). +-- - shift: flag indicating whether or not the Shift key is pressed. +-- - control: flag indicating whether or not the Control key is pressed. +-- - alt: flag indicating whether or not the Alt/Apple key is pressed. +-- <br /> +-- Note: The Alt-Option key in Mac OSX is not available. + +-- settings +local SCOPES_ENABLED = true +local ADD = '' +local CTRL = 'c'..ADD +local SHIFT = 's'..ADD +local ALT = 'a'..ADD +-- end settings + +-- Optimize for speed. +local string = _G.string +local string_char = string.char +local string_format = string.format +local pcall = _G.pcall +local next = _G.next +local type = _G.type +local unpack = _G.unpack +local OSX = _G.OSX + +--- +-- 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 +KEYSYMS = { -- from <gdk/gdkkeysyms.h> + [65056] = '\t', -- backtab; will be 'shift'ed + [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', +} + +-- The current key sequence. +local keychain = {} + +-- Clears the current key sequence. +local function clear_key_sequence() + keychain = {} + gui.statusbar_text = '' +end + +-- Return codes for run_key_command(). +local INVALID = -1 +local PROPAGATE = 0 +local CHAIN = 1 +local HALT = 2 + +-- Runs a key command associated with the current keychain. +-- @param lexer Optional lexer name for lexer-specific commands. +-- @param scope Optional scope name for scope-specific commands. +-- @return INVALID, PROPAGATE, CHAIN, or HALT. +local function run_key_command(lexer, scope) + local key = keys + if lexer and type(key) == 'table' and key[lexer] then key = key[lexer] end + if scope and type(key) == 'table' and key[scope] then key = key[scope] end + if type(key) ~= 'table' then return INVALID end + + for i = 1, #keychain do + key = key[keychain[i]] + if type(key) ~= 'table' then return INVALID end + end + if #key == 0 and next(key) then + gui.statusbar_text = L('Keychain:')..' '..table.concat(keychain, ' ') + return CHAIN + end + + local f, args = key[1], { unpack(key, 2) } + if type(key[1]) == 'string' then + if key[2] == 'buffer' then + f, args = buffer[f], { buffer, unpack(key, 3) } + elseif key[2] == 'view' then + f, args = view[f], { view, unpack(key, 3) } + end + end + + if type(f) ~= 'function' then error(L('Unknown command:')..tostring(f)) end + return f(unpack(args)) == false and PROPAGATE or HALT +end + +-- Key command order for lexer and scope args passed to run_key_command(). +local order = { + { true, true }, -- lexer and scope-specific commands + { true, false }, -- lexer-specific commands + { false, false } -- general commands +} + +-- 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 = buffer + local key + --print(code, string.char(code)) + if code < 256 then + key = string_char(code) + shift = false -- for printable characters, key is upper case + else + key = KEYSYMS[code] + if not key then return end + 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 + keychain[#keychain + 1] = key_seq + + local lexer, scope = buffer:get_lexer(), nil + if SCOPES_ENABLED then + scope = buffer:get_style_name(buffer.style_at[buffer.current_pos]) + end + local success, status + for i = SCOPES_ENABLED and 1 or 2, #order do + status = run_key_command(order[i][1] and lexer, order[i][2] and scope) + if status > 0 then -- CHAIN or HALT + if status == HALT then + -- Clear the key sequence, but keep any status messages from the key + -- command itself. + keychain = {} + if gui.statusbar_text == L('Invalid sequence') or + gui.statusbar_text:find('^'..L('Keychain:')) then + gui.statusbar_text = '' + end + end + return true + end + success = success or status ~= -1 + end + local size = #keychain - 1 + clear_key_sequence() + if not success and size > 0 then -- INVALID keychain sequence + gui.statusbar_text = L('Invalid sequence') + return true + end + -- PROPAGATE otherwise. +end +events.connect('keypress', keypress, 1) diff --git a/modules/textadept/editing.lua b/modules/textadept/editing.lua index fd618c9f..6a0f7cca 100644 --- a/modules/textadept/editing.lua +++ b/modules/textadept/editing.lua @@ -2,6 +2,7 @@ local L = _G.locale.localize local events = _G.events +local K = keys.KEYSYMS --- -- Editing commands for the textadept module. @@ -85,7 +86,7 @@ events.connect('char_added', events.connect('keypress', function(code, shift, control, alt) -- removes matched chars on backspace - if not AUTOPAIR or code ~= 0xff08 or buffer.selections ~= 1 then return end + if not AUTOPAIR or K[code] ~= '\b' or buffer.selections ~= 1 then return end local buffer = buffer local current_pos = buffer.current_pos local c = buffer.char_at[current_pos - 1] @@ -441,7 +442,7 @@ local function clear_highlighted_words() buffer:indicator_clear_range(0, buffer.length) end events.connect('keypress', - function(c) if c == 0xff1b then clear_highlighted_words() end end) -- Esc + function(c) if K[c] == 'esc' then clear_highlighted_words() end end) --- -- Highlights all occurances of the word under the caret and adds markers to the diff --git a/modules/textadept/find.lua b/modules/textadept/find.lua index 3798ead9..5eadddd2 100644 --- a/modules/textadept/find.lua +++ b/modules/textadept/find.lua @@ -168,12 +168,13 @@ end events.connect('command_entry_keypress', function(code) + local K = _G.keys.KEYSYMS if find.incremental then - if code == 0xff1b then -- escape + if K[code] == 'esc' then find.incremental = nil - elseif code < 256 or code == 0xff08 then -- character or backspace + elseif code < 256 or K[code] == '\b' then local text = gui.command_entry.entry_text - if code == 0xff08 then + if K[code] == '\b' then find_incremental(text:sub(1, -2)) else find_incremental(text..string.char(code)) diff --git a/modules/textadept/keys.lua b/modules/textadept/keys.lua index 490b31c8..a1960a19 100644 --- a/modules/textadept/keys.lua +++ b/modules/textadept/keys.lua @@ -1,134 +1,13 @@ -- Copyright 2007-2010 Mitchell mitchell<att>caladbolg.net. See LICENSE. local L = _G.locale.localize -local events = _G.events --- --- Manages and defines key commands in Textadept. +-- Defines key commands for Textadept. -- This set of key commands is pretty standard among other text editors. module('_m.textadept.keys', package.seeall) --- Markdown: --- ## Overview --- --- Key commands are defined in the global table `keys`. Each key-value pair in --- `keys` consists of either: --- --- * A string representing a key command and an associated action table. --- * A string language name and its associated `keys`-like table. --- * A string style name and its associated `keys`-like table. --- * A string representing a key command and its associated `keys`-like table. --- (This is a keychain sequence.) --- --- A key command string is built from a combination of the `CTRL`, `SHIFT`, --- `ALT`, and `ADD` constants as well as the pressed key itself. The value of --- `ADD` is inserted between each of `CTRL`, `SHIFT`, `ALT`, and the key. --- For example: --- --- -- keys.lua: --- CTRL = 'Ctrl' --- SHIFT = 'Shift' --- ALT = 'Alt' --- ADD = '+' --- -- pressing control, shift, alt and 'a' yields: 'Ctrl+Shift+Alt+A' --- --- For key values less than 255, Lua's [`string.char()`][string_char] is used to --- determine the key's string representation. Otherwise, the --- [`KEYSYMS`][keysyms] lookup table is used. --- --- [string_char]: http://www.lua.org/manual/5.1/manual.html#pdf-string.char --- [keysyms]: ../modules/_m.textadept.keys.html#KEYSYMS --- --- An action table is a table consisting of either: --- --- * A Lua function followed by a list of arguments to pass to that function. --- * A string representing a [buffer][buffer] or [view][view] function followed --- by its respective `'buffer'` or `'view'` string and then any arguments to --- pass to the resulting function. --- --- `buffer.`_`function`_ by itself cannot be used because at the time of --- evaluation, `buffer.`_`function`_ would apply only to the current --- buffer, not for all buffers. By using this string reference system, the --- correct `buffer.`_`function`_ will be evaluated every time. The same --- applies to `view`. --- --- [buffer]: ../modules/buffer.html --- [view]: ../modules/view.html --- --- Language names are the names of the lexer files in `lexers/` such as `cpp` --- and `lua`. Style names are different lexer styles, most of which are in --- `lexers/lexer.lua`; examples are `whitespace`, `comment`, and `string`. --- --- Key commands can be chained like in Emacs using keychain sequences. By --- default, the `Esc` key cancels the current keychain, but it can be redefined --- by setting the `keys.clear_sequence` field. Naturally, the clear sequence --- cannot be chained. --- --- ## Settings --- --- * `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 (the Apple key on Mac OSX). --- * `ADD`: The string representing used to join together a sequence of Control, --- Shift, or Alt modifier keys. --- --- ## Key Command Precedence --- --- When searching for a key command to execute in the `keys` table, key commands --- in the current style have priority, followed by the ones in the current --- lexer, and finally the ones in the global table. --- --- ## Example --- --- keys = { --- ['ctrl+f'] = { 'char_right', 'buffer' }, --- ['ctrl+b'] = { 'char_left', 'buffer' }, --- lua = { --- ['ctrl+c'] = { 'add_text', 'buffer', '-- ' }, --- comment = { --- ['ctrl+f'] = { function() print('comment') end } --- } --- } --- } --- --- The first two key commands are global and call `buffer:char_right()` and --- `buffer:char_left()` respectively. The last two commands apply only in the --- Lua lexer with the very last one only being available in Lua's `comment` --- style. If `ctrl+f` is pressed when the current style is `comment` in the --- `lua` lexer, the global key command with the same shortcut is overridden and --- `comment` is printed to standard out. --- --- ## Problems --- --- All Lua functions must be defined BEFORE they are reference in key commands. --- Therefore, any module containing key commands should be loaded after all --- other modules, whose functions are being referenced, have been loaded. --- --- ## Events --- --- The following is a list of all key events generated in --- `event_name(arguments)` format: --- --- * **keypress** (code, shift, control, alt)<br /> --- Called when a key is pressed. --- - code: the key code (according to `<gdk/gdkkeysyms.h>`). --- - shift: flag indicating whether or not the Shift key is pressed. --- - control: flag indicating whether or not the Control key is pressed. --- - alt: flag indicating whether or not the Alt/Apple key is pressed. --- <br /> --- Note: The Alt-Option key in Mac OSX is not available. - --- settings -local SCOPES_ENABLED = true -local ADD = '' -local CTRL = 'c'..ADD -local SHIFT = 's'..ADD -local ALT = 'a'..ADD --- end settings - -local keys = _M +local keys = _G.keys local b, v = 'buffer', 'view' local gui = gui @@ -473,163 +352,3 @@ else } keys.cy = { 'paste', b } end - ---- --- Provides access to key commands from _G. --- @class table --- @name _G.keys -_G.keys = _M - --------------------------------------------------------------------------------- -------------------------- Do not edit below this line. ------------------------- --------------------------------------------------------------------------------- - --- Optimize for speed. -local string = _G.string -local string_char = string.char -local string_format = string.format -local pcall = _G.pcall -local next = _G.next -local type = _G.type -local unpack = _G.unpack -local OSX = _G.OSX - ---- --- 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 -KEYSYMS = { -- from <gdk/gdkkeysyms.h> - [65056] = '\t', -- backtab; will be 'shift'ed - [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', -} - --- The current key sequence. -local keychain = {} - --- Clears the current key sequence. -local function clear_key_sequence() - keychain = {} - gui.statusbar_text = '' -end - --- Return codes for run_key_command(). -local INVALID = -1 -local PROPAGATE = 0 -local CHAIN = 1 -local HALT = 2 - --- Runs a key command associated with the current keychain. --- @param lexer Optional lexer name for lexer-specific commands. --- @param scope Optional scope name for scope-specific commands. --- @return INVALID, PROPAGATE, CHAIN, or HALT. -local function run_key_command(lexer, scope) - local key = keys - if lexer and type(key) == 'table' and key[lexer] then key = key[lexer] end - if scope and type(key) == 'table' and key[scope] then key = key[scope] end - if type(key) ~= 'table' then return INVALID end - - for i = 1, #keychain do - key = key[keychain[i]] - if type(key) ~= 'table' then return INVALID end - end - if #key == 0 and next(key) then - gui.statusbar_text = L('Keychain:')..' '..table.concat(keychain, ' ') - return CHAIN - end - - local f, args = key[1], { unpack(key, 2) } - if type(key[1]) == 'string' then - if key[2] == 'buffer' then - f, args = buffer[f], { buffer, unpack(key, 3) } - elseif key[2] == 'view' then - f, args = view[f], { view, unpack(key, 3) } - end - end - - if type(f) ~= 'function' then error(L('Unknown command:')..tostring(f)) end - return f(unpack(args)) == false and PROPAGATE or HALT -end - --- Key command order for lexer and scope args passed to run_key_command(). -local order = { - { true, true }, -- lexer and scope-specific commands - { true, false }, -- lexer-specific commands - { false, false } -- general commands -} - --- 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 = buffer - local key - --print(code, string.char(code)) - if code < 256 then - key = string_char(code) - shift = false -- for printable characters, key is upper case - else - key = KEYSYMS[code] - if not key then return end - 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 - keychain[#keychain + 1] = key_seq - - local lexer, scope = buffer:get_lexer(), nil - if SCOPES_ENABLED then - scope = buffer:get_style_name(buffer.style_at[buffer.current_pos]) - end - local success, status - for i = SCOPES_ENABLED and 1 or 2, #order do - status = run_key_command(order[i][1] and lexer, order[i][2] and scope) - if status > 0 then -- CHAIN or HALT - if status == HALT then - -- Clear the key sequence, but keep any status messages from the key - -- command itself. - keychain = {} - if gui.statusbar_text == L('Invalid sequence') or - gui.statusbar_text:find('^'..L('Keychain:')) then - gui.statusbar_text = '' - end - end - return true - end - success = success or status ~= -1 - end - local size = #keychain - 1 - clear_key_sequence() - if not success and size > 0 then -- INVALID keychain sequence - gui.statusbar_text = L('Invalid sequence') - return true - end - -- PROPAGATE otherwise. -end -events.connect('keypress', keypress, 1) |