diff options
-rw-r--r-- | core/keys.lua | 113 | ||||
-rw-r--r-- | modules/textadept/keys.lua | 383 | ||||
-rw-r--r-- | modules/textadept/menu.lua | 318 | ||||
-rw-r--r-- | src/textadept.c | 8 |
4 files changed, 384 insertions, 438 deletions
diff --git a/core/keys.lua b/core/keys.lua index 644efc18..bc26527c 100644 --- a/core/keys.lua +++ b/core/keys.lua @@ -12,12 +12,14 @@ module('keys', package.seeall) -- 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 representing a key command and an associated function or 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.) -- +-- Language names are the names of the lexer files in `lexers/` such as `cpp` +-- and `lua`. +-- -- 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. @@ -32,30 +34,15 @@ module('keys', package.seeall) -- -- 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. +-- [`KEYSYMS`](../modules/_m.textadept.keys.html#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`. +-- Normally, Lua functions are assigned to key commands, but those functions are +-- called without any arguments. In order to pass arguments to a function, +-- assign a table to the key command. This table contains the function followed +-- by its arguments in order. Any [buffer](../modules/buffer.html) or +-- [view](../modules/view.html) references are handled correctly at runtime. -- -- Key commands can be chained like in Emacs using keychain sequences. By -- default, the `Esc` key (`Apple+Esc` on Mac OSX) cancels the current keychain, @@ -64,8 +51,6 @@ module('keys', package.seeall) -- -- ## 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). @@ -75,8 +60,7 @@ module('keys', package.seeall) -- ## 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. +-- in the current lexer have priority, followed by the ones in the global table. -- -- ## Example -- @@ -84,19 +68,14 @@ module('keys', package.seeall) -- ['ctrl+f'] = { 'char_right', 'buffer' }, -- ['ctrl+b'] = { 'char_left', 'buffer' }, -- lua = { --- ['ctrl+c'] = { 'add_text', 'buffer', '-- ' }, --- comment = { --- ['ctrl+f'] = { function() print('comment') end } --- } +-- ['ctrl+f'] = { 'add_text', 'buffer', 'function' }, -- } -- } -- -- 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. +-- `buffer:char_left()` respectively. The last command applies only in the Lua +-- lexer. If `ctrl+f` is pressed in a Lua file, the global key command with the +-- same shortcut is overridden and `function` is added to the buffer. -- -- ## Problems -- @@ -119,7 +98,6 @@ module('keys', package.seeall) -- 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 @@ -133,6 +111,8 @@ local xpcall = _G.xpcall local next = _G.next local type = _G.type local unpack = _G.unpack +local no_args = {} +local getmetatable = getmetatable local error = function(e) events.emit('error', e) end --- @@ -179,44 +159,45 @@ 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 +local function run_key_command(lexer) + local key, key_type = keys, type(keys) + if lexer and key_type == 'table' and key[lexer] then key = key[lexer] end if type(key) ~= 'table' then return INVALID end - for i = 1, #keychain do - key = key[keychain[i]] + key = key[keychain[1]] + for i = 2, #keychain do if type(key) ~= 'table' then return INVALID end + key = key[keychain[i]] end - if #key == 0 and next(key) then + key_type = type(key) + + if key_type ~= 'function' and key_type ~= 'table' then return INVALID end + if key_type == 'table' and #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' and (key[2] == 'buffer' or key[2] == 'view') then - local v = _G[key[2]] - f, args = v[f], { v, unpack(key, 3) } + local f, args = key_type == 'function' and key or key[1], no_args + if key_type == 'table' then + args = key + -- If the argument is a view or buffer, use the current one instead. + if type(args[2]) == 'table' then + local mt, buffer, view = getmetatable(args[2]), buffer, view + if mt == getmetatable(buffer) then + args[2] = buffer + elseif mt == getmetatable(view) then + args[2] = view + end + end end - - local _, ret = xpcall(function() return f(unpack(args)) end, error) + local _, ret = xpcall(function() return f(unpack(args, 2)) end, error) return ret == 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. +-- It is called every time a key is pressed, and based on lexer, 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) @@ -241,13 +222,9 @@ local function keypress(code, shift, control, alt) 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) + local success + for i = 1, 2 do + local status = run_key_command(i == 1 and buffer:get_lexer()) if status > 0 then -- CHAIN or HALT if status == HALT then -- Clear the key sequence, but keep any status messages from the key diff --git a/modules/textadept/keys.lua b/modules/textadept/keys.lua index 4d906f38..7188a5d6 100644 --- a/modules/textadept/keys.lua +++ b/modules/textadept/keys.lua @@ -8,8 +8,8 @@ local L = _G.locale.localize module('_m.textadept.keys', package.seeall) local keys = _G.keys -local b, v = 'buffer', 'view' -local gui = gui +local _buffer, _view = buffer, view +local gui, m_textadept = gui, _m.textadept -- Utility functions used by both layouts. local function enclose_in_tag() @@ -68,9 +68,9 @@ if not OSX then -- Windows and Linux key commands. --[[ - C: D J K M U + C: D J K M T U A: A B D E F G H J K L M N P T U V W X Y Z - CS: A B C D G I J K L M N O Q T U V X Y Z + CS: A C D G I J K L M N O Q T U V X Y Z SA: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z CA: A B C D E F G H J K L M N O Q R S T U W X Y Z CSA: A B C D E F G H J K L M N O P Q R S T U V W X Y Z @@ -81,280 +81,267 @@ if not OSX then keys.ct = {} -- Textadept command chain -- File - local m_session = _m.textadept.session - keys.cn = { new_buffer } - keys.co = { io.open_file } - -- TODO: { 'reload', b } - keys.cs = { 'save', b } - keys.cS = { 'save_as', b } - keys.cw = { 'close', b } - keys.cW = { io.close_all } - -- TODO: { m_session.load } after prompting with open dialog - -- TODO: { m_session.save } after prompting with save dialog - keys.aq = { quit } + keys.cn = new_buffer + keys.co = io.open_file + -- TODO: { _buffer.reload, _buffer } + keys.cs = { _buffer.save, _buffer } + keys.cS = { _buffer.save_as, _buffer } + keys.cw = { _buffer.close, _buffer } + keys.cW = io.close_all + -- TODO: m_textadept.session.load after prompting with open dialog + -- TODO: m_textadept.session.save after prompting with save dialog + keys.aq = quit -- Edit - local m_editing = _m.textadept.editing - keys.cz = { 'undo', b } - keys.cy = { 'redo', b } - keys.cx = { 'cut', b } - keys.cc = { 'copy', b } - keys.cv = { 'paste', b } + local m_editing = m_textadept.editing + keys.cz = { _buffer.undo, _buffer } + keys.cy = { _buffer.redo, _buffer } + keys.cx = { _buffer.cut, _buffer } + keys.cc = { _buffer.copy, _buffer } + keys.cv = { _buffer.paste, _buffer } -- Delete is delete. - keys.ca = { 'select_all', b } - keys.ce = { m_editing.match_brace } - keys.cE = { m_editing.match_brace, 'select' } - keys['c\n'] = { m_editing.autocomplete_word, '%w_' } + keys.ca = { _buffer.select_all, _buffer } + keys.ce = m_editing.match_brace + keys.cE = { m_editing.match_brace, 'select' } + keys['c\n'] = { m_editing.autocomplete_word, '%w_' } keys['c\n\r'] = { m_editing.autocomplete_word, '%w_' } -- win32 - keys.cq = { m_editing.block_comment } + keys.cq = m_editing.block_comment -- TODO: { m_editing.current_word, 'delete' } - keys.csh = { m_editing.highlight_word } - -- TODO: { m_editing.transpose_chars } - -- TODO: { m_editing.convert_indentation } + keys.csh = m_editing.highlight_word + -- TODO: m_editing.transpose_chars + -- TODO: m_editing.convert_indentation keys.ac = { -- enClose in... - t = { enclose_in_tag }, - T = { m_editing.enclose, '<', ' />' }, - ['"'] = { m_editing.enclose, '"', '"' }, - ["'"] = { m_editing.enclose, "'", "'" }, - ['('] = { m_editing.enclose, '(', ')' }, - ['['] = { m_editing.enclose, '[', ']' }, - ['{'] = { m_editing.enclose, '{', '}' }, + t = enclose_in_tag, + T = { m_editing.enclose, '<', ' />' }, + ['"'] = { m_editing.enclose, '"', '"' }, + ["'"] = { m_editing.enclose, "'", "'" }, + ['('] = { m_editing.enclose, '(', ')' }, + ['['] = { m_editing.enclose, '[', ']' }, + ['{'] = { m_editing.enclose, '{', '}' }, c = any_char_mt(m_editing.enclose), } keys.as = { -- select in... - t = { m_editing.select_enclosed, '>', '<' }, + t = { m_editing.select_enclosed, '>', '<' }, ['"'] = { m_editing.select_enclosed, '"', '"' }, ["'"] = { m_editing.select_enclosed, "'", "'" }, ['('] = { m_editing.select_enclosed, '(', ')' }, ['['] = { m_editing.select_enclosed, '[', ']' }, ['{'] = { m_editing.select_enclosed, '{', '}' }, - w = { m_editing.current_word, 'select' }, - l = { m_editing.select_line }, - p = { m_editing.select_paragraph }, - b = { m_editing.select_indented_block }, - s = { m_editing.select_scope }, - g = { m_editing.grow_selection, 1 }, - c = any_char_mt(m_editing.select_enclosed), + w = { m_editing.current_word, 'select' }, + l = m_editing.select_line, + p = m_editing.select_paragraph, + b = m_editing.select_indented_block, + s = m_editing.select_scope, + g = { m_editing.grow_selection, 1 }, + c = any_char_mt(m_editing.select_enclosed), } -- Search - keys.cf = { gui.find.focus } -- find/replace - keys['f3'] = { gui.find.find_next } + keys.cf = gui.find.focus -- find/replace + keys['f3'] = gui.find.find_next -- Find Next is an when find pane is focused. -- Find Prev is ap when find pane is focused. -- Replace is ar when find pane is focused. - keys.cF = { gui.find.find_incremental } + keys.cF = gui.find.find_incremental -- Find in Files is ai when find pane is focused. - -- TODO: { gui.find.goto_file_in_list, true } + -- TODO: { gui.find.goto_file_in_list, true } -- TODO: { gui.find.goto_file_in_list, false } - keys.cg = { m_editing.goto_line } + keys.cg = m_editing.goto_line -- Tools - keys['f2'] = { gui.command_entry.focus } + keys['f2'] = gui.command_entry.focus -- Run - local m_run = _m.textadept.run - keys.cr = { m_run.run } - keys.cR = { m_run.compile } - keys.ar = { _m.textadept.filter_through.filter_through } + keys.cr = m_textadept.run.run + keys.cR = m_textadept.run.compile + keys.ar = m_textadept.filter_through.filter_through -- Snippets - local m_snippets = _m.textadept.snippets - keys['\t'] = { m_snippets._insert } - keys['s\t'] = { m_snippets._previous } - keys.cai = { m_snippets._cancel_current } - keys.ai = { m_snippets._select } + keys['\t'] = m_textadept.snippets._insert + keys['s\t'] = m_textadept.snippets._previous + keys.cai = m_textadept.snippets._cancel_current + keys.ai = m_textadept.snippets._select -- Buffers - keys.cb = { gui.switch_buffer } - keys['c\t'] = { 'goto_buffer', v, 1, false } - keys['cs\t'] = { 'goto_buffer', v, -1, false } - keys.ct.v = { - e = { toggle_setting, 'view_eol' }, - w = { toggle_setting, 'wrap_mode' }, - i = { toggle_setting, 'indentation_guides' }, - ['\t'] = { toggle_setting, 'use_tabs' }, - [' '] = { toggle_setting, 'view_ws' }, - v = { toggle_setting, 'virtual_space_options', 2 }, + keys.cb = gui.switch_buffer + keys['c\t'] = { _view.goto_buffer, _view, 1, false } + keys['cs\t'] = { _view.goto_buffer, _view, -1, false } + keys.cB = { + e = { toggle_setting, 'view_eol' }, + w = { toggle_setting, 'wrap_mode' }, + i = { toggle_setting, 'indentation_guides' }, + ['\t'] = { toggle_setting, 'use_tabs' }, + [' '] = { toggle_setting, 'view_ws' }, + v = { toggle_setting, 'virtual_space_options', 2 }, } - keys.cl = { _m.textadept.mime_types.select_lexer } - keys['f5'] = { 'colourise', b, 0, -1 } + keys.cl = m_textadept.mime_types.select_lexer + keys['f5'] = { _buffer.colourise, _buffer, 0, -1 } -- Views keys.cav = { - n = { gui.goto_view, 1, false }, - p = { gui.goto_view, -1, false }, - S = { 'split', v }, -- vertical - s = { 'split', v, false }, -- horizontal - w = { function() view:unsplit() return true end }, - W = { function() while view:unsplit() do end end }, - -- TODO: { function() view.size = view.size + 10 end } - -- TODO: { function() view.size = view.size - 10 end } + n = { gui.goto_view, 1, false }, + p = { gui.goto_view, -1, false }, + S = { _view.split, _view }, -- vertical + s = { _view.split, _view, false }, -- horizontal + w = function() view:unsplit() return true end, + W = function() while view:unsplit() do end end, + -- TODO: function() view.size = view.size + 10 end + -- TODO: function() view.size = view.size - 10 end } - keys.c0 = { function() buffer.zoom = 0 end } + keys.c0 = function() buffer.zoom = 0 end -- Miscellaneous not in standard menu. - keys.ao = { show_recent_file_list } - keys.caI = { show_style } + keys.ao = show_recent_file_list + keys.caI = show_style else -- Mac OSX key commands --[[ C: J M U W X Z - A: D E H J K L U Y + A: D E H J K L T U Y CS: C D G H I J K L M O Q S T U V W X Y Z - SA: A B C D H I J K L M N O Q R T U V X Y + SA: A C D H I J K L M N O Q R T U V X Y CA: A C E J K L M N O Q S U V W X Y Z CSA: A C D E H J K L M N O P Q R S T U V W X Y Z ]]-- keys.clear_sequence = 'aesc' - keys.at = {} -- Textadept command chain - -- File - local m_session = _m.textadept.session - keys.an = { new_buffer } - keys.ao = { io.open_file } - -- TODO: { 'reload', b } - keys.as = { 'save', b } - keys.aS = { 'save_as', b } - keys.aw = { 'close', b } + keys.an = new_buffer + keys.ao = io.open_file + -- TODO: { _buffer.reload, _buffer } + keys.as = { _buffer.save, _buffer } + keys.aS = { _buffer.save_as, _buffer } + keys.aw = { _buffer.close, _buffer } keys.aW = { io.close_all } - -- TODO: { m_session.load } after prompting with open dialog - -- TODO: { m_session.save } after prompting with save dialog - keys.aq = { quit } + -- TODO: m_textadept.session.load after prompting with open dialog + -- TODO: m_textadept.session.save after prompting with save dialog + keys.aq = quit -- Edit - local m_editing = _m.textadept.editing - keys.az = { 'undo', b } - keys.aZ = { 'redo', b } - keys.ax = { 'cut', b } - keys.ac = { 'copy', b } - keys.av = { 'paste', b } + local m_editing = m_textadept.editing + keys.az = { _buffer.undo, _buffer } + keys.aZ = { _buffer.redo, _buffer } + keys.ax = { _buffer.cut, _buffer } + keys.ac = { _buffer.copy, _buffer } + keys.av = { _buffer.paste, _buffer } -- Delete is delete. - keys.aa = { 'select_all', b } - keys.cm = { m_editing.match_brace } - keys.aE = { m_editing.match_brace, 'select' } + keys.aa = { _buffer.select_all, _buffer } + keys.cm = m_editing.match_brace + keys.aE = { m_editing.match_brace, 'select' } keys.esc = { m_editing.autocomplete_word, '%w_' } - keys.cq = { m_editing.block_comment } + keys.cq = m_editing.block_comment -- TODO: { m_editing.current_word, 'delete' } - keys.cat = { m_editing.highlight_word } - keys.ct = { m_editing.transpose_chars } - -- TODO: { m_editing.convert_indentation } + keys.cat = m_editing.highlight_word + keys.ct = m_editing.transpose_chars + -- TODO: m_editing.convert_indentation keys.cc = { -- enClose in... - t = { enclose_in_tag }, - T = { m_editing.enclose, '<', ' />' }, - ['"'] = { m_editing.enclose, '"', '"' }, - ["'"] = { m_editing.enclose, "'", "'" }, - ['('] = { m_editing.enclose, '(', ')' }, - ['['] = { m_editing.enclose, '[', ']' }, - ['{'] = { m_editing.enclose, '{', '}' }, - c = any_char_mt(m_editing.enclose), + t = enclose_in_tag, + T = { m_editing.enclose, '<', ' />' }, + ['"'] = { m_editing.enclose, '"', '"' }, + ["'"] = { m_editing.enclose, "'", "'" }, + ['('] = { m_editing.enclose, '(', ')' }, + ['['] = { m_editing.enclose, '[', ']' }, + ['{'] = { m_editing.enclose, '{', '}' }, + c = any_char_mt(m_editing.enclose), } keys.cs = { -- select in... - t = { m_editing.select_enclosed, '>', '<' }, + t = { m_editing.select_enclosed, '>', '<' }, ['"'] = { m_editing.select_enclosed, '"', '"' }, ["'"] = { m_editing.select_enclosed, "'", "'" }, ['('] = { m_editing.select_enclosed, '(', ')' }, ['['] = { m_editing.select_enclosed, '[', ']' }, ['{'] = { m_editing.select_enclosed, '{', '}' }, - w = { m_editing.current_word, 'select' }, - l = { m_editing.select_line }, - p = { m_editing.select_paragraph }, - b = { m_editing.select_indented_block }, - s = { m_editing.select_scope }, - g = { m_editing.grow_selection, 1 }, - c = any_char_mt(m_editing.select_enclosed), + w = { m_editing.current_word, 'select' }, + l = m_editing.select_line, + p = m_editing.select_paragraph, + b = m_editing.select_indented_block, + s = m_editing.select_scope, + g = { m_editing.grow_selection, 1 }, + c = any_char_mt(m_editing.select_enclosed), } -- Search - keys.af = { gui.find.focus } -- find/replace - keys.ag = { gui.find.find_next } - keys.aG = { gui.find.find_prev } - keys.ar = { gui.find.replace } - keys.ai = { gui.find.find_incremental } - keys.aF = { - function() - gui.find.in_files = true - gui.find.focus() - end - } - keys.cag = { gui.find.goto_file_in_list, true } + keys.af = gui.find.focus -- find/replace + keys.ag = gui.find.find_next + keys.aG = gui.find.find_prev + keys.ar = gui.find.replace + keys.ai = gui.find.find_incremental + keys.aF = function() + gui.find.in_files = true + gui.find.focus() + end + keys.cag = { gui.find.goto_file_in_list, true } keys.caG = { gui.find.goto_file_in_list, false } - keys.cg = { m_editing.goto_line } + keys.cg = m_editing.goto_line -- Tools - keys['f2'] = { gui.command_entry.focus } + keys['f2'] = gui.command_entry.focus -- Run - local m_run = _m.textadept.run - keys.cr = { m_run.run } - keys.cR = { m_run.compile } - keys.car = { _m.textadept.filter_through.filter_through } + keys.cr = { m_textadept.run.run } + keys.cR = { m_textadept.run.compile } + keys.car = { m_textadept.filter_through.filter_through } -- Snippets - local m_snippets = _m.textadept.snippets - keys['\t'] = { m_snippets._insert } - keys['s\t'] = { m_snippets._previous } - keys.cai = { m_snippets._cancel_current } - keys.ci = { m_snippets._select } + keys['\t'] = m_textadept.snippets._insert + keys['s\t'] = m_textadept.snippets._previous + keys.cai = m_textadept.snippets._cancel_current + keys.ci = m_textadept.snippets._select -- Buffers - keys.ab = { gui.switch_buffer } - keys['c\t'] = { 'goto_buffer', v, 1, false } - keys['cs\t'] = { 'goto_buffer', v, -1, false } - keys.at.v = { - e = { toggle_setting, 'view_eol' }, - w = { toggle_setting, 'wrap_mode' }, - i = { toggle_setting, 'indentation_guides' }, - ['\t'] = { toggle_setting, 'use_tabs' }, - [' '] = { toggle_setting, 'view_ws' }, - v = { toggle_setting, 'virtual_space_options', 2 }, + keys.ab = gui.switch_buffer + keys['c\t'] = { _view.goto_buffer, _view, 1, false } + keys['cs\t'] = { _view.goto_buffer, _view, -1, false } + keys.aB = { + e = { toggle_setting, 'view_eol' }, + w = { toggle_setting, 'wrap_mode' }, + i = { toggle_setting, 'indentation_guides' }, + ['\t'] = { toggle_setting, 'use_tabs' }, + [' '] = { toggle_setting, 'view_ws' }, + v = { toggle_setting, 'virtual_space_options', 2 }, } - keys.cl = { _m.textadept.mime_types.select_lexer } - keys['f5'] = { 'colourise', b, 0, -1 } + keys.cl = m_textadept.mime_types.select_lexer + keys['f5'] = { _buffer.colourise, _buffer, 0, -1 } -- Views keys.cv = { - n = { gui.goto_view, 1, false }, - p = { gui.goto_view, -1, false }, - S = { 'split', v }, -- vertical - s = { 'split', v, false }, -- horizontal - w = { function() view:unsplit() return true end }, - W = { function() while view:unsplit() do end end }, - -- TODO: { function() view.size = view.size + 10 end } - -- TODO: { function() view.size = view.size - 10 end } + n = { gui.goto_view, 1, false }, + p = { gui.goto_view, -1, false }, + S = { _view.split, _view }, -- vertical + s = { _view.split, _view, false }, -- horizontal + w = function() view:unsplit() return true end, + W = function() while view:unsplit() do end end, + -- TODO: function() view.size = view.size + 10 end + -- TODO: function() view.size = view.size - 10 end } - keys.c0 = { function() buffer.zoom = 0 end } + keys.c0 = function() buffer.zoom = 0 end -- Miscellaneous not in standard menu. - keys.co = { show_recent_file_list } - keys.caI = { show_style } + keys.co = show_recent_file_list + keys.caI = show_style -- Movement/selection commands - keys.cf = { 'char_right', b } - keys.cF = { 'char_right_extend', b } - keys.caf = { 'word_right', b } - keys.caF = { 'word_right_extend', b } - keys.cb = { 'char_left', b } - keys.cB = { 'char_left_extend', b } - keys.cab = { 'word_left', b } - keys.caB = { 'word_left_extend', b } - keys.cn = { 'line_down', b } - keys.cN = { 'line_down_extend', b } - keys.cp = { 'line_up', b } - keys.cP = { 'line_up_extend', b } - keys.ca = { 'vc_home', b } - keys.cA = { 'home_extend', b } - keys.ce = { 'line_end', b } - keys.cE = { 'line_end_extend', b } - --keys.ch = { 'delete_back', b } - keys.cah = { 'del_word_left', b } - keys.cd = { 'clear', b } - keys.cad = { 'del_word_right', b } - keys.ck = { - function() - buffer:line_end_extend() - buffer:cut() - end - } - keys.cy = { 'paste', b } + keys.cf = { _buffer.char_right, _buffer } + keys.cF = { _buffer.char_right_extend, _buffer } + keys.caf = { _buffer.word_right, _buffer } + keys.caF = { _buffer.word_right_extend, _buffer } + keys.cb = { _buffer.char_left, _buffer } + keys.cB = { _buffer.char_left_extend, _buffer } + keys.cab = { _buffer.word_left, _buffer } + keys.caB = { _buffer.word_left_extend, _buffer } + keys.cn = { _buffer.line_down, _buffer } + keys.cN = { _buffer.line_down_extend, _buffer } + keys.cp = { _buffer.line_up, _buffer } + keys.cP = { _buffer.line_up_extend, _buffer } + keys.ca = { _buffer.vc_home, _buffer } + keys.cA = { _buffer.home_extend, _buffer } + keys.ce = { _buffer.line_end, _buffer } + keys.cE = { _buffer.line_end_extend, _buffer } + keys.cah = { _buffer.del_word_left, _buffer } + keys.cd = { _buffer.clear, _buffer } + keys.cad = { _buffer.del_word_right, _buffer } + keys.ck = function() + buffer:line_end_extend() + buffer:cut() + end + keys.cy = { _buffer.paste, _buffer } end diff --git a/modules/textadept/menu.lua b/modules/textadept/menu.lua index 8f433efc..23745e65 100644 --- a/modules/textadept/menu.lua +++ b/modules/textadept/menu.lua @@ -23,13 +23,9 @@ module('_m.textadept.menu', package.seeall) -- -- [gui_gtkmenu]: ../modules/gui.html#gtkmenu -local SEPARATOR = 'separator' -local b, v = 'buffer', 'view' -local m_snippets = _m.textadept.snippets -local m_editing = _m.textadept.editing -local m_bookmarks = _m.textadept.bookmarks -local m_snapopen = _m.textadept.snapopen -local m_run = _m.textadept.run +local _buffer, _view = buffer, view +local m_textadept, m_editing = _m.textadept, _m.textadept.editing +local SEPARATOR = { 'separator' } local function set_encoding(encoding) buffer:set_encoding(encoding) @@ -76,90 +72,80 @@ end -- @name menubar menubar = { { title = L('_File'), - { L('gtk-new'), { new_buffer } }, - { L('gtk-open'), { io.open_file } }, - { L('_Reload'), { 'reload', b } }, - { L('gtk-save'), { 'save', b } }, - { L('gtk-save-as'), { 'save_as', b } }, - { SEPARATOR }, - { L('gtk-close'), { 'close', b } }, - { L('Close A_ll'), { io.close_all } }, - { SEPARATOR }, - { L('Loa_d Session...'), { - function() - local session_file = _SESSIONFILE or '' - local utf8_filename = gui.dialog('fileselect', - '--title', L('Load Session'), - '--with-directory', - session_file:match('.+[/\\]') or '', - '--with-file', - session_file:match('[^/\\]+$') or '', - '--no-newline') - if #utf8_filename > 0 then - _m.textadept.session.load(utf8_filename:iconv(_CHARSET, 'UTF-8')) - end + { L('gtk-new'), new_buffer }, + { L('gtk-open'), io.open_file }, + { L('_Reload'), { _buffer.reload, _buffer } }, + { L('gtk-save'), { _buffer.save, _buffer } }, + { L('gtk-save-as'), { _buffer.save_as, _buffer } }, + SEPARATOR, + { L('gtk-close'), { _buffer.close, _buffer } }, + { L('Close A_ll'), io.close_all }, + SEPARATOR, + { L('Loa_d Session...'), function() + local session_file = _SESSIONFILE or '' + local utf8_filename = gui.dialog('fileselect', + '--title', L('Load Session'), + '--with-directory', + session_file:match('.+[/\\]') or '', + '--with-file', + session_file:match('[^/\\]+$') or '', + '--no-newline') + if #utf8_filename > 0 then + _m.textadept.session.load(utf8_filename:iconv(_CHARSET, 'UTF-8')) end - } }, - { L('Sa_ve Session...'), { - function() - local session_file = _SESSIONFILE or '' - local utf8_filename = gui.dialog('filesave', - '--title', L('Save Session'), - '--with-directory', - session_file:match('.+[/\\]') or '', - '--with-file', - session_file:match('[^/\\]+$') or '', - '--no-newline') - if #utf8_filename > 0 then - _m.textadept.session.save(utf8_filename:iconv(_CHARSET, 'UTF-8')) - end + end }, + { L('Sa_ve Session...'), function() + local session_file = _SESSIONFILE or '' + local utf8_filename = gui.dialog('filesave', + '--title', L('Save Session'), + '--with-directory', + session_file:match('.+[/\\]') or '', + '--with-file', + session_file:match('[^/\\]+$') or '', + '--no-newline') + if #utf8_filename > 0 then + _m.textadept.session.save(utf8_filename:iconv(_CHARSET, 'UTF-8')) end - } }, - { SEPARATOR }, - { L('gtk-quit'), { quit } }, + end }, + SEPARATOR, + { L('gtk-quit'), quit }, }, { title = L('_Edit'), - { L('gtk-undo'), { 'undo', b } }, - { L('gtk-redo'), { 'redo', b } }, - { SEPARATOR }, - { L('gtk-cut'), { 'cut', b } }, - { L('gtk-copy'), { 'copy', b } }, - { L('gtk-paste'), { 'paste', b } }, - { L('gtk-delete'), { 'clear', b } }, - { L('gtk-select-all'), { 'select_all', b } }, - { SEPARATOR }, - { L('Match _Brace'), { m_editing.match_brace } }, + { L('gtk-undo'), { _buffer.undo, _buffer } }, + { L('gtk-redo'), { _buffer.redo, _buffer } }, + SEPARATOR, + { L('gtk-cut'), { _buffer.cut, _buffer } }, + { L('gtk-copy'), { _buffer.copy, _buffer } }, + { L('gtk-paste'), { _buffer.paste, _buffer } }, + { L('gtk-delete'), { _buffer.clear, _buffer } }, + { L('gtk-select-all'), { _buffer.select_all, _buffer } }, + SEPARATOR, + { L('Match _Brace'), m_editing.match_brace }, { L('Select t_o Brace'), { m_editing.match_brace, 'select' } }, { L('Complete _Word'), { m_editing.autocomplete_word, '%w_' } }, { L('De_lete Word'), { m_editing.current_word, 'delete' } }, - { L('Hi_ghlight Word'), { m_editing.highlight_word } }, - { L('Complete S_ymbol'), { - function() - local m = _m[buffer:get_lexer()] - if m and m.adeptsense then m.adeptsense.sense:complete() end - end - } }, - { L('S_how Documentation'), { - function() - local m = _m[buffer:get_lexer()] - if m and m.adeptsense then m.adeptsense.sense:show_apidoc() end - end - } }, - { L('Tran_spose Characters'), { m_editing.transpose_chars } }, - { L('_Join Lines'), { m_editing.join_lines } }, - { L('Convert _Indentation'), { m_editing.convert_indentation } }, + { L('Hi_ghlight Word'), m_editing.highlight_word }, + { L('Complete S_ymbol'), function() + local m = _m[buffer:get_lexer()] + if m and m.adeptsense then m.adeptsense.sense:complete() end + end }, + { L('S_how Documentation'), function() + local m = _m[buffer:get_lexer()] + if m and m.adeptsense then m.adeptsense.sense:show_apidoc() end + end }, + { L('Tran_spose Characters'), m_editing.transpose_chars }, + { L('_Join Lines'), m_editing.join_lines }, + { L('Convert _Indentation'), m_editing.convert_indentation }, { title = L('S_election'), { title = L('_Enclose in...'), - { L('_HTML Tags'), { - function() - m_editing.enclose('<', '>') - local buffer = buffer - local pos = buffer.current_pos - while buffer.char_at[pos - 1] ~= 60 do pos = pos - 1 end -- '<' - buffer:insert_text(-1, - '</'..buffer:text_range(pos, buffer.current_pos)) - end - } }, + { L('_HTML Tags'), function() + m_editing.enclose('<', '>') + local buffer = buffer + local pos = buffer.current_pos + while buffer.char_at[pos - 1] ~= 60 do pos = pos - 1 end -- '<' + buffer:insert_text(-1, + '</'..buffer:text_range(pos, buffer.current_pos)) + end }, { L('HTML Single _Tag'), { m_editing.enclose, '<', ' />' } }, { L('_Double Quotes'), { m_editing.enclose, '"', '"' } }, { L('_Single Quotes'), { m_editing.enclose, "'", "'" } }, @@ -177,69 +163,65 @@ menubar = { { L('_Bracket'), { m_editing.select_enclosed, '[', ']' } }, { L('B_race'), { m_editing.select_enclosed, '{', '}' } }, { L('_Word'), { m_editing.current_word, 'select' } }, - { L('_Line'), { m_editing.select_line } }, - { L('Para_graph'), { m_editing.select_paragraph } }, - { L('_Indented Block'), { m_editing.select_indented_block } }, - { L('S_cope'), { m_editing.select_scope } }, + { L('_Line'), m_editing.select_line }, + { L('Para_graph'), m_editing.select_paragraph }, + { L('_Indented Block'), m_editing.select_indented_block }, + { L('S_cope'), m_editing.select_scope }, }, }, { title = L('_Tools'), { title = L('_Find'), - { L('gtk-find'), { gui.find.focus } }, - { L('Find _Next'), { gui.find.call_find_next } }, - { L('Find _Previous'), { gui.find.call_find_prev } }, - { L('gtk-find-and-replace'), { gui.find.focus } }, - { L('Replace'), { gui.find.call_replace } }, - { L('Replace _All'), { gui.find.call_replace_all } }, - { L('Find _Incremental'), { gui.find.find_incremental } }, - { SEPARATOR }, - { L('Find in Fi_les'), { - function() - gui.find.in_files = true - gui.find.focus() - end - } }, + { L('gtk-find'), gui.find.focus }, + { L('Find _Next'), gui.find.call_find_next }, + { L('Find _Previous'), gui.find.call_find_prev }, + { L('gtk-find-and-replace'), gui.find.focus }, + { L('Replace'), gui.find.call_replace }, + { L('Replace _All'), gui.find.call_replace_all }, + { L('Find _Incremental'), gui.find.find_incremental }, + SEPARATOR, + { L('Find in Fi_les'), function() + gui.find.in_files = true + gui.find.focus() + end }, { L('Goto Next File Found'), { gui.find.goto_file_in_list, true } }, { L('Goto Previous File Found'), { gui.find.goto_file_in_list, false } }, - { SEPARATOR }, - { L('gtk-jump-to'), { m_editing.goto_line } }, + SEPARATOR, + { L('gtk-jump-to'), m_editing.goto_line }, }, - { L('Command _Entry'), { gui.command_entry.focus } }, - { SEPARATOR }, - { L('_Run'), { m_run.run } }, - { L('_Compile'), { m_run.compile } }, - { L('Fi_lter Through'), { _m.textadept.filter_through.filter_through } }, - { SEPARATOR }, + { L('Command _Entry'), gui.command_entry.focus }, + SEPARATOR, + { L('_Run'), m_textadept.run.run }, + { L('_Compile'), m_textadept.run.compile }, + { L('Fi_lter Through'), _m.textadept.filter_through.filter_through }, + SEPARATOR, { title = L('_Snippets'), - { L('_Insert'), { m_snippets._insert } }, - { L('In_sert...'), { m_snippets._select } }, - { L('_Previous Placeholder'), { m_snippets._prev } }, - { L('_Cancel'), { m_snippets._cancel_current } }, + { L('_Insert'), m_textadept.snippets._insert }, + { L('In_sert...'), m_textadept.snippets._select }, + { L('_Previous Placeholder'), m_textadept.snippets._previous }, + { L('_Cancel'), m_textadept.snippets._cancel_current }, }, { title = L('_Bookmark'), - { L('_Toggle on Current Line'), { m_bookmarks.toggle } }, - { L('_Clear All'), { m_bookmarks.clear } }, - { L('_Next'), { m_bookmarks.goto_next } }, - { L('_Previous'), { m_bookmarks.goto_prev } }, - { L('_Goto Bookmark...'), { m_bookmarks.goto } }, + { L('_Toggle on Current Line'), m_textadept.bookmarks.toggle }, + { L('_Clear All'), m_textadept.bookmarks.clear }, + { L('_Next'), m_textadept.bookmarks.goto_next }, + { L('_Previous'), m_textadept.bookmarks.goto_prev }, + { L('_Goto Bookmark...'), m_textadept.bookmarks.goto }, }, { title = L('Snap_open'), - { L('_User Home'), { m_snapopen.open, _USERHOME } }, - { L('_Textadept Home'), { m_snapopen.open, _HOME } }, - { L('_Current Directory'), { - function() - if buffer.filename then - m_snapopen.open(buffer.filename:match('^(.+)[/\\]')) - end + { L('_User Home'), { m_textadept.snapopen.open, _USERHOME } }, + { L('_Textadept Home'), { m_textadept.snapopen.open, _HOME } }, + { L('_Current Directory'), function() + if buffer.filename then + m_textadept.snapopen.open(buffer.filename:match('^(.+)[/\\]')) end - } }, + end }, }, }, { title = L('_Buffer'), { L('_Next Buffer'), { 'goto_buffer', v, 1, false } }, { L('_Previous Buffer'), { 'goto_buffer', v, -1, false } }, - { L('Switch _Buffer'), { gui.switch_buffer } }, - { SEPARATOR }, + { L('Switch _Buffer'), gui.switch_buffer }, + SEPARATOR, { L('Toggle View _EOL'), { toggle_setting, 'view_eol' } }, { L('Toggle _Wrap Mode'), { toggle_setting, 'wrap_mode' } }, { L('Toggle Show Indentation _Guides'), @@ -248,7 +230,7 @@ menubar = { { L('Toggle View White_space'), { toggle_setting, 'view_ws' } }, { L('Toggle _Virtual Space'), { toggle_setting, 'virtual_space_options', 2} }, - { SEPARATOR }, + SEPARATOR, { title = L('_Indentation'), { '2', { set_indentation, 2 } }, { '3', { set_indentation, 3 } }, @@ -267,31 +249,32 @@ menubar = { { L('MacRoman'), { set_encoding, 'MacRoman' } }, { L('UTF-16'), { set_encoding, 'UTF-16LE' } }, }, - { SEPARATOR }, - { L('_Refresh Syntax Highlighting'), { 'colourise', b, 0, -1 } }, + SEPARATOR, + { L('_Refresh Syntax Highlighting'), + { _buffer.colourise, _buffer, 0, -1 } }, }, { title = L('_View'), { L('_Next View'), { gui.goto_view, 1, false } }, { L('_Previous View'), { gui.goto_view, -1, false } }, - { SEPARATOR }, + SEPARATOR, { L('Split _Vertical'), { 'split', v } }, { L('Split _Horizontal'), { 'split', v, false } }, - { L('_Unsplit'), { function() view:unsplit() end } }, - { L('Unsplit _All'), { function() while view:unsplit() do end end } }, - { SEPARATOR }, - { L('_Grow'), { + { L('_Unsplit'), function() view:unsplit() end }, + { L('Unsplit _All'), function() while view:unsplit() do end end }, + SEPARATOR, + { L('_Grow'), function() if view.size then view.size = view.size + 10 end end - } }, - { L('_Shrink'), { + }, + { L('_Shrink'), function() if view.size then view.size = view.size - 10 end end - } }, + }, }, -- Lexer menu inserted here { title = L('_Help'), { L('_Manual'), { open_webpage, _HOME..'/doc/manual/1_Introduction.html' } }, { L('_LuaDoc'), { open_webpage, _HOME..'/doc/index.html' } }, - { SEPARATOR }, + SEPARATOR, { L('gtk-about'), { gui.dialog, 'ok-msgbox', '--title', 'Textadept', '--informative-text', _RELEASE, '--no-cancel' } @@ -309,15 +292,15 @@ table.insert(menubar, #menubar, lexer_menu) -- before 'Help' -- @class table -- @name context_menu context_menu = { - { L('gtk-undo'), { 'undo', b } }, - { L('gtk-redo'), { 'redo', b } }, - { SEPARATOR }, - { L('gtk-cut'), { 'cut', b } }, - { L('gtk-copy'), { 'copy', b } }, - { L('gtk-paste'), { 'paste', b } }, - { L('gtk-delete'), { 'clear', b } }, - { SEPARATOR }, - { L('gtk-select-all'), { 'select_all', b } } + { L('gtk-undo'), { _buffer.undo, _buffer } }, + { L('gtk-redo'), { _buffer.redo, _buffer } }, + SEPARATOR, + { L('gtk-cut'), { _buffer.cut, _buffer } }, + { L('gtk-copy'), { _buffer.copy, _buffer } }, + { L('gtk-paste'), { _buffer.paste, _buffer } }, + { L('gtk-delete'), { _buffer.clear, _buffer } }, + SEPARATOR, + { L('gtk-select-all'), { _buffer.select_all, _buffer } } } local menu_actions = {} @@ -377,30 +360,31 @@ set_menubar(menubar) set_contextmenu(context_menu) -- Most of this handling code comes from keys.lua. +local no_args = {} events.connect('menu_clicked', function(menu_id) - local active_table + local action, action_type if menu_id > 1000 then - active_table = context_actions[menu_id - 1000] + action = context_actions[menu_id - 1000] else - active_table = menu_actions[menu_id] + action = menu_actions[menu_id] + end + action_type = type(action) + if action_type ~= 'function' and action_type ~= 'table' then + error(L('Unknown command:')..' '..tostring(action)) end - local f, args - if active_table and #active_table > 0 then - local func = active_table[1] - if type(func) == 'function' then - f, args = func, { unpack(active_table, 2) } - elseif type(func) == 'string' then - local object = active_table[2] - if object == 'buffer' or object == 'view' then - local v = _G[object] - f, args = v[func], { v, unpack(active_table, 3) } + + local f, args = action_type == 'function' and action or action[1], no_args + if action_type == 'table' then + args = action + -- If the argument is a view or buffer, use the current one instead. + if type(args[2]) == 'table' then + local mt, buffer, view = getmetatable(args[2]), buffer, view + if mt == getmetatable(buffer) then + args[2] = buffer + elseif mt == getmetatable(view) then + args[2] = view end end - if f and args then - local ret, retval = pcall(f, unpack(args)) - if not ret then error(retval) end - else - error(L('Unknown command:')..' '..tostring(func)) - end end + f(unpack(args, 2)) end) diff --git a/src/textadept.c b/src/textadept.c index ef8516d3..7ff41533 100644 --- a/src/textadept.c +++ b/src/textadept.c @@ -1566,8 +1566,8 @@ static int l_call_scintilla(lua_State *lua, GtkWidget *editor, int msg, * @see l_buffer_mt_index */ static int l_call_buffer_function(lua_State *lua) { - GtkWidget *editor = l_togtkwidget(lua, lua_upvalueindex(1)); - int buffer_func_table_idx = lua_upvalueindex(2); + GtkWidget *editor = focused_editor; + int buffer_func_table_idx = lua_upvalueindex(1); int msg = l_rawgeti_int(lua, buffer_func_table_idx, 1); int rt_type = l_rawgeti_int(lua, buffer_func_table_idx, 2); int p1_type = l_rawgeti_int(lua, buffer_func_table_idx, 3); @@ -1592,9 +1592,7 @@ static int l_buffer_mt_index(lua_State *lua) { lua_remove(lua, -2); // buffer functions if (lua_istable(lua, -1)) { // Of the form { msg, rt_type, p1_type, p2_type } - lua_pushlightuserdata(lua, (GtkWidget *)focused_editor); - lua_insert(lua, lua_gettop(lua) - 1); // shift buffer functions down - lua_pushcclosure(lua, l_call_buffer_function, 2); + lua_pushcclosure(lua, l_call_buffer_function, 1); return 1; } else lua_pop(lua, 1); // non-table |