1-- Copyright 2007-2021 Mitchell. See LICENSE.
2
3local M = {}
4
5--[[ This comment is for LuaDoc.
6---
7-- Textadept's core event structure and handlers.
8--
9-- Textadept emits events when you do things like create a new buffer, press a
10-- key, click on a menu, etc. You can even emit events yourself using Lua. Each
11-- event has a set of event handlers, which are simply Lua functions called in
12-- the order they were connected to an event. For example, if you created a
13-- module that needs to do something each time Textadept creates a new buffer,
14-- connect a Lua function to the [`events.BUFFER_NEW`]() event:
15--
16--     events.connect(events.BUFFER_NEW, function()
17--       -- Do something here.
18--     end)
19--
20-- Events themselves are nothing special. You do not have to declare one before
21-- using it. Events are simply strings containing arbitrary event names. When
22-- either you or Textadept emits an event, Textadept runs all event handlers
23-- connected to the event, passing any given arguments to the event's handler
24-- functions. If an event handler explicitly returns a value that is not `nil`,
25-- Textadept will not call subsequent handlers. This is useful if you want to
26-- stop the propagation of an event like a keypress if your event handler
27-- handled it, or if you want to use the event framework to pass values.
28--
29-- @field APPLEEVENT_ODOC (string)
30--   Emitted when macOS tells Textadept to open a file.
31--   Arguments:
32--
33--   * _`uri`_: The UTF-8-encoded URI to open.
34-- @field AUTO_C_CHAR_DELETED (string)
35--   Emitted after deleting a character while an autocompletion or user list is
36--   active.
37-- @field AUTO_C_CANCELED (string)
38--   Emitted when canceling an autocompletion or user list.
39-- @field AUTO_C_COMPLETED (string)
40--   Emitted after inserting an item from an autocompletion list into the
41--   buffer.
42--   Arguments:
43--
44--   * _`text`_: The selection's text.
45--   * _`position`_: The autocompleted word's beginning position.
46-- @field AUTO_C_SELECTION (string)
47--   Emitted after selecting an item from an autocompletion list, but before
48--   inserting that item into the buffer.
49--   Automatic insertion can be canceled by calling
50--   [`buffer:auto_c_cancel()`]() before returning from the event handler.
51--   Arguments:
52--
53--   * _`text`_: The selection's text.
54--   * _`position`_: The autocompleted word's beginning position.
55-- @field AUTO_C_SELECTION_CHANGE (string)
56--   Emitted as items are highlighted in an autocompletion or user list.
57--   Arguments:
58--
59--   * _`id`_: Either the *id* from [`buffer.user_list_show()`]() or `0` for an
60--     autocompletion list.
61--   * _`text`_: The current selection's text.
62--   * _`position`_: The position the list was displayed at.
63-- @field BUFFER_AFTER_SWITCH (string)
64--   Emitted right after switching to another buffer.
65--   The buffer being switched to is `buffer`.
66--   Emitted by [`view.goto_buffer()`]().
67-- @field BUFFER_BEFORE_SWITCH (string)
68--   Emitted right before switching to another buffer.
69--   The buffer being switched from is `buffer`.
70--   Emitted by [`view.goto_buffer()`]().
71-- @field BUFFER_DELETED (string)
72--   Emitted after deleting a buffer.
73--   Emitted by [`buffer.delete()`]().
74-- @field BUFFER_NEW (string)
75--   Emitted after creating a new buffer.
76--   The new buffer is `buffer`.
77--   Emitted on startup and by [`buffer.new()`]().
78-- @field CALL_TIP_CLICK (string)
79--   Emitted when clicking on a calltip.
80--   Arguments:
81--
82--   * _`position`_: `1` if the up arrow was clicked, 2 if the down arrow was
83--     clicked, and 0 otherwise.
84-- @field CHAR_ADDED (string)
85--   Emitted after the user types a text character into the buffer.
86--   Arguments:
87--
88--   * _`code`_: The text character's character code.
89-- @field COMMAND_TEXT_CHANGED (string)
90--   Emitted when the text in the command entry changes.
91--   `ui.command_entry:get_text()` returns the current text.
92-- @field DOUBLE_CLICK (string)
93--   Emitted after double-clicking the mouse button.
94--   Arguments:
95--
96--   * _`position`_: The position double-clicked.
97--   * _`line`_: The line number of the position double-clicked.
98--   * _`modifiers`_: A bit-mask of any modifier keys held down:
99--     `view.MOD_CTRL`, `view.MOD_SHIFT`, `view.MOD_ALT`, and `view.MOD_META`.
100--     On macOS, the Command modifier key is reported as `view.MOD_CTRL` and
101--     Ctrl is `view.MOD_META`.
102--     Note: If you set `view.rectangular_selection_modifier` to
103--     `view.MOD_CTRL`, the "Control" modifier is reported as *both* "Control"
104--     and "Alt" due to a Scintilla limitation with GTK.
105-- @field CSI (string)
106--   Emitted when the terminal version receives an unrecognized CSI sequence.
107--   Arguments:
108--
109--   * _`cmd`_: The 24-bit CSI command value. The lowest byte contains the
110--     command byte. The second lowest byte contains the leading byte, if any
111--     (e.g. '?'). The third lowest byte contains the intermediate byte, if any
112--     (e.g. '$').
113--   * _`args`_: Table of numeric arguments of the CSI sequence.
114-- @field DWELL_END (string)
115--   Emitted after `DWELL_START` when the user moves the mouse, presses a key,
116--   or scrolls the view.
117--   Arguments:
118--
119--   * _`position`_: The position closest to *x* and *y*.
120--   * _`x`_: The x-coordinate of the mouse in the view.
121--   * _`y`_: The y-coordinate of the mouse in the view.
122-- @field DWELL_START (string)
123--   Emitted when the mouse is stationary for [`view.mouse_dwell_time`]()
124--   milliseconds.
125--   Arguments:
126--
127--   * _`position`_: The position closest to *x* and *y*.
128--   * _`x`_: The x-coordinate of the mouse in the view.
129--   * _`y`_: The y-coordinate of the mouse in the view.
130-- @field ERROR (string)
131--   Emitted when an error occurs.
132--   Arguments:
133--
134--   * _`text`_: The error message text.
135-- @field FIND (string)
136--   Emitted to find text via the Find & Replace Pane.
137--   Arguments:
138--
139--   * _`text`_: The text to search for.
140--   * _`next`_: Whether or not to search forward.
141-- @field FIND_TEXT_CHANGED (string)
142--   Emitted when the text in the "Find" field of the Find & Replace Pane
143--   changes.
144--   `ui.find.find_entry_text` contains the current text.
145-- @field FOCUS (string)
146--   Emitted when Textadept receives focus.
147--   This event is never emitted when Textadept is running in the terminal.
148-- @field INDICATOR_CLICK (string)
149--   Emitted when clicking the mouse on text that has an indicator present.
150--   Arguments:
151--
152--   * _`position`_: The clicked text's position.
153--   * _`modifiers`_: A bit-mask of any modifier keys held down:
154--     `view.MOD_CTRL`, `view.MOD_SHIFT`, `view.MOD_ALT`, and `view.MOD_META`.
155--     On macOS, the Command modifier key is reported as `view.MOD_CTRL` and
156--     Ctrl is `view.MOD_META`.
157--     Note: If you set `view.rectangular_selection_modifier` to
158--     `view.MOD_CTRL`, the "Control" modifier is reported as *both* "Control"
159--     and "Alt" due to a Scintilla limitation with GTK.
160-- @field INDICATOR_RELEASE (string)
161--   Emitted when releasing the mouse after clicking on text that has an
162--   indicator present.
163--   Arguments:
164--
165--   * _`position`_: The clicked text's position.
166-- @field INITIALIZED (string)
167--   Emitted after Textadept finishes initializing.
168-- @field KEYPRESS (string)
169--   Emitted when pressing a key.
170--   If any handler returns `true`, the key is not inserted into the buffer.
171--   Arguments:
172--
173--   * _`code`_: The numeric key code.
174--   * _`shift`_: The "Shift" modifier key is held down.
175--   * _`ctrl`_: The "Control" modifier key is held down.
176--   * _`alt`_: The "Alt"/"Option" modifier key is held down.
177--   * _`cmd`_: The "Command" modifier key on macOS is held down.
178--   * _`caps_lock`_: The "Caps Lock" modifier is on.
179-- @field MARGIN_CLICK (string)
180--   Emitted when clicking the mouse inside a sensitive margin.
181--   Arguments:
182--
183--   * _`margin`_: The margin number clicked.
184--   * _`position`_: The beginning position of the clicked margin's line.
185--   * _`modifiers`_: A bit-mask of any modifier keys held down:
186--     `view.MOD_CTRL`, `view.MOD_SHIFT`, `view.MOD_ALT`, and `view.MOD_META`.
187--     On macOS, the Command modifier key is reported as `view.MOD_CTRL` and
188--     Ctrl is `view.MOD_META`.
189--     Note: If you set `view.rectangular_selection_modifier` to
190--     `view.MOD_CTRL`, the "Control" modifier is reported as *both* "Control"
191--     and "Alt" due to a Scintilla limitation with GTK.
192-- @field MENU_CLICKED (string)
193--   Emitted after selecting a menu item.
194--   Arguments:
195--
196--   * _`menu_id`_: The numeric ID of the menu item, which was defined in
197--     [`ui.menu()`]().
198-- @field MOUSE (string)
199--   Emitted by the terminal version for an unhandled mouse event.
200--   A handler should return `true` if it handled the event. Otherwise Textadept
201--   will try again. (This side effect for a `false` or `nil` return is useful
202--   for sending the original mouse event to a different view that a handler
203--   has switched to.)
204--   Arguments:
205--
206--   * _`event`_: The mouse event: `view.MOUSE_PRESS`, `view.MOUSE_DRAG`, or
207--     `view.MOUSE_RELEASE`.
208--   * _`button`_: The mouse button number.
209--   * _`y`_: The y-coordinate of the mouse event, starting from 1.
210--   * _`x`_: The x-coordinate of the mouse event, starting from 1.
211--   * _`shift`_: The "Shift" modifier key is held down.
212--   * _`ctrl`_: The "Control" modifier key is held down.
213--   * _`alt`_: The "Alt"/"Option" modifier key is held down.
214-- @field QUIT (string)
215--   Emitted when quitting Textadept.
216--   When connecting to this event, connect with an index of 1 if the handler
217--   needs to run before Textadept closes all open buffers. If a handler returns
218--   `true`, Textadept does not quit. It is not recommended to return `false`
219--   from a quit handler, as that may interfere with Textadept's normal shutdown
220--   procedure.
221--   Emitted by [`quit()`]().
222-- @field REPLACE (string)
223--   Emitted to replace selected (found) text.
224--   Arguments:
225--
226--   * _`text`_: The replacement text.
227-- @field REPLACE_ALL (string)
228--   Emitted to replace all occurrences of found text.
229--   Arguments:
230--
231--   * _`find_text`_: The text to search for.
232--   * _`repl_text`_: The replacement text.
233-- @field RESET_AFTER (string)
234--   Emitted after resetting Textadept's Lua state.
235--   Emitted by [`reset()`]().
236--   Arguments:
237--
238--   * _`persist`_: Table of data persisted by `events.RESET_BEFORE`. All
239--     handlers will have access to this same table.
240-- @field RESET_BEFORE (string)
241--   Emitted before resetting Textadept's Lua state.
242--   Emitted by [`reset()`]().
243--   Arguments:
244--
245--   * _`persist`_: Table to store persistent data in for use by
246--     `events.RESET_AFTER`. All handlers will have access to this same table.
247-- @field RESUME (string)
248--   Emitted when resuming Textadept from a suspended state.
249--   This event is only emitted by the terminal version.
250-- @field SAVE_POINT_LEFT (string)
251--   Emitted after leaving a save point.
252-- @field SAVE_POINT_REACHED (string)
253--   Emitted after reaching a save point.
254-- @field SUSPEND (string)
255--   Emitted when suspending Textadept. If any handler returns `true`, Textadept
256--   does not suspend.
257--   This event is only emitted by the terminal version.
258-- @field TAB_CLICKED (string)
259--   Emitted when the user clicks on a buffer tab.
260--   When connecting to this event, connect with an index of 1 if the handler
261--   needs to run before Textadept switches between buffers.
262--   Note that Textadept always displays a context menu on right-click.
263--   Arguments:
264--
265--   * _`index`_: The numeric index of the clicked tab.
266--   * _`button`_: The mouse button number that was clicked, either `1` (left
267--     button), `2` (middle button), `3` (right button), `4` (wheel up), or `5`
268--    (wheel down).
269--   * _`shift`_: The "Shift" modifier key is held down.
270--   * _`ctrl`_: The "Control" modifier key is held down.
271--   * _`alt`_: The "Alt"/"Option" modifier key is held down.
272--   * _`cmd`_: The "Command" modifier key on macOS is held down.
273-- @field UNFOCUS (string)
274--   Emitted when Textadept loses focus.
275--   This event is never emitted when Textadept is running in the terminal.
276-- @field UPDATE_UI (string)
277--   Emitted after the view is visually updated.
278--   Arguments:
279--
280--   * _`updated`_: A bitmask of changes since the last update.
281--
282--     + `buffer.UPDATE_CONTENT`
283--       Buffer contents, styling, or markers have changed.
284--     + `buffer.UPDATE_SELECTION`
285--       Buffer selection has changed (including caret movement).
286--     + `view.UPDATE_V_SCROLL`
287--       Buffer has scrolled vertically.
288--     + `view.UPDATE_H_SCROLL`
289--       Buffer has scrolled horizontally.
290-- @field URI_DROPPED (string)
291--   Emitted after dragging and dropping a URI into a view.
292--   Arguments:
293--
294--   * _`text`_: The UTF-8-encoded URI dropped.
295-- @field USER_LIST_SELECTION (string)
296--   Emitted after selecting an item in a user list.
297--   Arguments:
298--
299--   * _`id`_: The *id* from [`buffer.user_list_show()`]().
300--   * _`text`_: The selection's text.
301--   * _`position`_: The position the list was displayed at.
302-- @field VIEW_NEW (string)
303--   Emitted after creating a new view.
304--   The new view is `view`.
305--   Emitted on startup and by [`view.split()`]().
306-- @field VIEW_BEFORE_SWITCH (string)
307--   Emitted right before switching to another view.
308--   The view being switched from is `view`.
309--   Emitted by [`ui.goto_view()`]().
310-- @field VIEW_AFTER_SWITCH (string)
311--   Emitted right after switching to another view.
312--   The view being switched to is `view`.
313--   Emitted by [`ui.goto_view()`]().
314-- @field ZOOM (string)
315--   Emitted after changing [`view.zoom`]().
316--   Emitted by [`view.zoom_in()`]() and [`view.zoom_out()`]().
317module('events')]]
318
319-- Map of event names to tables of handler functions.
320-- Handler tables are auto-created as needed.
321-- @class table
322-- @name handlers
323local handlers = setmetatable({}, {__index = function(t, k)
324  t[k] = {}
325  return t[k]
326end})
327
328---
329-- Adds function *f* to the set of event handlers for event *event* at position
330-- *index*.
331-- If *index* not given, appends *f* to the set of handlers. *event* may be any
332-- arbitrary string and does not need to have been previously defined.
333-- @param event The string event name.
334-- @param f The Lua function to connect to *event*.
335-- @param index Optional index to insert the handler into.
336-- @usage events.connect('my_event', function(msg) ui.print(msg) end)
337-- @see disconnect
338-- @name connect
339function M.connect(event, f, index)
340  assert_type(event, 'string', 1)
341  assert_type(f, 'function', 2)
342  assert_type(index, 'number/nil', 3)
343  M.disconnect(event, f) -- in case it already exists
344  table.insert(handlers[event], index or #handlers[event] + 1, f)
345end
346
347---
348-- Removes function *f* from the set of handlers for event *event*.
349-- @param event The string event name.
350-- @param f The Lua function connected to *event*.
351-- @see connect
352-- @name disconnect
353function M.disconnect(event, f)
354  assert_type(f, 'function', 2)
355  for i = 1, #handlers[assert_type(event, 'string', 1)] do
356    if handlers[event][i] == f then table.remove(handlers[event], i) break end
357  end
358end
359
360local error_emitted = false
361---
362-- Sequentially calls all handler functions for event *event* with the given
363-- arguments.
364-- *event* may be any arbitrary string and does not need to have been previously
365-- defined. If any handler explicitly returns a value that is not `nil`,
366-- `emit()` returns that value and ceases to call subsequent handlers. This is
367-- useful for stopping the propagation of an event like a keypress after it has
368-- been handled, or for passing back values from handlers.
369-- @param event The string event name.
370-- @param ... Arguments passed to the handler.
371-- @return `nil` unless any any handler explicitly returned a non-`nil` value;
372--   otherwise returns that value
373-- @usage events.emit('my_event', 'my message')
374-- @name emit
375function M.emit(event, ...)
376  local event_handlers = handlers[assert_type(event, 'string', 1)]
377  local i = 1
378  while i <= #event_handlers do
379    local handler = event_handlers[i]
380    local ok, result = pcall(handler, ...)
381    if not ok then
382      if not error_emitted then
383        error_emitted = true
384        M.emit(events.ERROR, result)
385        error_emitted = false
386      else
387        io.stderr:write(result) -- prevent infinite loop
388      end
389    end
390    if result ~= nil then return result end
391    if event_handlers[i] == handler then i = i + 1 end -- unless M.disconnect()
392  end
393end
394
395-- Handles Scintilla notifications.
396M.connect('SCN', function(notification)
397  local iface = _SCINTILLA.events[notification.code]
398  local args = {}
399  for i = 2, #iface do args[i - 1] = notification[iface[i]] end
400  return M.emit(iface[1], table.unpack(args))
401end)
402
403-- Set event constants.
404for _, v in pairs(_SCINTILLA.events) do M[v[1]:upper()] = v[1] end
405local textadept_events = {'appleevent_odoc','buffer_after_switch','buffer_before_switch','buffer_deleted','buffer_new','csi','command_text_changed','error','find','find_text_changed','focus','initialized','keypress','menu_clicked','mouse','quit','replace','replace_all','reset_after','reset_before','resume','suspend', 'tab_clicked','unfocus','view_after_switch','view_before_switch','view_new'}
406for _, v in pairs(textadept_events) do M[v:upper()] = v end
407
408return M
409