diff options
Diffstat (limited to 'modules/textadept/menu.lua')
-rw-r--r-- | modules/textadept/menu.lua | 394 |
1 files changed, 249 insertions, 145 deletions
diff --git a/modules/textadept/menu.lua b/modules/textadept/menu.lua index f2bd2d17..acec62b3 100644 --- a/modules/textadept/menu.lua +++ b/modules/textadept/menu.lua @@ -10,25 +10,54 @@ local M = {} -- place. A menu item itself is a table whose first element is a menu label and -- whose second element is a menu command to run. Submenus have `title` keys -- assigned to string text. --- If applicable, load this module last in your *~/.textadept/init.lua*, after --- [`textadept.keys`]() since it looks up defined key commands to show them in --- menus. module('textadept.menu')]] -local _L, buffer, view = _L, buffer, view -local editing, utils = textadept.editing, textadept.keys.utils +local _L = _L local SEPARATOR = {''} +-- The following buffer functions need to be constantized in order for menu +-- items to identify the key associated with the functions. +local menu_buffer_functions = { + 'undo', 'redo', 'cut', 'copy', 'paste', 'line_duplicate', 'clear', + 'select_all', 'upper_case', 'lower_case', 'move_selected_lines_up', + 'move_selected_lines_down', 'zoom_in', 'zoom_out', 'colourise' +} +for i = 1, #menu_buffer_functions do + buffer[menu_buffer_functions[i]] = buffer[menu_buffer_functions[i]] +end + +-- Commonly used functions in menu commands. +local sel_enc = textadept.editing.select_enclosed +local enc = textadept.editing.enclose +local function set_indentation(i) + buffer.tab_width = i + events.emit(events.UPDATE_UI) -- for updating statusbar +end +local function set_eol_mode(mode) + buffer.eol_mode = mode + buffer:convert_eols(mode) + events.emit(events.UPDATE_UI) -- for updating statusbar +end +local function set_encoding(encoding) + buffer:set_encoding(encoding) + events.emit(events.UPDATE_UI) -- for updating statusbar +end +local function open_page(url) + local cmd = (WIN32 and 'start ""') or (OSX and 'open') or 'xdg-open' + spawn(string.format('%s "%s"', cmd, not OSX and url or 'file://'..url)) +end + --- -- The default main menubar. -- Individual menus, submenus, and menu items can be retrieved by name in -- addition to table index number. -- @class table -- @name menubar --- @usage textadept.menubar[_L['_File']]['_New'] -- returns {'_New', buffer.new} --- @usage textadept.menubar[_L['_File']]['_New'][2] = function() ... end +-- @usage textadept.menu.menubar[_L['_File']][_L['_New']] +-- @usage textadept.menu.menubar[_L['_File']][_L['_New']][2] = function() .. end local default_menubar = { - { title = _L['_File'], + { + title = _L['_File'], {_L['_New'], buffer.new}, {_L['_Open'], io.open_file}, {_L['Open _Recent...'], io.open_recent_file}, @@ -43,9 +72,10 @@ local default_menubar = { {_L['Loa_d Session...'], textadept.session.load}, {_L['Sav_e Session...'], textadept.session.save}, SEPARATOR, - {_L['_Quit'], quit}, + {_L['_Quit'], quit} }, - { title = _L['_Edit'], + { + title = _L['_Edit'], {_L['_Undo'], buffer.undo}, {_L['_Redo'], buffer.redo}, SEPARATOR, @@ -54,161 +84,241 @@ local default_menubar = { {_L['_Paste'], buffer.paste}, {_L['Duplicate _Line'], buffer.line_duplicate}, {_L['_Delete'], buffer.clear}, - {_L['D_elete Word'], utils.delete_word}, + {_L['D_elete Word'], function() + textadept.editing.select_word() + buffer:delete_back() + end}, {_L['Select _All'], buffer.select_all}, SEPARATOR, - {_L['_Match Brace'], editing.match_brace}, - {_L['Complete _Word'], {editing.autocomplete, 'word'}}, - {_L['_Highlight Word'], editing.highlight_word}, - {_L['Toggle _Block Comment'], editing.block_comment}, - {_L['T_ranspose Characters'], editing.transpose_chars}, - {_L['_Join Lines'], editing.join_lines}, - {_L['_Filter Through'], - {ui.command_entry.enter_mode, 'filter_through', 'bash'}}, - { title = _L['_Select'], - {_L['Select to _Matching Brace'], {editing.match_brace, 'select'}}, - {_L['Select between _XML Tags'], {editing.select_enclosed, '>', '<'}}, - {_L['Select in XML _Tag'], {editing.select_enclosed, '<', '>'}}, - {_L['Select in _Single Quotes'], {editing.select_enclosed, "'", "'"}}, - {_L['Select in _Double Quotes'], {editing.select_enclosed, '"', '"'}}, - {_L['Select in _Parentheses'], {editing.select_enclosed, '(', ')'}}, - {_L['Select in _Brackets'], {editing.select_enclosed, '[', ']'}}, - {_L['Select in B_races'], {editing.select_enclosed, '{', '}'}}, - {_L['Select _Word'], editing.select_word}, - {_L['Select _Line'], editing.select_line}, - {_L['Select Para_graph'], editing.select_paragraph}, + {_L['_Match Brace'], textadept.editing.match_brace}, + {_L['Complete _Word'], function() + textadept.editing.autocomplete('word') + end}, + {_L['_Highlight Word'], textadept.editing.highlight_word}, + {_L['Toggle _Block Comment'], textadept.editing.block_comment}, + {_L['T_ranspose Characters'], textadept.editing.transpose_chars}, + {_L['_Join Lines'], textadept.editing.join_lines}, + {_L['_Filter Through'], function() + ui.command_entry.enter_mode('filter_through', 'bash') + end}, + { + title = _L['_Select'], + {_L['Select to _Matching Brace'], function() + textadept.editing.match_brace('select') + end}, + {_L['Select between _XML Tags'], function() sel_enc('>', '<') end}, + {_L['Select in XML _Tag'], function() sel_enc('<', '>') end}, + {_L['Select in _Single Quotes'], function() sel_enc("'", "'") end}, + {_L['Select in _Double Quotes'], function() sel_enc('"', '"') end}, + {_L['Select in _Parentheses'], function() sel_enc('(', ')') end}, + {_L['Select in _Brackets'], function() sel_enc('[', ']') end}, + {_L['Select in B_races'], function() sel_enc('{', '}') end}, + {_L['Select _Word'], textadept.editing.select_word}, + {_L['Select _Line'], textadept.editing.select_line}, + {_L['Select Para_graph'], textadept.editing.select_paragraph} }, - { title = _L['Selectio_n'], + { + title = _L['Selectio_n'], {_L['_Upper Case Selection'], buffer.upper_case}, {_L['_Lower Case Selection'], buffer.lower_case}, SEPARATOR, - {_L['Enclose as _XML Tags'], utils.enclose_as_xml_tags}, - {_L['Enclose as Single XML _Tag'], {editing.enclose, '<', ' />'}}, - {_L['Enclose in Single _Quotes'], {editing.enclose, "'", "'"}}, - {_L['Enclose in _Double Quotes'], {editing.enclose, '"', '"'}}, - {_L['Enclose in _Parentheses'], {editing.enclose, '(', ')'}}, - {_L['Enclose in _Brackets'], {editing.enclose, '[', ']'}}, - {_L['Enclose in B_races'], {editing.enclose, '{', '}'}}, + {_L['Enclose as _XML Tags'], function() + enc('<', '>') + 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['Enclose as Single XML _Tag'], function() enc('<', ' />') end}, + {_L['Enclose in Single _Quotes'], function() enc("'", "'") end}, + {_L['Enclose in _Double Quotes'], function() enc('"', '"') end}, + {_L['Enclose in _Parentheses'], function() enc('(', ')') end}, + {_L['Enclose in _Brackets'], function() enc('[', ']') end}, + {_L['Enclose in B_races'], function() enc('{', '}') end}, SEPARATOR, {_L['_Move Selected Lines Up'], buffer.move_selected_lines_up}, - {_L['Move Selected Lines Do_wn'], buffer.move_selected_lines_down}, - }, + {_L['Move Selected Lines Do_wn'], buffer.move_selected_lines_down} + } }, - { title = _L['_Search'], - {_L['_Find'], utils.find}, + { + title = _L['_Search'], + {_L['_Find'], function() + ui.find.in_files = false + ui.find.focus() + end}, {_L['Find _Next'], ui.find.find_next}, {_L['Find _Previous'], ui.find.find_prev}, {_L['_Replace'], ui.find.replace}, {_L['Replace _All'], ui.find.replace_all}, {_L['Find _Incremental'], ui.find.find_incremental}, SEPARATOR, - {_L['Find in Fi_les'], {utils.find, true}}, - {_L['Goto Nex_t File Found'], {ui.find.goto_file_found, false, true}}, - {_L['Goto Previou_s File Found'], {ui.find.goto_file_found, false, false}}, + {_L['Find in Fi_les'], function() + ui.find.in_files = true + ui.find.focus() + end}, + {_L['Goto Nex_t File Found'], function() + ui.find.goto_file_found(false, true) + end}, + {_L['Goto Previou_s File Found'], function() + ui.find.goto_file_found(false, false) + end}, SEPARATOR, - {_L['_Jump to'], editing.goto_line}, + {_L['_Jump to'], textadept.editing.goto_line} }, - { title = _L['_Tools'], - {_L['Command _Entry'], {ui.command_entry.enter_mode, 'lua_command', 'lua'}}, - {_L['Select Co_mmand'], utils.select_command}, + { + title = _L['_Tools'], + {_L['Command _Entry'], function() + ui.command_entry.enter_mode('lua_command', 'lua') + end}, + {_L['Select Co_mmand'], function() M.select_command() end}, SEPARATOR, {_L['_Run'], textadept.run.run}, {_L['_Compile'], textadept.run.compile}, {_L['Buil_d'], textadept.run.build}, {_L['S_top'], textadept.run.stop}, - {_L['_Next Error'], {textadept.run.goto_error, false, true}}, - {_L['_Previous Error'], {textadept.run.goto_error, false, false}}, + {_L['_Next Error'], function() textadept.run.goto_error(false, true) end}, + {_L['_Previous Error'], function() + textadept.run.goto_error(false, false) + end}, SEPARATOR, - { title = _L['_Bookmark'], + { + title = _L['_Bookmark'], {_L['_Toggle Bookmark'], textadept.bookmarks.toggle}, {_L['_Clear Bookmarks'], textadept.bookmarks.clear}, - {_L['_Next Bookmark'], {textadept.bookmarks.goto_mark, true}}, - {_L['_Previous Bookmark'], {textadept.bookmarks.goto_mark, false}}, + {_L['_Next Bookmark'], function() + textadept.bookmarks.goto_mark(true) + end}, + {_L['_Previous Bookmark'], function() + textadept.bookmarks.goto_mark(false) + end}, {_L['_Goto Bookmark...'], textadept.bookmarks.goto_mark}, }, - { title = _L['Snap_open'], - {_L['Snapopen _User Home'], {io.snapopen, _USERHOME}}, - {_L['Snapopen _Textadept Home'], {io.snapopen, _HOME}}, - {_L['Snapopen _Current Directory'], utils.snapopen_filedir}, + { + title = _L['Snap_open'], + {_L['Snapopen _User Home'], function() io.snapopen(_USERHOME) end}, + {_L['Snapopen _Textadept Home'], function() io.snapopen(_HOME) end}, + {_L['Snapopen _Current Directory'], function() + if buffer.filename then + io.snapopen(buffer.filename:match('^(.+)[/\\]')) + end + end}, {_L['Snapopen Current _Project'], io.snapopen}, }, - { title = _L['_Snippets'], + { + title = _L['_Snippets'], {_L['_Insert Snippet...'], textadept.snippets._select}, {_L['_Expand Snippet/Next Placeholder'], textadept.snippets._insert}, {_L['_Previous Snippet Placeholder'], textadept.snippets._previous}, {_L['_Cancel Snippet'], textadept.snippets._cancel_current}, }, SEPARATOR, - {_L['_Complete Symbol'], utils.autocomplete_symbol}, + {_L['_Complete Symbol'], function() + textadept.editing.autocomplete(buffer:get_lexer(true)) + end}, {_L['Show _Documentation'], textadept.editing.show_documentation}, - {_L['Show St_yle'], utils.show_style}, + {_L['Show St_yle'], function() + local char = buffer:text_range(buffer.current_pos, + buffer:position_after(buffer.current_pos)) + local bytes = string.rep(' 0x%X', #char):format(char:byte(1, #char)) + local style = buffer.style_at[buffer.current_pos] + local text = string.format("'%s' (U+%04X:%s)\n%s %s\n%s %s (%d)", char, + utf8.codepoint(char), bytes, _L['Lexer'], + buffer:get_lexer(true), _L['Style'], + buffer.style_name[style], style) + buffer:call_tip_show(buffer.current_pos, text) + end} }, - { title = _L['_Buffer'], - {_L['_Next Buffer'], {view.goto_buffer, view, 1, true}}, - {_L['_Previous Buffer'], {view.goto_buffer, view, -1, true}}, + { + title = _L['_Buffer'], + {_L['_Next Buffer'], function() view:goto_buffer(1, true) end}, + {_L['_Previous Buffer'], function() view:goto_buffer(-1, true) end}, {_L['_Switch to Buffer...'], ui.switch_buffer}, SEPARATOR, - { title = _L['_Indentation'], - {_L['Tab width: _2'], {utils.set_indentation, 2}}, - {_L['Tab width: _3'], {utils.set_indentation, 3}}, - {_L['Tab width: _4'], {utils.set_indentation, 4}}, - {_L['Tab width: _8'], {utils.set_indentation, 8}}, + { + title = _L['_Indentation'], + {_L['Tab width: _2'], function() set_indentation(2) end}, + {_L['Tab width: _3'], function() set_indentation(3) end}, + {_L['Tab width: _4'], function() set_indentation(4) end}, + {_L['Tab width: _8'], function() set_indentation(8) end}, SEPARATOR, - {_L['_Toggle Use Tabs'], {utils.toggle_property, 'use_tabs'}}, - {_L['_Convert Indentation'], editing.convert_indentation}, + {_L['_Toggle Use Tabs'], function() + buffer.use_tabs = not buffer.use_tabs + events.emit(events.UPDATE_UI) -- for updating statusbar + end}, + {_L['_Convert Indentation'], textadept.editing.convert_indentation} }, - { title = _L['_EOL Mode'], - {_L['CRLF'], {utils.set_eol_mode, buffer.EOL_CRLF}}, - {_L['CR'], {utils.set_eol_mode, buffer.EOL_CR}}, - {_L['LF'], {utils.set_eol_mode, buffer.EOL_LF}}, + { + title = _L['_EOL Mode'], + {_L['CRLF'], function() set_eol_mode(buffer.EOL_CRLF) end}, + {_L['CR'], function() set_eol_mode(buffer.EOL_CR) end}, + {_L['LF'], function() set_eol_mode(buffer.EOL_LF) end} }, - { title = _L['E_ncoding'], - {_L['_UTF-8 Encoding'], {utils.set_encoding, 'UTF-8'}}, - {_L['_ASCII Encoding'], {utils.set_encoding, 'ASCII'}}, - {_L['_ISO-8859-1 Encoding'], {utils.set_encoding, 'ISO-8859-1'}}, - {_L['_MacRoman Encoding'], {utils.set_encoding, 'MacRoman'}}, - {_L['UTF-1_6 Encoding'], {utils.set_encoding, 'UTF-16LE'}}, + { + title = _L['E_ncoding'], + {_L['_UTF-8 Encoding'], function() set_encoding('UTF-8') end}, + {_L['_ASCII Encoding'], function() set_encoding('ASCII') end}, + {_L['_ISO-8859-1 Encoding'], function() set_encoding('ISO-8859-1') end}, + {_L['_MacRoman Encoding'], function() set_encoding('MacRoman') end}, + {_L['UTF-1_6 Encoding'], function() set_encoding('UTF-16LE') end} }, SEPARATOR, - {_L['Toggle View _EOL'], {utils.toggle_property, 'view_eol'}}, - {_L['Toggle _Wrap Mode'], {utils.toggle_property, 'wrap_mode'}}, - {_L['Toggle View White_space'], {utils.toggle_property, 'view_ws'}}, + {_L['Toggle View _EOL'], function() + buffer.view_eol = not buffer.view_eol + end}, + {_L['Toggle _Wrap Mode'], function() + buffer.wrap_mode = buffer.wrap_mode == 0 and buffer.WRAP_WHITESPACE or 0 + end}, + {_L['Toggle View White_space'], function() + buffer.view_ws = buffer.view_ws == 0 and buffer.WS_VISIBLEALWAYS or 0 + end}, SEPARATOR, {_L['Select _Lexer...'], textadept.file_types.select_lexer}, - {_L['_Refresh Syntax Highlighting'], {buffer.colourise, buffer, 0, -1}}, + {_L['_Refresh Syntax Highlighting'], function() buffer:colourise(0, -1) end} }, - { title = _L['_View'], - {_L['_Next View'], {ui.goto_view, 1, true}}, - {_L['_Previous View'], {ui.goto_view, -1, true}}, + { + title = _L['_View'], + {_L['_Next View'], function() ui.goto_view(1, true) end}, + {_L['_Previous View'], function() ui.goto_view(-1, true) end}, SEPARATOR, - {_L['Split View _Horizontal'], {view.split, view}}, - {_L['Split View _Vertical'], {view.split, view, true}}, - {_L['_Unsplit View'], {view.unsplit, view}}, - {_L['Unsplit _All Views'], utils.unsplit_all}, - {_L['_Grow View'], utils.grow}, - {_L['Shrin_k View'], utils.shrink}, + {_L['Split View _Horizontal'], function() view:split() end}, + {_L['Split View _Vertical'], function() view:split(true) end}, + {_L['_Unsplit View'], function() view:unsplit() end}, + {_L['Unsplit _All Views'], function() while view:unsplit() do end end}, + {_L['_Grow View'], function() + if view.size then view.size = view.size + buffer:text_height(0) end + end}, + {_L['Shrin_k View'], function() + if view.size then view.size = view.size - buffer:text_height(0) end + end}, SEPARATOR, - {_L['Toggle Current _Fold'], utils.toggle_current_fold}, + {_L['Toggle Current _Fold'], function() + buffer:toggle_fold(buffer:line_from_position(buffer.current_pos)) + end}, SEPARATOR, - {_L['Toggle Show In_dent Guides'], - {utils.toggle_property, 'indentation_guides'}}, - {_L['Toggle _Virtual Space'], - {utils.toggle_property, 'virtual_space_options', - buffer.VS_USERACCESSIBLE}}, + {_L['Toggle Show In_dent Guides'], function() + local off = buffer.indentation_guides == 0 + buffer.indentation_guides = off and buffer.IV_LOOKBOTH or 0 + end}, + {_L['Toggle _Virtual Space'], function() + local off = buffer.virtual_space_options == 0 + buffer.virtual_space_options = off and buffer.VS_USERACCESSIBLE or 0 + end}, SEPARATOR, {_L['Zoom _In'], buffer.zoom_in}, {_L['Zoom _Out'], buffer.zoom_out}, - {_L['_Reset Zoom'], utils.reset_zoom}, + {_L['_Reset Zoom'], function() buffer.zoom = 0 end} }, - { title = _L['_Help'], - {_L['Show _Manual'], {utils.open_webpage, _HOME..'/doc/manual.html'}}, - {_L['Show _LuaDoc'], {utils.open_webpage, _HOME..'/doc/api.html'}}, + { + title = _L['_Help'], + {_L['Show _Manual'], function() open_page(_HOME..'/doc/manual.html') end}, + {_L['Show _LuaDoc'], function() open_page(_HOME..'/doc/api.html') end}, SEPARATOR, - {_L['_About'], - {ui.dialogs.msgbox, {title = 'Textadept', text = _RELEASE, - informative_text = _COPYRIGHT, - icon_file = _HOME..'/core/images/ta_64x64.png'}}}, - }, + {_L['_About'], function() + ui.dialogs.msgbox({ + title = 'Textadept', text = _RELEASE, informative_text = _COPYRIGHT, + icon_file = _HOME..'/core/images/ta_64x64.png' + }) + end} + } } --- @@ -311,27 +421,6 @@ local function read_menu_table(menu, contextmenu) return gtkmenu end --- Builds the item and commands tables for the filtered list dialog. --- @param menu The menu to read from. --- @param title The title of the menu. --- @param items The current list of items. --- @param commands The current list of commands. -local function build_command_tables(menu, title, items, commands) - for i = 1, #menu do - if menu[i].title then - build_command_tables(menu[i], menu[i].title, items, commands) - elseif menu[i][1] ~= '' then - local label, f = menu[i][1], menu[i][2] - if title then label = title..': '..label end - items[#items + 1] = label:gsub('_([^_])', '%1') - items[#items + 1] = key_shortcuts[get_id(f)] or '' - commands[#commands + 1] = f - end - end -end - -local items, commands - -- Returns a proxy table for menu table *menu* such that when a menu item is -- changed or added, *update* is called to update the menu in the UI. -- @param menu The menu or table of menus to create a proxy for. @@ -343,7 +432,7 @@ local function proxy_menu(menu, update, menubar) return setmetatable({}, { __index = function(_, k) local v - if type(k) == 'number' then + if type(k) == 'number' or k == 'title' then v = menu[k] elseif type(k) == 'string' then for i = 1, #menu do @@ -378,11 +467,10 @@ local function set_menubar(menubar) _menubar[#_menubar + 1] = ui.menu(read_menu_table(menubar[i])) end ui.menubar = _menubar - items, commands = {}, {} - build_command_tables(menubar, nil, items, commands) proxies.menubar = proxy_menu(menubar, set_menubar) end -set_menubar(default_menubar) +proxies.menubar = proxy_menu(default_menubar, function() end) -- for keys.lua +events.connect(events.INITIALIZED, function() set_menubar(default_menubar) end) -- Sets `ui.context_menu` and `ui.tab_context_menu` from menu item lists -- *buffer_menu* and *tab_menu*, respectively. @@ -408,12 +496,37 @@ local function set_contextmenus(buffer_menu, tab_menu) set_contextmenus(nil, menu) end) end -set_contextmenus() +events.connect(events.INITIALIZED, set_contextmenus) + +-- Performs the appropriate action when clicking a menu item. +events.connect(events.MENU_CLICKED, function(menu_id) + local actions = menu_id < 1000 and menu_actions or contextmenu_actions + local action = actions[menu_id < 1000 and menu_id or menu_id - 1000] + assert(type(action) == 'function' or type(action) == 'table', + _L['Unknown command:']..' '..tostring(action)) + keys.run_command(action, type(action)) +end) --- -- Prompts the user to select a menu command to run. -- @name select_command function M.select_command() + local items, commands = {}, {} + -- Builds the item and commands tables for the filtered list dialog. + -- @param menu The menu to read from. + local function build_command_tables(menu) + for i = 1, #menu do + if menu[i].title then + build_command_tables(menu[i]) + elseif menu[i][1] ~= '' then + local label = menu.title and menu.title..': '..menu[i][1] or menu[i][1] + items[#items + 1] = label:gsub('_([^_])', '%1') + items[#items + 1] = key_shortcuts[get_id(menu[i][2])] or '' + commands[#commands + 1] = menu[i][2] + end + end + end + build_command_tables(getmetatable(M.menubar).menu) local button, i = ui.dialogs.filteredlist{ title = _L['Run Command'], columns = {_L['Command'], _L['Key Command']}, items = items, width = CURSES and ui.size[1] - 2 or nil @@ -422,15 +535,6 @@ function M.select_command() keys.run_command(commands[i], type(commands[i])) end --- Performs the appropriate action when clicking a menu item. -events.connect(events.MENU_CLICKED, function(menu_id) - local actions = menu_id < 1000 and menu_actions or contextmenu_actions - local action = actions[menu_id < 1000 and menu_id or menu_id - 1000] - assert(type(action) == 'function' or type(action) == 'table', - _L['Unknown command:']..' '..tostring(action)) - keys.run_command(action, type(action)) -end) - return setmetatable(M, { __index = function(_, k) return proxies[k] or M[k] end, __newindex = function(_, k, v) |