1-- Copyright 2007-2021 Mitchell. See LICENSE. 2 3local ui = ui 4 5--[[ This comment is for LuaDoc. 6--- 7-- Utilities for interacting with Textadept's user interface. 8-- @field title (string, Write-only) 9-- The title text of Textadept's window. 10-- @field context_menu (userdata) 11-- The buffer's context menu, a [`ui.menu()`](). 12-- This is a low-level field. You probably want to use the higher-level 13-- [`textadept.menu.context_menu`](). 14-- @field tab_context_menu (userdata) 15-- The context menu for the buffer's tab, a [`ui.menu()`](). 16-- This is a low-level field. You probably want to use the higher-level 17-- [`textadept.menu.tab_context_menu`](). 18-- @field clipboard_text (string) 19-- The text on the clipboard. 20-- @field statusbar_text (string, Write-only) 21-- The text displayed in the statusbar. 22-- @field buffer_statusbar_text (string, Write-only) 23-- The text displayed in the buffer statusbar. 24-- @field maximized (bool) 25-- Whether or not Textadept's window is maximized. 26-- @field tabs (bool) 27-- Whether or not to display the tab bar when multiple buffers are open. 28-- The default value is `true`. 29-- @field silent_print (bool) 30-- Whether or not to print messages to buffers silently. 31-- This is not guaranteed to be a constant value, as Textadept may change it 32-- for the editor's own purposes. This flag should be used only in conjunction 33-- with a group of [`ui.print()`]() and [`ui._print()`]() function calls. 34-- The default value is `false`, and focuses buffers when messages are printed 35-- to them. 36module('ui')]] 37 38ui.silent_print = false 39 40-- Helper function for jumping to another view to print to, or creating a new 41-- view to print to (the latter depending on settings). 42local function prepare_view() 43 if #_VIEWS > 1 then ui.goto_view(1) elseif not ui.tabs then view:split() end 44end 45 46-- Helper function for printing messages to buffers. 47-- @see ui._print 48local function _print(buffer_type, ...) 49 local buffer 50 for _, buf in ipairs(_BUFFERS) do 51 if buf._type == buffer_type then buffer = buf break end 52 end 53 if not buffer then 54 prepare_view() 55 buffer = _G.buffer.new() 56 buffer._type = buffer_type 57 elseif not ui.silent_print then 58 for _, view in ipairs(_VIEWS) do 59 if view.buffer._type == buffer_type then 60 ui.goto_view(view) 61 goto view_found 62 end 63 end 64 prepare_view() 65 view:goto_buffer(buffer) 66 ::view_found:: 67 end 68 local args, n = {...}, select('#', ...) 69 for i = 1, n do args[i] = tostring(args[i]) end 70 buffer:append_text(table.concat(args, '\t')) 71 buffer:append_text('\n') 72 buffer:goto_pos(buffer.length + 1) 73 buffer:set_save_point() 74end 75--- 76-- Prints the given string messages to the buffer of string type *buffer_type*. 77-- Opens a new buffer for printing messages to if necessary. If the message 78-- buffer is already open in a view, the message is printed to that view. 79-- Otherwise the view is split (unless `ui.tabs` is `true`) and the message 80-- buffer is displayed before being printed to. 81-- @param buffer_type String type of message buffer. 82-- @param ... Message strings. 83-- @usage ui._print(_L['[Message Buffer]'], message) 84-- @name _print 85function ui._print(buffer_type, ...) 86 _print(assert_type(buffer_type, 'string', 1), ...) 87end 88 89--- 90-- Prints the given string messages to the message buffer. 91-- Opens a new buffer if one has not already been opened for printing messages. 92-- @param ... Message strings. 93-- @name print 94function ui.print(...) ui._print(_L['[Message Buffer]'], ...) end 95 96-- Returns 0xBBGGRR colors transformed into "#RRGGBB" for the colorselect 97-- dialog. 98-- @param value Number color to transform. 99-- @return string or nil if the transform failed 100local function torgb(value) 101 local bbggrr = string.format('%06X', value) 102 local b, g, r = bbggrr:match('^(%x%x)(%x%x)(%x%x)$') 103 return r and g and b and string.format('#%s%s%s', r, g, b) or nil 104end 105 106-- Documentation is in core/.ui.dialogs.luadoc. 107ui.dialogs = setmetatable({}, {__index = function(_, k) 108 -- Wrapper for `ui.dialog(k)`, transforming the given table of arguments into 109 -- a set of command line arguments and transforming the resulting standard 110 -- output into Lua objects. 111 -- @param options Table of key-value command line options for gtdialog. 112 -- @param f Work function for progressbar dialogs. 113 -- @return Lua objects depending on the dialog kind 114 return function(options, f) 115 if not options.button1 then options.button1 = _L['OK'] end 116 if k == 'filteredlist' and not options.width then 117 options.width = ui.size[1] - 2 * (CURSES and 1 or 100) 118 end 119 -- Transform key-value pairs into command line arguments. 120 local args = {} 121 for option, value in pairs(options) do 122 if assert_type(value, 'string/number/table/boolean', option) then 123 args[#args + 1] = '--' .. option:gsub('_', '-') 124 if type(value) == 'table' then 125 for i, val in ipairs(value) do 126 local narg = string.format('%s[%d]', option, i) 127 assert_type(val, 'string/number', narg) 128 if option == 'palette' and type(val) == 'number' then 129 value[i] = torgb(val) -- nil return is okay 130 elseif option == 'select' and assert_type(val, 'number', narg) then 131 value[i] = val - 1 -- convert from 1-based index to 0-based index 132 end 133 end 134 elseif option == 'color' and type(value) == 'number' then 135 value = torgb(value) 136 elseif option == 'select' and assert_type(value, 'number', option) then 137 value = value - 1 -- convert from 1-based index to 0-based index 138 end 139 if type(value) ~= 'boolean' then args[#args + 1] = value end 140 end 141 end 142 if k == 'progressbar' then 143 args[#args + 1] = assert_type(f, 'function', 2) 144 end 145 -- Call gtdialog, stripping any trailing newline in the output. 146 local result = ui.dialog( 147 k:gsub('_', '-'), table.unpack(args)):match('^(.-)\n?$') 148 -- Depending on the dialog type, transform the result into Lua objects. 149 if k == 'fileselect' or k == 'filesave' then 150 if result == '' then return nil end 151 if WIN32 and not CURSES then result = result:iconv(_CHARSET, 'UTF-8') end 152 if k == 'filesave' or not options.select_multiple then return result end 153 local filenames = {} 154 for filename in result:gmatch('[^\n]+') do 155 filenames[#filenames + 1] = filename 156 end 157 return filenames 158 elseif k == 'filteredlist' or k == 'optionselect' or 159 k:find('input') and result:match('^[^\n]+\n?(.*)$'):find('\n') then 160 local button, value = result:match('^([^\n]+)\n?(.*)$') 161 if not options.string_output then button = tonumber(button) end 162 if k == 'optionselect' then 163 options.select_multiple = true 164 elseif k:find('input') then 165 options.string_output, options.select_multiple = true, true 166 end 167 local items, patt = {}, not k:find('input') and '[^\n]+' or '([^\n]*)\n' 168 for item in (value .. '\n'):gmatch(patt) do 169 items[#items + 1] = options.string_output and item or tonumber(item) + 1 170 end 171 return button, options.select_multiple and items or items[1] 172 elseif k == 'colorselect' then 173 if options.string_output then return result ~= '' and result or nil end 174 local r, g, b = result:match('^#(%x%x)(%x%x)(%x%x)$') 175 local bgr = r and g and b and string.format('0x%s%s%s', b, g, r) or nil 176 return tonumber(bgr) 177 elseif k == 'fontselect' or k == 'progressbar' then 178 return result ~= '' and result or nil 179 elseif not options.string_output then 180 local i, value = result:match('^(%-?%d+)\n?(.*)$') 181 i = tonumber(i) 182 if k:find('dropdown') then 183 value = i > 0 and tonumber(value) + 1 or nil 184 end 185 return i, value 186 end 187 return result:match('([^\n]+)\n?(.*)$') 188 end 189end}) 190 191local buffers_zorder = {} 192 193-- Adds new buffers to the z-order list. 194events.connect(events.BUFFER_NEW, function() 195 if buffer ~= ui.command_entry then table.insert(buffers_zorder, 1, buffer) end 196end) 197 198-- Updates the z-order list. 199local function update_zorder() 200 local i = 1 201 while i <= #buffers_zorder do 202 if buffers_zorder[i] == buffer or not _BUFFERS[buffers_zorder[i]] then 203 table.remove(buffers_zorder, i) 204 else 205 i = i + 1 206 end 207 end 208 table.insert(buffers_zorder, 1, buffer) 209end 210events.connect(events.BUFFER_AFTER_SWITCH, update_zorder) 211events.connect(events.VIEW_AFTER_SWITCH, update_zorder) 212events.connect(events.BUFFER_DELETED, update_zorder) 213 214-- Saves and restores buffer zorder data during a reset. 215events.connect( 216 events.RESET_BEFORE, function(persist) persist.ui_zorder = buffers_zorder end) 217events.connect( 218 events.RESET_AFTER, function(persist) buffers_zorder = persist.ui_zorder end) 219 220--- 221-- Prompts the user to select a buffer to switch to. 222-- Buffers are listed in the order they were opened unless `zorder` is `true`, 223-- in which case buffers are listed by their z-order (most recently viewed to 224-- least recently viewed). 225-- @param zorder Flag that indicates whether or not to list buffers by their 226-- z-order. The default value is `false`. 227-- @name switch_buffer 228function ui.switch_buffer(zorder) 229 local buffers = not zorder and _BUFFERS or buffers_zorder 230 local columns, utf8_list = {_L['Name'], _L['Filename']}, {} 231 for i = not zorder and 1 or 2, #buffers do 232 local buffer = buffers[i] 233 local filename = buffer.filename or buffer._type or _L['Untitled'] 234 if buffer.filename then filename = filename:iconv('UTF-8', _CHARSET) end 235 local basename = buffer.filename and filename:match('[^/\\]+$') or filename 236 utf8_list[#utf8_list + 1] = (buffer.modify and '*' or '') .. basename 237 utf8_list[#utf8_list + 1] = filename 238 end 239 local button, i = ui.dialogs.filteredlist{ 240 title = _L['Switch Buffers'], columns = columns, items = utf8_list 241 } 242 if button ~= 1 or not i then return end 243 view:goto_buffer(buffers[not zorder and i or i + 1]) 244end 245 246--- 247-- Switches to the existing view whose buffer's filename is *filename*. 248-- If no view was found and *split* is `true`, splits the current view in order 249-- to show the requested file. If *split* is `false`, shifts to the next or 250-- *preferred_view* view in order to show the requested file. If *sloppy* is 251-- `true`, requires only the basename of *filename* to match a buffer's 252-- `filename`. If the requested file was not found, it is opened in the desired 253-- view. 254-- @param filename The filename of the buffer to go to. 255-- @param split Optional flag that indicates whether or not to open the buffer 256-- in a split view if there is only one view. The default value is `false`. 257-- @param preferred_view Optional view to open the desired buffer in if the 258-- buffer is not visible in any other view. 259-- @param sloppy Optional flag that indicates whether or not to not match 260-- *filename* to `buffer.filename` exactly. When `true`, matches *filename* to 261-- only the last part of `buffer.filename` This is useful for run and compile 262-- commands which output relative filenames and paths instead of full ones and 263-- it is likely that the file in question is already open. The default value 264-- is `false`. 265-- @name goto_file 266function ui.goto_file(filename, split, preferred_view, sloppy) 267 assert_type(filename, 'string', 1) 268 local patt = string.format( -- TODO: escape filename properly 269 '%s%s$', not sloppy and '^' or '', 270 not sloppy and filename or filename:match('[^/\\]+$')) 271 if WIN32 then 272 patt = patt:gsub('%a', function(letter) 273 return string.format('[%s%s]', letter:upper(), letter:lower()) 274 end) 275 end 276 if #_VIEWS == 1 and split and not (view.buffer.filename or ''):find(patt) then 277 view:split() 278 else 279 local other_view = _VIEWS[preferred_view] 280 for _, view in ipairs(_VIEWS) do 281 local filename = view.buffer.filename or '' 282 if filename:find(patt) then ui.goto_view(view) return end 283 if not other_view and view ~= _G.view then other_view = view end 284 end 285 if other_view then ui.goto_view(other_view) end 286 end 287 for _, buf in ipairs(_BUFFERS) do 288 if (buf.filename or ''):find(patt) then view:goto_buffer(buf) return end 289 end 290 io.open_file(filename) 291end 292 293-- Ensure title, statusbar, etc. are updated for new views. 294events.connect(events.VIEW_NEW, function() events.emit(events.UPDATE_UI, 3) end) 295 296-- Switches between buffers when a tab is clicked. 297events.connect( 298 events.TAB_CLICKED, function(index) view:goto_buffer(_BUFFERS[index]) end) 299 300-- Sets the title of the Textadept window to the buffer's filename. 301local function set_title() 302 local filename = buffer.filename or buffer._type or _L['Untitled'] 303 if buffer.filename then 304 filename = select(2, pcall(string.iconv, filename, 'UTF-8', _CHARSET)) 305 end 306 local basename = buffer.filename and filename:match('[^/\\]+$') or filename 307 ui.title = string.format( 308 '%s %s Textadept (%s)', basename, buffer.modify and '*' or '-', filename) 309 buffer.tab_label = basename .. (buffer.modify and '*' or '') 310end 311 312-- Changes Textadept title to show the buffer as being "clean" or "dirty". 313events.connect(events.SAVE_POINT_REACHED, set_title) 314events.connect(events.SAVE_POINT_LEFT, set_title) 315 316-- Open uri(s). 317events.connect(events.URI_DROPPED, function(utf8_uris) 318 for utf8_path in utf8_uris:gmatch('file://([^\r\n]+)') do 319 local path = utf8_path:gsub('%%(%x%x)', function(hex) 320 return string.char(tonumber(hex, 16)) 321 end):iconv(_CHARSET, 'UTF-8') 322 -- In WIN32, ignore a leading '/', but not '//' (network path). 323 if WIN32 and not path:match('^//') then path = path:sub(2, -1) end 324 local mode = lfs.attributes(path, 'mode') 325 if mode and mode ~= 'directory' then io.open_file(path) end 326 end 327 ui.goto_view(view) -- work around any view focus synchronization issues 328end) 329events.connect(events.APPLEEVENT_ODOC, function(uri) 330 return events.emit(events.URI_DROPPED, 'file://' .. uri) 331end) 332 333-- Sets buffer statusbar text. 334events.connect(events.UPDATE_UI, function(updated) 335 if updated & 3 == 0 then return end -- ignore scrolling 336 local text = not CURSES and '%s %d/%d %s %d %s %s %s %s' or 337 '%s %d/%d %s %d %s %s %s %s' 338 local pos = buffer.current_pos 339 local line, max = buffer:line_from_position(pos), buffer.line_count 340 local col = buffer.column[pos] 341 local lang = buffer:get_lexer() 342 local eol = buffer.eol_mode == buffer.EOL_CRLF and _L['CRLF'] or _L['LF'] 343 local tabs = string.format( 344 '%s %d', buffer.use_tabs and _L['Tabs:'] or _L['Spaces:'], buffer.tab_width) 345 local encoding = buffer.encoding or '' 346 ui.buffer_statusbar_text = string.format( 347 text, _L['Line:'], line, max, _L['Col:'], col, lang, eol, tabs, encoding) 348end) 349 350-- Save buffer properties. 351local function save_buffer_state() 352 -- Save view state. 353 buffer._anchor, buffer._current_pos = buffer.anchor, buffer.current_pos 354 local n = buffer.main_selection 355 buffer._anchor_virtual_space = buffer.selection_n_anchor_virtual_space[n] 356 buffer._caret_virtual_space = buffer.selection_n_caret_virtual_space[n] 357 buffer._top_line = view:doc_line_from_visible(view.first_visible_line) 358 buffer._x_offset = view.x_offset 359 -- Save fold state. 360 local folds, i = {}, view:contracted_fold_next(1) 361 while i >= 1 do folds[#folds + 1], i = i, view:contracted_fold_next(i + 1) end 362 buffer._folds = folds 363end 364events.connect(events.BUFFER_BEFORE_SWITCH, save_buffer_state) 365events.connect(events.FILE_BEFORE_RELOAD, save_buffer_state) 366 367-- Restore buffer properties. 368local function restore_buffer_state() 369 if not buffer._folds then return end 370 -- Restore fold state. 371 for _, line in ipairs(buffer._folds) do view:toggle_fold(line) end 372 -- Restore view state. 373 buffer:set_sel(buffer._anchor, buffer._current_pos) 374 buffer.selection_n_anchor_virtual_space[1] = buffer._anchor_virtual_space 375 buffer.selection_n_caret_virtual_space[1] = buffer._caret_virtual_space 376 buffer:choose_caret_x() 377 local _top_line, top_line = buffer._top_line, view.first_visible_line 378 view:line_scroll(0, view:visible_from_doc_line(_top_line) - top_line) 379 view.x_offset = buffer._x_offset or 0 380end 381events.connect(events.BUFFER_AFTER_SWITCH, restore_buffer_state) 382events.connect(events.FILE_AFTER_RELOAD, restore_buffer_state) 383 384-- Updates titlebar and statusbar. 385local function update_bars() 386 set_title() 387 events.emit(events.UPDATE_UI, 3) 388end 389events.connect(events.BUFFER_NEW, update_bars) 390events.connect(events.BUFFER_AFTER_SWITCH, update_bars) 391events.connect(events.VIEW_AFTER_SWITCH, update_bars) 392 393-- Save view state. 394local function save_view_state() 395 buffer._view_ws, buffer._wrap_mode = view.view_ws, view.wrap_mode 396 buffer._margin_type_n, buffer._margin_width_n = {}, {} 397 for i = 1, view.margins do 398 buffer._margin_type_n[i] = view.margin_type_n[i] 399 buffer._margin_width_n[i] = view.margin_width_n[i] 400 end 401end 402events.connect(events.BUFFER_BEFORE_SWITCH, save_view_state) 403events.connect(events.VIEW_BEFORE_SWITCH, save_view_state) 404 405-- Restore view state. 406local function restore_view_state() 407 if not buffer._margin_type_n then return end 408 view.view_ws, view.wrap_mode = buffer._view_ws, buffer._wrap_mode 409 for i = 1, view.margins do 410 view.margin_type_n[i] = buffer._margin_type_n[i] 411 view.margin_width_n[i] = buffer._margin_width_n[i] 412 end 413end 414events.connect(events.BUFFER_AFTER_SWITCH, restore_view_state) 415events.connect(events.VIEW_AFTER_SWITCH, restore_view_state) 416 417events.connect( 418 events.RESET_AFTER, function() ui.statusbar_text = _L['Lua reset'] end) 419 420-- Prompts for confirmation if any buffers are modified. 421events.connect(events.QUIT, function() 422 local utf8_list = {} 423 for _, buffer in ipairs(_BUFFERS) do 424 if not buffer.modify then goto continue end 425 local filename = buffer.filename or buffer._type or _L['Untitled'] 426 if buffer.filename then filename = filename:iconv('UTF-8', _CHARSET) end 427 utf8_list[#utf8_list + 1] = filename 428 ::continue:: 429 end 430 if #utf8_list == 0 then return end 431 local button = ui.dialogs.msgbox{ 432 title = _L['Quit without saving?'], 433 text = _L['The following buffers are unsaved:'], 434 informative_text = table.concat(utf8_list, '\n'), 435 icon = 'gtk-dialog-question', button1 = _L['Cancel'], 436 button2 = _L['Quit without saving'], 437 width = CURSES and ui.size[1] - 2 or nil 438 } 439 if button ~= 2 then return true end -- prevent quit 440end) 441 442-- Keeps track of, and switches back to the previous buffer after buffer close. 443events.connect( 444 events.BUFFER_BEFORE_SWITCH, function() view._prev_buffer = buffer end) 445events.connect(events.BUFFER_DELETED, function() 446 if _BUFFERS[view._prev_buffer] and buffer ~= view._prev_buffer then 447 restore_view_state() -- events.BUFFER_AFTER_SWITCH is not emitted in time 448 view:goto_buffer(view._prev_buffer) 449 end 450end) 451 452-- Properly handle clipboard text between views in curses, enables and disables 453-- mouse mode, and focuses and resizes views based on mouse events. 454if CURSES then 455 events.connect(events.VIEW_BEFORE_SWITCH, function() 456 ui._clipboard_text = ui.clipboard_text 457 end) 458 events.connect(events.VIEW_AFTER_SWITCH, function() 459 ui.clipboard_text = ui._clipboard_text 460 end) 461 462 if not WIN32 then 463 local function enable_mouse() io.stdout:write("\x1b[?1002h"):flush() end 464 local function disable_mouse() io.stdout:write("\x1b[?1002l"):flush() end 465 enable_mouse() 466 events.connect(events.SUSPEND, disable_mouse) 467 events.connect(events.RESUME, enable_mouse) 468 events.connect(events.QUIT, disable_mouse) 469 end 470 471 -- Retrieves the view or split at the given terminal coordinates. 472 -- @param view View or split to test for coordinates within. 473 -- @param y The y terminal coordinate. 474 -- @param x The x terminal coordinate. 475 local function get_view(view, y, x) 476 if not view[1] and not view[2] then return view end 477 local vertical, size = view.vertical, view.size 478 if vertical and x < size or not vertical and y < size then 479 return get_view(view[1], y, x) 480 elseif vertical and x > size or not vertical and y > size then 481 -- Zero y or x relative to the other view based on split orientation. 482 return get_view( 483 view[2], vertical and y or y - size - 1, vertical and x - size - 1 or x) 484 else 485 return view -- in-between views; return the split itself 486 end 487 end 488 489 local resize 490 events.connect(events.MOUSE, function(event, button, y, x) 491 if event == view.MOUSE_RELEASE or button ~= 1 then return end 492 if event == view.MOUSE_PRESS then 493 local view = get_view(ui.get_split_table(), y - 1, x) -- title is at y = 1 494 if not view[1] and not view[2] then 495 ui.goto_view(view) 496 resize = nil 497 else 498 resize = function(y2, x2) 499 local i = getmetatable(view[1]) == getmetatable(_G.view) and 1 or 2 500 view[i].size = view.size + (view.vertical and x2 - x or y2 - y) 501 end 502 end 503 elseif resize then 504 resize(y, x) 505 end 506 return resize ~= nil -- false resends mouse event to current view 507 end) 508end 509 510events.connect(events.INITIALIZED, function() 511 local lua_error = (not WIN32 and '^/' or '^%a:[/\\]') .. '.-%.lua:%d+:' 512 -- Print internal Lua error messages as they are reported. 513 -- Attempt to mimic the Lua interpreter's error message format so tools that 514 -- look for it can recognize these errors too. 515 events.connect(events.ERROR, function(text) 516 if text and text:find(lua_error) then text = 'lua: ' .. text end 517 ui.print(text) 518 end) 519end) 520 521--[[ The tables below were defined in C. 522 523--- 524-- A table of menus defining a menubar. (Write-only). 525-- This is a low-level field. You probably want to use the higher-level 526-- `textadept.menu.menubar`. 527-- @see textadept.menu.menubar 528-- @class table 529-- @name menubar 530local menubar 531 532--- 533-- A table containing the width and height pixel values of Textadept's window. 534-- @class table 535-- @name size 536local size 537 538The functions below are Lua C functions. 539 540--- 541-- Low-level function for prompting the user with a [gtdialog][] of kind *kind* 542-- with the given string and table arguments, returning a formatted string of 543-- the dialog's output. 544-- You probably want to use the higher-level functions in the [`ui.dialogs`]() 545-- module. 546-- Table arguments containing strings are allowed and expanded in place. This is 547-- useful for filtered list dialogs with many items. 548-- 549-- [gtdialog]: https://orbitalquark.github.io/gtdialog/manual.html 550-- @param kind The kind of gtdialog. 551-- @param ... Parameters to the gtdialog. 552-- @return string gtdialog result. 553-- @class function 554-- @name dialog 555local dialog 556 557--- 558-- Returns a split table that contains Textadept's current split view structure. 559-- This is primarily used in session saving. 560-- @return table of split views. Each split view entry is a table with 4 561-- fields: `1`, `2`, `vertical`, and `size`. `1` and `2` have values of either 562-- nested split view entries or the views themselves; `vertical` is a flag 563-- that indicates if the split is vertical or not; and `size` is the integer 564-- position of the split resizer. 565-- @class function 566-- @name get_split_table 567local get_split_table 568 569--- 570-- Shifts to view *view* or the view *view* number of views relative to the 571-- current one. 572-- Emits `VIEW_BEFORE_SWITCH` and `VIEW_AFTER_SWITCH` events. 573-- @param view A view or relative view number (typically 1 or -1). 574-- @see _G._VIEWS 575-- @see events.VIEW_BEFORE_SWITCH 576-- @see events.VIEW_AFTER_SWITCH 577-- @class function 578-- @name goto_view 579local goto_view 580 581--- 582-- Low-level function for creating a menu from table *menu_table* and returning 583-- the userdata. 584-- You probably want to use the higher-level `textadept.menu.menubar`, 585-- `textadept.menu.context_menu`, or `textadept.menu.tab_context_menu` tables. 586-- Emits a `MENU_CLICKED` event when a menu item is selected. 587-- @param menu_table A table defining the menu. It is an ordered list of tables 588-- with a string menu item, integer menu ID, and optional GDK keycode and 589-- modifier mask. The latter two are used to display key shortcuts in the 590-- menu. '_' characters are treated as a menu mnemonics. If the menu item is 591-- empty, a menu separator item is created. Submenus are just nested 592-- menu-structure tables. Their title text is defined with a `title` key. 593-- @usage ui.menu{ {'_New', 1}, {'_Open', 2}, {''}, {'_Quit', 4} } 594-- @usage ui.menu{ {'_New', 1, string.byte('n'), 4} } -- 'Ctrl+N' 595-- @see events.MENU_CLICKED 596-- @see textadept.menu.menubar 597-- @see textadept.menu.context_menu 598-- @see textadept.menu.tab_context_menu 599-- @class function 600-- @name menu 601local menu 602 603--- 604-- Processes pending GTK events, including reading from spawned processes. 605-- This function is primarily used in unit tests. 606-- @class function 607-- @name update 608local update 609]] 610