aboutsummaryrefslogtreecommitdiff
path: root/core/ext
diff options
context:
space:
mode:
authormitchell <70453897+667e-11@users.noreply.github.com>2007-10-07 19:31:20 -0400
committermitchell <70453897+667e-11@users.noreply.github.com>2007-10-07 19:31:20 -0400
commitb7380f66ab0ba1815a4da0adfa0c9d03be582df8 (patch)
treefecb406d68c15b20237d77161dfcedc4b3f3e8fa /core/ext
parentaf47e2fb39f959bb9cdcf74bd834d83687e1ec3f (diff)
downloadtextadept-b7380f66ab0ba1815a4da0adfa0c9d03be582df8.tar.gz
textadept-b7380f66ab0ba1815a4da0adfa0c9d03be582df8.zip
Moved key command manager to core.
Diffstat (limited to 'core/ext')
-rw-r--r--core/ext/key_commands.lua218
-rw-r--r--core/ext/keys.lua230
2 files changed, 448 insertions, 0 deletions
diff --git a/core/ext/key_commands.lua b/core/ext/key_commands.lua
new file mode 100644
index 00000000..d8c99a24
--- /dev/null
+++ b/core/ext/key_commands.lua
@@ -0,0 +1,218 @@
+-- Copyright 2007 Mitchell mitchell<att>caladbolg.net. See LICENSE.
+
+---
+-- Defines the key commands used by the Textadept key command manager.
+-- For non-ascii keys, see textadept.keys for string aliases.
+module('textadept.key_commands', package.seeall)
+
+--[[
+ C: G Q
+ A: A C G J K L O Q W X Z
+ CS: C D G J L Q R S T U W
+ SA: A C D E G H I J K L M O Q R S T W X Z
+ CA: A C G H J K L O Q S T V W X Y Z
+ CSA: C D G H J K L O Q R S T U W X Z
+]]--
+
+---
+-- Global container that holds all key commands.
+-- @class table
+-- @name keys
+_G.keys = {}
+local keys = keys
+
+keys.clear_sequence = 'esc'
+
+local b, v = 'buffer', 'view'
+local t = textadept
+
+keys.ct = {} -- Textadept command chain
+keys.ct.v = {} -- View chain
+
+-- Standard commands. New, open, save, etc.
+keys.ct.n = { t.new_buffer }
+keys.cr = { t.io.open }
+keys.car = { 'reload', b }
+keys.co = { 'save', b }
+keys.cso = { 'save_as', b }
+keys.cx = { 'close', b }
+keys.csx = { t.io.close_all }
+keys.cz = { 'undo', b }
+keys.csz = { 'redo', b }
+keys.cs = { t.find.focus } -- find/replace
+
+-- Recent files.
+local RECENT_FILES = 1
+t.events.add_handler('user_list_selection',
+ function(type, text) if type == RECENT_FILES then t.io.open(text) end end)
+keys.ar = { function()
+ local buffer = buffer
+ local list = ''
+ local sep = buffer.auto_c_separator
+ buffer.auto_c_separator = ('|'):byte()
+ for _, filename in ipairs(t.io.recent_files) do
+ list = filename..'|'..list
+ end
+ buffer:user_list_show( RECENT_FILES, list:sub(1, -2) )
+ buffer.auto_c_separator = sep
+end }
+
+-- Buffer/view commands.
+keys.an = { 'goto_buffer', v, 1, false }
+keys.ap = { 'goto_buffer', v, -1, false }
+keys.ct.s = { 'split', v, false } -- horizontal
+keys.ct.ss = { 'split', v } -- vertical
+keys.can = { t.goto_view, 1, false }
+keys.cap = { t.goto_view, -1, false }
+keys.san = { function() view.size = view.size + 10 end }
+keys.sap = { function() view.size = view.size - 10 end }
+keys.ct.x = { function() view:unsplit() return true end }
+keys.ct.sx = { function() while view:unsplit() do end end }
+
+-- Movement/selection commands
+keys.cf = { 'char_right', b }
+keys.csf = { 'char_right_extend', b }
+keys.af = { 'word_right', b }
+keys.saf = { 'word_right_extend', b }
+keys.cb = { 'char_left', b }
+keys.csb = { 'char_left_extend', b }
+keys.ab = { 'word_left', b }
+keys.sab = { 'word_left_extend', b }
+keys.cn = { 'line_down', b }
+keys.csn = { 'line_down_extend', b }
+keys.cp = { 'line_up', b }
+keys.csp = { 'line_up_extend', b }
+keys.ca = { 'vc_home', b }
+keys.csa = { 'home_extend', b }
+keys.ce = { 'line_end', b }
+keys.cse = { 'line_end_extend', b }
+keys.cv = { 'page_down', b }
+keys.csv = { 'page_down_extend', b }
+keys.av = { 'para_down', b }
+keys.sav = { 'para_down_extend', b }
+keys.cy = { 'page_up', b }
+keys.csy = { 'page_up_extend', b }
+keys.ay = { 'para_up', b }
+keys.say = { 'para_up_extend', b }
+keys.ch = { 'delete_back', b }
+keys.ah = { 'del_word_left', b }
+keys.cd = { 'clear', b }
+keys.ad = { 'del_word_right', b }
+keys.csaf = { 'char_right_rect_extend', b }
+keys.csab = { 'char_left_rect_extend', b }
+keys.csan = { 'line_down_rect_extend', b }
+keys.csap = { 'line_up_rect_extend', b }
+keys.csaa = { 'vc_home_rect_extend', b }
+keys.csae = { 'line_end_rect_extend', b }
+keys.csav = { 'page_down_rect_extend', b }
+keys.csay = { 'page_up_rect_extend', b }
+
+-- Snippets commands.
+local m_snippets = _m.textadept.lsnippets
+keys.ci = { m_snippets.insert }
+keys.csi = { m_snippets.prev }
+keys.cai = { m_snippets.cancel_current }
+keys.casi = { m_snippets.list }
+keys.ai = { m_snippets.show_style }
+
+-- Editing commands.
+local m_editing = _m.textadept.editing
+keys.cm = { m_editing.match_brace }
+keys.csm = { m_editing.match_brace, 'select' }
+keys['c '] = { m_editing.autocomplete_word, '%w_' }
+keys['a '] = { m_editing.autocomplete_word_from_dict,
+ '/usr/share/dict/cracklib-small' }
+keys.cl = { m_editing.goto_line }
+keys.ck = { m_editing.smart_cutcopy, }
+keys.csk = { m_editing.smart_cutcopy, 'copy' }
+keys.cu = { m_editing.smart_paste, }
+keys.au = { m_editing.smart_paste, 'cycle' }
+keys.sau = { m_editing.smart_paste, 'reverse' }
+keys.cw = { m_editing.current_word, 'delete' }
+keys.at = { m_editing.transpose_chars }
+keys.csh = { m_editing.squeeze, }
+keys.cj = { m_editing.join_lines }
+keys.cau = { m_editing.move_line, 'up' }
+keys.cad = { m_editing.move_line, 'down' }
+keys.csai = { m_editing.convert_indentation }
+keys.cae = { -- code execution
+ r = { m_editing.ruby_exec },
+ l = { m_editing.lua_exec }
+}
+keys.ae = { -- enclose in...
+ t = { m_editing.enclose, 'tag' },
+ st = { m_editing.enclose, 'single_tag' },
+ ['s"'] = { m_editing.enclose, 'dbl_quotes' },
+ ["'"] = { m_editing.enclose, 'sng_quotes' },
+ ['('] = { m_editing.enclose, 'parens' },
+ ['['] = { m_editing.enclose, 'brackets' },
+ ['{'] = { m_editing.enclose, 'braces' },
+ c = { m_editing.enclose, 'chars' },
+}
+keys.as = { -- select in...
+ e = { m_editing.select_enclosed },
+ t = { m_editing.select_enclosed, 'tags' },
+ ['s"'] = { m_editing.select_enclosed, 'dbl_quotes' },
+ ["'"] = { m_editing.select_enclosed, 'sng_quotes' },
+ ['('] = { m_editing.select_enclosed, 'parens' },
+ ['['] = { m_editing.select_enclosed, 'brackets' },
+ ['{'] = { m_editing.select_enclosed, 'braces' },
+ w = { m_editing.current_word, 'select' },
+ l = { m_editing.select_line },
+ p = { m_editing.select_paragraph },
+ i = { m_editing.select_indented_block },
+ s = { m_editing.select_scope },
+ g = { m_editing.grow_selection, 1 },
+ a = { 'select_all', b },
+}
+
+-- Multiple lines commands.
+local m_mlines = _m.textadept.mlines
+keys.am = {
+ a = { m_mlines.add },
+ sa = { m_mlines.add_multiple },
+ r = { m_mlines.remove },
+ sr = { m_mlines.remove_multiple },
+ u = { m_mlines.update },
+ c = { m_mlines.clear },
+}
+
+-- Macro commands.
+local m_macro = _m.textadept.macros
+keys.cam = { m_macro.toggle_record }
+keys.csam = { m_macro.play }
+
+-- Project manager commands.
+local function pm_activate(text) t.pm.entry_text = text t.pm.activate() end
+keys['c\t'] = { t.pm.focus }
+keys.ct.b = { pm_activate, 'buffers' }
+keys.ct.c = { pm_activate, 'ctags' }
+keys.ct.m = { pm_activate, 'macros' }
+keys.ct.v.p = { t.pm.toggle_visible }
+
+-- Toggle setting commands.
+local function toggle_setting(setting)
+ local state = buffer[setting]
+ if type(state) == 'boolean' then
+ buffer[setting] = not state
+ elseif type(state) == 'number' then
+ buffer[setting] = buffer[setting] == 0 and 1 or 0
+ end
+ t.events.update_ui() -- for updating statusbar
+end
+keys.ct.v.e = { toggle_setting, 'view_eol' }
+keys.ct.v.r = { toggle_setting, 'wrap_mode' }
+keys.ct.v.i = { toggle_setting, 'indentation_guides' }
+keys.ct.v.t = { toggle_setting, 'use_tabs' }
+keys.ct.v.w = { toggle_setting, 'view_ws' }
+
+-- Miscellaneous commands.
+keys.cc = { t.focus_command }
+local m_events = t.events
+keys.cab = { m_events.handle, 'call_tip_click', 1 }
+keys.caf = { m_events.handle, 'call_tip_click', 2 }
+keys.ct.f = { function()
+ local buffer = buffer
+ buffer:toggle_fold( buffer:line_from_position(buffer.current_pos) )
+end }
+keys.f5 = { 'colourise', b, 1, -1 }
diff --git a/core/ext/keys.lua b/core/ext/keys.lua
new file mode 100644
index 00000000..f182d044
--- /dev/null
+++ b/core/ext/keys.lua
@@ -0,0 +1,230 @@
+-- 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('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 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 '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 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.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, 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 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