logo

oasis-root

Compiled tree of Oasis Linux based on own branch at <https://hacktivis.me/git/oasis/> git clone https://anongit.hacktivis.me/git/oasis-root.git

vis.lua (11713B)


  1. ---
  2. -- Vis Lua plugin API standard library
  3. -- @module vis
  4. ---
  5. -- @type Vis
  6. --- Map a new operator.
  7. --
  8. -- Sets up a mapping in normal, visual and operator pending mode.
  9. -- The operator function will receive the @{File}, @{Range} and position
  10. -- to operate on and is expected to return the new cursor position.
  11. --
  12. -- @tparam string key the key to associate with the new operator
  13. -- @tparam function operator the operator logic implemented as Lua function
  14. -- @tparam[opt] string help the single line help text as displayed in `:help`
  15. -- @treturn bool whether the new operator could be installed
  16. -- @usage
  17. -- vis:operator_new("gq", function(file, range, pos)
  18. -- local status, out, err = vis:pipe(file, range, "fmt")
  19. -- if status ~= 0 then
  20. -- vis:info(err)
  21. -- else
  22. -- file:delete(range)
  23. -- file:insert(range.start, out)
  24. -- end
  25. -- return range.start -- new cursor location
  26. -- end, "Formatting operator, filter range through fmt(1)")
  27. --
  28. vis.operator_new = function(vis, key, operator, help)
  29. local id = vis:operator_register(operator)
  30. if id < 0 then
  31. return false
  32. end
  33. local binding = function()
  34. vis:operator(id)
  35. end
  36. vis:map(vis.modes.NORMAL, key, binding, help)
  37. vis:map(vis.modes.VISUAL, key, binding, help)
  38. vis:map(vis.modes.OPERATOR_PENDING, key, binding, help)
  39. return true
  40. end
  41. --- Map a new motion.
  42. --
  43. -- Sets up a mapping in normal, visual and operator pending mode.
  44. -- The motion function will receive the @{Window} and an initial position
  45. -- (in bytes from the start of the file) as argument and is expected to
  46. -- return the resulting position.
  47. -- @tparam string key the key to associate with the new option
  48. -- @tparam function motion the motion logic implemented as Lua function
  49. -- @tparam[opt] string help the single line help text as displayed in `:help`
  50. -- @treturn bool whether the new motion could be installed
  51. -- @usage
  52. -- vis:motion_new("<C-l>", function(win, pos)
  53. -- return pos+1
  54. -- end, "Advance to next byte")
  55. --
  56. vis.motion_new = function(vis, key, motion, help)
  57. local id = vis:motion_register(motion)
  58. if id < 0 then
  59. return false
  60. end
  61. local binding = function()
  62. vis:motion(id)
  63. end
  64. vis:map(vis.modes.NORMAL, key, binding, help)
  65. vis:map(vis.modes.VISUAL, key, binding, help)
  66. vis:map(vis.modes.OPERATOR_PENDING, key, binding, help)
  67. return true
  68. end
  69. --- Map a new text object.
  70. --
  71. -- Sets up a mapping in visual and operator pending mode.
  72. -- The text object function will receive the @{Window} and an initial
  73. -- position(in bytes from the start of the file) as argument and is
  74. -- expected to return the resulting range or `nil`.
  75. -- @tparam string key the key associated with the new text object
  76. -- @tparam function textobject the text object logic implemented as Lua function
  77. -- @tparam[opt] string help the single line help text as displayed in `:help`
  78. -- @treturn bool whether the new text object could be installed
  79. -- @usage
  80. -- vis:textobject_new("<C-l>", function(win, pos)
  81. -- return pos, pos+1
  82. -- end, "Single byte text object")
  83. --
  84. vis.textobject_new = function(vis, key, textobject, help)
  85. local id = vis:textobject_register(textobject)
  86. if id < 0 then
  87. return false
  88. end
  89. local binding = function()
  90. vis:textobject(id)
  91. end
  92. vis:map(vis.modes.VISUAL, key, binding, help)
  93. vis:map(vis.modes.OPERATOR_PENDING, key, binding, help)
  94. return true
  95. end
  96. --- Check whether a Lua module exists
  97. --
  98. -- Checks whether a subsequent @{require} call will succeed.
  99. -- @tparam string name the module name to check
  100. -- @treturn bool whether the module was found
  101. vis.module_exist = function(_, name)
  102. for _, searcher in ipairs(package.searchers or package.loaders) do
  103. local loader = searcher(name)
  104. if type(loader) == 'function' then
  105. return true
  106. end
  107. end
  108. return false
  109. end
  110. vis.lexers = {}
  111. if not vis:module_exist('lpeg') then
  112. vis:info('WARNING: could not find lpeg module')
  113. elseif not vis:module_exist('lexer') then
  114. vis:info('WARNING: could not find lexer module')
  115. else
  116. vis.lexers = require('lexer')
  117. --- Cache of loaded lexers
  118. --
  119. -- Caching lexers causes lexer tables to be constructed once and reused
  120. -- during each HIGHLIGHT event. Additionally it allows to modify the lexer
  121. -- used for syntax highlighting from Lua code.
  122. local lexers = {}
  123. local load_lexer = vis.lexers.load
  124. vis.lexers.load = function (name, alt_name, cache)
  125. if cache and lexers[alt_name or name] then return lexers[alt_name or name] end
  126. local lexer = load_lexer(name, alt_name)
  127. if cache then lexers[alt_name or name] = lexer end
  128. return lexer
  129. end
  130. vis.lpeg = require('lpeg')
  131. end
  132. --- Events.
  133. --
  134. -- User scripts can subscribe Lua functions to certain events. Multiple functions
  135. -- can be associated with the same event. They will be called in the order they were
  136. -- registered. The first function which returns a non `nil` value terminates event
  137. -- propagation. The remaining event handler will not be called.
  138. --
  139. -- Keep in mind that the editor is blocked while the event handlers
  140. -- are being executed, avoid long running tasks.
  141. --
  142. -- @section Events
  143. --- Event names.
  144. --- @table events
  145. local events = {
  146. FILE_CLOSE = "Event::FILE_CLOSE", -- see @{file_close}
  147. FILE_OPEN = "Event::FILE_OPEN", -- see @{file_open}
  148. FILE_SAVE_POST = "Event::FILE_SAVE_POST", -- see @{file_save_post}
  149. FILE_SAVE_PRE = "Event::FILE_SAVE_PRE", -- see @{file_save_pre}
  150. INIT = "Event::INIT", -- see @{init}
  151. INPUT = "Event::INPUT", -- see @{input}
  152. QUIT = "Event::QUIT", -- see @{quit}
  153. START = "Event::START", -- see @{start}
  154. WIN_CLOSE = "Event::WIN_CLOSE", -- see @{win_close}
  155. WIN_HIGHLIGHT = "Event::WIN_HIGHLIGHT", -- see @{win_highlight}
  156. WIN_OPEN = "Event::WIN_OPEN", -- see @{win_open}
  157. WIN_STATUS = "Event::WIN_STATUS", -- see @{win_status}
  158. TERM_CSI = "Event::TERM_CSI", -- see @{term_csi}
  159. PROCESS_RESPONSE = "Event::PROCESS_RESPONSE", -- see @{process_response}
  160. UI_DRAW = "Event::UI_DRAW", -- see @{ui_draw}
  161. }
  162. events.file_close = function(...) events.emit(events.FILE_CLOSE, ...) end
  163. events.file_open = function(...) events.emit(events.FILE_OPEN, ...) end
  164. events.file_save_post = function(...) events.emit(events.FILE_SAVE_POST, ...) end
  165. events.file_save_pre = function(...) return events.emit(events.FILE_SAVE_PRE, ...) end
  166. events.init = function(...) events.emit(events.INIT, ...) end
  167. events.input = function(...) return events.emit(events.INPUT, ...) end
  168. events.quit = function(...) events.emit(events.QUIT, ...) end
  169. events.start = function(...) events.emit(events.START, ...) end
  170. events.win_close = function(...) events.emit(events.WIN_CLOSE, ...) end
  171. events.win_highlight = function(...) events.emit(events.WIN_HIGHLIGHT, ...) end
  172. events.win_open = function(...) events.emit(events.WIN_OPEN, ...) end
  173. events.win_status = function(...) events.emit(events.WIN_STATUS, ...) end
  174. events.term_csi = function(...) events.emit(events.TERM_CSI, ...) end
  175. events.process_response = function(...) events.emit(events.PROCESS_RESPONSE, ...) end
  176. events.ui_draw = function(...) events.emit(events.UI_DRAW, ...) end
  177. local handlers = {}
  178. --- Subscribe to an event.
  179. --
  180. -- Register an event handler.
  181. -- @tparam string event the event name
  182. -- @tparam function handler the event handler
  183. -- @tparam[opt] int index the index at which to insert the handler (1 is the highest priority)
  184. -- @usage
  185. -- vis.events.subscribe(vis.events.FILE_SAVE_PRE, function(file, path)
  186. -- -- do something useful
  187. -- return true
  188. -- end)
  189. events.subscribe = function(event, handler, index)
  190. if not event then error("Invalid event name") end
  191. if type(handler) ~= 'function' then error("Invalid event handler") end
  192. if not handlers[event] then handlers[event] = {} end
  193. events.unsubscribe(event, handler)
  194. table.insert(handlers[event], index or #handlers[event]+1, handler)
  195. end
  196. --- Unsubscribe from an event.
  197. --
  198. -- Remove a registered event handler.
  199. -- @tparam string event the event name
  200. -- @tparam function handler the event handler to unsubscribe
  201. -- @treturn bool whether the handler was successfully removed
  202. events.unsubscribe = function(event, handler)
  203. local h = handlers[event]
  204. if not h then return end
  205. for i = 1, #h do
  206. if h[i] == handler then
  207. table.remove(h, i)
  208. return true
  209. end
  210. end
  211. return false
  212. end
  213. --- Generate event.
  214. --
  215. -- Invokes all event handlers in the order they were registered.
  216. -- Passes all arguments to the handler. The first handler which returns a non `nil`
  217. -- value terminates the event propagation. The other handlers will not be called.
  218. --
  219. -- @tparam string event the event name
  220. -- @tparam ... ... the remaining parameters are passed on to the handler
  221. events.emit = function(event, ...)
  222. local h = handlers[event]
  223. if not h then return end
  224. for i = 1, #h do
  225. local ret = h[i](...)
  226. if type(ret) ~= 'nil' then return ret end
  227. end
  228. end
  229. vis.events = events
  230. ---
  231. -- @type Window
  232. --- The file type associated with this window.
  233. -- @tfield string syntax the syntax lexer name or `nil` if unset
  234. --- Change syntax lexer to use for this window
  235. -- @function set_syntax
  236. -- @tparam string syntax the syntax lexer name or `nil` to disable syntax highlighting
  237. -- @treturn bool whether the lexer could be changed
  238. vis.types.window.set_syntax = function(win, syntax)
  239. local lexers = vis.lexers
  240. win:style_define(win.STYLE_DEFAULT, lexers.STYLE_DEFAULT or '')
  241. win:style_define(win.STYLE_CURSOR, lexers.STYLE_CURSOR or '')
  242. win:style_define(win.STYLE_CURSOR_PRIMARY, lexers.STYLE_CURSOR_PRIMARY or '')
  243. win:style_define(win.STYLE_CURSOR_LINE, lexers.STYLE_CURSOR_LINE or '')
  244. win:style_define(win.STYLE_SELECTION, lexers.STYLE_SELECTION or '')
  245. win:style_define(win.STYLE_LINENUMBER, lexers.STYLE_LINENUMBER or '')
  246. win:style_define(win.STYLE_LINENUMBER_CURSOR, lexers.STYLE_LINENUMBER_CURSOR or '')
  247. win:style_define(win.STYLE_COLOR_COLUMN, lexers.STYLE_COLOR_COLUMN or '')
  248. win:style_define(win.STYLE_STATUS, lexers.STYLE_STATUS or '')
  249. win:style_define(win.STYLE_STATUS_FOCUSED, lexers.STYLE_STATUS_FOCUSED or '')
  250. win:style_define(win.STYLE_SEPARATOR, lexers.STYLE_SEPARATOR or '')
  251. win:style_define(win.STYLE_INFO, lexers.STYLE_INFO or '')
  252. win:style_define(win.STYLE_EOF, lexers.STYLE_EOF or '')
  253. if syntax == nil or syntax == 'off' then
  254. win.syntax = nil
  255. return true
  256. end
  257. if not lexers.load then return false end
  258. local lexer = lexers.load(syntax)
  259. if not lexer then return false end
  260. for id, token_name in ipairs(lexer._TAGS) do
  261. local style = lexers['STYLE_' .. token_name:upper():gsub("%.", "_")] or ''
  262. if type(style) == 'table' then
  263. local s = ''
  264. if style.attr then
  265. s = string.format("%s,%s", s, attr)
  266. elseif style.fore then
  267. s = string.format("%s,fore:%s", s, style.fore)
  268. elseif style.back then
  269. s = string.format("%s,back:%s", s, style.back)
  270. end
  271. style = s
  272. end
  273. if style ~= nil then win:style_define(id, style) end
  274. end
  275. win.syntax = syntax
  276. return true
  277. end
  278. ---
  279. -- @type File
  280. --- Check whether LPeg pattern matches at a given file position.
  281. -- @function match_at
  282. -- @param pattern the LPeg pattern
  283. -- @tparam int pos the absolute file position which should be tested for a match
  284. -- @tparam[opt] int horizon the number of bytes around `pos` to consider (defaults to 1K)
  285. -- @treturn int start,end the range of the matched region or `nil`
  286. vis.types.file.match_at = function(file, pattern, pos, horizon)
  287. horizon = horizon or 1024
  288. local lpeg = vis.lpeg
  289. if not lpeg then return nil end
  290. local before, after = pos - horizon, pos + horizon
  291. if before < 0 then before = 0 end
  292. local data = file:content(before, after - before)
  293. local string_pos = pos - before + 1
  294. local I = lpeg.Cp()
  295. local p = lpeg.P{ I * pattern * I + 1 * lpeg.V(1) }
  296. local s, e = 1
  297. while true do
  298. s, e = p:match(data, s)
  299. if not s then return nil end
  300. if s <= string_pos and string_pos < e then
  301. return before + s - 1, before + e - 1
  302. end
  303. s = e
  304. end
  305. end
  306. require('vis-std')