-- Copyright 2007-2012 Mitchell mitchell.att.foicica.com. See LICENSE. local M = {} --[[ This comment is for LuaDoc. --- -- Textadept's core event structure and handlers. -- -- ## Overview -- -- Events occur when you do things like create a new buffer, press a key, click -- on a menu, etc. You can even emit events yourself using Lua. Each event has a -- set of event handlers, which are simply Lua functions called in the order -- they were connected to an event. This enables dynamically loaded modules to -- connect to events. -- -- Events themselves are nothing special. They do not have to be declared in -- order to be used. They are simply strings containing an arbitrary event name. -- When an event of this name is emitted, either by Textadept or you, all event -- handlers assigned to it are run. Events can be given any number of arguments. -- These arguments will be passed to the event's handler functions. If an event -- handler returns a `true` or `false` boolean value explicitly, no subsequent -- handlers are called. This is useful if you want to stop the propagation of an -- event like a keypress if it has already been handled. -- -- @field APPLEEVENT_ODOC (string) -- Called when Mac OSX tells Textadept to open a document. -- Arguments: -- -- * _`uri`_: The UTF-8-encoded URI to open. -- @field AUTO_C_CHAR_DELETED (string) -- Called when deleting a character while the autocompletion list is active. -- @field AUTO_C_RELEASE (string) -- Called when canceling the autocompletion list. -- @field AUTO_C_SELECTION (string) -- Called when selecting an item in the autocompletion list and before -- inserting the selection. -- Automatic insertion can be cancelled by calling -- [`buffer:auto_c_cancel()`][] before returning from the event handler. -- Arguments: -- -- * _`text`_: The text of the selection. -- * _`position`_: The position in the buffer of the beginning of the -- autocompleted word. -- @field BUFFER_AFTER_SWITCH (string) -- Called right after switching to another buffer. -- Emitted by [`view:goto_buffer()`][]. -- @field BUFFER_BEFORE_SWITCH (string) -- Called right before switching to another buffer. -- Emitted by [`view:goto_buffer()`][]. -- @field BUFFER_DELETED (string) -- Called after deleting a buffer. -- Emitted by [`buffer:delete()`][]. -- @field BUFFER_NEW (string) -- Called after creating a new buffer. -- Emitted on startup and by [`new_buffer()`][]. -- @field CALL_TIP_CLICK (string) -- Called when clicking on a calltip. -- Arguments: -- -- * _`position`_: `1` if the up arrow was clicked, 2 if the down arrow was -- clicked, and 0 otherwise. -- @field CHAR_ADDED (string) -- Called after adding an ordinary text character to the buffer. -- Arguments: -- -- * _`ch`_: The text character byte. -- @field COMMAND_ENTRY_COMMAND (string) -- Called to run the command entered into the Command Entry. -- If any handler returns `true`, the Command Entry does not hide -- automatically. -- Arguments: -- -- * _`command`_: The command text. -- @field COMMAND_ENTRY_KEYPRESS (string) -- Called when pressing a key in the Command Entry. -- If any handler returns `true`, the key is not inserted into the entry. -- Arguments: -- -- * _`code`_: The numeric key code. -- * _`shift`_: The "Shift" modifier key is held down. -- * _`ctrl`_: The "Control"/"Command" modifier key is held down. -- * _`alt`_: The "Alt"/"Option" modifier key is held down. -- * _`meta`_: The "Control" modifier key on Mac OSX is held down. -- @field DOUBLE_CLICK (string) -- Called after double-clicking the mouse button. -- Arguments: -- -- * _`position`_: The position in the buffer double-clicked. -- * _`line`_: The line number double-clicked. -- * _`modifiers`_: A bit-mask of modifier keys held down. Modifiers are -- `_SCINTILLA.constants.SCMOD_ALT`, `_SCINTILLA.constants.SCMOD_CTRL`, -- `_SCINTILLA.constants.SCMOD_SHIFT`, and -- `_SCINTILLA.constants.SCMOD_META`. -- Note: If you set `buffer.rectangular_selection_modifier` to -- `_SCINTILLA.constants.SCMOD_CTRL`, the "Control" modifier is reported as -- *both* "Control" and "Alt" due to a Scintilla limitation with GTK+. -- @field DWELL_END (string) -- Called after a `DWELL_START` when the mouse moves, a key is pressed, etc. -- Arguments: -- -- * _`position`_: The position in the buffer closest to *x* and *y*. -- * _`x`_: The x-coordinate of the mouse in the view. -- * _`y`_: The y-coordinate of the mouse in the view. -- @field DWELL_START (string) -- Called after keeping the mouse stationary for the [dwell period][] -- Arguments: -- -- * _`position`_: The position in the buffer closest to *x* and *y*. -- * _`x`_: The x-coordinate of the mouse in the view. -- * _`y`_: The y-coordinate of the mouse in the view. -- @field ERROR (string) -- Called when an error occurs. -- Arguments: -- -- * _`text`_: The error text. -- @field FIND (string) -- Called to find text via the Find dialog box. -- Arguments: -- -- * _`text`_: The text to search for. -- * _`next`_: Whether or not to search forward. -- @field HOTSPOT_CLICK (string) -- Called when clicking on text that is in a style with the hotspot attribute -- set. -- Arguments: -- -- * _`position`_: The position in the buffer clicked. -- * _`modifiers`_: A bit-mask of modifier keys held down. Modifiers are -- `_SCINTILLA.constants.SCMOD_ALT`, `_SCINTILLA.constants.SCMOD_CTRL`, -- `_SCINTILLA.constants.SCMOD_SHIFT`, and -- `_SCINTILLA.constants.SCMOD_META`. -- Note: If you set `buffer.rectangular_selection_modifier` to -- `_SCINTILLA.constants.SCMOD_CTRL`, the "Control" modifier is reported as -- *both* "Control" and "Alt" due to a Scintilla limitation with GTK+. -- @field HOTSPOT_DOUBLE_CLICK (string) -- Called when double-clicking on text that is in a style with the hotspot -- attribute set. -- Arguments: -- -- * _`position`_: The position in the buffer double-clicked. -- * _`modifiers`_: A bit-mask of modifier keys held down. Modifiers are -- `_SCINTILLA.constants.SCMOD_ALT`, `_SCINTILLA.constants.SCMOD_CTRL`, -- `_SCINTILLA.constants.SCMOD_SHIFT`, and -- `_SCINTILLA.constants.SCMOD_META`. -- Note: If you set `buffer.rectangular_selection_modifier` to -- `_SCINTILLA.constants.SCMOD_CTRL`, the "Control" modifier is reported as -- *both* "Control" and "Alt" due to a Scintilla limitation with GTK+. -- @field HOTSPOT_RELEASE_CLICK (string) -- Called after releasing the mouse after clicking on text that was in a style -- with the hotspot attribute set. -- Arguments: -- -- * _`position`_: The position in the buffer unclicked. -- @field INDICATOR_CLICK (string) -- Called when clicking the mouse on text that has an indicator. -- Arguments: -- -- * _`position`_: The position in the buffer clicked. -- * _`modifiers`_: A bit-mask of modifier keys held down. Modifiers are -- `_SCINTILLA.constants.SCMOD_ALT`, `_SCINTILLA.constants.SCMOD_CTRL`, -- `_SCINTILLA.constants.SCMOD_SHIFT`, and -- `_SCINTILLA.constants.SCMOD_META`. -- Note: If you set `buffer.rectangular_selection_modifier` to -- `_SCINTILLA.constants.SCMOD_CTRL`, the "Control" modifier is reported as -- *both* "Control" and "Alt" due to a Scintilla limitation with GTK+. -- @field INDICATOR_RELEASE (string) -- Called after releasing the mouse after clicking on text that had an -- indicator. -- Arguments: -- -- * _`position`_: The position in the buffer unclicked. -- @field KEYPRESS (string) -- Called when pressing a key. -- If any handler returns `true`, the key is not inserted into the buffer. -- Arguments: -- -- * _`code`_: The numeric key code. -- * _`shift`_: The "Shift" modifier key is held down. -- * _`ctrl`_: The "Control"/"Command" modifier key is held down. -- * _`alt`_: The "Alt"/"Option" modifier key is held down. -- * _`meta`_: The "Control" modifier key on Mac OSX is held down. -- @field MARGIN_CLICK (string) -- Called when clicking the mouse inside a margin. -- Arguments: -- -- * _`margin`_: The margin number clicked. -- * _`position`_: The position of the start of the line in the buffer whose -- margin line was clicked. -- * _`modifiers`_: A bit-mask of modifier keys held down. Modifiers are -- `_SCINTILLA.constants.SCMOD_ALT`, `_SCINTILLA.constants.SCMOD_CTRL`, -- `_SCINTILLA.constants.SCMOD_SHIFT`, and -- `_SCINTILLA.constants.SCMOD_META`. -- Note: If you set `buffer.rectangular_selection_modifier` to -- `_SCINTILLA.constants.SCMOD_CTRL`, the "Control" modifier is reported as -- *both* "Control" and "Alt" due to a Scintilla limitation with GTK+. -- @field MENU_CLICKED (string) -- Called after selecting a menu item. -- Arguments: -- -- * _`menu_id`_: The numeric ID of the menu item set in [`gui.menu()`][]. -- @field QUIT (string) -- Called when quitting Textadept. -- When connecting to this event, connect with an index of 1 or the handler -- will be ignored. -- Emitted by [`quit()`][]. -- @field REPLACE (string) -- Called to replace selected (found) text. -- Arguments: -- -- * _`text`_: The text to replace the selected text with. -- @field REPLACE_ALL (string) -- Called to replace all occurrences of found text. -- Arguments: -- -- * _`find_text`_: The text to search for. -- * _`repl_text`_: The text to replace found text with. -- @field RESET_AFTER (string) -- Called after resetting the Lua state. -- Emitted by [`reset()`][]. -- @field RESET_BEFORE (string) -- Called before resetting the Lua state. -- Emitted by [`reset()`][]. -- @field SAVE_POINT_LEFT (string) -- Called after leaving a save point. -- @field SAVE_POINT_REACHED (string) -- Called after reaching a save point. -- @field UPDATE_UI (string) -- Called when the text, styling, or selection range in the buffer changes. -- @field URI_DROPPED (string) -- Called after dragging and dropping a URI such as a file name onto the view. -- Arguments: -- -- * _`text`_: The UTF-8-encoded URI text. -- @field USER_LIST_SELECTION (string) -- Called after selecting an item in a user list. -- Arguments: -- -- * _`list_type`_: The *list_type* from [`buffer:user_list_show()`][]. -- * _`text`_: The text of the selection. -- * _`position`_: The position in the buffer the list was displayed at. -- @field VIEW_NEW (string) -- Called after creating a new view. -- Emitted on startup and by [`view:split()`][]. -- @field VIEW_BEFORE_SWITCH (string) -- Called right before switching to another view. -- Emitted by [`gui.goto_view()`][]. -- @field VIEW_AFTER_SWITCH (string) -- Called right after switching to another view. -- Emitted by [`gui.goto_view()`][]. -- -- [`buffer:auto_c_cancel()`]: buffer.html#auto_c_cancel -- [`view:goto_buffer()`]: view.html#goto_buffer -- [`new_buffer()`]: _G.html#new_buffer -- [`buffer:delete()`]: buffer.html#delete -- [dwell period]: buffer.html#mouse_dwell_time -- [`gui.menu()`]: gui.html#menu -- [`quit()`]: _G.html#quit -- [`reset()`]: _G.html#reset -- [`buffer:user_list_show()`]: buffer.html#user_list_show -- [`view:split()`]: view.html#split -- [`gui.goto_view()`]: gui.html#goto_view module('events')]] --- -- Map of event names with tables of the functions connected to them. -- @class table -- @name handlers M.handlers = {} --- -- Adds function *f* to the set of event handlers for *event* at position -- *index*, returning a handler ID for *f*. *event* is an arbitrary event name -- that does not need to have been previously defined. -- @param event The string event name. -- @param f The Lua function to connect to *event*. -- @param index Optional index to insert the handler into. -- @return handler ID. -- @usage events.connect('my_event', function(msg) gui.print(msg) end) -- @see disconnect -- @name connect function M.connect(event, f, index) if not event then error(_L['Undefined event name']) end if not M.handlers[event] then M.handlers[event] = {} end local h = M.handlers[event] if index then table.insert(h, index, f) else h[#h + 1] = f end return index or #h end --- -- Removes handler ID *id*, returned by `events.connect()`, from the set of -- event handlers for *event*. -- @param event The string event name. -- @param id ID of the handler returned by `events.connect()`. -- @see connect -- @name disconnect function M.disconnect(event, id) if not M.handlers[event] then return end table.remove(M.handlers[event], id) end local error_emitted = false --- -- Sequentially calls all handler functions for *event* with the given -- arguments. -- *event* is an arbitrary event name that does not need to have been previously -- defined. If any handler explicitly returns `true` or `false`, the event is -- not propagated any further, iteration ceases, and `emit()` returns that -- value. This is useful for stopping the propagation of an event like a -- keypress after it has been handled. -- @param event The string event name. -- @param ... Arguments passed to the handler. -- @return `true` or `false` if any handler explicitly returned such; `nil` -- otherwise. -- @usage events.emit('my_event', 'my message') -- @name emit function M.emit(event, ...) if not event then error(_L['Undefined event name']) end local h = M.handlers[event] if not h then return end local pcall, table_unpack, type = pcall, table.unpack, type for i = 1, #h do local ok, result = pcall(h[i], table_unpack{...}) if not ok then if not error_emitted then error_emitted = true M.emit(events.ERROR, result) error_emitted = false else io.stderr:write(result) end end if type(result) == 'boolean' then return result end end end --- Map of Scintilla notifications to their handlers. local c = _SCINTILLA.constants local scnotifications = { [c.SCN_CHARADDED] = {'char_added', 'ch'}, [c.SCN_SAVEPOINTREACHED] = {'save_point_reached'}, [c.SCN_SAVEPOINTLEFT] = {'save_point_left'}, [c.SCN_DOUBLECLICK] = {'double_click', 'position', 'line', 'modifiers'}, [c.SCN_UPDATEUI] = {'update_ui'}, [c.SCN_MODIFIED] = {'modified', 'modification_type'}, -- undocumented [c.SCN_MARGINCLICK] = {'margin_click', 'margin', 'position', 'modifiers'}, [c.SCN_USERLISTSELECTION] = { 'user_list_selection', 'wParam', 'text', 'position' }, [c.SCN_URIDROPPED] = {'uri_dropped', 'text'}, [c.SCN_DWELLSTART] = {'dwell_start', 'position', 'x', 'y'}, [c.SCN_DWELLEND] = {'dwell_end', 'position', 'x', 'y'}, [c.SCN_HOTSPOTCLICK] = {'hotspot_click', 'position', 'modifiers'}, [c.SCN_HOTSPOTDOUBLECLICK] = { 'hotspot_double_click', 'position', 'modifiers' }, [c.SCN_CALLTIPCLICK] = {'call_tip_click', 'position'}, [c.SCN_AUTOCSELECTION] = {'auto_c_selection', 'text', 'position'}, [c.SCN_INDICATORCLICK] = {'indicator_click', 'position', 'modifiers'}, [c.SCN_INDICATORRELEASE] = {'indicator_release', 'position'}, [c.SCN_AUTOCCANCELLED] = {'auto_c_cancelled'}, [c.SCN_AUTOCCHARDELETED] = {'auto_c_char_deleted'}, [c.SCN_HOTSPOTRELEASECLICK] = {'hotspot_release_click', 'position'}, } -- Handles Scintilla notifications. M.connect('SCN', function(n) local f = scnotifications[n.code] if not f then return end local args = {} for i = 2, #f do args[i - 1] = n[f[i]] end return M.emit(f[1], table.unpack(args)) end) -- Set event constants. for _, n in pairs(scnotifications) do M[n[1]:upper()] = n[1] end local ta_events = { 'appleevent_odoc', 'buffer_after_switch', 'buffer_before_switch', 'buffer_deleted', 'buffer_new', 'command_entry_command', 'command_entry_keypress', 'error', 'find', 'keypress', 'menu_clicked', 'quit', 'replace', 'replace_all', 'reset_after', 'reset_before', 'view_after_switch', 'view_before_switch', 'view_new' } for _, e in pairs(ta_events) do M[e:upper()] = e end return M