Compare commits

...

254 Commits

Author SHA1 Message Date
George Sokianos 9cfb20bd0e Merge branch 'master' of ssh://git.walkero.gr:2222/walkero/lite-xl 2022-03-27 20:42:42 +01:00
George Sokianos 9c2abb38db innosetup changes 2022-03-27 20:42:04 +01:00
Jefferson González 951f0913da
syntax: add pattern to boost tokenizer performance (#896) 2022-03-25 11:25:32 -04:00
Jefferson González a2d5a7a904
Merge pull request #894 from adamharrison/fix-anonymous-syntaxes
Fixed anonymous syntaxes.
2022-03-22 22:06:25 -04:00
Adam Harrison 3e7a97737e Re-enabled comment cache. 2022-03-22 10:35:44 -04:00
Adam Harrison 17645ba4ec Fixed anonyous syntaxes. 2022-03-22 10:17:42 -04:00
jgmdev bbac4d1560 treeview: add proper predicate for delete command 2022-03-20 01:58:39 -04:00
jgmdev c3bcf68851 treeview: use root_view:get_primary_node().active_view for focus. 2022-03-20 01:05:07 -04:00
jgmdev f0cc973e38 treeview: also handle focus change from mouse and then commandview 2022-03-20 00:53:13 -04:00
Jefferson González ad25216de7
Merge pull request #890 from Guldoman/PR_treeview_fix_scroll
Fix `TreeView` scroll via scrollbar
2022-03-20 00:31:26 -04:00
Jefferson González 331c78faac
Merge pull request #889 from Guldoman/PR_move_to_selection
Move cursor to the beginning/end of its selection
2022-03-20 00:22:00 -04:00
Guldoman 46f9be2960
Hide hovered `TreeView` item when dragging the scrollbar 2022-03-20 04:46:57 +01:00
Guldoman 699655bebf
Don't specify delta movement when simulating `TreeView:on_mouse_moved` 2022-03-20 04:45:14 +01:00
Guldoman 3765ef1d7a
Move cursor to the beginning or the end of its selection
When using `doc:move-to-{previous,next}-char` in a selection, we were 
moving the cursor to the character before the initial/after the last 
character of the selection.
Now we follow what other editors do and move it to just before the 
initial/just after the final character.
2022-03-20 04:28:26 +01:00
jgmdev b741c204db treeview: better handle previous view when focus/unfocus from CommandView 2022-03-19 23:10:26 -04:00
jgmdev 3ffabced62 treeview: move delete command to proper predicate 2022-03-19 22:33:41 -04:00
Jefferson González b5ead3992e
Merge pull request #888 from Guldoman/PR_treeview_collapse_to_parent
Make `treeview:collapse` select parent if current item can't collapse
2022-03-18 18:01:44 -04:00
Guldoman 3ec0f38446
Make `treeview:collapse` select parent if current item can't collapse 2022-03-18 16:16:55 +01:00
Guldoman 2d5af22dc9
Don't draw `treeview` tooltip if its position is not defined 2022-03-18 16:10:24 +01:00
jgmdev a7fc7b4408 treeview: fix crash on tooltip.x been nil 2022-03-18 06:02:48 -04:00
Jefferson González 30de42f4ab
Merge pull request #755 from Jipok/draw_tab_rework
rootview.lua: Refactor Node:draw_tab
2022-03-18 05:05:55 -04:00
jgmdev e8427ae168 treeview: fixed github merging error 2022-03-18 04:23:32 -04:00
Jefferson González 5a63f6dc2e
Merge pull request #770 from takase1121/treeview-initial-size
add option for treeview initial size
2022-03-18 04:18:56 -04:00
jgmdev 02f6dcc07d treeview: added @AlexSol suggestions
* suggestions included collapse, expand and focus
* also added missing common.merge
* some other minor fixes
2022-03-18 03:57:14 -04:00
takase1121 d5da711b6f add selections in treeview 2022-03-18 03:11:13 -04:00
Guldoman 000caf2e43 Allow opening non existing files from arguments 2022-03-18 01:09:02 -04:00
Guldoman 9763701cbf Reset syntax when a filename is provided 2022-03-18 00:36:25 -04:00
Guldoman fb4a5f3828 Add command to create a new named Doc 2022-03-18 00:34:15 -04:00
Jefferson González 0e323f4a35
Merge pull request #886 from adamharrison/fix-left-click-issues
Fixed a bunch of problems relating to multicursor.
2022-03-17 21:00:51 -04:00
Jefferson González 1f468fca24
Merge pull request #883 from jgmdev/detectindent-improvements
plugin detectident: fixes and improvements
2022-03-17 18:29:46 -04:00
Adam Harrison ba5289dc75 Typo. 2022-03-17 16:57:18 -04:00
Adam Harrison 82325b6a08 Fixed a bunch of problems. Fixed left+click not allowing for square selections, fixed esc not exiting multicursor mode, and allowed cntrl+click to remove a cursor. 2022-03-17 16:55:52 -04:00
Jan 120c769e7e
seperate dirmonitor logic, add build time detection of features (#866)
this also adds libkqueue support
2022-03-17 13:43:01 -04:00
jgmdev 5830b7d9f0 plugin detectindent: pre-compile regexes 2022-03-17 00:14:36 -04:00
Jefferson González 20763ed7ff
Merge pull request #864 from jgmdev/markdown-adjustments
language_md: removed scale adjustment code
2022-03-15 22:14:34 -04:00
jgmdev dcbebef2ab plugin detectident: fixes and improvements
* Improved performance 67x by not using the tokenizer, this means that
  now opening files or saving them where indentation is re-detected
  is much more faster.
* Improved the algorithm to detect the space size.
2022-03-15 21:17:15 -04:00
jgmdev 2ce4dbc8ef language_md: some more improvements
* handle images with links
* handle escaping of * and `
* support coloring a heading custom id
* reverted from regex to lua patterns
2022-03-13 14:43:50 -04:00
Jefferson González 17017b63ae
Merge pull request #875 from jgmdev/language-c-cpp-improvements
Languages c and cpp improvements
2022-03-11 17:44:17 -04:00
Jefferson González 1725a48ce2
Merge pull request #879 from jgmdev/plugins-skip-version
config: added skip_plugins_version
2022-03-11 17:30:36 -04:00
Jefferson González d0d0028472
Merge pull request #877 from jgmdev/statusview-commands
StatusView: added ability to hide and commands
2022-03-11 17:26:02 -04:00
jgmdev 620b669517 statusview: added ability to hide and commands
Also fixed the right panel not been draggable.
2022-03-11 17:23:16 -04:00
Jefferson González 46795972f0
Merge pull request #876 from jgmdev/nagview-scroll
nagview: support vscroll when message is too long
2022-03-11 17:11:38 -04:00
jgmdev b880aa42f9 language_c: fixes and improvements
* Do not compete with language_cpp.lua over the .h and .inl files,
  these files can contain both cpp and c so we choose the former which
  supports both syntaxes.
* Added support for magic and uppercase constants.
2022-03-11 17:05:08 -04:00
jgmdev 4b0531cdfc language_cpp: improvements and fixes
* Removed pcall(require, "plugins.language_c") since it doesn't works
  as it seems to have been intended.
* Removed duplicate keywords
* Added support for magic and uppercase constants.
* Basically merged most changes from the lite-xl-plugins repo.
2022-03-11 17:02:42 -04:00
jgmdev d9909cf4ea config: added skip_plugins_version
This new config flag ignores the plugins version check at startup
which helps a lot when working on new or old plugins that doesn't match
the mod or lite-xl version and you still desire to load them to fix them
by checking with lite-xl it self which errors need to be corrected.
2022-03-10 22:29:33 -04:00
jgmdev 0aa53a0e7f nagview: support vscroll when message is too long
Also some other minor changes:
* fix transition when nagview is closed
* do not draw or update when not visible
* do not process events when not visible
* cleaned a bit the logic on next and show
* fixes #848
2022-03-10 06:57:16 -04:00
Jefferson González e08353ea08
Merge pull request #873 from jgmdev/user-and-project-reload-fix
Fix config overwritten on user/project modules save
2022-03-09 19:40:35 -04:00
jgmdev eeb2b28a03 config overwritten on user/project modules save
When a user modifies and saves the init.lua or a project module file the
reload_customizations() function was performing unnecessary reloading
of core.config and core.style. This resulted on the replacement of config
tables with new tables, breaking all active references been used by
the consumers of this config options. Been redundant this means
that every consumer was using its own copy of a configuration table
different from the one referenced on core.config and user changes not
taking place.
2022-03-09 02:15:01 -04:00
Adam 960b482061
Fixed some issues with inotify and multiple events at the same time. (#872)
* Fixed some issues with inotify and multiple events at the same time. Seems to be working now.

* Cleaned up and simplified function, and commented, and fixed a number of bugs.

* Simplifying and fixing further.

* Improved performance for skipping large amounts of files.

* Added in extra checks, and changed paths. We should probably unify these path styles.

* Fixed stutter.

* Removed extraneous functions.

* Cleaned up more, added more testing; dealt with multiple sequential events correctly.
2022-03-08 19:30:25 -05:00
Jefferson González 52a47e0d73
Merge pull request #870 from lite-xl/treeview-fix
plugin treeview: skip rootview events if not visible.
2022-03-07 18:50:36 -04:00
Jefferson González 8a31a2b169
Merge pull request #871 from Guldoman/fix_bad_clip_draw_text
Always check if the beginning of the text needs to be clipped
2022-03-07 18:49:14 -04:00
Guldoman 5714da81f8
Always check if the beginning of the text needs to be clipped 2022-03-07 23:35:56 +01:00
jgmdev 48e86bb117 plugin treeview: skip rootview events if not visible. 2022-03-07 16:52:32 -04:00
Jefferson González 6386bac4e5
Merge pull request #869 from jgmdev/fix-loading-order
Initialization: load core views before user plugins
2022-03-06 21:22:53 -04:00
jgmdev 9d4e475a2c init: also load default nodes and commands before user plugins and project module. 2022-03-06 21:21:00 -04:00
jgmdev d7d88a2037 init: load core views before user plugins 2022-03-06 21:03:16 -04:00
Jefferson González 1be425d1de
Merge pull request #867 from Guldoman/fix_load_project_module
Load project module on startup
2022-03-06 21:02:22 -04:00
Guldoman 240afa7abd
Load project module on startup 2022-03-06 21:09:22 +01:00
Adam f85612e0f0
Fix Project Scanning (#746)
Removed dmon, and replaced with logic that works across Linux, Mac, FreeBSD and Windows. Have tested on all platforms, and seems to work.

Co-authored-by: Jan200101 <sentrycraft123@gmail.com>
2022-03-06 00:59:22 -05:00
jgmdev 6cb403b450 meson: increased min version to 0.47 and added check flag on run_command. 2022-03-05 20:15:48 -04:00
Jefferson González d82b668a09
Merge pull request #863 from jgmdev/scale-fix
plugin scale: replace non existing font.set_size with font.copy
2022-03-05 20:03:05 -04:00
jgmdev c4f7380a95 language_md: removed scale adjustment code that was needed because of a bug on scale plugin. 2022-03-05 19:13:24 -04:00
jgmdev e5ca08e13f plugin scale: replace non existing font.set_size with font.copy 2022-03-05 19:03:33 -04:00
Jefferson González 1fa1960b05
Merge pull request #862 from jgmdev/markdown-consolidation
language_md: improvements consolidating #814 and #840 thanks to @Not-a-web-Developer and @TorchedSammy
2022-03-04 14:58:25 -04:00
jgmdev 2d3d9a1671 language_md: improvements consolidating #814 and #840 2022-03-04 14:29:29 -04:00
Jefferson González 0e79607895
Merge pull request #859 from jgmdev/commit-on-version
Add git commit on version of devel builds
2022-03-04 14:18:36 -04:00
Jefferson González 2f7da44275
Merge pull request #860 from Guldoman/tokenizer_start_of_line
Allow syntax patterns to match with the beginning of the line
2022-03-04 14:13:14 -04:00
Guldoman caefc9112a
Force syntax patterns starting with `^` to match with the whole line
Before, syntax patterns/regexes that started with `^` didn't have the 
desired effect of matching with the start of the line.

Now those patterns are used only when matching the whole line.
2022-03-04 11:27:01 +01:00
Guldoman fbb893c6b1
Fix `^` regex matching when using an offset
Before, if `offset > 1` was used, the match would have failed because 
the beginning of the string was never met.

Now we force the beginning of the string to be the one specified by the 
offset.
2022-03-03 22:09:48 +01:00
Jefferson González 2736072dce
Merge pull request #858 from Guldoman/move_towards_epsilon
Use epsilon to compare values in `View:move_towards`
2022-03-03 15:21:25 -04:00
jgmdev a587182982 meson: when running git rev-parse use HEAD instead of checking current branch name 2022-03-02 18:55:46 -04:00
jgmdev 1e765b5c28 EmptyView: handle lite-xl version strings that overlap. 2022-03-02 03:55:03 -04:00
jgmdev 05e355d383 meson: append git commit if possible and to non release builds 2022-03-02 03:53:04 -04:00
Guldoman 3d879286a4
Use epsilon to compare values in `move_towards` 2022-03-01 22:41:54 +01:00
Jefferson González be5f94e913
Merge pull request #852 from jgmdev/statusview-enhacements
Improvements to the StatusView, closes #677
2022-02-28 18:19:46 -04:00
jgmdev e9775ced78 StatusView: added items hovered option for bg color and param to on_draw as suggested by @Guldoman 2022-02-28 17:56:17 -04:00
jgmdev 4c80aa7a40 StatusView: reposition left and right panel offsets on window resize. 2022-02-28 17:04:37 -04:00
jgmdev 15a31418ca StatusView: restored indent confirmed 2022-02-24 14:22:52 -04:00
jgmdev 128e10ba47 StatusView: add spaces as separate items instead of pre-pending them to items. 2022-02-24 13:58:40 -04:00
jgmdev 131bdf9fb2 StatusView: implemented and applied get_item_visible_area. 2022-02-24 02:23:29 -04:00
jgmdev b1ce685489 StatusView: better calculate panel sizes and handle out of bounds drags. 2022-02-24 00:21:21 -04:00
jgmdev c5648e1f02 StatusView: implemented dragging and scrolling. 2022-02-23 19:25:25 -04:00
jgmdev 3452d499e5 Show hand cursor on clickable elements as suggested by @Guldoman 2022-02-23 06:07:53 -04:00
jgmdev 90983b22a4 StatusView v2.0, some changes include:
* Items are now objects that can be retrieved and manipulated.
* clip rect is used for left and right panes
* initial support for items to do their own custom drawing by also
  doing a clip rect for them
* a custom background color can be specified for the item.
* a command or function can be executed on item click.
* Introduced functionality to easily hide or show all or specific items.
* Better handling of deprecated get_items()
* Spacing is automatically added to items and cleaned on deprecated
  items.
* Default items where separated and given the names:
  doc:file, doc:position, doc:indentation, doc:lines,
  doc:line-ending, core.commandview.
* Some default right or left click actions where given to the default
  items.
* Started adding required bits to support dragging to left and right
  panes when some items aren't visible.

Note: should work well enough already but maybe some repetitive stuff can be
cleaned out.
2022-02-23 05:15:14 -04:00
jgmdev 7cb4e93d14 Improvements to the StatusView
* Support for predicates by introducing add_item().
* Support for performing click actions on items.
* Support for optional tooltips on item hover.
* Deprecate the usage of get_item().
2022-02-21 03:40:25 -04:00
jgmdev feaa3b2547 Docs: changes process start from method to function. 2022-02-15 20:49:46 -04:00
Adam 61e712db12
Fixed rendering computations for y offset. (#843)
* Fixed rendering computations for y offset.

* Force monospacing if every ascii character has the same integer advance.

* Added in explanatory comment.

* Fixed issues.

* Made lines less long.
2022-02-15 15:57:07 -05:00
Adam 05e1968b0b
Merge pull request #845 from Guldoman/file_drop_override
Add `View:on_file_dropped`
2022-02-15 11:44:37 -05:00
Guldoman 539f929e30
Allow intercepting `filedropped` events
The event is first sent to the underlying `View`; if not handled, it's 
managed as before.
2022-02-15 00:45:59 +01:00
jgmdev 2079e1f707 Plugin projectsearch: set command view text to current document selection. 2022-02-13 01:31:58 -04:00
Francesco Abbate b02aae939c Fix again bug with invalid ignore_files patterns
The pattern cannot be tested in advance as it seems that Lua inspect
the pattern only partially, the part that is actually used.

We resort to use pcall to catch any error when using the pattern.
2022-02-11 23:00:15 +01:00
Adam a6f32ca0d0
Merge pull request #822 from AlexSol/update_contextMenu
Travel by contextMenu using keyboard
2022-02-11 12:10:14 -05:00
Adam 3a0af6ee04
Merge pull request #828 from adamharrison/signal-processes
Changed signalling so it'll target the whole process group.
2022-02-11 12:01:53 -05:00
Adam af6a06bc8b
Merge pull request #832 from jgmdev/autocomplete-update
Update autocomplete with changes needed for latest LSP plugin.
2022-02-11 12:01:35 -05:00
Adam b05a02cb28
Merge pull request #839 from jgmdev/nil-nodes-fix
Fix cases of nil node.
2022-02-11 11:52:43 -05:00
Adam a4833b21e4
Merge pull request #842 from Guldoman/no_scroll_selection
Don't scroll when selecting the whole doc
2022-02-11 11:48:01 -05:00
Francesco Abbate 85531b0d3f
Include addons with build-package for bundles 2022-02-11 06:33:53 +01:00
Guldoman 5526041da3
Check entire selection to trigger `DocView:scroll_to_make_visible`
This is needed for example when a selection has both `line1` and `col1`
at 1, and the left arrow is pressed: `line2` and `col2` change, while
`line1` and `col1` don't, but we still want to scroll.
2022-02-11 06:16:29 +01:00
Guldoman 59ba759167
Don't scroll DocView when executing `doc:select-all` 2022-02-11 06:00:38 +01:00
jgmdev bb4569da53 Fix cases of nil node. 2022-02-09 10:40:02 -04:00
Jefferson González dc5888bc07
Merge pull request #837 from Guldoman/process_running_poll
Update process status when calling `process:running`
2022-02-08 16:36:18 -04:00
Guldoman d4b8155cbc
Update process status when calling `process:running` 2022-02-08 18:10:54 +01:00
Jefferson González 8caccbf6f0
Merge pull request #836 from Guldoman/toggle_comment_empty_line
Ignore empty lines in `line_comment`
2022-02-07 15:31:48 -04:00
Guldoman f23cb33f7c
Ignore empty lines in `line_comment` 2022-02-07 19:22:43 +01:00
jgmdev aec6806d8f Added system.get_process_id() to api docs. 2022-02-04 15:43:42 -04:00
Adam 212d4e2729
Merge pull request #833 from jgmdev/add-system-get_process_id
Implemented system.get_process_id()
2022-02-04 12:24:54 -05:00
jgmdev df0635ad35 Implemented system.get_process_id() 2022-02-03 22:20:42 -04:00
Adam e5f17aea4b
Merge pull request #821 from dheisom-gomes/improvements
Improvements on core.add_thread function
2022-02-03 19:23:54 -05:00
Adam d78f310a4e
Merge pull request #829 from Guldoman/draw_rect_alpha_format
Use SDL to manage color format mapping in `ren_draw_rect`
2022-02-03 17:02:49 -05:00
jgmdev 6773e85cb8 Update autocomplete with changes needed for latest LSP plugin. 2022-02-03 15:01:39 -04:00
Guldoman 9a6cd2b453
Use SDL to manage color format mapping in `ren_draw_rect` 2022-02-03 01:50:43 +01:00
Adam Harrison d2d5617774 Changed signalling so it'll target the whole process group. 2022-01-30 20:51:30 -05:00
Dheisom Gomes f5e9146b1c Merge branch 'master' of https://github.com/lite-xl/lite-xl into improvements 2022-01-30 15:49:37 -03:00
Adam Harrison af76f544be Fixing performance regression. Due to the way the hashes work, we must 0 out the whole thing. 2022-01-29 15:19:22 -05:00
AlexSol efedbae663 Travel by contextMenu using keyboard 2022-01-29 16:23:00 +02:00
Adam 3d40725b8f
Merge pull request #816 from adamharrison/fix-process-api
Fixing up Process API
2022-01-28 23:53:37 -05:00
Dheisom Gomes 13adedb23a Go back to `common.match_pattern` and use `table.unpack` directly on function `core.add_thread` 2022-01-28 18:30:19 -03:00
Adam Harrison 941523868e Merge branch 'fix-process-api' of github.com:adamharrison/lite-xl into fix-process-api 2022-01-28 15:44:16 -05:00
Adam Harrison 3773a812bd Incorporate realtakase's suggestions. 2022-01-28 15:39:57 -05:00
Adam 0a70b13a73
Merge pull request #809 from lite-xl/merge-master-2.0
Merge master 2.0
2022-01-28 14:38:22 -05:00
Dheisom Gomes 22d8f69b5c Error correction getting "unpack" function 2022-01-28 12:13:52 -03:00
Dheisom Gomes 6331a23c6b Added support to use a array of regex to ignore files 2022-01-28 12:02:30 -03:00
Dheisom Gomes 8c0685d440 Added support to pass extra arguments to functions on core.add_thread 2022-01-28 11:53:30 -03:00
Francesco Abbate 3a53b05b29 Do no error out on malformed ignore patterns 2022-01-25 14:16:40 +01:00
Francesco Abbate 3e39da071d Fix problem with project module save hook 2022-01-24 09:34:54 +01:00
Francesco Abbate bc9f8a4075 Use new mutex in dmon to avoid possible lock-up
We rely on one variable _dmon.modify_watches shared between thread to
ensure that we don't lock with the dmon polling thread waiting indefinitely
and helding a lock.

To ensure that the polling thread sees modifications done to 'modify_watches'
we use an additional mutex that act as a memory barrier.
2022-01-24 09:34:54 +01:00
Adam Harrison 456126400a Added in new merge method, and run it on plugins. Also made it so plugin configs can be set anywhere, even if we don't know the plugin beforehand. 2022-01-22 18:39:23 -05:00
Adam 7e6d9df58d
Merge pull request #805 from Jan200101/PR/fallback-force
use lua fallback earlier when fallbacks are forced
2022-01-22 17:30:01 -05:00
Jan200101 227ca7d0e5
use lua fallback earlier when fallbacks are forced 2022-01-22 23:29:28 +01:00
Francesco Abbate f7193c4fa2 Remove unused whitespace_replacements function 2022-01-22 21:46:02 +01:00
Adam Harrison f9ad83e53e Fixed windows not converting utf8 environment block to utf16. 2022-01-22 13:34:47 -05:00
Adam Harrison ed4128bc65 Added in support for env on linux. 2022-01-22 12:36:30 -05:00
Adam Harrison 428c757a13 Implemented @guldoman's suggestion for how to close file handles. 2022-01-22 12:02:59 -05:00
Adam 9cb25acd7b
Merge pull request #815 from takase1121/fix-subprocess-read
fix invalid memory access
2022-01-22 11:18:19 -05:00
takase1121 f24ebf853d
fix invalid memory access 2022-01-22 23:30:48 +08:00
Adam 8c8bd4692c
Merge pull request #808 from adamharrison/fix-commenting
Fix commenting selections.
2022-01-20 23:28:05 -05:00
Adam Harrison b523bd5cee Fixed end of block-style line comments. 2022-01-20 22:17:21 -05:00
Francesco Abbate f6a0e12e31 Merge branch 'master-2.0' 2022-01-19 20:31:33 +01:00
Francesco Abbate cd83df1abf Bump version and changelog to prepare 2.0.5 release 2022-01-19 18:18:25 +01:00
Adam 6025c43241
Merge pull request #743 from takase1121/better-logview
multiple improvements to logging
2022-01-18 23:25:36 -05:00
Adam Harrison cdbfecc5ce Streamlined, and fixed guldo's problem. 2022-01-18 21:38:43 -05:00
Adam Harrison 30cc205cd4 Fixed issue first mentioned in #771. 2022-01-18 21:38:38 -05:00
Adam d3e1636881
Merge pull request #771 from takase1121/multiline-comment-command
add toggle-block-comment
2022-01-18 21:07:46 -05:00
Adam 3b3e41c095
Merge pull request #799 from Guldoman/bit32_polyfill
Improve bit32 polyfill
2022-01-18 21:06:48 -05:00
Adam a4e5d9f043
Merge pull request #798 from Jan200101/PR/wrap
Add fallbacks to all common dependencies
2022-01-18 20:54:36 -05:00
Francesco Abbate 2dd154edeb Remove remaining debug message 2022-01-18 10:42:20 +01:00
Jan200101 192c577966
Add fallbacks to all common dependencies 2022-01-15 00:53:46 +01:00
Francesco Abbate 7e9b2f30da Treat final '/' or '/$' in ignore rule as part of the pattern
Evolve the rule for directory in ignore_files to be more natural
and easy to understand.

When a final '/' or '/$' is found we consider the pattern to match
a directory and the pattern is not modifed. In turns, is used, before
matching a directory's name a final '/' is appended to its name
before checking if it matches the pattern.

With the previous rule a final '/' in the pattern meant also a directory
but the '/' was removed from the pattern.
2022-01-13 16:43:37 +01:00
Francesco Abbate 2456452f65 Fix error to close view when deleting a file 2022-01-13 16:38:20 +01:00
Guldoman e51c76c72b
Assert for negative `field` in bit32 polyfill
This more closely matches the behavior of lua5.2.
2022-01-12 19:56:09 +01:00
Francesco Abbate ae1890d29a Fix project files reading with symlink 2022-01-12 00:32:10 +01:00
Guldoman 51975472a9
Add bit32 polyfill globally 2022-01-12 00:07:53 +01:00
Guldoman 7eb9908f1a
Improve bit32 polyfill 2022-01-12 00:07:14 +01:00
Francesco Abbate 4cdd42de1a Ensure config.plugins are restored on new config
When a user's or project's module configuration file is changed we
make sure that the config.plugins fields are all restored so that
all plugins already loaded can continue to work.
2022-01-10 09:54:47 +01:00
Francesco Abbate 656a89c494 Fix checks when opening new project directory 2022-01-09 23:26:11 +01:00
Francesco Abbate 827f3f876d Remove remaining debug code fragment 2022-01-09 23:26:11 +01:00
Francesco Abbate 648b977c1e Use a timeout in dmon thread with pending events 2022-01-09 23:26:11 +01:00
Francesco Abbate 9be22f0b8d Attempt to fix dmon critical section for windows
Should fix commit bb12f085f3.

When taking the critical section we should always send the
event to wakeup the events thread. In addition use
TryEnterCriticalSection to send the event only if needed
reducing the number of spurious events sent.
2022-01-09 23:26:11 +01:00
Francesco Abbate 19ec86d971 Do not use timeout in dmon linux select
Wait indefinitely in select and wake-up the thread when needed.
2022-01-09 23:26:11 +01:00
Francesco Abbate 44a8dc320b Fix some errors with previous commits 2022-01-09 23:26:11 +01:00
Francesco Abbate 6584bdfd33 On Windows wait indefinitely in dmon thread
Avoid waiting with a finite timeout and wait indefinitely in
dmon thread. When we need to unwatch we send a signal to a special
event meant to wakeup the waiting thread.
2022-01-09 23:26:11 +01:00
Francesco Abbate f0aea5b1a4 Report error codes from dmon_watch_add 2022-01-09 23:26:11 +01:00
Francesco Abbate 39366d3a09 Ensure project rescan thread is terminated
When changing a project we need to ensure that the old threads
are no longer run.
2022-01-09 23:26:11 +01:00
Francesco Abbate 1520c12580 Remove DMON_LOG_ERROR to return an error code 2022-01-09 23:26:11 +01:00
Francesco Abbate 7473fbf32c Fix undue asserts in dmon_extra
Some asserts are placed in case that can effectively occur
so we remove the assertion and we return false. In turn we
adapt the logic accordingly so when false is returned to add
a watch we do not open that directory.
2022-01-09 23:26:11 +01:00
Francesco Abbate 5032e7352e Write an initial project module if not present 2022-01-09 23:26:11 +01:00
Francesco Abbate a703840068 Add some comments for ignore_files logic 2022-01-09 23:26:11 +01:00
Francesco Abbate 295c65da92 Use compiled ignore_files pattern
Try to digest the ignore_files pattern before potentially processing
a lot of files because it may be expensive.
2022-01-09 23:26:11 +01:00
Francesco Abbate 5b154c189f First version of paths in ignore_files
Works correctly and the logic seems sound even if somewhat quirky.

`^%.` match any file of directory whose basename begins with a dot.

`^/node_modules$/"` match a directory named `node_modules` at the project's root.

  Note that the final '/' needs to be at the end. The '/' after the '^' needs to be there to trigger
  a match of the full path filename so we are sure it is at the root.

  PROBLEM: the '/' to trigger full path match could be in a pattern's special expression like:
  [^/]

`^%.git$/` match any directory name '.git' anywhere in the project.

`^/%.git$/` match a directory named '.git' only at the project's root.

`^/subprojects/.+/` match any directory in a top-level folder named "subprojects".

`^/build/` match any top level directory whose name begins with "build"

  PROBLEM: may be surprising, one may expects it matches only a directory named 'build'. It actually acts like
  it was `^/build.*/`.
2022-01-09 23:20:47 +01:00
Adam Harrison 31d448971a Restored floating point time granularity. 2022-01-08 12:59:15 -05:00
Adam 93076bdc41
Merge pull request #781 from Jan200101/PR/lua54
Migrate to Lua 5.4
2022-01-08 12:11:48 -05:00
takase1121 fc809b3172
comment the entire line when using block comment 2022-01-08 18:45:38 +08:00
Francesco Abbate 143b389365 Clear TreeView cache when closing project 2022-01-07 10:41:18 +01:00
takase1121 087314aea4
fix lhelper script 2022-01-07 17:31:24 +08:00
Francesco Abbate 7ded5c8199 Fix problem when reloading project directory 2022-01-06 18:00:48 +01:00
Francesco Abbate 1e7075ca9f Do not force choosing project dir to suggestion
When changing or opening a project directory do not
take the selected item from suggestion but simply the
entered text as it is.

Otherwise the user may be unable to choose a directory
if the text matches the beginning of suggestion.

Close #791
2022-01-05 23:42:47 +01:00
Francesco Abbate 1b57107352 Fix problem with special file types
For special file types like the ones in /dev/ the info
entry's type is neither file neither dir.

We prevent these kind of files from being listed in the
project.
2022-01-05 23:32:26 +01:00
Francesco Abbate 9929ca9c2d Fix logic with project directories suggestions
Attempt to fix issue #791.

The logic set with the previous commit for suggest_directory
is similar to the one we use except the previous expression
was false do to operator precedence for "and" versus "or".

With the modification here, when opening a project directory,
we suggest the recently used projects
if the text is equal to dirname(project_dir) + "/" which
happens to be the text the command view is initially set to.
In addition we do the same if text is "". If the condition is
not met we return the suggestions from common.dir_path_suggest to
match the text entered.

Works well on Linux but may not solve the problem on Windows, it
should be tested.
2022-01-05 23:21:47 +01:00
Francesco Abbate f3cf7ac9c7 Do not reload core.keymap module
Avoid reloading the core.keymap module when user's config
or project module change.

The reason is the plugins like autocomplete can add keymaps
and the additions from plugins would be lost.

Close issue #793
2022-01-04 18:06:30 +01:00
Francesco Abbate bf578d5ee4 Fix logic for file create event
When we get a file or directory creation event we need to
ensure that all the parent directories pass the ignore_files
test.
2022-01-03 18:55:01 +01:00
takase1121 df0f6fb94c
make set_selections consistent 2022-01-02 19:18:08 +08:00
takase1121 e079ddfa37
refactor toggle-block-comments, make command spaces aware, set selections correctly 2022-01-02 19:14:03 +08:00
Adam 067e7cc6cd
Merge pull request #784 from Jan200101/PR/label-update
add Libraries label
2022-01-01 12:18:41 -05:00
Jan200101 73a867fc89
add Libraries label 2021-12-31 13:53:31 +01:00
Jan200101 99ddf1fb92
Migrate to Lua 5.4 2021-12-31 13:53:01 +01:00
Adam 186248911a
Merge pull request #782 from Nightwing13/patch-2
[Fix] Pointer Bug in ToolBar plugin
2021-12-30 19:25:03 -05:00
Francesco Abbate 445c79bb52 Revert "No longer store autocomplete options in config"
This reverts commit 0f1b84040d.

The new mechanism to save config.plugins upon user's configuration
reload let us stay compatible with existing plugins.
2021-12-31 00:22:49 +01:00
Francesco Abbate 03350cc14b Restore config.plugins when reloading config
Some plugins store options in:

config.plugins.<plugin-name>

so we restore all the kay-values of config.plugins when
reloading the user preferences.
2021-12-31 00:20:52 +01:00
Francesco Abbate 85d26adb62 Fix project's module loading when changing project
Fix a bag of subtle problem about when loading the
project module.

We need to load the project's module before to scan
the project directory.
2021-12-30 23:57:23 +01:00
Francesco Abbate 68aea88510 Fix error with ignore_files
There was a double error because the config.ignore_files was
used at two differect places in different ways.

Now we apply coherently the original rule to apply
config.ignore_files to the basename of each file or directory.
2021-12-30 22:24:43 +01:00
Francesco Abbate 9578359b2b Remove inotify recursive directory monitoring
We no longer use in Lite XL recursive directory monitoring as
it was a source of problems.

To enforce this at compile time we use the preprocessor to
remove the implementation of recursive monitoring for the Linux
implementation only.
2021-12-30 15:45:09 +01:00
Francesco Abbate fd074ff39a Fix problem when opening project's module document
It wasn't fine to call core.open_doc without filename argument
and later call Doc:save without providing both the filename and
the absolute filename. It was giving a Doc in an inconsistent
status where self.filename was set but not self.abs_filename.
Added an asset to detect early the problem if ever happens again.

In turn the problem prevented the project's module hook to work if the
file was newly created.
2021-12-30 15:26:40 +01:00
Francesco Abbate adaf023541 Always watch/unwatch subdirectories on all systems
Simplifies and uniformize the logic on the Lua side for the
setting of directories' watches. Now we always use the methods:

systems.watch_dir_add / rm

on all the project's directories at any depth when we are not
in files limit mode.

In files limited mode the functions systems.watch_dir_add/rm are
called only on the expanded folders. The shown_subdir table is also
updated only in files limited mode.

On the C side, using the dmon library, we remove the recursive argument
from the system.watch_dir and we always call it recursively except on
Linux. At the same time the functions:

systems.watch_dir_add / rm

are provided but as dummy functions that does nothing except on Linux
where they work as before to add / remove sub-directories in the inotify
watch.

In this was on the Lua side we always act we if the watches needed to be
set for each sub-directory explicitly, independently of the system.

The important improvement introduced is that we always avoid calling
dmon_watch recursively on Linux. This latter thing is problematic with
inotify and is therefore avoided on Linux.

On the other side we simplifies the logic on the Lua side and remove
conditions based on the OS used.
2021-12-30 15:25:27 +01:00
Nightwing 60322a93a8
Update toolbarview.lua 2021-12-30 11:12:24 +09:00
Nightwing 46aaf57b45
[Fix] Pointer Bug in ToolBar plugin
Fixes an issue where the pointer moves down when "open user module" button is spammed
2021-12-30 06:26:58 +09:00
Adam 1552f18a87
Merge pull request #753 from Jipok/highlight_selection
Add for config.highlight_current_line new variant: no_selection
2021-12-29 14:43:17 -05:00
Adam 416a06c566
Merge pull request #765 from Guldoman/treeview_remove_deleted
Better "Remove changed files/dirs from `TreeView` cache"
2021-12-29 12:41:36 -05:00
Adam 3696937fec
Merge pull request #769 from takase1121/font_gc
fix FontGroup __gc method
2021-12-29 12:18:28 -05:00
Adam d2e02bbed3
Merge pull request #778 from eli-schwartz/meson
Meson: retain compatibility for very old versions
2021-12-29 12:13:59 -05:00
Francesco Abbate 88ed312f6b Fix NagView missing mouse events 2021-12-29 16:00:53 +01:00
Eli Schwartz 7a961c8c8e
meson: lower the minimum buildsystem requirements even more
Only a couple trivial features from meson ~0.50 were being used, and
none of them are really needed:

- configure_file() with the install kwarg has always defaulted to
  inferring its value from whether an install_dir was defined. This is
  fine, we don't need to set `install: true` in that case. The kwarg was
  only even added to meson 0.50 for consistency and to allow
  conditionally overriding the file to not install, even when
  install_dir is set. This project does not need that feature.

- path building could historically be done with the join_paths()
  function. Recent versions of meson (0.49) added cosmetic sugar in the
  form of string operator overloading to allow using the division
  operator on two strings. By removing this and using the backwards
  compatible form, we can support older versions of meson.

- sdl2 dependency lookup with hardcoded config-tool method is very
  opinionated about the correct way to look up sdl2, but meson can try
  multiple methods if you permit it, and there is no reason to think
  that config-tool is the only one that returns correct results.

By removing these features, the minimum can be dropped all the way down
to a version that is available on the oldest supported versions of
Ubuntu (18.04), Debian (oldoldstable / Stretch) and anywhere else of
consequence.
2021-12-28 17:50:10 -05:00
Eli Schwartz fcb3c41082
meson: lower the minimum buildsystem requirements
No features of 0.54 are being used, so 0.50 should be perfectly fine.

This drops the minimum requirement down to a version available in the
latest Ubuntu LTS (20.04), which only has 0.53
2021-12-28 17:19:16 -05:00
Francesco Abbate 8550049db8 Draw NagView in overlay mode
The NagView takes some actual space in the Y and when it appears
it cause the documents' content to be displaced.

The movement of the documents' content is annoying and should be
avoided so we draw the NagView entirely in overlay mode using defer
draw and we always keep its y size to zero to don't affect the
other application contents.
2021-12-28 16:43:27 +01:00
Francesco Abbate 2cf3c6f747 Ensure project reload when changing project module
Changes in project's module required an application restart to work.

Now the project will be re-scanned when the project's module changes.

In addition ensure borderless window config is changed when changed
in user's preferences.
2021-12-28 14:36:21 +01:00
Francesco Abbate 05b003eeb5 Avoid references to project's dir in TreeView
It is not a good practice to keep a reference to the project's
directory object outside of the "core" module itself.

The TreeView was using such a reference in the cache item for each
file or directory entry. Replace the reference to the object with
the absolute name of the project directory.
2021-12-28 14:36:21 +01:00
Francesco Abbate 0f1b84040d No longer store autocomplete options in config
Plugins should not store private stuff in core.config because this
latter can be reloaded due to user changing preferences.
2021-12-28 12:25:48 +01:00
Francesco Abbate 1f0785b73f Scan project folder after project module is loaded
Otherwise the initial scan of the project folder is done without
taking into account the config.ignore_files directives.
2021-12-28 10:59:01 +01:00
Adam 1e6046e499
Merge pull request #763 from Jipok/portable_try
Support portable user config(Fix #762)
2021-12-26 16:59:13 -05:00
takase1121 33f7fe4fda
toggle comment for whole line if nothing is selected 2021-12-26 15:12:28 +08:00
takase1121 69a857bbbf
fallback to toggle-line-comment and vice versa if needed 2021-12-26 15:05:27 +08:00
takase1121 4d31b1bc40
add toggle-block-comment 2021-12-25 12:57:00 +08:00
takase1121 16df6d8bce
add option for initial size 2021-12-24 21:32:28 +08:00
takase1121 84a3906323
fix FontGroup __gc method 2021-12-24 15:04:52 +08:00
Adam 4be8a8b582
Merge pull request #764 from lorsatti/master
Add CFBundleIdentifier to Info.plist.in
2021-12-23 19:55:41 -05:00
Guldoman d16e46dba5
Update website location 2021-12-23 23:26:52 +01:00
Guldoman a122d7e916
Correct `get_key_name` comment 2021-12-23 00:06:24 +01:00
Guldoman eac82e69fb
Add parameters to `core.on_dirmonitor_{modify,delete}` 2021-12-22 23:43:56 +01:00
Francesco Abbate 9155be7a22
Ensure TreeView cache entry is removed on delete
Address issue:

https://github.com/lite-xl/lite-xl/issues/689

Attempt to provide a more accurate fix to commit:

59f64088e1

For this latter what happens is that any change inside a directory
cause the corresponding entry to be folded in the TreeView.

The new change is more accurate because we remove only the stale
entry corresponding to the delete event and we do not reset the
cache of the parent directory using the modify event.
2021-12-22 23:40:54 +01:00
Guldoman 9e7bdf49e9
Revert "Merge pull request #697 from Guldoman/treeview_remove_changed"
This reverts commit 4e078cc217, reversing
changes made to 0c488c9492.
2021-12-22 23:39:26 +01:00
Lorenzo Orsatti 66bc551488
Add CFBundleIdentifier to Info.plist.in
The CFBundleIdentifier key is necessary to associate lite-xl application to all files with a certain extension.
2021-12-22 22:18:22 +01:00
Jipok a19baeacb1 Support portable user config(Fix #762) 2021-12-22 23:36:03 +05:00
takase1121 00e2e281d3
remove unsaved flag from log.txt 2021-12-22 10:54:25 +08:00
takase1121 2f65d5a26f
make the date field consistent 2021-12-22 10:53:53 +08:00
takase1121 8f06ef9b81
ensure date is rendered properly 2021-12-22 10:50:35 +08:00
Adam 590c8bb456
Merge pull request #759 from Jipok/again
Support for remaped special keys(Fix #757)
2021-12-21 19:56:33 -05:00
Adam e0d0d17c4d
Merge pull request #760 from adamharrison/fix-multiline-paste-system-clipboard
Restore External Paste to Non-Multiline Paste
2021-12-21 17:15:03 -05:00
Adam Harrison 978550d2a2 Restores external pastes to be normal pastes. 2021-12-21 16:23:34 -05:00
Jipok 773a85cd2d Support for remaped special keys(Fix #757) 2021-12-22 02:22:34 +05:00
Adam 61379a9ab8
Merge pull request #713 from Jipok/master
Copy/cut whole line if selection empty
2021-12-21 15:53:12 -05:00
takase1121 54f6579e9d
change INFO to use style.text 2021-12-21 17:38:25 +08:00
takase1121 3e175f5ad5
Merge branch 'master' of github.com:lite-xl/lite-xl into better-logview 2021-12-21 17:37:19 +08:00
takase1121 8d7867d118
adapt style.good and style.error 2021-12-21 17:36:32 +08:00
Jipok 6c1c983d1c rootview.lua: Refactor Node:draw_tab 2021-12-21 02:20:31 +05:00
Francesco Abbate 50247fcd92 Move to 2.0.4 version number 2021-12-20 16:19:15 +01:00
Francesco Abbate 3109263c5d Call dmon_unwatch when changing project
Fix a conspicuous omission to call the dmon_unwatch function
when changing project directory.

This uncovered a bug or a quirk of the dmon library where the watch_ids
can change as a result of calling dmon_unwatch because they are just
indexes on a contiguous array. Use a workaround to always unwatch the
first valid watch_id N times.
2021-12-20 14:42:48 +01:00
Jipok c353dd6eda Add for config.highlight_current_line new variant: no_selection 2021-12-20 16:23:01 +05:00
Guldoman 29318be9c7 Consume unmatched character correctly
We must consume the whole UTF-8 character, not just a single byte.
2021-12-20 12:04:20 +01:00
Francesco Abbate 37c00c877a Ensure TreeView cache entry is removed on delete
Address issue:

https://github.com/lite-xl/lite-xl/issues/689

Attempt to provide a more accurate fix to commit:

59f64088e1

For this latter what happens is that any change inside a directory
cause the corresponding entry to be folded in the TreeView.

The new change is more accurate because we remove only the stale
entry corresponding to the delete event and we do not reset the
cache of the parent directory using the modify event.
2021-12-20 11:03:49 +01:00
Francesco Abbate 405bd1c2bd Fix logic in project's file insertion
The function "file_search" in core.init was sometimes giving a wrong index
value, off by one.

The problem happened for example when the entry to search was "less than"
the first entry, the function returned a value of two instead of one as
expected.

The bug was easily observed creating a new directory with a name that comes
as the first in alphabetical order within the project.
2021-12-20 09:05:45 +01:00
Adam Harrison e512c57637 Added an exclusion for lineguide in the commandview. 2021-12-20 08:43:48 +01:00
Guldoman 23f83857c5 Don't search if there are no files 2021-12-20 08:40:43 +01:00
takase1121 695c7bf781
add instruction when logview is open 2021-12-19 09:36:43 +08:00
takase1121 1526cd176c
move selection logic to mouse click 2021-12-19 09:36:24 +08:00
takase1121 becdb99222
center icons to accomodate for size difference 2021-12-18 22:42:33 +08:00
takase1121 31df408d93
make timestamp fix sized 2021-12-18 20:11:50 +08:00
takase1121 fd3b4334ce
add clipping to drawing log items 2021-12-18 20:11:24 +08:00
takase1121 b5dff196f6
make error icon red 2021-12-18 20:10:25 +08:00
takase1121 ab4ecd515b
multiple improvements to logging
- added style.log table
- removed contextmenu
- use ctrl+click to copy individual log entries
- use icon instead of + or - for log items in logview
2021-12-18 10:51:44 +08:00
Jipok 7381a13d6f Revert "Make pasting multiple lines from clipboard same way as a single line"
This reverts commit 6a135f7c06.
2021-12-12 02:23:47 +05:00
Jipok 6a135f7c06 Make pasting multiple lines from clipboard same way as a single line 2021-12-10 19:25:28 +05:00
Jipok 4a563ddea1 Delete old forgotten self.cursor_clipboard 2021-12-10 19:23:49 +05:00
Jipok 4eee123eff Make cursor_clipboard globa, not per doc 2021-12-08 17:34:10 +05:00
Jipok acc7ceefd4 Correct paste after 'Cut/copy whole line' 2021-12-06 17:59:49 +05:00
Jipok 93d9e61a03 Copy/cut whole line if selection empty 2021-12-05 20:30:03 +05:00
81 changed files with 3976 additions and 3124 deletions

3
.github/labeler.yml vendored
View File

@ -33,3 +33,6 @@
"Category: C Core":
- src/**/*
"Category: Libraries":
- lib/**/*

3
.gitignore vendored
View File

@ -2,8 +2,7 @@ build*/
.build*/
lhelper/
submodules/
subprojects/lua/
subprojects/reproc/
subprojects/*/
/appimage*
.ccls-cache
.lite-debug.log

View File

@ -154,6 +154,7 @@ See the [licenses] file for details on licenses used by the required dependencie
[Get color themes]: https://github.com/lite-xl/lite-xl-colors
[changelog]: https://github.com/lite-xl/lite-xl/blob/master/changelog.md
[Lite XL plugins repository]: https://github.com/lite-xl/lite-xl-plugins
[plugins repository]: https://github.com/rxi/lite-plugins
[colors repository]: https://github.com/lite-xl/lite-xl-colors
[LICENSE]: LICENSE
[licenses]: licenses/licenses.md

View File

@ -1,5 +1,55 @@
This files document the changes done in Lite XL for each release.
### 2.0.5
Revamp the project's user module so that modifications are immediately applied.
Add a mechanism to ignore files or directory based on their project's path.
The new mechanism is backward compatible.*
Essentially there are two mechanisms:
- if a '/' or a '/$' appear at the end of the pattern it will match only directories
- if a '/' appears anywhere in the pattern except at the end the pattern will be
applied to the path
In the first case, when the pattern corresponds to a directory, a '/' will be
appended to the name of each directory before checking the pattern.
In the second case, when the pattern corresponds to a path, the complete path of
the file or directory will be used with an initial '/' added to the path.
Fix several problems with the directory monitoring library.
Now the application should no longer assert when some related system call fails
and we fallback to rescan when an error happens.
On linux no longer use the recursive monitoring which was a source of problem.
Directory monitoring is now aware of symlinks and treat them appropriately.
Fix problem when encountering special files type on linux.
Improve directory monitoring so that the related thread actually waits without using
any CPU time when there are no events.
Improve the suggestion when changing project folder or opening a new one.
Now the previously used directory are suggested but if the path is changed the
actual existing directories that match the pattern are suggested.
In addition always use the text entered in the command view even if a suggested entry
is highlighted.
The NagView warning window now no longer moves the document content.
### 2.0.4
Fix some bugs related to newly introduced directory monitoring using the dmon library.
Fix a problem with plain text search using Lua patterns by error.
Fix a problem with visualization of UTF-8 characters that caused garbage characters
visualization.
Other fixes and improvements contributed by @Guldoman.
### 2.0.3
Replace periodic rescan of project folder with a notification based system using the

36
data/core/bit.lua Normal file
View File

@ -0,0 +1,36 @@
local bit = {}
local LUA_NBITS = 32
local ALLONES = (~(((~0) << (LUA_NBITS - 1)) << 1))
local function trim(x)
return (x & ALLONES)
end
local function mask(n)
return (~((ALLONES << 1) << ((n) - 1)))
end
local function check_args(field, width)
assert(field >= 0, "field cannot be negative")
assert(width > 0, "width must be positive")
assert(field + width < LUA_NBITS and field + width >= 0,
"trying to access non-existent bits")
end
function bit.extract(n, field, width)
local w = width or 1
check_args(field, w)
local m = trim(n)
return m >> field & mask(w)
end
function bit.replace(n, v, field, width)
local w = width or 1
check_args(field, w)
local m = trim(n)
local x = v & mask(width);
return m & ~(mask(w) << field) | (x << field)
end
return bit

View File

@ -64,7 +64,7 @@ end
function command.add_defaults()
local reg = {
"core", "root", "command", "doc", "findreplace",
"files", "drawwhitespace", "dialog"
"files", "drawwhitespace", "dialog", "log", "statusbar"
}
for _, name in ipairs(reg) do
require("core.commands." .. name)

View File

@ -10,8 +10,18 @@ local restore_title_view = false
local function suggest_directory(text)
text = common.home_expand(text)
return common.home_encode_list((text == "" or text == common.home_expand(common.dirname(core.project_dir)))
and core.recent_projects or common.dir_path_suggest(text))
local basedir = common.dirname(core.project_dir)
return common.home_encode_list((basedir and text == basedir .. PATHSEP or text == "") and
core.recent_projects or common.dir_path_suggest(text))
end
local function check_directory_path(path)
local abs_path = system.absolute_path(path)
local info = abs_path and system.get_file_info(abs_path)
if not info or info.type ~= 'dir' then
return nil
end
return abs_path
end
command.add(nil, {
@ -93,6 +103,15 @@ command.add(nil, {
core.root_view:open_doc(core.open_doc())
end,
["core:new-named-doc"] = function()
core.command_view:enter(
"File name",
function(text)
core.root_view:open_doc(core.open_doc(text))
end
)
end,
["core:open-file"] = function()
local view = core.active_view
if view.doc and view.doc.abs_filename then
@ -141,46 +160,50 @@ command.add(nil, {
end,
["core:open-project-module"] = function()
local filename = ".lite_project.lua"
if system.get_file_info(filename) then
core.root_view:open_doc(core.open_doc(filename))
else
local doc = core.open_doc()
core.root_view:open_doc(doc)
doc:save(filename)
if not system.get_file_info(".lite_project.lua") then
core.try(core.write_init_project_module, ".lite_project.lua")
end
local doc = core.open_doc(".lite_project.lua")
core.root_view:open_doc(doc)
doc:save()
end,
["core:change-project-folder"] = function()
local dirname = common.dirname(core.project_dir)
if dirname then
core.command_view:set_text(common.home_encode(dirname))
core.command_view:set_text(common.home_encode(dirname) .. PATHSEP)
end
core.command_view:enter("Change Project Folder", function(text, item)
text = system.absolute_path(common.home_expand(item and item.text or text))
if text == core.project_dir then return end
local path_stat = system.get_file_info(text)
if not path_stat or path_stat.type ~= 'dir' then
core.error("Cannot open folder %q", text)
core.command_view:enter("Change Project Folder", function(text)
local path = common.home_expand(text)
local abs_path = check_directory_path(path)
if not abs_path then
core.error("Cannot open directory %q", path)
return
end
core.confirm_close_docs(core.docs, core.open_folder_project, text)
if abs_path == core.project_dir then return end
core.confirm_close_docs(core.docs, function(dirpath)
core.open_folder_project(dirpath)
end, abs_path)
end, suggest_directory)
end,
["core:open-project-folder"] = function()
local dirname = common.dirname(core.project_dir)
if dirname then
core.command_view:set_text(common.home_encode(dirname))
core.command_view:set_text(common.home_encode(dirname) .. PATHSEP)
end
core.command_view:enter("Open Project", function(text, item)
text = common.home_expand(item and item.text or text)
local path_stat = system.get_file_info(text)
if not path_stat or path_stat.type ~= 'dir' then
core.error("Cannot open folder %q", text)
core.command_view:enter("Open Project", function(text)
local path = common.home_expand(text)
local abs_path = check_directory_path(path)
if not abs_path then
core.error("Cannot open directory %q", path)
return
end
system.exec(string.format("%q %q", EXEFILE, text))
if abs_path == core.project_dir then
core.error("Directory %q is currently opened", abs_path)
return
end
system.exec(string.format("%q %q", EXEFILE, abs_path))
end, suggest_directory)
end,

View File

@ -47,19 +47,32 @@ end
local function cut_or_copy(delete)
local full_text = ""
local text = ""
core.cursor_clipboard = {}
core.cursor_clipboard_whole_line = {}
for idx, line1, col1, line2, col2 in doc():get_selections() do
if line1 ~= line2 or col1 ~= col2 then
local text = doc():get_text(line1, col1, line2, col2)
text = doc():get_text(line1, col1, line2, col2)
full_text = full_text == "" and text or (full_text .. " " .. text)
core.cursor_clipboard_whole_line[idx] = false
if delete then
doc():delete_to_cursor(idx, 0)
end
full_text = full_text == "" and text or (full_text .. "\n" .. text)
doc().cursor_clipboard[idx] = text
else -- Cut/copy whole line
text = doc().lines[line1]
full_text = full_text == "" and text or (full_text .. text)
core.cursor_clipboard_whole_line[idx] = true
if delete then
if line1 < #doc().lines then
doc():remove(line1, 1, line1 + 1, 1)
else
doc().cursor_clipboard[idx] = ""
doc():remove(line1 - 1, math.huge, line1, math.huge)
end
end
doc().cursor_clipboard["full"] = full_text
end
core.cursor_clipboard[idx] = text
end
core.cursor_clipboard["full"] = full_text
system.set_clipboard(full_text)
end
@ -84,7 +97,90 @@ local function set_cursor(x, y, snap_type)
core.blink_reset()
end
local selection_commands = {
local function line_comment(comment, line1, col1, line2, col2)
local start_comment = (type(comment) == 'table' and comment[1] or comment) .. " "
local end_comment = (type(comment) == 'table' and " " .. comment[2])
local uncomment = true
local start_offset = math.huge
for line = line1, line2 do
local text = doc().lines[line]
local s = text:find("%S")
if s then
local cs, ce = text:find(start_comment, s, true)
if cs ~= s then
uncomment = false
end
start_offset = math.min(start_offset, s)
end
end
local end_line = col2 == #doc().lines[line2]
for line = line1, line2 do
local text = doc().lines[line]
local s = text:find("%S")
if s and uncomment then
if end_comment and text:sub(#text - #end_comment, #text - 1) == end_comment then
doc():remove(line, #text - #end_comment, line, #text)
end
local cs, ce = text:find(start_comment, s, true)
if ce then
doc():remove(line, cs, line, ce + 1)
end
elseif s then
doc():insert(line, start_offset, start_comment)
if end_comment then
doc():insert(line, #doc().lines[line], " " .. comment[2])
end
end
end
col1 = col1 + (col1 > start_offset and #start_comment or 0) * (uncomment and -1 or 1)
col2 = col2 + (col2 > start_offset and #start_comment or 0) * (uncomment and -1 or 1)
if end_comment and end_line then
col2 = col2 + #end_comment * (uncomment and -1 or 1)
end
return line1, col1, line2, col2
end
local function block_comment(comment, line1, col1, line2, col2)
-- automatically skip spaces
local word_start = doc():get_text(line1, col1, line1, math.huge):find("%S")
local word_end = doc():get_text(line2, 1, line2, col2):find("%s*$")
col1 = col1 + (word_start and (word_start - 1) or 0)
col2 = word_end and word_end or col2
local block_start = doc():get_text(line1, col1, line1, col1 + #comment[1])
local block_end = doc():get_text(line2, col2 - #comment[2], line2, col2)
if block_start == comment[1] and block_end == comment[2] then
-- remove up to 1 whitespace after the comment
local start_len, stop_len = #comment[1], #comment[2]
if doc():get_text(line1, col1 + #comment[1], line1, col1 + #comment[1] + 1):find("%s$") then
start_len = start_len + 1
end
if doc():get_text(line2, col2 - #comment[2] - 1, line2, col2):find("^%s") then
stop_len = stop_len + 1
end
doc():remove(line1, col1, line1, col1 + start_len)
col2 = col2 - (line1 == line2 and start_len or 0)
doc():remove(line2, col2 - stop_len, line2, col2)
return line1, col1, line2, col2 - stop_len
else
doc():insert(line1, col1, comment[1] .. " ")
col2 = col2 + (line1 == line2 and (#comment[1] + 1) or 0)
doc():insert(line2, col2, " " .. comment[2])
return line1, col1, line2, col2 + #comment[2] + 1
end
end
local commands = {
["doc:select-none"] = function()
local line, col = doc():get_selection()
doc():set_selection(line, col)
end,
["doc:cut"] = function()
cut_or_copy(true)
end,
@ -93,13 +189,6 @@ local selection_commands = {
cut_or_copy(false)
end,
["doc:select-none"] = function()
local line, col = doc():get_selection()
doc():set_selection(line, col)
end
}
local commands = {
["doc:undo"] = function()
doc():undo()
end,
@ -111,13 +200,29 @@ local commands = {
["doc:paste"] = function()
local clipboard = system.get_clipboard()
-- If the clipboard has changed since our last look, use that instead
if doc().cursor_clipboard["full"] ~= clipboard then
doc().cursor_clipboard = {}
local external_paste = core.cursor_clipboard["full"] ~= clipboard
if external_paste then
core.cursor_clipboard = {}
core.cursor_clipboard_whole_line = {}
end
local value, whole_line
for idx, line1, col1, line2, col2 in doc():get_selections() do
local value = doc().cursor_clipboard[idx] or clipboard
if #core.cursor_clipboard_whole_line == (#doc().selections/4) then
value = core.cursor_clipboard[idx]
whole_line = core.cursor_clipboard_whole_line[idx] == true
else
value = clipboard
whole_line = not external_paste and clipboard:find("\n") ~= nil
end
if whole_line then
doc():insert(line1, 1, value:gsub("\r", ""))
if col1 == 1 then
doc():move_to_cursor(idx, #value)
end
else
doc():text_input(value:gsub("\r", ""), idx)
end
end
end,
["doc:newline"] = function()
@ -171,6 +276,11 @@ local commands = {
["doc:select-all"] = function()
doc():set_selection(1, 1, math.huge, math.huge)
-- avoid triggering DocView:scroll_to_make_visible
dv().last_line1 = 1
dv().last_col1 = 1
dv().last_line2 = #doc().lines
dv().last_col2 = #doc().lines[#doc().lines]
end,
["doc:select-lines"] = function()
@ -263,34 +373,30 @@ local commands = {
end
end,
["doc:toggle-block-comments"] = function()
local comment = doc().syntax.block_comment
if not comment then
if doc().syntax.comment then
command.perform "doc:toggle-line-comments"
end
return
end
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
-- if nothing is selected, toggle the whole line
if line1 == line2 and col1 == col2 then
col1 = 1
col2 = #doc().lines[line2]
end
doc():set_selections(idx, block_comment(comment, line1, col1, line2, col2))
end
end,
["doc:toggle-line-comments"] = function()
local comment = doc().syntax.comment
if not comment then return end
local indentation = doc():get_indent_string()
local comment_text = comment .. " "
for idx, line1, _, line2 in doc_multiline_selections(true) do
local uncomment = true
local start_offset = math.huge
for line = line1, line2 do
local text = doc().lines[line]
local s = text:find("%S")
local cs, ce = text:find(comment_text, s, true)
if s and cs ~= s then
uncomment = false
start_offset = math.min(start_offset, s)
end
end
for line = line1, line2 do
local text = doc().lines[line]
local s = text:find("%S")
if uncomment then
local cs, ce = text:find(comment_text, s, true)
if ce then
doc():remove(line, cs, line, ce + 1)
end
elseif s then
doc():insert(line, start_offset, comment_text)
end
local comment = doc().syntax.comment or doc().syntax.block_comment
if comment then
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
doc():set_selections(idx, line_comment(comment, line1, col1, line2, col2))
end
end
end,
@ -388,7 +494,7 @@ local commands = {
end
for i,docview in ipairs(core.get_views_referencing_doc(doc())) do
local node = core.root_view.root_node:get_node_for_view(docview)
node:close_view(core.root_view, docview)
node:close_view(core.root_view.root_node, docview)
end
os.remove(filename)
core.log("Removed \"%s\"", filename)
@ -415,7 +521,18 @@ local commands = {
["doc:split-cursor"] = function(x, y, clicks)
local line, col = dv():resolve_screen_position(x, y)
local removal_target = nil
for idx, line1, col1 in doc():get_selections(true) do
if line1 == line and col1 == col and #doc().selections > 4 then
removal_target = idx
end
end
if removal_target then
doc():remove_selection(removal_target)
else
doc():add_selection(line, col, line, col)
end
dv().mouse_selecting = { line, col, "set" }
end,
["doc:create-cursor-previous-line"] = function()
@ -461,21 +578,20 @@ commands["doc:move-to-previous-char"] = function()
for idx, line1, col1, line2, col2 in doc():get_selections(true) do
if line1 ~= line2 or col1 ~= col2 then
doc():set_selections(idx, line1, col1)
else
doc():move_to_cursor(idx, translate.previous_char)
end
end
doc():move_to(translate.previous_char)
end
commands["doc:move-to-next-char"] = function()
for idx, line1, col1, line2, col2 in doc():get_selections(true) do
if line1 ~= line2 or col1 ~= col2 then
doc():set_selections(idx, line2, col2)
else
doc():move_to_cursor(idx, translate.next_char)
end
end
doc():move_to(translate.next_char)
end
command.add("core.docview", commands)
command.add(function()
return core.active_view:is(DocView) and core.active_view.doc:has_any_selection()
end ,selection_commands)

View File

@ -55,7 +55,7 @@ end
local function find(label, search_fn)
last_view, last_sel = core.active_view,
{ core.active_view.doc:get_selection() }
local text = last_view.doc:get_text(unpack(last_sel))
local text = last_view.doc:get_text(table.unpack(last_sel))
found_expression = false
core.command_view:set_text(text, true)

View File

@ -0,0 +1,16 @@
local core = require "core"
local command = require "core.command"
command.add(nil, {
["log:open-as-doc"] = function()
local doc = core.open_doc("logs.txt")
core.root_view:open_doc(doc)
doc:insert(1, 1, core.get_log())
doc.new_file = false
doc:clean()
end,
["log:copy-to-clipboard"] = function()
system.set_clipboard(core.get_log())
end
})

View File

@ -0,0 +1,71 @@
local core = require "core"
local command = require "core.command"
local common = require "core.common"
local style = require "core.style"
local StatusView = require "core.statusview"
local function status_view_item_names()
local items = core.status_view:get_items_list()
local names = {}
for _, item in ipairs(items) do
table.insert(names, item.name)
end
return names
end
local function status_view_items_data(names)
local data = {}
for _, name in ipairs(names) do
local item = core.status_view:get_item(name)
table.insert(data, {
text = command.prettify_name(item.name),
info = item.alignment == StatusView.Item.LEFT and "Left" or "Right",
name = item.name
})
end
return data
end
local function status_view_get_items(text)
local names = status_view_item_names()
local results = common.fuzzy_match(names, text)
results = status_view_items_data(results)
return results
end
command.add(nil, {
["status-bar:toggle"] = function()
core.status_view:toggle()
end,
["status-bar:show"] = function()
core.status_view:show()
end,
["status-bar:hide"] = function()
core.status_view:hide()
end,
["status-bar:disable-messages"] = function()
core.status_view:display_messages(false)
end,
["status-bar:enable-messages"] = function()
core.status_view:display_messages(true)
end,
["status-bar:hide-item"] = function()
core.command_view:enter("Status bar item to hide",
function(text, item)
core.status_view:hide_items(item.name)
end,
status_view_get_items
)
end,
["status-bar:show-item"] = function()
core.command_view:enter("Status bar item to show",
function(text, item)
core.status_view:show_items(item.name)
end,
status_view_get_items
)
end,
["status-bar:reset-items"] = function()
core.status_view:show_items()
end,
})

View File

@ -17,6 +17,14 @@ function common.clamp(n, lo, hi)
end
function common.merge(a, b)
local t = {}
for k, v in pairs(a) do t[k] = v end
if b then for k, v in pairs(b) do t[k] = v end end
return t
end
function common.round(n)
return n >= 0 and math.floor(n + 0.5) or math.ceil(n - 0.5)
end
@ -42,7 +50,7 @@ end
function common.distance(x1, y1, x2, y2)
return math.sqrt(math.pow(x2-x1, 2)+math.pow(y2-y1, 2))
return math.sqrt(((x2-x1) ^ 2)+((y2-y1) ^ 2))
end
@ -437,4 +445,5 @@ function common.rm(path, recursively)
return true
end
return common

View File

@ -6,19 +6,19 @@ config.message_timeout = 5
config.mouse_wheel_scroll = 50 * SCALE
config.scroll_past_end = true
config.file_size_limit = 10
config.ignore_files = "^%."
config.ignore_files = { "^%." }
config.symbol_pattern = "[%a_][%w_]*"
config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"
config.undo_merge_timeout = 0.3
config.max_undos = 10000
config.max_tabs = 8
config.always_show_tabs = true
-- Possible values: false, true, "no_selection"
config.highlight_current_line = true
config.line_height = 1.2
config.indent_size = 2
config.tab_type = "soft"
config.line_limit = 80
config.max_symbols = 4000
config.max_project_files = 2000
config.transitions = true
config.animation_rate = 1.0
@ -29,10 +29,19 @@ config.borderless = false
config.tab_close_button = true
config.max_clicks = 3
-- Disable plugin loading setting to false the config entry
-- of the same name.
config.plugins = {}
-- set as true to be able to test non supported plugins
config.skip_plugins_version = false
config.plugins = {}
-- Allow you to set plugin configs even if we haven't seen the plugin before.
setmetatable(config.plugins, {
__index = function(t, k)
if rawget(t, k) == nil then rawset(t, k, {}) end
return rawget(t, k)
end
})
-- Disable these plugins by default.
config.plugins.trimwhitespace = false
config.plugins.lineguide = false
config.plugins.drawwhitespace = false

View File

@ -91,6 +91,7 @@ function ContextMenu:show(x, y)
self.position.x, self.position.y = x, y
self.show_context_menu = true
core.request_cursor("arrow")
return true
end
return false
@ -101,6 +102,7 @@ function ContextMenu:hide()
self.items = nil
self.selected = -1
self.height = 0
core.request_cursor(core.active_view.cursor)
end
function ContextMenu:each_item()
@ -126,9 +128,6 @@ function ContextMenu:on_mouse_moved(px, py)
break
end
end
if self.selected >= 0 then
core.request_cursor("arrow")
end
return true
end
@ -140,8 +139,38 @@ function ContextMenu:on_selected(item)
end
end
function ContextMenu:on_mouse_pressed(button, x, y, clicks)
local selected = (self.items or {})[self.selected]
local function change_value(value, change)
return value + change
end
function ContextMenu:focus_previous()
self.selected = (self.selected == -1 or self.selected == 1) and #self.items or change_value(self.selected, -1)
if self:get_item_selected() == DIVIDER then
self.selected = change_value(self.selected, -1)
end
end
function ContextMenu:focus_next()
self.selected = (self.selected == -1 or self.selected == #self.items) and 1 or change_value(self.selected, 1)
if self:get_item_selected() == DIVIDER then
self.selected = change_value(self.selected, 1)
end
end
function ContextMenu:get_item_selected()
return (self.items or {})[self.selected]
end
function ContextMenu:call_selected_item()
local selected = self:get_item_selected()
self:hide()
if selected then
self:on_selected(selected)
end
end
function ContextMenu:on_mouse_pressed(button, px, py, clicks)
local selected = self:get_item_selected()
local caught = false
self:hide()
@ -153,7 +182,7 @@ function ContextMenu:on_mouse_pressed(button, x, y, clicks)
end
if button == "right" then
caught = self:show(x, y)
caught = self:show(px, py)
end
return caught
end

220
data/core/dirwatch.lua Normal file
View File

@ -0,0 +1,220 @@
local common = require "core.common"
local config = require "core.config"
local dirwatch = {}
function dirwatch:__index(idx)
local value = rawget(self, idx)
if value ~= nil then return value end
return dirwatch[idx]
end
function dirwatch.new()
local t = {
scanned = {},
watched = {},
reverse_watched = {},
monitor = dirmonitor.new(),
windows_watch_top = nil,
windows_watch_count = 0
}
setmetatable(t, dirwatch)
return t
end
function dirwatch:scan(directory, bool)
if bool == false then return self:unwatch(directory) end
self.scanned[directory] = system.get_file_info(directory).modified
end
-- Should be called on every directory in a subdirectory.
-- In windows, this is a no-op for anything underneath a top-level directory,
-- but code should be called anyway, so we can ensure that we have a proper
-- experience across all platforms. Should be an absolute path.
function dirwatch:watch(directory, bool)
if bool == false then return self:unwatch(directory) end
if not self.watched[directory] and not self.scanned[directory] then
if PLATFORM == "Windows" then
if not self.windows_watch_top or directory:find(self.windows_watch_top, 1, true) ~= 1 then
-- Get the highest level of directory that is common to this directory, and the original.
local target = directory
while self.windows_watch_top and self.windows_watch_top:find(target, 1, true) ~= 1 do
target = common.dirname(target)
end
if target ~= self.windows_watch_top then
local value = self.monitor:watch(target)
if value and value < 0 then
return self:scan(directory)
end
self.windows_watch_top = target
self.windows_watch_count = self.windows_watch_count + 1
end
end
self.watched[directory] = true
else
local value = self.monitor:watch(directory)
-- If for whatever reason, we can't watch this directory, revert back to scanning.
-- Don't bother trying to find out why, for now.
if value and value < 0 then
return self:scan(directory)
end
self.watched[directory] = value
self.reverse_watched[value] = directory
end
end
end
-- this should be an absolute path
function dirwatch:unwatch(directory)
if self.watched[directory] then
if PLATFORM ~= "Windows" then
self.monitor:unwatch(self.watched[directory])
self.reverse_watched[directory] = nil
else
self.windows_watch_count = self.windows_watch_count - 1
if self.windows_watch_count == 0 then
self.windows_watch_top = nil
self.monitor:unwatch(directory)
end
end
self.watched[directory] = nil
elseif self.scanned[directory] then
self.scanned[directory] = nil
end
end
-- designed to be run inside a coroutine.
function dirwatch:check(change_callback, scan_time, wait_time)
self.monitor:check(function(id)
if PLATFORM == "Windows" then
change_callback(common.dirname(self.windows_watch_top .. PATHSEP .. id))
elseif self.reverse_watched[id] then
change_callback(self.reverse_watched[id])
end
end)
local start_time = system.get_time()
for directory, old_modified in pairs(self.scanned) do
if old_modified then
local new_modified = system.get_file_info(directory).modified
if old_modified < new_modified then
change_callback(directory)
self.scanned[directory] = new_modified
end
end
if system.get_time() - start_time > scan_time then
coroutine.yield(wait_time)
start_time = system.get_time()
end
end
end
-- inspect config.ignore_files patterns and prepare ready to use entries.
local function compile_ignore_files()
local ipatterns = config.ignore_files
local compiled = {}
-- config.ignore_files could be a simple string...
if type(ipatterns) ~= "table" then ipatterns = {ipatterns} end
for i, pattern in ipairs(ipatterns) do
-- we ignore malformed pattern that raise an error
if pcall(string.match, "a", pattern) then
table.insert(compiled, {
use_path = pattern:match("/[^/$]"), -- contains a slash but not at the end
-- An '/' or '/$' at the end means we want to match a directory.
match_dir = pattern:match(".+/%$?$"), -- to be used as a boolen value
pattern = pattern -- get the actual pattern
})
end
end
return compiled
end
local function fileinfo_pass_filter(info, ignore_compiled)
if info.size >= config.file_size_limit * 1e6 then return false end
local basename = common.basename(info.filename)
-- replace '\' with '/' for Windows where PATHSEP = '\'
local fullname = "/" .. info.filename:gsub("\\", "/")
for _, compiled in ipairs(ignore_compiled) do
local test = compiled.use_path and fullname or basename
if compiled.match_dir then
if info.type == "dir" and string.match(test .. "/", compiled.pattern) then
return false
end
else
if string.match(test, compiled.pattern) then
return false
end
end
end
return true
end
local function compare_file(a, b)
return a.filename < b.filename
end
-- compute a file's info entry completed with "filename" to be used
-- in project scan or falsy if it shouldn't appear in the list.
local function get_project_file_info(root, file, ignore_compiled)
local info = system.get_file_info(root .. PATHSEP .. file)
-- info can be not nil but info.type may be nil if is neither a file neither
-- a directory, for example for /dev/* entries on linux.
if info and info.type then
info.filename = file
return fileinfo_pass_filter(info, ignore_compiled) and info
end
end
-- "root" will by an absolute path without trailing '/'
-- "path" will be a path starting without '/' and without trailing '/'
-- or the empty string.
-- It will identifies a sub-path within "root.
-- The current path location will therefore always be: root .. path.
-- When recursing "root" will always be the same, only "path" will change.
-- Returns a list of file "items". In each item the "filename" will be the
-- complete file path relative to "root" *without* the trailing '/', and without the starting '/'.
function dirwatch.get_directory_files(dir, root, path, t, entries_count, recurse_pred)
local t0 = system.get_time()
local t_elapsed = system.get_time() - t0
local dirs, files = {}, {}
local ignore_compiled = compile_ignore_files()
local all = system.list_dir(root .. PATHSEP .. path)
if not all then return nil end
for _, file in ipairs(all or {}) do
local info = get_project_file_info(root, (path ~= "" and (path .. PATHSEP) or "") .. file, ignore_compiled)
if info then
table.insert(info.type == "dir" and dirs or files, info)
entries_count = entries_count + 1
end
end
local recurse_complete = true
table.sort(dirs, compare_file)
for _, f in ipairs(dirs) do
table.insert(t, f)
if recurse_pred(dir, f.filename, entries_count, t_elapsed) then
local _, complete, n = dirwatch.get_directory_files(dir, root, f.filename, t, entries_count, recurse_pred)
recurse_complete = recurse_complete and complete
entries_count = n
else
recurse_complete = false
end
end
table.sort(files, compare_file)
for _, f in ipairs(files) do
table.insert(t, f)
end
return t, recurse_complete, entries_count
end
return dirwatch

View File

@ -33,7 +33,6 @@ end
function Doc:reset()
self.lines = { "\n" }
self.selections = { 1, 1, 1, 1 }
self.cursor_clipboard = {}
self.undo_stack = { idx = 1 }
self.redo_stack = { idx = 1 }
self.clean_change_id = 1
@ -55,6 +54,7 @@ end
function Doc:set_filename(filename, abs_filename)
self.filename = filename
self.abs_filename = abs_filename
self:reset_syntax()
end
@ -85,6 +85,8 @@ function Doc:save(filename, abs_filename)
assert(self.filename, "no filename set to default to")
filename = self.filename
abs_filename = self.abs_filename
else
assert(self.filename or abs_filename, "calling save on unnamed doc without absolute path")
end
local fp = assert( io.open(filename, "wb") )
for _, line in ipairs(self.lines) do
@ -94,7 +96,6 @@ function Doc:save(filename, abs_filename)
fp:close()
self:set_filename(filename, abs_filename)
self.new_file = false
self:reset_syntax()
self:clean()
end
@ -197,8 +198,14 @@ function Doc:add_selection(line1, col1, line2, col2, swap)
self:set_selections(target, line1, col1, line2, col2, swap, 0)
end
function Doc:remove_selection(idx)
common.splice(self.selections, (idx - 1) * 4 + 1, 4)
end
function Doc:set_selection(line1, col1, line2, col2, swap)
self.selections, self.cursor_clipboard = {}, {}
self.selections = {}
self:set_selections(1, line1, col1, line2, col2, swap)
end
@ -208,12 +215,10 @@ function Doc:merge_cursors(idx)
if self.selections[i] == self.selections[j] and
self.selections[i+1] == self.selections[j+1] then
common.splice(self.selections, i, 4)
common.splice(self.cursor_clipboard, i, 1)
break
end
end
end
if #self.selections <= 4 then self.cursor_clipboard = {} end
end
local function selection_iterator(invariant, idx)

View File

@ -224,7 +224,6 @@ function DocView:scroll_to_make_visible(line, col)
end
end
function DocView:on_mouse_moved(x, y, ...)
DocView.super.on_mouse_moved(self, x, y, ...)
@ -284,13 +283,15 @@ end
function DocView:update()
-- scroll to make caret visible and reset blink timer if it moved
local line, col = self.doc:get_selection()
if (line ~= self.last_line or col ~= self.last_col) and self.size.x > 0 then
local line1, col1, line2, col2 = self.doc:get_selection()
if (line1 ~= self.last_line1 or col1 ~= self.last_col1 or
line2 ~= self.last_line2 or col2 ~= self.last_col2) and self.size.x > 0 then
if core.active_view == self then
self:scroll_to_make_visible(line, col)
self:scroll_to_make_visible(line1, col1)
end
core.blink_reset()
self.last_line, self.last_col = line, col
self.last_line1, self.last_col1 = line1, col1
self.last_line2, self.last_col2 = line2, col2
end
-- update blink timer
@ -331,13 +332,22 @@ end
function DocView:draw_line_body(idx, x, y)
-- draw highlight if any selection ends on this line
local draw_highlight = false
local hcl = config.highlight_current_line
if hcl ~= false then
for lidx, line1, col1, line2, col2 in self.doc:get_selections(false) do
if line1 == idx then
if hcl == "no_selection" then
if (line1 ~= line2) or (col1 ~= col2) then
draw_highlight = false
break
end
end
draw_highlight = true
break
end
end
if draw_highlight and config.highlight_current_line and core.active_view == self then
end
if draw_highlight and core.active_view == self then
self:draw_line_highlight(x + self.scroll.x, y)
end
@ -369,9 +379,8 @@ function DocView:draw_line_gutter(idx, x, y, width)
break
end
end
local yoffset = self:get_line_text_y_offset()
x = x + style.padding.x
common.draw_text(self:get_font(), color, idx, "right", x, y + yoffset, width, self:get_line_height())
common.draw_text(self:get_font(), color, idx, "right", x, y, width, self:get_line_height())
end

View File

@ -8,8 +8,18 @@ local function draw_text(x, y, color)
local th = style.big_font:get_height()
local dh = 2 * th + style.padding.y * 2
local x1, y1 = x, y + (dh - th) / 2
x = renderer.draw_text(style.big_font, "Lite XL", x1, y1, color)
renderer.draw_text(style.font, "version " .. VERSION, x1, y1 + th, color)
local xv = x1
local title = "Lite XL"
local version = "version " .. VERSION
local title_width = style.big_font:get_width(title)
local version_width = style.font:get_width(version)
if version_width > title_width then
version = VERSION
version_width = style.font:get_width(version)
xv = x1 - (version_width - title_width)
end
x = renderer.draw_text(style.big_font, title, x1, y1, color)
renderer.draw_text(style.font, version, xv, y1 + th, color)
x = x + style.padding.x
renderer.draw_rect(x, y, math.ceil(1 * SCALE), dh, color)
local lines = {

File diff suppressed because it is too large Load Diff

View File

@ -215,6 +215,7 @@ keymap.add_direct {
["ctrl+l"] = "doc:select-lines",
["ctrl+shift+l"] = { "find-replace:select-add-all", "doc:select-word" },
["ctrl+/"] = "doc:toggle-line-comments",
["ctrl+shift+/"] = "doc:toggle-block-comments",
["ctrl+up"] = "doc:move-lines-up",
["ctrl+down"] = "doc:move-lines-down",
["ctrl+shift+d"] = "doc:duplicate-lines",

View File

@ -1,5 +1,6 @@
local core = require "core"
local common = require "core.common"
local keymap = require "core.keymap"
local style = require "core.style"
local View = require "core.view"
@ -36,12 +37,15 @@ local LogView = View:extend()
LogView.context = "session"
function LogView:new()
LogView.super.new(self)
self.last_item = core.log_items[#core.log_items]
self.expanding = {}
self.scrollable = true
self.yoffset = 0
core.status_view:show_message("i", style.text, "ctrl+click to copy entry")
end
@ -77,25 +81,30 @@ function LogView:each_item()
end
function LogView:on_mouse_moved(px, py, ...)
LogView.super.on_mouse_moved(self, px, py, ...)
local hovered = false
for _, item, x, y, w, h in self:each_item() do
function LogView:on_mouse_pressed(button, px, py, clicks)
if LogView.super.on_mouse_pressed(self, button, px, py, clicks) then
return true
end
local index, selected
for i, item, x, y, w, h in self:each_item() do
if px >= x and py >= y and px < x + w and py < y + h then
hovered = true
self.hovered_item = item
index = i
selected = item
break
end
end
if not hovered then self.hovered_item = nil end
end
function LogView:on_mouse_pressed(button, mx, my, clicks)
if LogView.super.on_mouse_pressed(self, button, mx, my, clicks) then return end
if self.hovered_item then
self:expand_item(self.hovered_item)
if selected then
if keymap.modkeys["ctrl"] then
system.set_clipboard(core.get_log(selected))
core.status_view:show_message("i", style.text, "copied entry #"..index.." to clipboard.")
else
self:expand_item(selected)
end
end
return true
end
@ -131,21 +140,37 @@ local function draw_text_multiline(font, text, x, y, color)
return resx, y
end
-- this is just to get a date string that's consistent
local datestr = os.date()
function LogView:draw()
self:draw_background(style.background)
local th = style.font:get_height()
local lh = th + style.padding.y -- for one line
for _, item, x, y, w in self:each_item() do
local iw = math.max(
style.icon_font:get_width(style.log.ERROR.icon),
style.icon_font:get_width(style.log.INFO.icon)
)
local tw = style.font:get_width(datestr)
for _, item, x, y, w, h in self:each_item() do
core.push_clip_rect(x, y, w, h)
x = x + style.padding.x
x = common.draw_text(
style.icon_font,
style.log[item.level].color,
style.log[item.level].icon,
"center",
x, y, iw, lh
)
x = x + style.padding.x
-- timestamps are always 15% of the width
local time = os.date(nil, item.time)
x = common.draw_text(style.font, style.dim, time, "left", x, y, w, lh)
x = x + style.padding.x
common.draw_text(style.font, style.dim, time, "left", x, y, tw, lh)
x = x + tw + style.padding.x
x = common.draw_text(style.code_font, style.dim, is_expanded(item) and "-" or "+", "left", x, y, w, lh)
x = x + style.padding.x
w = w - (x - self:get_content_offset())
if is_expanded(item) then
@ -165,6 +190,8 @@ function LogView:draw()
end
_, y = common.draw_text(style.font, style.text, line, "left", x, y, w, lh)
end
core.pop_clip_rect()
end
end

View File

@ -16,8 +16,12 @@ local NagView = View:extend()
function NagView:new()
NagView.super.new(self)
self.size.y = 0
self.show_height = 0
self.force_focus = false
self.queue = {}
self.scrollable = true
self.target_height = 0
self.on_mouse_pressed_root = nil
end
function NagView:get_title()
@ -46,20 +50,20 @@ function NagView:get_target_height()
return self.target_height + 2 * style.padding.y
end
function NagView:update()
NagView.super.update(self)
if core.active_view == self and self.title then
self:move_towards(self.size, "y", self:get_target_height())
self:move_towards(self, "underline_progress", 1)
function NagView:get_scrollable_size()
local w, h = system.get_window_size()
if self.visible and self:get_target_height() > h then
self.size.y = h
return self:get_target_height()
else
self:move_towards(self.size, "y", 0)
self.size.y = 0
end
return 0
end
function NagView:draw_overlay()
function NagView:dim_window_content()
local ox, oy = self:get_content_offset()
oy = oy + self.size.y
oy = oy + self.show_height
local w, h = core.root_view.size.x, core.root_view.size.y - oy
core.root_view:defer_draw(function()
renderer.draw_rect(ox, oy, w, h, style.nagbar_dim)
@ -81,7 +85,7 @@ function NagView:each_option()
bh = self:get_buttons_height()
ox,oy = self:get_content_offset()
ox = ox + self.size.x
oy = oy + self.size.y - bh - style.padding.y
oy = oy + self.show_height - bh - style.padding.y
for i = #self.options, 1, -1 do
opt = self.options[i]
@ -94,6 +98,8 @@ function NagView:each_option()
end
function NagView:on_mouse_moved(mx, my, ...)
if not self.visible then return end
core.set_active_view(self)
NagView.super.on_mouse_moved(self, mx, my, ...)
for i, _, x,y,w,h in self:each_option() do
if mx >= x and my >= y and mx < x + w and my < y + h then
@ -103,18 +109,55 @@ function NagView:on_mouse_moved(mx, my, ...)
end
end
local function register_mouse_pressed(self)
if self.on_mouse_pressed_root then return end
-- RootView is loaded locally to avoid NagView and RootView being
-- mutually recursive
local RootView = require "core.rootview"
self.on_mouse_pressed_root = RootView.on_mouse_pressed
local this = self
function RootView:on_mouse_pressed(button, x, y, clicks)
if
not this:on_mouse_pressed(button, x, y, clicks)
then
return this.on_mouse_pressed_root(self, button, x, y, clicks)
else
return true
end
end
self.new_on_mouse_pressed_root = RootView.on_mouse_pressed
end
local function unregister_mouse_pressed(self)
local RootView = require "core.rootview"
if
self.on_mouse_pressed_root
and
-- just in case prevent overwriting what something else may
-- have overwrote after us, but after testing with various
-- plugins this doesn't seems to happen, but just in case
self.new_on_mouse_pressed_root == RootView.on_mouse_pressed
then
RootView.on_mouse_pressed = self.on_mouse_pressed_root
self.on_mouse_pressed_root = nil
self.new_on_mouse_pressed_root = nil
end
end
function NagView:on_mouse_pressed(button, mx, my, clicks)
if NagView.super.on_mouse_pressed(self, button, mx, my, clicks) then return end
if not self.visible then return false end
if NagView.super.on_mouse_pressed(self, button, mx, my, clicks) then return true end
for i, _, x,y,w,h in self:each_option() do
if mx >= x and my >= y and mx < x + w and my < y + h then
self:change_hovered(i)
command.perform "dialog:select"
break
end
end
return true
end
function NagView:on_text_input(text)
if not self.visible then return end
if text:lower() == "y" then
command.perform "dialog:select-yes"
elseif text:lower() == "n" then
@ -122,20 +165,39 @@ function NagView:on_text_input(text)
end
end
function NagView:update()
if not self.visible and self.show_height <= 0 then return end
NagView.super.update(self)
function NagView:draw()
if self.size.y <= 0 or not self.title then return end
if self.visible and core.active_view == self and self.title then
self:move_towards(self, "show_height", self:get_target_height())
self:move_towards(self, "underline_progress", 1)
else
self:move_towards(self, "show_height", 0)
if self.show_height <= 0 then
self.title = nil
self.message = nil
self.options = nil
self.on_selected = nil
end
end
end
self:draw_overlay()
self:draw_background(style.nagbar)
local function draw_nagview_message(self)
self:dim_window_content()
-- draw message's background
local ox, oy = self:get_content_offset()
renderer.draw_rect(ox, oy, self.size.x, self.show_height, style.nagbar)
ox = ox + style.padding.x
core.push_clip_rect(ox, oy, self.size.x, self.show_height)
-- if there are other items, show it
if #self.queue > 0 then
local str = string.format("[%d]", #self.queue)
ox = common.draw_text(style.font, style.nagbar_text, str, "left", ox, oy, self.size.x, self.size.y)
ox = common.draw_text(style.font, style.nagbar_text, str, "left", ox, oy, self.size.x, self.show_height)
ox = ox + style.padding.x
end
@ -168,6 +230,17 @@ function NagView:draw()
common.draw_text(opt.font, style.nagbar_text, opt.text, "center", fx,fy,fw,fh)
end
self:draw_scrollbar()
core.pop_clip_rect()
end
function NagView:draw()
if (not self.visible and self.show_height <= 0) or not self.title then
return
end
core.root_view:defer_draw(draw_nagview_message, self)
end
function NagView:get_message_height()
@ -178,23 +251,31 @@ function NagView:get_message_height()
return h
end
function NagView:next()
local opts = table.remove(self.queue, 1) or {}
if opts.title and opts.message and opts.options then
self.visible = true
self.title = opts.title
self.message = opts.message and opts.message .. "\n"
self.options = opts.options
self.on_selected = opts.on_selected
if self.message and self.options then
local message_height = self:get_message_height()
-- self.target_height is the nagview height needed to display the message and
-- the buttons, excluding the top and bottom padding space.
self.target_height = math.max(message_height, self:get_buttons_height())
self:change_hovered(common.find_index(self.options, "default_yes"))
self.force_focus = true
core.set_active_view(self)
-- We add a hook to manage all the mouse_pressed events.
register_mouse_pressed(self)
else
self.force_focus = false
core.set_active_view(core.next_active_view or core.last_active_view)
self.visible = false
unregister_mouse_pressed(self)
end
self.force_focus = self.message ~= nil
core.set_active_view(self.message ~= nil and self or
core.next_active_view or core.last_active_view)
end
function NagView:show(title, message, options, on_select)
@ -204,7 +285,7 @@ function NagView:show(title, message, options, on_select)
opts.options = assert(options, "No options")
opts.on_selected = on_select or noop
table.insert(self.queue, opts)
if #self.queue > 0 and not self.title then self:next() end
self:next()
end
return NagView

View File

@ -260,8 +260,8 @@ end
local function close_button_location(x, w)
local cw = style.icon_font:get_width("C")
local pad = style.padding.y
return x + w - pad - cw, cw, pad
local pad = style.padding.x / 2
return x + w - cw - pad, cw, pad
end
@ -476,51 +476,59 @@ function Node:update()
end
end
function Node:draw_tab(text, is_active, is_hovered, is_close_hovered, x, y, w, h, standalone)
function Node:draw_tab_title(view, font, is_active, is_hovered, x, y, w, h)
local text = view and view:get_name() or ""
local dots_width = font:get_width("")
local align = "center"
if font:get_width(text) > w then
align = "left"
for i = 1, #text do
local reduced_text = text:sub(1, #text - i)
if font:get_width(reduced_text) + dots_width <= w then
text = reduced_text .. ""
break
end
end
end
local color = style.dim
if is_active then color = style.text end
if is_hovered then color = style.text end
common.draw_text(font, color, text, align, x, y, w, h)
end
function Node:draw_tab_borders(view, is_active, is_hovered, x, y, w, h, standalone)
-- Tabs deviders
local ds = style.divider_size
local dots_width = style.font:get_width("")
local color = style.dim
local padding_y = style.padding.y
renderer.draw_rect(x + w, y + padding_y, ds, h - padding_y * 2, style.dim)
renderer.draw_rect(x + w, y + padding_y, ds, h - padding_y*2, style.dim)
if standalone then
renderer.draw_rect(x-1, y-1, w+2, h+2, style.background2)
end
-- Full border
if is_active then
color = style.text
renderer.draw_rect(x, y, w, h, style.background)
renderer.draw_rect(x + w, y, ds, h, style.divider)
renderer.draw_rect(x - ds, y, ds, h, style.divider)
end
local cx, cw, cspace = close_button_location(x, w)
return x + ds, y, w - ds*2, h
end
function Node:draw_tab(view, is_active, is_hovered, is_close_hovered, x, y, w, h, standalone)
x, y, w, h = self:draw_tab_borders(view, is_active, is_hovered, x, y, w, h, standalone)
-- Close button
local cx, cw, cpad = close_button_location(x, w)
local show_close_button = ((is_active or is_hovered) and not standalone and config.tab_close_button)
if show_close_button then
local close_style = is_close_hovered and style.text or style.dim
common.draw_text(style.icon_font, close_style, "C", nil, cx, y, 0, h)
common.draw_text(style.icon_font, close_style, "C", nil, cx, y, cw, h)
end
if is_hovered then
color = style.text
end
local padx = style.padding.x
-- Normally we should substract "cspace" from text_avail_width and from the
-- clipping width. It is the padding space we give to the left and right of the
-- close button. However, since we are using dots to terminate filenames, we
-- choose to ignore "cspace" accepting that the text can possibly "touch" the
-- close button.
local text_avail_width = cx - x - padx
core.push_clip_rect(x, y, cx - x, h)
x, w = x + padx, w - padx * 2
local align = "center"
if style.font:get_width(text) > text_avail_width then
align = "left"
for i = 1, #text do
local reduced_text = text:sub(1, #text - i)
if style.font:get_width(reduced_text) + dots_width <= text_avail_width then
text = reduced_text .. ""
break
end
end
end
common.draw_text(style.font, color, text, align, x, y, w, h)
-- Title
x = x + cpad
w = cx - x
core.push_clip_rect(x, y, w, h)
self:draw_tab_title(view, style.font, is_active, is_hovered, x, y, w, h)
core.pop_clip_rect()
end
@ -547,7 +555,7 @@ function Node:draw_tabs()
for i = self.tab_offset, self.tab_offset + tabs_number - 1 do
local view = self.views[i]
local x, y, w, h = self:get_tab_rect(i)
self:draw_tab(view:get_name(), view == self.active_view,
self:draw_tab(view, view == self.active_view,
i == self.hovered_tab, i == self.hovered_close,
x, y, w, h)
end

View File

@ -30,7 +30,9 @@ end
function RootView:get_active_node()
return self.root_node:get_node_for_view(core.active_view)
local node = self.root_node:get_node_for_view(core.active_view)
if not node then node = self:get_primary_node() end
return node
end
@ -46,6 +48,7 @@ end
function RootView:get_active_node_default()
local node = self.root_node:get_node_for_view(core.active_view)
if not node then node = self:get_primary_node() end
if node.locked then
local default_view = self:get_primary_node().views[1]
assert(default_view, "internal error: cannot find original document node.")
@ -269,6 +272,12 @@ function RootView:on_mouse_moved(x, y, dx, dy)
end
function RootView:on_file_dropped(filename, x, y)
local node = self.root_node:get_child_overlapping_point(x, y)
return node and node.active_view:on_file_dropped(filename, x, y)
end
function RootView:on_mouse_wheel(...)
local x, y = self.mouse.x, self.mouse.y
local node = self.root_node:get_child_overlapping_point(x, y)
@ -381,8 +390,8 @@ function RootView:draw_grabbed_tab()
local _,_, w, h = dn.node:get_tab_rect(dn.idx)
local x = self.mouse.x - w / 2
local y = self.mouse.y - h / 2
local text = dn.node.views[dn.idx] and dn.node.views[dn.idx]:get_name() or ""
self.root_node:draw_tab(text, true, true, false, x, y, w, h, true)
local view = dn.node.views[dn.idx]
self.root_node:draw_tab(view, true, true, false, x, y, w, h, true)
end

View File

@ -12,8 +12,9 @@ else
local prefix = EXEDIR:match("^(.+)[/\\]bin$")
DATADIR = prefix and (prefix .. '/share/lite-xl') or (EXEDIR .. '/data')
end
USERDIR = (os.getenv("XDG_CONFIG_HOME") and os.getenv("XDG_CONFIG_HOME") .. "/lite-xl")
or (HOME and (HOME .. '/.config/lite-xl') or (EXEDIR .. '/user'))
USERDIR = (system.get_file_info(EXEDIR .. '/user') and (EXEDIR .. '/user'))
or ((os.getenv("XDG_CONFIG_HOME") and os.getenv("XDG_CONFIG_HOME") .. "/lite-xl"))
or (HOME and (HOME .. '/.config/lite-xl'))
package.path = DATADIR .. '/?.lua;' .. package.path
package.path = DATADIR .. '/?/init.lua;' .. package.path
@ -31,3 +32,5 @@ end }
table.pack = table.pack or pack or function(...) return {...} end
table.unpack = table.unpack or unpack
bit32 = bit32 or require "core.bit"

File diff suppressed because it is too large Load Diff

View File

@ -72,4 +72,9 @@ style.syntax["function"] = { common.color "#93DDFA" }
style.syntax_fonts = {}
-- style.syntax_fonts["comment"] = renderer.font.load(path_to_font, size_of_font, rendering_options)
style.log = {
INFO = { icon = "i", color = style.text },
ERROR = { icon = "!", color = style.error }
}
return style

View File

@ -7,6 +7,11 @@ local plain_text_syntax = { name = "Plain Text", patterns = {}, symbols = {} }
function syntax.add(t)
-- this rule gives us a performance gain for the tokenizer in lines with
-- long amounts of consecutive spaces without affecting other patterns
if t.patterns then
table.insert(t.patterns, 1, { pattern = "%s+", type = "normal" })
end
table.insert(syntax.items, t)
end

View File

@ -136,20 +136,42 @@ function tokenizer.tokenize(incoming_syntax, text, state)
end
local function find_text(text, p, offset, at_start, close)
local target, res = p.pattern or p.regex, { 1, offset - 1 }, p.regex
local code = type(target) == "table" and target[close and 2 or 1] or target
local target, res = p.pattern or p.regex, { 1, offset - 1 }
local p_idx = close and 2 or 1
local code = type(target) == "table" and target[p_idx] or target
if p.whole_line == nil then p.whole_line = { } end
if p.whole_line[p_idx] == nil then
-- Match patterns that start with '^'
p.whole_line[p_idx] = code:match("^%^") and true or false
if p.whole_line[p_idx] then
-- Remove '^' from the beginning of the pattern
if type(target) == "table" then
target[p_idx] = code:sub(2)
else
p.pattern = p.pattern and code:sub(2)
p.regex = p.regex and code:sub(2)
end
end
end
if p.regex and type(p.regex) ~= "table" then
p._regex = p._regex or regex.compile(p.regex)
code = p._regex
end
repeat
local next = res[2] + 1
-- If the pattern contained '^', allow matching only the whole line
if p.whole_line[p_idx] and next > 1 then
return
end
-- go to the start of the next utf-8 character
while text:byte(next) and common.is_utf8_cont(text, next) do
next = next + 1
end
res = p.pattern and { text:find(at_start and "^" .. code or code, next) }
or { regex.match(code, text, next, at_start and regex.ANCHORED or 0) }
res = p.pattern and { text:find((at_start or p.whole_line[p_idx]) and "^" .. code or code, next) }
or { regex.match(code, text, next, (at_start or p.whole_line[p_idx]) and regex.ANCHORED or 0) }
if res[1] and close and target[3] then
local count = 0
for i = res[1] - 1, 1, -1 do

View File

@ -25,7 +25,8 @@ function View:move_towards(t, k, dest, rate)
return self:move_towards(self, t, k, dest, rate)
end
local val = t[k]
if not config.transitions or math.abs(val - dest) < 0.5 then
local diff = math.abs(val - dest)
if not config.transitions or diff < 0.5 then
t[k] = dest
else
rate = rate or 0.5
@ -35,7 +36,7 @@ function View:move_towards(t, k, dest, rate)
end
t[k] = common.lerp(val, dest, rate)
end
if val ~= dest then
if diff > 1e-8 then
core.redraw = true
end
end
@ -98,6 +99,11 @@ function View:on_mouse_moved(x, y, dx, dy)
end
function View:on_file_dropped(filename, x, y)
return false
end
function View:on_text_input(text)
-- no-op
end

View File

@ -10,14 +10,18 @@ local RootView = require "core.rootview"
local DocView = require "core.docview"
local Doc = require "core.doc"
config.plugins.autocomplete = {
config.plugins.autocomplete = common.merge({
-- Amount of characters that need to be written for autocomplete
min_len = 3,
-- The max amount of visible items
max_height = 6,
-- The max amount of scrollable items
max_suggestions = 100,
}
-- Maximum amount of symbols to cache per document
max_symbols = 4000,
-- Font size of the description box
desc_font_size = 12
}, config.plugins.autocomplete)
local autocomplete = {}
@ -33,7 +37,7 @@ local triggered_manually = false
local mt = { __tostring = function(t) return t.text end }
function autocomplete.add(t, triggered_manually)
function autocomplete.add(t, manually_triggered)
local items = {}
for text, info in pairs(t.items) do
if type(info) == "table" then
@ -44,7 +48,8 @@ function autocomplete.add(t, triggered_manually)
text = text,
info = info.info,
desc = info.desc, -- Description shown on item selected
cb = info.cb, -- A callback called once when item is selected
onhover = info.onhover, -- A callback called once when item is hovered
onselect = info.onselect, -- A callback called when item is selected
data = info.data -- Optional data that can be used on cb
},
mt
@ -56,7 +61,7 @@ function autocomplete.add(t, triggered_manually)
end
end
if not triggered_manually then
if not manually_triggered then
autocomplete.map[t.name] = { files = t.files or ".*", items = items }
else
autocomplete.map_manually[t.name] = { files = t.files or ".*", items = items }
@ -66,7 +71,7 @@ end
--
-- Thread that scans open document symbols and cache them
--
local max_symbols = config.max_symbols
local max_symbols = config.plugins.autocomplete.max_symbols
core.add_thread(function()
local cache = setmetatable({}, { __mode = "k" })
@ -85,7 +90,9 @@ core.add_thread(function()
doc.disable_symbols = true
core.status_view:show_message("!", style.accent,
"Too many symbols in document "..doc.filename..
": stopping auto-complete for this document according to config.max_symbols.")
": stopping auto-complete for this document according to "..
"config.plugins.autocomplete.max_symbols."
)
collectgarbage('collect')
return {}
end
@ -159,16 +166,6 @@ local function reset_suggestions()
end
end
local function in_table(value, table_array)
for i, element in pairs(table_array) do
if element == value then
return true
end
end
return false
end
local function update_suggestions()
local doc = core.active_view.doc
local filename = doc and doc.filename or ""
@ -199,6 +196,7 @@ local function update_suggestions()
j = j + 1
end
end
suggestions_idx = 1
end
local function get_partial_symbol()
@ -249,6 +247,11 @@ local function get_suggestions_rect(av)
max_width = 150
end
-- if portion not visiable to right, reposition to DocView right margin
if (x - av.position.x) + max_width > av.size.x then
x = (av.size.x + av.position.x) - max_width - (style.padding.x * 2)
end
return
x - style.padding.x,
y - style.padding.y,
@ -256,20 +259,99 @@ local function get_suggestions_rect(av)
max_items * (th + style.padding.y) + style.padding.y
end
local function wrap_line(line, max_chars)
if #line > max_chars then
local lines = {}
local line_len = #line
local new_line = ""
local prev_char = ""
local position = 0
local indent = line:match("^%s+")
for char in line:gmatch(".") do
position = position + 1
if #new_line < max_chars then
new_line = new_line .. char
prev_char = char
if position >= line_len then
table.insert(lines, new_line)
end
else
if
not prev_char:match("%s")
and
not string.sub(line, position+1, 1):match("%s")
and
position < line_len
then
new_line = new_line .. "-"
end
table.insert(lines, new_line)
if indent then
new_line = indent .. char
else
new_line = char
end
end
end
return lines
end
return line
end
local previous_scale = SCALE
local desc_font = style.code_font:copy(
config.plugins.autocomplete.desc_font_size * SCALE
)
local function draw_description_box(text, av, sx, sy, sw, sh)
if previous_scale ~= SCALE then
desc_font = style.code_font:copy(
config.plugins.autocomplete.desc_font_size * SCALE
)
previous_scale = SCALE
end
local font = desc_font
local lh = font:get_height()
local y = sy + style.padding.y
local x = sx + sw + style.padding.x / 4
local width = 0
local char_width = font:get_width(" ")
local draw_left = false;
local max_chars = 0
if sx - av.position.x < av.size.x - (sx - av.position.x) - sw then
max_chars = (((av.size.x+av.position.x) - x) / char_width) - 5
else
draw_left = true;
max_chars = (
(sx - av.position.x - (style.padding.x / 4) - style.scrollbar_size)
/ char_width
) - 5
end
local lines = {}
for line in string.gmatch(text.."\n", "(.-)\n") do
width = math.max(width, style.font:get_width(line))
local wrapper_lines = wrap_line(line, max_chars)
if type(wrapper_lines) == "table" then
for _, wrapped_line in pairs(wrapper_lines) do
width = math.max(width, font:get_width(wrapped_line))
table.insert(lines, wrapped_line)
end
else
width = math.max(width, font:get_width(line))
table.insert(lines, line)
end
end
local height = #lines * style.font:get_height()
if draw_left then
x = sx - (style.padding.x / 4) - width - (style.padding.x * 2)
end
local height = #lines * font:get_height()
-- draw background rect
renderer.draw_rect(
sx + sw + style.padding.x / 4,
x,
sy,
width + style.padding.x * 2,
height + style.padding.y * 2,
@ -277,13 +359,10 @@ local function draw_description_box(text, av, sx, sy, sw, sh)
)
-- draw text
local lh = style.font:get_height()
local y = sy + style.padding.y
local x = sx + sw + style.padding.x / 4
for _, line in pairs(lines) do
common.draw_text(
style.font, style.text, line, "left", x + style.padding.x, y, width, lh
font, style.text, line, "left",
x + style.padding.x, y, width, lh
)
y = y + lh
end
@ -320,10 +399,9 @@ local function draw_suggestions_box(av)
end
y = y + lh
if suggestions_idx == i then
if s.cb then
s.cb(suggestions_idx, s)
s.cb = nil
s.data = nil
if s.onhover then
s.onhover(suggestions_idx, s)
s.onhover = nil
end
if s.desc and #s.desc > 0 then
draw_description_box(s.desc, av, rx, ry, rw, rh)
@ -487,10 +565,17 @@ command.add(predicate, {
["autocomplete:complete"] = function()
local doc = core.active_view.doc
local line, col = doc:get_selection()
local text = suggestions[suggestions_idx].text
local item = suggestions[suggestions_idx]
local text = item.text
local inserted = false
if item.onselect then
inserted = item.onselect(suggestions_idx, item)
end
if not inserted then
doc:insert(line, col, text)
doc:remove(line, col, line, col - #partial)
doc:set_selection(line, col + #text - #partial)
end
reset_suggestions()
end,

View File

@ -3,6 +3,7 @@ local core = require "core"
local command = require "core.command"
local keymap = require "core.keymap"
local ContextMenu = require "core.contextmenu"
local DocView = require "core.docview"
local RootView = require "core.rootview"
local menu = ContextMenu()
@ -32,7 +33,7 @@ function RootView:draw(...)
menu:draw()
end
command.add(nil, {
command.add(function() return getmetatable(core.active_view) == DocView end, {
["context:show"] = function()
menu:show(core.active_view.position.x, core.active_view.position.y)
end
@ -42,23 +43,24 @@ keymap.add {
["menu"] = "context:show"
}
local function copy_log()
local item = core.active_view.hovered_item
if item then
system.set_clipboard(core.get_log(item))
end
end
local function open_as_doc()
local doc = core.open_doc("logs.txt")
core.root_view:open_doc(doc)
doc:insert(1, 1, core.get_log())
end
menu:register("core.logview", {
{ text = "Copy entry", command = copy_log },
{ text = "Open as file", command = open_as_doc }
command.add(function() return menu.show_context_menu == true end, {
["context:focus-previous"] = function()
menu:focus_previous()
end,
["context:focus-next"] = function()
menu:focus_next()
end,
["context:hide"] = function()
menu:hide()
end,
["context:on-selected"] = function()
menu:call_selected_item()
end,
})
keymap.add { ["return"] = "context:on-selected" }
keymap.add { ["up"] = "context:focus-previous" }
keymap.add { ["down"] = "context:focus-next" }
keymap.add { ["escape"] = "context:hide" }
if require("plugins.scale") then
menu:register("core.docview", {

View File

@ -3,93 +3,254 @@ local core = require "core"
local command = require "core.command"
local common = require "core.common"
local config = require "core.config"
local core_syntax = require "core.syntax"
local DocView = require "core.docview"
local Doc = require "core.doc"
local tokenizer = require "core.tokenizer"
local cache = setmetatable({}, { __mode = "k" })
local comments_cache = {}
local auto_detect_max_lines = 150
local function add_to_stat(stat, val)
for i = 1, #stat do
if val == stat[i][1] then
stat[i][2] = stat[i][2] + 1
return
local function indent_occurrences_more_than_once(stat, idx)
if stat[idx-1] and stat[idx-1] == stat[idx] then
return true
elseif stat[idx+1] and stat[idx+1] == stat[idx] then
return true
end
end
stat[#stat + 1] = {val, 1}
return false
end
local function optimal_indent_from_stat(stat)
if #stat == 0 then return nil, 0 end
local bins = {}
for k = 1, #stat do
local indent = stat[k][1]
table.sort(stat, function(a, b) return a > b end)
local best_indent = 0
local best_score = 0
local count = #stat
for x=1, count do
local indent = stat[x]
local score = 0
local mult_prev, lines_prev
for i = k, #stat do
if stat[i][1] % indent == 0 then
local mult = stat[i][1] / indent
if not mult_prev or (mult_prev + 1 == mult and lines_prev / stat[i][2] > 0.1) then
-- we add the number of lines to the score only if the previous
-- multiple of "indent" was populated with enough lines.
score = score + stat[i][2]
end
mult_prev, lines_prev = mult, stat[i][2]
for y=1, count do
if y ~= x and stat[y] % indent == 0 then
score = score + 1
elseif
indent > stat[y]
and
indent_occurrences_more_than_once(stat, y)
then
score = 0
break
end
end
bins[#bins + 1] = {indent, score}
if score > best_score then
best_indent = indent
best_score = score
end
table.sort(bins, function(a, b) return a[2] > b[2] end)
return bins[1][1], bins[1][2]
if score > 0 then
break
end
end
return best_score > 0 and best_indent or nil, best_score
end
-- return nil if it is a comment or blank line or the initial part of the
-- line otherwise.
-- we don't need to have the whole line to detect indentation.
local function get_first_line_part(tokens)
local i, n = 1, #tokens
while i + 1 <= n do
local ttype, ttext = tokens[i], tokens[i + 1]
if ttype ~= "comment" and ttext:gsub("%s+", "") ~= "" then
return ttext
local function escape_comment_tokens(token)
local special_chars = "*-%[].()+?^$"
local escaped = ""
for x=1, token:len() do
local found = false
for y=1, special_chars:len() do
if token:sub(x, x) == special_chars:sub(y, y) then
escaped = escaped .. "%" .. token:sub(x, x)
found = true
break
end
i = i + 2
end
if not found then
escaped = escaped .. token:sub(x, x)
end
end
return escaped
end
local function get_comment_patterns(syntax)
if comments_cache[syntax] then
if #comments_cache[syntax] > 0 then
return comments_cache[syntax]
else
return nil
end
end
local comments = {}
for idx=1, #syntax.patterns do
local pattern = syntax.patterns[idx]
local startp = ""
if
type(pattern.type) == "string"
and
(pattern.type == "comment" or pattern.type == "string")
then
local not_is_string = pattern.type ~= "string"
if pattern.pattern then
startp = type(pattern.pattern) == "table"
and pattern.pattern[1] or pattern.pattern
if not_is_string and startp:sub(1, 1) ~= "^" then
startp = "^%s*" .. startp
elseif not_is_string then
startp = "^%s*" .. startp:sub(2, startp:len())
end
if type(pattern.pattern) == "table" then
table.insert(comments, {"p", startp, pattern.pattern[2]})
elseif not_is_string then
table.insert(comments, {"p", startp})
end
elseif pattern.regex then
startp = type(pattern.regex) == "table"
and pattern.regex[1] or pattern.regex
if not_is_string and startp:sub(1, 1) ~= "^" then
startp = "^\\s*" .. startp
elseif not_is_string then
startp = "^\\s*" .. startp:sub(2, startp:len())
end
if type(pattern.regex) == "table" then
table.insert(comments, {
"r", regex.compile(startp), regex.compile(pattern.regex[2])
})
elseif not_is_string then
table.insert(comments, {"r", regex.compile(startp)})
end
end
elseif pattern.syntax then
local subsyntax = type(pattern.syntax) == 'table' and pattern.syntax
or core_syntax.get("file"..pattern.syntax, "")
local sub_comments = get_comment_patterns(subsyntax)
if sub_comments then
for s=1, #sub_comments do
table.insert(comments, sub_comments[s])
end
end
end
end
if #comments == 0 then
local single_line_comment = syntax.comment
and escape_comment_tokens(syntax.comment) or nil
local block_comment = nil
if syntax.block_comment then
block_comment = {
escape_comment_tokens(syntax.block_comment[1]),
escape_comment_tokens(syntax.block_comment[2])
}
end
if single_line_comment then
table.insert(comments, {"p", "^%s*" .. single_line_comment})
end
if block_comment then
table.insert(comments, {"p", "^%s*" .. block_comment[1], block_comment[2]})
end
end
comments_cache[syntax] = comments
if #comments > 0 then
return comments
end
return nil
end
local function get_non_empty_lines(syntax, lines)
return coroutine.wrap(function()
local tokens, state
local comments = get_comment_patterns(syntax)
local i = 0
local end_regex = nil
local end_pattern = nil
local inside_comment = false
for _, line in ipairs(lines) do
tokens, state = tokenizer.tokenize(syntax, line, state)
local line_start = get_first_line_part(tokens)
if line_start then
if line:gsub("^%s+", "") ~= "" then
local is_comment = false
if comments then
if not inside_comment then
for c=1, #comments do
local comment = comments[c]
if comment[1] == "p" then
if comment[3] then
local start, ending = line:find(comment[2])
if start then
if not line:find(comment[3], ending+1) then
is_comment = true
inside_comment = true
end_pattern = comment[3]
end
break
end
elseif line:find(comment[2]) then
is_comment = true
break
end
else
if comment[3] then
local start, ending = regex.match(
comment[2], line, 1, regex.ANCHORED
)
if start then
if not regex.match(
comment[3], line, ending+1, regex.ANCHORED
)
then
is_comment = true
inside_comment = true
end_regex = comment[3]
end
break
end
elseif regex.match(comment[2], line, 1, regex.ANCHORED) then
is_comment = true
break
end
end
end
elseif end_pattern and line:find(end_pattern) then
is_comment = true
inside_comment = false
end_pattern = nil
elseif end_regex and regex.match(end_regex, line) then
is_comment = true
inside_comment = false
end_regex = nil
end
end
if
not is_comment
and
not inside_comment
then
i = i + 1
coroutine.yield(i, line_start)
coroutine.yield(i, line)
end
end
end
end)
end
local auto_detect_max_lines = 100
local function detect_indent_stat(doc)
local stat = {}
local tab_count = 0
local runs = 1
local max_lines = auto_detect_max_lines
for i, text in get_non_empty_lines(doc.syntax, doc.lines) do
local str = text:match("^ %s+%S")
if str then add_to_stat(stat, #str - 1) end
local str = text:match("^\t+")
if str then tab_count = tab_count + 1 end
local spaces = text:match("^ +")
if spaces then table.insert(stat, spaces:len()) end
local tabs = text:match("^\t+")
if tabs then tab_count = tab_count + 1 end
-- if nothing found for first lines try at least 4 more times
if i == max_lines and runs < 5 and #stat == 0 and tab_count == 0 then
max_lines = max_lines + auto_detect_max_lines
runs = runs + 1
-- Stop parsing when files is very long. Not needed for euristic determination.
if i > auto_detect_max_lines then break end
elseif i > max_lines then break end
end
table.sort(stat, function(a, b) return a[1] < b[1] end)
local indent, score = optimal_indent_from_stat(stat)
if tab_count > score then
return "hard", config.indent_size, tab_count
@ -101,7 +262,7 @@ end
local function update_cache(doc)
local type, size, score = detect_indent_stat(doc)
local score_threshold = 4
local score_threshold = 2
if score < score_threshold then
-- use default values
type = config.tab_type
@ -130,9 +291,11 @@ end
local function set_indent_type(doc, type)
local _, indent_size = doc:get_indent_info()
cache[doc] = {type = type,
cache[doc] = {
type = type,
size = indent_size,
confirmed = true}
confirmed = true
}
doc.indent_info = cache[doc]
end
@ -158,9 +321,11 @@ end
local function set_indent_size(doc, size)
local indent_type = doc:get_indent_info()
cache[doc] = {type = indent_type,
cache[doc] = {
type = indent_type,
size = size,
confirmed = true}
confirmed = true
}
doc.indent_info = cache[doc]
end
@ -168,14 +333,14 @@ local function set_indent_size_command()
core.command_view:enter(
"Specify indent size for current file",
function(value) -- submit
local value = math.floor(tonumber(value))
value = math.floor(tonumber(value))
local doc = core.active_view.doc
set_indent_size(doc, value)
end,
nil, -- suggest
nil, -- cancel
function(value) -- validate
local value = tonumber(value)
value = tonumber(value)
return value ~= nil and value >= 1
end
)
@ -187,20 +352,24 @@ command.add("core.docview", {
["indent:set-file-indent-size"] = set_indent_size_command
})
command.add(function()
command.add(
function()
return core.active_view:is(DocView)
and cache[core.active_view.doc]
and cache[core.active_view.doc].type == "soft"
end, {
["indent:switch-file-to-tabs-indentation"] = function() set_indent_type(core.active_view.doc, "hard") end
["indent:switch-file-to-tabs-indentation"] = function()
set_indent_type(core.active_view.doc, "hard")
end
})
command.add(function()
command.add(
function()
return core.active_view:is(DocView)
and cache[core.active_view.doc]
and cache[core.active_view.doc].type == "hard"
end, {
["indent:switch-file-to-spaces-indentation"] = function() set_indent_type(core.active_view.doc, "soft") end
["indent:switch-file-to-spaces-indentation"] = function()
set_indent_type(core.active_view.doc, "soft")
end
})

View File

@ -3,8 +3,9 @@ local syntax = require "core.syntax"
syntax.add {
name = "C",
files = { "%.c$", "%.h$", "%.inl$" },
files = { "%.c$" },
comment = "//",
block_comment = { "/*", "*/" },
patterns = {
{ pattern = "//.-\n", type = "comment" },
{ pattern = { "/%*", "%*/" }, type = "comment" },
@ -16,10 +17,21 @@ syntax.add {
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
{ pattern = "struct%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
{ pattern = "union%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
{
pattern = "^%s*#define%s+()[%a_][%a%d_]*",
type = { "keyword", "symbol" }
},
-- Uppercase constants of at least 2 chars in len
{
pattern = "_?%u[%u_][%u%d_]*%f[%s%+%*%-%.%(%)%?%^%%=/<>~|&;:,!]",
type = "number"
},
-- Magic constants
{ pattern = "__[%u%l]+__", type = "number" },
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
{ pattern = "[%a_][%w_]*", type = "symbol" },
{ pattern = "#include%s()<.->", type = {"keyword", "string"} },
{ pattern = "#[%a_][%w_]*", type = "keyword" },
{ pattern = "[%a_][%w_]*", type = "symbol" },
},
symbols = {
["if"] = "keyword",
@ -44,6 +56,8 @@ syntax.add {
["case"] = "keyword",
["default"] = "keyword",
["auto"] = "keyword",
["struct"] = "keyword",
["union"] = "keyword",
["void"] = "keyword2",
["int"] = "keyword2",
["short"] = "keyword2",
@ -60,6 +74,7 @@ syntax.add {
["#if"] = "keyword",
["#ifdef"] = "keyword",
["#ifndef"] = "keyword",
["#elif"] = "keyword",
["#else"] = "keyword",
["#elseif"] = "keyword",
["#endif"] = "keyword",

View File

@ -1,6 +1,4 @@
-- mod-version:2 -- lite-xl 2.0
pcall(require, "plugins.language_c")
local syntax = require "core.syntax"
syntax.add {
@ -10,6 +8,7 @@ syntax.add {
"%.c++$", "%.hh$", "%.H$", "%.hxx$", "%.hpp$", "%.h++$"
},
comment = "//",
block_comment = { "/*", "*/" },
patterns = {
{ pattern = "//.-\n", type = "comment" },
{ pattern = { "/%*", "%*/" }, type = "comment" },
@ -18,20 +17,45 @@ syntax.add {
{ pattern = "0x%x+", type = "number" },
{ pattern = "%d+[%d%.eE]*f?", type = "number" },
{ pattern = "%.?%d+f?", type = "number" },
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
{
pattern = "^%s*#define%s+()[%a_][%a%d_]*",
type = { "keyword", "symbol" }
},
{
pattern = "#include%s+()<.->",
type = { "keyword", "string" }
},
{ pattern = "[%+%-=/%*%^%%<>!~|:&]", type = "operator" },
{ pattern = "struct%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
{ pattern = "class%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
{ pattern = "union%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
{ pattern = "namespace%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
{ pattern = "[%a_][%w_]*::", type = "symbol" },
{ pattern = "::", type = "symbol" },
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
-- Match scope operator element access
{ pattern = "[%a_][%w_]*[%s]*%f[:]", type = "literal" },
-- Uppercase constants of at least 2 chars in len
{
pattern = "_?%u[%u_][%u%d_]*%f[%s%+%*%-%.%(%)%?%^%%=/<>~|&;:,!]",
type = "number"
},
-- Magic constants
{ pattern = "__[%u%l]+__", type = "number" },
-- Somehow makes macros properly work
{ pattern = "#[%a_][%w_]*", type = "normal" },
-- Everything else to make the tokenizer work properly
{ pattern = "[%a_][%w_]*", type = "symbol" },
{ pattern = "#include%s()<.->", type = {"keyword", "string"} },
{ pattern = "#[%a_][%w_]*", type = "keyword" },
},
symbols = {
["alignof"] = "keyword",
["alignas"] = "keyword",
["and"] = "keyword",
["and_eq"] = "keyword",
["not"] = "keyword",
["not_eq"] = "keyword",
["or"] = "keyword",
["or_eq"] = "keyword",
["xor"] = "keyword",
["xor_eq"] = "keyword",
["private"] = "keyword",
["protected"] = "keyword",
["public"] = "keyword",
@ -39,9 +63,12 @@ syntax.add {
["nullptr"] = "keyword",
["operator"] = "keyword",
["asm"] = "keyword",
["bitand"] = "keyword",
["bitor"] = "keyword",
["catch"] = "keyword",
["throw"] = "keyword",
["try"] = "keyword",
["class"] = "keyword",
["compl"] = "keyword",
["explicit"] = "keyword",
["export"] = "keyword",
@ -63,7 +90,6 @@ syntax.add {
["co_yield"] = "keyword",
["decltype"] = "keyword",
["delete"] = "keyword",
["export"] = "keyword",
["friend"] = "keyword",
["typeid"] = "keyword",
["typename"] = "keyword",
@ -71,6 +97,7 @@ syntax.add {
["override"] = "keyword",
["virtual"] = "keyword",
["using"] = "keyword",
["namespace"] = "keyword",
["new"] = "keyword",
["noexcept"] = "keyword",
["if"] = "keyword",
@ -84,6 +111,8 @@ syntax.add {
["continue"] = "keyword",
["return"] = "keyword",
["goto"] = "keyword",
["struct"] = "keyword",
["union"] = "keyword",
["typedef"] = "keyword",
["enum"] = "keyword",
["extern"] = "keyword",
@ -95,7 +124,6 @@ syntax.add {
["case"] = "keyword",
["default"] = "keyword",
["auto"] = "keyword",
["const"] = "keyword",
["void"] = "keyword2",
["int"] = "keyword2",
["short"] = "keyword2",
@ -105,12 +133,18 @@ syntax.add {
["char"] = "keyword2",
["unsigned"] = "keyword2",
["bool"] = "keyword2",
["true"] = "keyword2",
["false"] = "keyword2",
["true"] = "literal",
["false"] = "literal",
["NULL"] = "literal",
["wchar_t"] = "keyword2",
["char8_t"] = "keyword2",
["char16_t"] = "keyword2",
["char32_t"] = "keyword2",
["#include"] = "keyword",
["#if"] = "keyword",
["#ifdef"] = "keyword",
["#ifndef"] = "keyword",
["#elif"] = "keyword",
["#else"] = "keyword",
["#elseif"] = "keyword",
["#endif"] = "keyword",
@ -120,4 +154,3 @@ syntax.add {
["#pragma"] = "keyword",
},
}

View File

@ -4,6 +4,7 @@ local syntax = require "core.syntax"
syntax.add {
name = "CSS",
files = { "%.css$" },
block_comment = { "/*", "*/" },
patterns = {
{ pattern = "\\.", type = "normal" },
{ pattern = "//.-\n", type = "comment" },

View File

@ -4,6 +4,7 @@ local syntax = require "core.syntax"
syntax.add {
name = "HTML",
files = { "%.html?$" },
block_comment = { "<!--", "-->" },
patterns = {
{
pattern = {

View File

@ -5,6 +5,7 @@ syntax.add {
name = "JavaScript",
files = { "%.js$", "%.json$", "%.cson$" },
comment = "//",
block_comment = { "/*", "*/" },
patterns = {
{ pattern = "//.-\n", type = "comment" },
{ pattern = { "/%*", "%*/" }, type = "comment" },

View File

@ -6,6 +6,7 @@ syntax.add {
files = "%.lua$",
headers = "^#!.*[ /]lua",
comment = "--",
block_comment = { "--[[", "]]" },
patterns = {
{ pattern = { '"', '"', '\\' }, type = "string" },
{ pattern = { "'", "'", '\\' }, type = "string" },

View File

@ -1,25 +1,104 @@
-- mod-version:2 -- lite-xl 2.0
local syntax = require "core.syntax"
local style = require "core.style"
local core = require "core"
local initial_color = style.syntax["keyword2"]
-- Add 3 type of font styles for use on markdown files
for _, attr in pairs({"bold", "italic", "bold_italic"}) do
local attributes = {}
if attr ~= "bold_italic" then
attributes[attr] = true
else
attributes["bold"] = true
attributes["italic"] = true
end
-- no way to copy user custom font with additional attributes :(
style.syntax_fonts["markdown_"..attr] = renderer.font.load(
DATADIR .. "/fonts/JetBrainsMono-Regular.ttf",
style.code_font:get_size(),
attributes
)
-- also add a color for it
style.syntax["markdown_"..attr] = style.syntax["keyword2"]
end
local in_squares_match = "^%[%]"
local in_parenthesis_match = "^%(%)"
syntax.add {
name = "Markdown",
files = { "%.md$", "%.markdown$" },
block_comment = { "<!--", "-->" },
patterns = {
{ pattern = "\\.", type = "normal" },
---- HTML rules imported and adapted from language_html
---- to not conflict with markdown rules
-- Inline JS and CSS
{
pattern = {
"<%s*[sS][cC][rR][iI][pP][tT]%s+[tT][yY][pP][eE]%s*=%s*" ..
"['\"]%a+/[jJ][aA][vV][aA][sS][cC][rR][iI][pP][tT]['\"]%s*>",
"<%s*/[sS][cC][rR][iI][pP][tT]>"
},
syntax = ".js",
type = "function"
},
{
pattern = {
"<%s*[sS][cC][rR][iI][pP][tT]%s*>",
"<%s*/%s*[sS][cC][rR][iI][pP][tT]>"
},
syntax = ".js",
type = "function"
},
{
pattern = {
"<%s*[sS][tT][yY][lL][eE][^>]*>",
"<%s*/%s*[sS][tT][yY][lL][eE]%s*>"
},
syntax = ".css",
type = "function"
},
-- Comments
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
-- Tags
{ pattern = "%f[^<]![%a_][%w_]*", type = "keyword2" },
{ pattern = "%f[^<][%a_][%w_]*", type = "function" },
{ pattern = "%f[^<]/[%a_][%w_]*", type = "function" },
-- Attributes
{
pattern = "[a-z%-]+%s*()=%s*()\".-\"",
type = { "keyword", "operator", "string" }
},
{
pattern = "[a-z%-]+%s*()=%s*()'.-'",
type = { "keyword", "operator", "string" }
},
{
pattern = "[a-z%-]+%s*()=%s*()%-?%d[%d%.]*",
type = { "keyword", "operator", "number" }
},
-- Entities
{ pattern = "&#?[a-zA-Z0-9]+;", type = "keyword2" },
---- Markdown rules
-- code blocks
{ pattern = { "```c++", "```" }, type = "string", syntax = ".cpp" },
{ pattern = { "```cpp", "```" }, type = "string", syntax = ".cpp" },
{ pattern = { "```python", "```" }, type = "string", syntax = ".py" },
{ pattern = { "```ruby", "```" }, type = "string", syntax = ".rb" },
{ pattern = { "```perl", "```" }, type = "string", syntax = ".pl" },
{ pattern = { "```php", "```" }, type = "string", syntax = ".php" },
{ pattern = { "```javascript", "```" }, type = "string", syntax = ".js" },
{ pattern = { "```json", "```" }, type = "string", syntax = ".js" },
{ pattern = { "```html", "```" }, type = "string", syntax = ".html" },
{ pattern = { "```ini", "```" }, type = "string", syntax = ".ini" },
{ pattern = { "```xml", "```" }, type = "string", syntax = ".xml" },
{ pattern = { "```css", "```" }, type = "string", syntax = ".css" },
{ pattern = { "```lua", "```" }, type = "string", syntax = ".lua" },
{ pattern = { "```bash", "```" }, type = "string", syntax = ".sh" },
{ pattern = { "```sh", "```" }, type = "string", syntax = ".sh" },
{ pattern = { "```java", "```" }, type = "string", syntax = ".java" },
{ pattern = { "```c#", "```" }, type = "string", syntax = ".cs" },
{ pattern = { "```cmake", "```" }, type = "string", syntax = ".cmake" },
@ -32,7 +111,6 @@ syntax.add {
{ pattern = { "```v", "```" }, type = "string", syntax = ".v" },
{ pattern = { "```toml", "```" }, type = "string", syntax = ".toml" },
{ pattern = { "```yaml", "```" }, type = "string", syntax = ".yaml" },
{ pattern = { "```php", "```" }, type = "string", syntax = ".php" },
{ pattern = { "```nim", "```" }, type = "string", syntax = ".nim" },
{ pattern = { "```typescript", "```" }, type = "string", syntax = ".ts" },
{ pattern = { "```rescript", "```" }, type = "string", syntax = ".res" },
@ -41,16 +119,105 @@ syntax.add {
{ pattern = { "```lobster", "```" }, type = "string", syntax = ".lobster" },
{ pattern = { "```liquid", "```" }, type = "string", syntax = ".liquid" },
{ pattern = { "```", "```" }, type = "string" },
{ pattern = { "``", "``", "\\" }, type = "string" },
{ pattern = { "`", "`", "\\" }, type = "string" },
{ pattern = { "~~", "~~", "\\" }, type = "keyword2" },
{ pattern = "%-%-%-+", type = "comment" },
{ pattern = "%*%s+", type = "operator" },
{ pattern = { "%*", "[%*\n]", "\\" }, type = "operator" },
{ pattern = { "%_", "[%_\n]", "\\" }, type = "keyword2" },
{ pattern = "#.-\n", type = "keyword" },
{ pattern = "!?%[.-%]%(.-%)", type = "function" },
{ pattern = "https?://%S+", type = "function" },
{ pattern = { "``", "``" }, type = "string" },
{ pattern = { "%f[\\`]%`[%S]", "`" }, type = "string" },
-- strike
{ pattern = { "~~", "~~" }, type = "keyword2" },
-- highlight
{ pattern = { "==", "==" }, type = "literal" },
-- lines
{ pattern = "^%-%-%-+\n", type = "comment" },
{ pattern = "^%*%*%*+\n", type = "comment" },
{ pattern = "^___+\n", type = "comment" },
-- bullets
{ pattern = "^%s*%*%s", type = "number" },
{ pattern = "^%s*%-%s", type = "number" },
{ pattern = "^%s*%+%s", type = "number" },
-- numbered bullet
{ pattern = "^%s*[0-9]+%.%s", type = "number" },
-- blockquote
{ pattern = "^%s*>+%s", type = "string" },
-- bold and italic
{ pattern = { "%*%*%*%S", "%*%*%*" }, type = "markdown_bold_italic" },
{ pattern = { "%*%*%S", "%*%*" }, type = "markdown_bold" },
-- handle edge case where asterisk can be at end of line and not close
{
pattern = { "%f[\\%*]%*[%S]", "%*%f[^%*]" },
type = "markdown_italic"
},
-- alternative bold italic formats
{ pattern = "^___[%s%p%w]+___%s" , type = "markdown_bold_italic" },
{ pattern = "^__[%s%p%w]+__%s" , type = "markdown_bold" },
{ pattern = "^_[%s%p%w]+_%s" , type = "markdown_italic" },
{ pattern = { "%s___", "___%f[%s]" }, type = "markdown_bold_italic" },
{ pattern = { "%s__", "__%f[%s]" }, type = "markdown_bold" },
{ pattern = { "%s_[%S]", "_%f[%s]" }, type = "markdown_italic" },
-- heading with custom id
{
pattern = "^#+%s[%w%s%p]+(){()#[%w%-]+()}",
type = { "keyword", "function", "string", "function" }
},
-- headings
{ pattern = "^#+%s.+\n", type = "keyword" },
-- superscript and subscript
{
pattern = "%^()%d+()%^",
type = { "function", "number", "function" }
},
{
pattern = "%~()%d+()%~",
type = { "function", "number", "function" }
},
-- definitions
{ pattern = "^:%s.+", type = "function" },
-- emoji
{ pattern = ":[a-zA-Z0-9_%-]+:", type = "literal" },
-- images and link
{
pattern = "!?%[!?%[()["..in_squares_match.."]+()%]%(()["..in_parenthesis_match.."]+()%)%]%(()["..in_parenthesis_match.."]+()%)",
type = { "function", "string", "function", "number", "function", "number", "function" }
},
{
pattern = "!?%[!?%[?()["..in_squares_match.."]+()%]?%]%(()["..in_parenthesis_match.."]+()%)",
type = { "function", "string", "function", "number", "function" }
},
-- reference links
{
pattern = "%[()["..in_squares_match.."]+()%] *()%[()["..in_squares_match.."]+()%]",
type = { "function", "string", "function", "function", "number", "function" }
},
{
pattern = "^%s*%[%^()["..in_squares_match.."]+()%]: ",
type = { "function", "number", "function" }
},
{
pattern = "^%s*%[%^?()["..in_squares_match.."]+()%]:%s+.+\n",
type = { "function", "number", "function" }
},
{
pattern = "!?%[%^?()["..in_squares_match.."]+()%]",
type = { "function", "number", "function" }
},
-- url's and email
{
pattern = "<[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+%.[a-zA-Z0-9-.]+>",
type = "function"
},
{ pattern = "<https?://%S+>", type = "function" },
{ pattern = "https?://%S+", type = "function" }
},
symbols = { },
}
-- Adjust the color on theme changes
core.add_thread(function()
while true do
if initial_color ~= style.syntax["keyword2"] then
for _, attr in pairs({"bold", "italic", "bold_italic"}) do
style.syntax["markdown_"..attr] = style.syntax["keyword2"]
end
initial_color = style.syntax["keyword2"]
end
coroutine.yield(1)
end
end)

View File

@ -5,6 +5,7 @@ syntax.add {
name = "XML",
files = { "%.xml$" },
headers = "<%?xml",
block_comment = { "<!--", "-->" },
patterns = {
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
{ pattern = { '%f[^>][^<]', '%f[<]' }, type = "normal" },

View File

@ -176,7 +176,7 @@ function ResultsView:draw()
local text
if self.searching then
if files_number then
text = string.format("Searching %d%% (%d of %d files, %d matches) for %q...",
text = string.format("Searching %.f%% (%d of %d files, %d matches) for %q...",
per * 100, self.last_file_idx, files_number,
#self.results, self.query)
else
@ -229,8 +229,21 @@ local function begin_search(text, fn)
end
local function set_command_view_text()
local view = core.active_view
local doc = (view and view.doc) and view.doc or nil
if doc then
core.command_view:set_text(
doc:get_text(table.unpack({ doc:get_selection() })),
true
)
end
end
command.add(nil, {
["project-search:find"] = function()
set_command_view_text()
core.command_view:enter("Find Text In Project", function(text)
text = text:lower()
begin_search(text, function(line_text)
@ -249,6 +262,7 @@ command.add(nil, {
end,
["project-search:fuzzy-find"] = function()
set_command_view_text()
core.command_view:enter("Fuzzy Find Text In Project", function(text)
begin_search(text, function(line_text)
return common.fuzzy_match(line_text, text) and 1

View File

@ -8,10 +8,10 @@ local style = require "core.style"
local RootView = require "core.rootview"
local CommandView = require "core.commandview"
config.plugins.scale = {
config.plugins.scale = common.merge({
mode = "code",
use_mousewheel = true
}
}, config.plugins.scale)
local scale_steps = 0.05
@ -50,12 +50,8 @@ local function set_scale(scale)
style.code_font = renderer.font.copy(style.code_font, s * style.code_font:get_size())
end
for _, font in pairs(style.syntax_fonts) do
renderer.font.set_size(font, s * font:get_size())
end
for _, font in pairs(style.syntax_fonts) do
renderer.font.set_size(font, s * font:get_size())
for name, font in pairs(style.syntax_fonts) do
style.syntax_fonts[name] = renderer.font.copy(font, s * font:get_size())
end
-- restore scroll positions

View File

@ -84,11 +84,12 @@ end
function ToolbarView:on_mouse_pressed(button, x, y, clicks)
local caught = ToolbarView.super.on_mouse_pressed(self, button, x, y, clicks)
if caught then return end
if caught then return caught end
core.set_active_view(core.last_active_view)
if self.hovered_item then
command.perform(self.hovered_item.command)
end
return true
end

View File

@ -8,9 +8,15 @@ local style = require "core.style"
local View = require "core.view"
local ContextMenu = require "core.contextmenu"
local RootView = require "core.rootview"
local CommandView = require "core.commandview"
config.plugins.treeview = common.merge({
-- Amount of clicks to open a file
clicks_to_open = 2,
-- Default treeview width
size = 200 * SCALE
}, config.plugins.treeview)
local default_treeview_size = 200 * SCALE
local tooltip_offset = style.font:get_height()
local tooltip_border = 1
local tooltip_delay = 0.5
@ -39,19 +45,26 @@ function TreeView:new()
self.scrollable = true
self.visible = true
self.init_size = true
self.target_size = default_treeview_size
self.target_size = config.plugins.treeview.size
self.cache = {}
self.tooltip = { x = 0, y = 0, begin = 0, alpha = 0 }
self.cursor_pos = { x = 0, y = 0 }
self.item_icon_width = 0
self.item_text_spacing = 0
local on_dirmonitor_modify = core.on_dirmonitor_modify
function core.on_dirmonitor_modify(dir, filepath)
if self.cache[dir.name] then
self.cache[dir.name][filepath] = nil
end
on_dirmonitor_modify(dir, filepath)
self:add_core_hooks()
end
function TreeView:add_core_hooks()
-- When a file or directory is deleted we delete the corresponding cache entry
-- because if the entry is recreated we may use wrong information from cache.
local on_delete = core.on_dirmonitor_delete
core.on_dirmonitor_delete = function(dir, filepath)
local cache = self.cache[dir.name]
if cache then cache[filepath] = nil end
on_delete(dir, filepath)
end
end
@ -90,7 +103,7 @@ function TreeView:get_cached(dir, item, dirname)
end
t.name = basename
t.type = item.type
t.dir = dir -- points to top level "dir" item
t.dir_name = dir.name -- points to top level "dir" item
dir_cache[cache_name] = t
end
return t
@ -170,6 +183,21 @@ function TreeView:each_item()
end
function TreeView:set_selection(selection, selection_y)
self.selected_item = selection
if selection and selection_y
and (selection_y <= 0 or selection_y >= self.size.y) then
local lh = self:get_item_height()
if selection_y >= self.size.y - lh then
selection_y = selection_y - self.size.y + lh
end
local _, y = self:get_content_offset()
self.scroll.to.y = selection and (selection_y - y)
end
end
function TreeView:get_text_bounding_box(item, x, y, w, h)
local icon_width = style.icon_font:get_width("D")
local xoffset = item.depth * style.padding.x + style.padding.x + icon_width
@ -180,8 +208,14 @@ end
function TreeView:on_mouse_moved(px, py, ...)
if not self.visible then return end
TreeView.super.on_mouse_moved(self, px, py, ...)
if self.dragging_scrollbar then return end
self.cursor_pos.x = px
self.cursor_pos.y = py
if self.dragging_scrollbar then
self.hovered_item = nil
return
end
local item_changed, tooltip_changed
for item, x,y,w,h in self:each_item() do
@ -217,32 +251,28 @@ end
function TreeView:on_mouse_pressed(button, x, y, clicks)
if not self.visible then return end
local caught = TreeView.super.on_mouse_pressed(self, button, x, y, clicks)
if caught or button ~= "left" then
return true
end
local hovered_item = self.hovered_item
if not hovered_item then
return false
elseif hovered_item.type == "dir" then
if self.hovered_item then
self:set_selection(self.hovered_item)
if keymap.modkeys["ctrl"] and button == "left" then
create_directory_in(hovered_item)
else
hovered_item.expanded = not hovered_item.expanded
if hovered_item.dir.files_limit then
core.update_project_subdir(hovered_item.dir, hovered_item.filename, hovered_item.expanded)
core.project_subdir_set_show(hovered_item.dir, hovered_item.filename, hovered_item.expanded)
end
create_directory_in(self.selected_item)
elseif self.selected_item.type == "dir"
or (self.selected_item.type == "file"
and clicks == config.plugins.treeview.clicks_to_open
)
then
command.perform "treeview:open"
end
else
core.try(function()
if core.last_active_view and core.active_view == self then
core.set_active_view(core.last_active_view)
end
local doc_filename = core.normalize_to_project_dir(hovered_item.abs_filename)
core.root_view:open_doc(core.open_doc(doc_filename))
end)
return false
end
return true
end
@ -257,6 +287,8 @@ function TreeView:update()
self:move_towards(self.size, "x", dest)
end
if not self.visible then return end
local duration = system.get_time() - self.tooltip.begin
if self.hovered_item and self.tooltip.x and duration > tooltip_delay then
self:move_towards(self.tooltip, "alpha", tooltip_alpha, tooltip_alpha_rate)
@ -267,6 +299,13 @@ function TreeView:update()
self.item_icon_width = style.icon_font:get_width("D")
self.item_text_spacing = style.icon_font:get_width("f") / 2
-- this will make sure hovered_item is updated
-- we don't want events when the thing is scrolling fast
local dy = math.abs(self.scroll.to.y - self.scroll.y)
if self.scroll.to.y ~= 0 and dy < self:get_item_height() then
self:on_mouse_moved(self.cursor_pos.x, self.cursor_pos.y, 0, 0)
end
TreeView.super.update(self)
end
@ -350,6 +389,10 @@ end
function TreeView:draw_item_background(item, active, hovered, x, y, w, h)
if hovered then
local hover_color = { table.unpack(style.line_highlight) }
hover_color[4] = 160
renderer.draw_rect(x, y, w, h, hover_color)
elseif active then
renderer.draw_rect(x, y, w, h, style.line_highlight)
end
end
@ -366,6 +409,7 @@ end
function TreeView:draw()
if not self.visible then return end
self:draw_background(style.background2)
local _y, _h = self.position.y, self.size.y
@ -375,18 +419,46 @@ function TreeView:draw()
for item, x,y,w,h in self:each_item() do
if y + h >= _y and y < _y + _h then
self:draw_item(item,
item.abs_filename == active_filename,
item == self.selected_item,
item == self.hovered_item,
x, y, w, h)
end
end
self:draw_scrollbar()
if self.hovered_item and self.tooltip.alpha > 0 then
if self.hovered_item and self.tooltip.x and self.tooltip.alpha > 0 then
core.root_view:defer_draw(self.draw_tooltip, self)
end
end
function TreeView:get_parent(item)
local parent_path = common.dirname(item.abs_filename)
if not parent_path then return end
for it, _, y in self:each_item() do
if it.abs_filename == parent_path then
return it, y
end
end
end
function TreeView:toggle_expand(toggle)
local item = self.selected_item
if not item then return end
if item.type == "dir" then
if type(toggle) == "boolean" then
item.expanded = toggle
else
item.expanded = not item.expanded
end
local hovered_dir = core.project_dir_by_name(item.dir_name)
if hovered_dir and hovered_dir.files_limit then
core.update_project_subdir(hovered_dir, item.filename, item.expanded)
end
end
end
-- init
local view = TreeView()
@ -405,7 +477,7 @@ if config.plugins.toolbarview ~= false and toolbar_plugin then
toolbar_view = ToolbarView()
treeview_node:split("down", toolbar_view, {y = true})
local min_toolbar_width = toolbar_view:get_min_width()
view:set_target_size("x", math.max(default_treeview_size, min_toolbar_width))
view:set_target_size("x", math.max(config.plugins.treeview.size, min_toolbar_width))
command.add(nil, {
["toolbar:toggle"] = function()
toolbar_view:toggle_visible()
@ -446,6 +518,12 @@ function RootView:draw(...)
menu:draw()
end
local on_quit_project = core.on_quit_project
function core.on_quit_project()
view.cache = {}
on_quit_project()
end
local function is_project_folder(path)
return core.project_dir == path
end
@ -476,65 +554,131 @@ menu:register(
}
)
local previous_view = nil
-- Register the TreeView commands and keymap
command.add(nil, {
["treeview:toggle"] = function()
view.visible = not view.visible
end})
end,
command.add(function() return view.hovered_item ~= nil end, {
["treeview:rename"] = function()
local old_filename = view.hovered_item.filename
local old_abs_filename = view.hovered_item.abs_filename
core.command_view:set_text(old_filename)
core.command_view:enter("Rename", function(filename)
filename = core.normalize_to_project_dir(filename)
local abs_filename = core.project_absolute_path(filename)
local res, err = os.rename(old_abs_filename, abs_filename)
if res then -- successfully renamed
for _, doc in ipairs(core.docs) do
if doc.abs_filename and old_abs_filename == doc.abs_filename then
doc:set_filename(filename, abs_filename) -- make doc point to the new filename
doc:reset_syntax()
break -- only first needed
end
end
core.log("Renamed \"%s\" to \"%s\"", old_filename, filename)
["treeview:toggle-focus"] = function()
if not core.active_view:is(TreeView) then
if core.active_view:is(CommandView) then
previous_view = core.last_active_view
else
core.error("Error while renaming \"%s\" to \"%s\": %s", old_abs_filename, abs_filename, err)
previous_view = core.active_view
end
end, common.path_suggest)
if not previous_view then
previous_view = core.root_view:get_primary_node().active_view
end
core.set_active_view(view)
if not view.selected_item then
for it, _, y in view:each_item() do
view:set_selection(it, y)
break
end
end
else
core.set_active_view(
previous_view or core.root_view:get_primary_node().active_view
)
end
end
})
command.add(TreeView, {
["treeview:next"] = function()
local item = view.selected_item
local item_y
local stop = false
for it, _, y in view:each_item() do
if item == it then
stop = true
elseif stop then
item = it
item_y = y
break
end
end
view:set_selection(item, item_y)
end,
["treeview:new-file"] = function()
if not is_project_folder(view.hovered_item.abs_filename) then
core.command_view:set_text(view.hovered_item.filename .. "/")
["treeview:previous"] = function()
local last_item
local last_item_y
for it, _, y in view:each_item() do
if it == view.selected_item then
if not last_item then
last_item = it
last_item_y = y
end
core.command_view:enter("Filename", function(filename)
local doc_filename = core.project_dir .. PATHSEP .. filename
local file = io.open(doc_filename, "a+")
file:write("")
file:close()
break
end
last_item = it
last_item_y = y
end
view:set_selection(last_item, last_item_y)
end,
["treeview:open"] = function()
local item = view.selected_item
if not item then return end
if item.type == "dir" then
view:toggle_expand()
else
core.try(function()
if core.last_active_view and core.active_view == view then
core.set_active_view(core.last_active_view)
end
local doc_filename = core.normalize_to_project_dir(item.abs_filename)
core.root_view:open_doc(core.open_doc(doc_filename))
core.log("Created %s", doc_filename)
end, common.path_suggest)
end,
["treeview:new-folder"] = function()
if not is_project_folder(view.hovered_item.abs_filename) then
core.command_view:set_text(view.hovered_item.filename .. "/")
end)
end
core.command_view:enter("Folder Name", function(filename)
local dir_path = core.project_dir .. PATHSEP .. filename
common.mkdirp(dir_path)
core.log("Created %s", dir_path)
end, common.path_suggest)
end,
["treeview:deselect"] = function()
view.selected_item = nil
end,
["treeview:collapse"] = function()
if view.selected_item then
if view.selected_item.type == "dir" and view.selected_item.expanded then
view:toggle_expand(false)
else
local parent_item, y = view:get_parent(view.selected_item)
if parent_item then
view:set_selection(parent_item, y)
end
end
end
end,
["treeview:expand"] = function()
view:toggle_expand(true)
end,
})
local function treeitem() return view.hovered_item or view.selected_item end
command.add(
function()
return treeitem() ~= nil
and (
core.active_view == view or core.active_view == menu
or (view.toolbar and core.active_view == view.toolbar)
-- sometimes the context menu is shown on top of statusbar
or core.active_view == core.status_view
)
end, {
["treeview:delete"] = function()
local filename = view.hovered_item.abs_filename
local relfilename = view.hovered_item.filename
local filename = treeitem().abs_filename
local relfilename = treeitem().filename
local file_info = system.get_file_info(filename)
local file_type = file_info.type == "dir" and "Directory" or "File"
-- Ask before deleting
@ -568,22 +712,83 @@ command.add(function() return view.hovered_item ~= nil end, {
end
end
)
end
})
command.add(function() return treeitem() ~= nil end, {
["treeview:rename"] = function()
local old_filename = treeitem().filename
local old_abs_filename = treeitem().abs_filename
core.command_view:set_text(old_filename)
core.command_view:enter("Rename", function(filename)
filename = core.normalize_to_project_dir(filename)
local abs_filename = core.project_absolute_path(filename)
local res, err = os.rename(old_abs_filename, abs_filename)
if res then -- successfully renamed
for _, doc in ipairs(core.docs) do
if doc.abs_filename and old_abs_filename == doc.abs_filename then
doc:set_filename(filename, abs_filename) -- make doc point to the new filename
doc:reset_syntax()
break -- only first needed
end
end
core.log("Renamed \"%s\" to \"%s\"", old_filename, filename)
else
core.error("Error while renaming \"%s\" to \"%s\": %s", old_abs_filename, abs_filename, err)
end
end, common.path_suggest)
end,
["treeview:new-file"] = function()
if not is_project_folder(treeitem().abs_filename) then
core.command_view:set_text(treeitem().filename .. "/")
end
core.command_view:enter("Filename", function(filename)
local doc_filename = core.project_dir .. PATHSEP .. filename
local file = io.open(doc_filename, "a+")
file:write("")
file:close()
core.root_view:open_doc(core.open_doc(doc_filename))
core.log("Created %s", doc_filename)
end, common.path_suggest)
end,
["treeview:new-folder"] = function()
if not is_project_folder(treeitem().abs_filename) then
core.command_view:set_text(treeitem().filename .. "/")
end
core.command_view:enter("Folder Name", function(filename)
local dir_path = core.project_dir .. PATHSEP .. filename
common.mkdirp(dir_path)
core.log("Created %s", dir_path)
end, common.path_suggest)
end,
["treeview:open-in-system"] = function()
local hovered_item = view.hovered_item
local hovered_item = treeitem()
if PLATFORM == "Windows" then
system.exec(string.format("start \"\" %q", hovered_item.abs_filename))
elseif string.find(PLATFORM, "Mac") then
system.exec(string.format("open %q", hovered_item.abs_filename))
elseif PLATFORM == "Linux" then
elseif PLATFORM == "Linux" or string.find(PLATFORM, "BSD") then
system.exec(string.format("xdg-open %q", hovered_item.abs_filename))
end
end,
})
keymap.add { ["ctrl+\\"] = "treeview:toggle" }
keymap.add {
["ctrl+\\"] = "treeview:toggle",
["up"] = "treeview:previous",
["down"] = "treeview:next",
["left"] = "treeview:collapse",
["right"] = "treeview:expand",
["return"] = "treeview:open",
["escape"] = "treeview:deselect",
["delete"] = "treeview:delete",
["ctrl+return"] = "treeview:new-folder"
}
-- Return the treeview with toolbar and contextmenu to allow
-- user or plugin modifications

View File

@ -124,7 +124,7 @@ process.options = {}
---@return process | nil
---@return string errmsg
---@return process.errortype | integer errcode
function process:start(command_and_params, options) end
function process.start(command_and_params, options) end
---
---Translates an error code into a useful text message

View File

@ -192,6 +192,12 @@ function system.get_clipboard() end
---@param text string
function system.set_clipboard(text) end
---
---Get the process id of lite-xl it self.
---
---@return integer
function system.get_process_id() end
---
---Get amount of iterations since the application was launched
---also known as SDL_GetPerformanceCounter() / SDL_GetPerformanceFrequency()

File diff suppressed because it is too large Load Diff

View File

@ -1,162 +0,0 @@
#ifndef __DMON_EXTRA_H__
#define __DMON_EXTRA_H__
//
// Copyright 2021 Sepehr Taghdisian (septag@github). All rights reserved.
// License: https://github.com/septag/dmon#license-bsd-2-clause
//
// Extra header functionality for dmon.h for the backend based on inotify
//
// Add/Remove directory functions:
// dmon_watch_add: Adds a sub-directory to already valid watch_id. sub-directories are assumed to be relative to watch root_dir
// dmon_watch_add: Removes a sub-directory from already valid watch_id. sub-directories are assumed to be relative to watch root_dir
// Reason: The inotify backend does not work well when watching in recursive mode a root directory of a large tree, it may take
// quite a while until all inotify watches are established, and events will not be received in this time. Also, since one
// inotify watch will be established per subdirectory, it is possible that the maximum amount of inotify watches per user
// will be reached. The default maximum is 8192.
// When using inotify backend users may turn off the DMON_WATCHFLAGS_RECURSIVE flag and add/remove selectively the
// sub-directories to be watched based on application-specific logic about which sub-directory actually needs to be watched.
// The function dmon_watch_add and dmon_watch_rm are used to this purpose.
//
#ifndef __DMON_H__
#error "Include 'dmon.h' before including this file"
#endif
#ifdef __cplusplus
extern "C" {
#endif
DMON_API_DECL bool dmon_watch_add(dmon_watch_id id, const char* subdir);
DMON_API_DECL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir);
#ifdef __cplusplus
}
#endif
#ifdef DMON_IMPL
#if DMON_OS_LINUX
DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir)
{
DMON_ASSERT(id.id > 0 && id.id <= DMON_MAX_WATCHES);
bool skip_lock = pthread_self() == _dmon.thread_handle;
if (!skip_lock)
pthread_mutex_lock(&_dmon.mutex);
dmon__watch_state* watch = &_dmon.watches[id.id - 1];
// check if the directory exists
// if watchdir contains absolute/root-included path, try to strip the rootdir from it
// else, we assume that watchdir is correct, so save it as it is
struct stat st;
dmon__watch_subdir subdir;
if (stat(watchdir, &st) == 0 && (st.st_mode & S_IFDIR)) {
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir);
if (strstr(subdir.rootdir, watch->rootdir) == subdir.rootdir) {
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir + strlen(watch->rootdir));
}
} else {
char fullpath[DMON_MAX_PATH];
dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir);
dmon__strcat(fullpath, sizeof(fullpath), watchdir);
if (stat(fullpath, &st) != 0 || (st.st_mode & S_IFDIR) == 0) {
_DMON_LOG_ERRORF("Watch directory '%s' is not valid", watchdir);
if (!skip_lock)
pthread_mutex_unlock(&_dmon.mutex);
return false;
}
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir);
}
int dirlen = (int)strlen(subdir.rootdir);
if (subdir.rootdir[dirlen - 1] != '/') {
subdir.rootdir[dirlen] = '/';
subdir.rootdir[dirlen + 1] = '\0';
}
// check that the directory is not already added
for (int i = 0, c = stb_sb_count(watch->subdirs); i < c; i++) {
if (strcmp(subdir.rootdir, watch->subdirs[i].rootdir) == 0) {
_DMON_LOG_ERRORF("Error watching directory '%s', because it is already added.", watchdir);
if (!skip_lock)
pthread_mutex_unlock(&_dmon.mutex);
return false;
}
}
const uint32_t inotify_mask = IN_MOVED_TO | IN_CREATE | IN_MOVED_FROM | IN_DELETE | IN_MODIFY;
char fullpath[DMON_MAX_PATH];
dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir);
dmon__strcat(fullpath, sizeof(fullpath), subdir.rootdir);
int wd = inotify_add_watch(watch->fd, fullpath, inotify_mask);
if (wd == -1) {
_DMON_LOG_ERRORF("Error watching directory '%s'. (inotify_add_watch:err=%d)", watchdir, errno);
if (!skip_lock)
pthread_mutex_unlock(&_dmon.mutex);
return false;
}
stb_sb_push(watch->subdirs, subdir);
stb_sb_push(watch->wds, wd);
if (!skip_lock)
pthread_mutex_unlock(&_dmon.mutex);
return true;
}
DMON_API_IMPL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir)
{
DMON_ASSERT(id.id > 0 && id.id <= DMON_MAX_WATCHES);
bool skip_lock = pthread_self() == _dmon.thread_handle;
if (!skip_lock)
pthread_mutex_lock(&_dmon.mutex);
dmon__watch_state* watch = &_dmon.watches[id.id - 1];
char subdir[DMON_MAX_PATH];
dmon__strcpy(subdir, sizeof(subdir), watchdir);
if (strstr(subdir, watch->rootdir) == subdir) {
dmon__strcpy(subdir, sizeof(subdir), watchdir + strlen(watch->rootdir));
}
int dirlen = (int)strlen(subdir);
if (subdir[dirlen - 1] != '/') {
subdir[dirlen] = '/';
subdir[dirlen + 1] = '\0';
}
int i, c = stb_sb_count(watch->subdirs);
for (i = 0; i < c; i++) {
if (strcmp(watch->subdirs[i].rootdir, subdir) == 0) {
break;
}
}
if (i >= c) {
_DMON_LOG_ERRORF("Watch directory '%s' is not valid", watchdir);
if (!skip_lock)
pthread_mutex_unlock(&_dmon.mutex);
return false;
}
inotify_rm_watch(watch->fd, watch->wds[i]);
/* Remove entry from subdirs and wds by swapping position with the last entry */
watch->subdirs[i] = stb_sb_last(watch->subdirs);
stb_sb_pop(watch->subdirs);
watch->wds[i] = stb_sb_last(watch->wds);
stb_sb_pop(watch->wds);
if (!skip_lock)
pthread_mutex_unlock(&_dmon.mutex);
return true;
}
#endif // DMON_OS_LINUX
#endif // DMON_IMPL
#endif // __DMON_EXTRA_H__

View File

@ -1 +0,0 @@
lite_includes += include_directories('.')

View File

@ -22,33 +22,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
## septag/dmon
Copyright 2019 Sepehr Taghdisian. All rights reserved.
https://github.com/septag/dmon
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## Fira Sans
Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A.

View File

@ -1,18 +1,41 @@
project('lite-xl',
['c'],
version : '2.0.3',
version : '2.0.5',
license : 'MIT',
meson_version : '>= 0.54',
default_options : ['c_std=gnu11']
meson_version : '>= 0.47',
default_options : [
'c_std=gnu11',
'wrap_mode=nofallback'
]
)
#===============================================================================
# Project version including git commit if possible
#===============================================================================
version = meson.project_version()
if get_option('buildtype') != 'release'
git_command = find_program('git', required : false)
if git_command.found()
git_commit = run_command(
[git_command, 'rev-parse', 'HEAD'],
check : false
).stdout().strip()
if git_commit != ''
version += ' (git-' + git_commit.substring(0, 8) + ')'
endif
endif
endif
#===============================================================================
# Configuration
#===============================================================================
conf_data = configuration_data()
conf_data.set('PROJECT_BUILD_DIR', meson.current_build_dir())
conf_data.set('PROJECT_SOURCE_DIR', meson.current_source_dir())
conf_data.set('PROJECT_VERSION', meson.project_version())
conf_data.set('PROJECT_VERSION', version)
#===============================================================================
# Compiler Settings
@ -24,7 +47,7 @@ endif
cc = meson.get_compiler('c')
lite_includes = []
lite_cargs = []
lite_cargs = ['-DSDL_MAIN_HANDLED', '-DPCRE2_STATIC']
# On macos we need to use the SDL renderer to support retina displays
if get_option('renderer') or host_machine.system() == 'darwin'
lite_cargs += '-DLITE_USE_SDL_RENDERER'
@ -46,14 +69,32 @@ endif
if not get_option('source-only')
libm = cc.find_library('m', required : false)
libdl = cc.find_library('dl', required : false)
threads_dep = dependency('threads')
lua_dep = dependency('lua5.2', fallback: ['lua', 'lua_dep'],
default_options: ['shared=false', 'use_readline=false', 'app=false']
lua_fallback = ['lua', 'lua_dep']
lua_quick_fallback = []
if get_option('wrap_mode') == 'forcefallback'
lua_quick_fallback = lua_fallback
endif
lua_dep = dependency('lua5.4', fallback: lua_quick_fallback, required : false)
if not lua_dep.found()
lua_dep = dependency('lua', fallback: ['lua', 'lua_dep'],
default_options: ['default_library=static', 'line_editing=false', 'interpreter=false']
)
pcre2_dep = dependency('libpcre2-8')
freetype_dep = dependency('freetype2')
sdl_dep = dependency('sdl2', method: 'config-tool')
lite_deps = [lua_dep, sdl_dep, freetype_dep, pcre2_dep, libm, libdl, threads_dep]
endif
pcre2_dep = dependency('libpcre2-8', fallback: ['pcre2', 'libpcre2_8'],
default_options: ['default_library=static', 'grep=false', 'test=false']
)
freetype_dep = dependency('freetype2', fallback: ['freetype2', 'freetype_dep'],
default_options: ['default_library=static', 'zlib=disabled', 'bzip2=disabled', 'png=disabled', 'harfbuzz=disabled', 'brotli=disabled']
)
sdl_dep = dependency('sdl2', fallback: ['sdl2', 'sdl2_dep'],
default_options: ['default_library=static']
)
lite_deps = [lua_dep, sdl_dep, freetype_dep, pcre2_dep, libm, libdl]
endif
#===============================================================================
# Install Configuration
@ -94,21 +135,19 @@ endif
install_data('licenses/licenses.md', install_dir : lite_docdir)
install_subdir('data' / 'core' , install_dir : lite_datadir, exclude_files : 'start.lua')
install_subdir('data/core' , install_dir : lite_datadir, exclude_files : 'start.lua')
foreach data_module : ['fonts', 'plugins', 'colors']
install_subdir('data' / data_module , install_dir : lite_datadir)
install_subdir(join_paths('data', data_module), install_dir : lite_datadir)
endforeach
configure_file(
input : 'data/core/start.lua',
output : 'start.lua',
configuration : conf_data,
install : true,
install_dir : lite_datadir / 'core',
install_dir : join_paths(lite_datadir, 'core'),
)
if not get_option('source-only')
subdir('lib/dmon')
subdir('src')
subdir('scripts')
endif

View File

@ -2,3 +2,4 @@ option('bundle', type : 'boolean', value : false, description: 'Build a macOS bu
option('source-only', type : 'boolean', value : false, description: 'Configure source files only, doesn\'t checks for dependencies')
option('portable', type : 'boolean', value : false, description: 'Portable install')
option('renderer', type : 'boolean', value : false, description: 'Use SDL renderer')
option('dirmonitor_backend', type : 'combo', value : '', choices : ['', 'inotify', 'kqueue', 'win32', 'dummy'], description: 'define what dirmonitor backend to use')

View File

@ -17,11 +17,11 @@
<screenshots>
<screenshot type="default">
<caption>The editor window</caption>
<image>https://lite-xl.github.io/assets/img/screenshots/editor.png</image>
<image>https://lite-xl.com/assets/img/editor.png</image>
</screenshot>
</screenshots>
<url type="homepage">https://lite-xl.github.io</url>
<url type="homepage">https://lite-xl.com</url>
<provides>
<binary>lite-xl</binary>

View File

@ -8,6 +8,8 @@
<string>lite-xl</string>
<key>CFBundleIconFile</key>
<string>icon.icns</string>
<key>CFBundleIdentifier</key>
<string>com.lite-xl</string>
<key>CFBundleName</key>
<string>Lite XL</string>
<key>CFBundlePackageType</key>

View File

@ -1,54 +0,0 @@
`core.set_project_dir`:
Reset project directories and set its directory.
It chdir into the directory, empty the `core.project_directories` and add
the given directory.
`core.add_project_directory`:
Add a new top-level directory to the project.
Also called from modules and commands outside core.init.
local function `scan_project_folder`:
Scan all files for a given top-level project directory.
Can emit a warning about file limit.
Called only from within core.init module.
`core.scan_project_subdir`: (before was named `core.scan_project_folder`)
scan a single folder, without recursion. Used when too many files.
Local function `scan_project_folder`:
Populate the project folder top directory. Done only once when the directory
is added to the project.
`core.add_project_directory`:
Add a new top-level folder to the project.
`core.set_project_dir`:
Set the initial project directory.
`core.dir_rescan_add_job`:
Add a job to rescan after an elapsed time a project's subdirectory to fix for any
changes.
Local function `rescan_project_subdir`:
Rescan a project's subdirectory, compare to the current version and patch the list if
a difference is found.
`core.project_scan_thread`:
Should disappear now that we use dmon.
`core.project_scan_topdir`:
New function to scan a top level project folder.
`config.project_scan_rate`:
`core.project_scan_thread_id`:
`core.reschedule_project_scan`:
`core.project_files_limit`:
A eliminer.
`core.get_project_files`:
To be fixed. Use `find_project_files_co` for a single directory
In TreeView remove usage of self.last to detect new scan that changed the files list.

View File

@ -63,10 +63,10 @@ main() {
elif [[ "$OSTYPE" == "msys" ]]; then
if [[ $lhelper == true ]]; then
pacman --noconfirm -S \
${MINGW_PACKAGE_PREFIX}-{gcc,meson,ninja,ntldd,pkg-config} unzip
${MINGW_PACKAGE_PREFIX}-{gcc,meson,ninja,ntldd,pkg-config,mesa} unzip
else
pacman --noconfirm -S \
${MINGW_PACKAGE_PREFIX}-{gcc,meson,ninja,ntldd,pkg-config,freetype,pcre2,SDL2} unzip
${MINGW_PACKAGE_PREFIX}-{gcc,meson,ninja,ntldd,pkg-config,mesa,freetype,pcre2,SDL2} unzip
fi
fi
}

View File

@ -72,4 +72,4 @@ main() {
fi
}
main
main "$@"

View File

@ -216,11 +216,11 @@ main() {
rm -rf "Lite XL.app"; mv "${dest_dir}" "Lite XL.app"
dest_dir="Lite XL.app"
exe_file="$(pwd)/${dest_dir}/Contents/MacOS/lite-xl"
data_dir="$(pwd)/${dest_dir}/Contents/Resources"
fi
fi
if [[ $bundle == false && $portable == false ]]; then
echo "Creating a compressed archive..."
data_dir="$(pwd)/${dest_dir}/$prefix/share/lite-xl"
exe_file="$(pwd)/${dest_dir}/$prefix/bin/lite-xl"
fi
@ -240,6 +240,7 @@ main() {
$stripcmd "${exe_file}"
echo "Creating a compressed archive ${package_name}"
if [[ $binary == true ]]; then
rm -f "${package_name}".tar.gz
rm -f "${package_name}".zip

View File

@ -1,20 +1,23 @@
#include "api.h"
int luaopen_system(lua_State *L);
int luaopen_renderer(lua_State *L);
int luaopen_regex(lua_State *L);
int luaopen_process(lua_State *L);
int luaopen_dirmonitor(lua_State* L);
static const luaL_Reg libs[] = {
{ "system", luaopen_system },
{ "renderer", luaopen_renderer },
{ "regex", luaopen_regex },
{ "process", luaopen_process },
{ "dirmonitor", luaopen_dirmonitor },
{ NULL, NULL }
};
void api_load_libs(lua_State *L) {
for (int i = 0; libs[i].name; i++)
luaL_requiref(L, libs[i].name, libs[i].func, 1);
}

View File

@ -7,6 +7,7 @@
#define API_TYPE_FONT "Font"
#define API_TYPE_PROCESS "Process"
#define API_TYPE_DIRMONITOR "Dirmonitor"
#define API_CONSTANT_DEFINE(L, idx, key, n) (lua_pushnumber(L, n), lua_setfield(L, idx - 1, key))

96
src/api/dirmonitor.c Normal file
View File

@ -0,0 +1,96 @@
#include "api.h"
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
#elif __linux__
#include <sys/inotify.h>
#include <limits.h>
#else
#include <sys/event.h>
#endif
#include <unistd.h>
#include <errno.h>
#include <dirent.h>
#include <fcntl.h>
#include <string.h>
#include <stdbool.h>
#ifndef DIRMONITOR_BACKEND
#error No dirmonitor backend defined
#endif
#define GLUE_HELPER(x, y) x##y
#define GLUE(x, y) GLUE_HELPER(x, y)
#define init_dirmonitor GLUE(init_dirmonitor_, DIRMONITOR_BACKEND)
#define deinit_dirmonitor GLUE(deinit_dirmonitor_, DIRMONITOR_BACKEND)
#define check_dirmonitor GLUE(check_dirmonitor_, DIRMONITOR_BACKEND)
#define add_dirmonitor GLUE(add_dirmonitor_, DIRMONITOR_BACKEND)
#define remove_dirmonitor GLUE(remove_dirmonitor_, DIRMONITOR_BACKEND)
struct dirmonitor {}; // dirmonitor struct is defined in each backend
// define functions so we know their signature
struct dirmonitor* init_dirmonitor();
void deinit_dirmonitor(struct dirmonitor*);
int check_dirmonitor(struct dirmonitor*, int (*)(int, const char*, void*), void*);
int add_dirmonitor(struct dirmonitor*, const char*);
void remove_dirmonitor(struct dirmonitor*, int);
static int f_check_dir_callback(int watch_id, const char* path, void* L) {
lua_pushvalue(L, -1);
#ifdef DIRMONITOR_WIN32
char buffer[PATH_MAX*4];
int count = WideCharToMultiByte(CP_UTF8, 0, (WCHAR*)path, watch_id, buffer, PATH_MAX*4 - 1, NULL, NULL);
lua_pushlstring(L, buffer, count);
#else
lua_pushnumber(L, watch_id);
#endif
lua_call(L, 1, 1);
int result = lua_toboolean(L, -1);
lua_pop(L, 1);
return !result;
}
static int f_dirmonitor_new(lua_State* L) {
struct dirmonitor** monitor = lua_newuserdata(L, sizeof(struct dirmonitor**));
*monitor = init_dirmonitor();
luaL_setmetatable(L, API_TYPE_DIRMONITOR);
return 1;
}
static int f_dirmonitor_gc(lua_State* L) {
deinit_dirmonitor(*((struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR)));
return 0;
}
static int f_dirmonitor_watch(lua_State *L) {
lua_pushnumber(L, add_dirmonitor(*(struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR), luaL_checkstring(L, 2)));
return 1;
}
static int f_dirmonitor_unwatch(lua_State *L) {
remove_dirmonitor(*(struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR), luaL_checknumber(L, 2));
return 0;
}
static int f_dirmonitor_check(lua_State* L) {
lua_pushnumber(L, check_dirmonitor(*(struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR), f_check_dir_callback, L));
return 1;
}
static const luaL_Reg dirmonitor_lib[] = {
{ "new", f_dirmonitor_new },
{ "__gc", f_dirmonitor_gc },
{ "watch", f_dirmonitor_watch },
{ "unwatch", f_dirmonitor_unwatch },
{ "check", f_dirmonitor_check },
{NULL, NULL}
};
int luaopen_dirmonitor(lua_State* L) {
luaL_newmetatable(L, API_TYPE_DIRMONITOR);
luaL_setfuncs(L, dirmonitor_lib, 0);
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
return 1;
}

View File

@ -0,0 +1,22 @@
#include <stdlib.h>
struct dirmonitor {
};
struct dirmonitor* init_dirmonitor_dummy() {
return NULL;
}
void deinit_dirmonitor_dummy(struct dirmonitor* monitor) {
}
int check_dirmonitor_dummy(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) {
return -1;
}
int add_dirmonitor_dummy(struct dirmonitor* monitor, const char* path) {
return -1;
}
void remove_dirmonitor_dummy(struct dirmonitor* monitor, int fd) {
}

View File

@ -0,0 +1,58 @@
#include <sys/inotify.h>
#include <limits.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
struct dirmonitor {
int fd;
};
struct dirmonitor* init_dirmonitor_inotify() {
struct dirmonitor* monitor = calloc(sizeof(struct dirmonitor), 1);
monitor->fd = inotify_init();
fcntl(monitor->fd, F_SETFL, O_NONBLOCK);
return monitor;
}
void deinit_dirmonitor_inotify(struct dirmonitor* monitor) {
close(monitor->fd);
free(monitor);
}
int check_dirmonitor_inotify(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) {
char buf[PATH_MAX + sizeof(struct inotify_event)];
ssize_t offset = 0;
while (1) {
ssize_t len = read(monitor->fd, &buf[offset], sizeof(buf) - offset);
if (len == -1 && errno != EAGAIN) {
return errno;
}
if (len <= 0) {
return 0;
}
while (len > sizeof(struct inotify_event) && len >= ((struct inotify_event*)buf)->len + sizeof(struct inotify_event)) {
change_callback(((const struct inotify_event *)buf)->wd, NULL, data);
len -= sizeof(struct inotify_event) + ((struct inotify_event*)buf)->len;
memmove(buf, &buf[sizeof(struct inotify_event) + ((struct inotify_event*)buf)->len], len);
offset = len;
}
}
}
int add_dirmonitor_inotify(struct dirmonitor* monitor, const char* path) {
return inotify_add_watch(monitor->fd, path, IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO);
}
void remove_dirmonitor_inotify(struct dirmonitor* monitor, int fd) {
inotify_rm_watch(monitor->fd, fd);
}

View File

@ -0,0 +1,53 @@
#include <sys/event.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
struct dirmonitor {
int fd;
};
struct dirmonitor* init_dirmonitor_kqueue() {
struct dirmonitor* monitor = calloc(sizeof(struct dirmonitor), 1);
monitor->fd = kqueue();
return monitor;
}
void deinit_dirmonitor_kqueue(struct dirmonitor* monitor) {
close(monitor->fd);
free(monitor);
}
int check_dirmonitor_kqueue(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) {
struct kevent event;
while (1) {
struct timespec tm = {0};
int nev = kevent(monitor->fd, NULL, 0, &event, 1, &tm);
if (nev == -1) {
return errno;
}
if (nev <= 0) {
return 0;
}
change_callback(event.ident, NULL, data);
}
}
int add_dirmonitor_kqueue(struct dirmonitor* monitor, const char* path) {
int fd = open(path, O_RDONLY);
struct kevent change;
EV_SET(&change, fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_DELETE | NOTE_EXTEND | NOTE_WRITE | NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME, 0, (void*)path);
kevent(monitor->fd, &change, 1, NULL, 0, NULL);
return fd;
}
void remove_dirmonitor_kqueue(struct dirmonitor* monitor, int fd) {
close(fd);
}

View File

@ -0,0 +1,77 @@
#include <stdbool.h>
#include <windows.h>
struct dirmonitor {
HANDLE handle;
char buffer[8192];
OVERLAPPED overlapped;
bool running;
};
struct dirmonitor* init_dirmonitor_win32() {
struct dirmonitor* monitor = calloc(sizeof(struct dirmonitor), 1);
return monitor;
}
static void close_monitor_handle(struct dirmonitor* monitor) {
if (monitor->handle) {
if (monitor->running) {
BOOL result = CancelIoEx(monitor->handle, &monitor->overlapped);
DWORD error = GetLastError();
if (result == TRUE || error != ERROR_NOT_FOUND) {
DWORD bytes_transferred;
GetOverlappedResult( monitor->handle, &monitor->overlapped, &bytes_transferred, TRUE );
}
monitor->running = false;
}
CloseHandle(monitor->handle);
}
monitor->handle = NULL;
}
void deinit_dirmonitor_win32(struct dirmonitor* monitor) {
close_monitor_handle(monitor);
free(monitor);
}
int check_dirmonitor_win32(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) {
if (!monitor->running) {
if (ReadDirectoryChangesW(monitor->handle, monitor->buffer, sizeof(monitor->buffer), TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME, NULL, &monitor->overlapped, NULL) == 0) {
return GetLastError();
}
monitor->running = true;
}
DWORD bytes_transferred;
if (!GetOverlappedResult(monitor->handle, &monitor->overlapped, &bytes_transferred, FALSE)) {
int error = GetLastError();
return error == ERROR_IO_PENDING || error == ERROR_IO_INCOMPLETE ? 0 : error;
}
monitor->running = false;
for (FILE_NOTIFY_INFORMATION* info = (FILE_NOTIFY_INFORMATION*)monitor->buffer; (char*)info < monitor->buffer + sizeof(monitor->buffer); info = (FILE_NOTIFY_INFORMATION*)((char*)info) + info->NextEntryOffset) {
change_callback(info->FileNameLength, (char*)info->FileName, data);
if (!info->NextEntryOffset)
break;
}
monitor->running = false;
return 0;
}
int add_dirmonitor_win32(struct dirmonitor* monitor, const char* path) {
close_monitor_handle(monitor);
monitor->handle = CreateFileA(path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
if (monitor->handle && monitor->handle != INVALID_HANDLE_VALUE) {
return 1;
}
monitor->handle = NULL;
return -1;
}
void remove_dirmonitor_win32(struct dirmonitor* monitor, int fd) {
close_monitor_handle(monitor);
}

View File

@ -2,6 +2,7 @@
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <SDL.h>
@ -59,17 +60,18 @@ typedef enum {
#ifdef _WIN32
static volatile long PipeSerialNumber;
static void close_fd(HANDLE handle) { CloseHandle(handle); }
static void close_fd(HANDLE* handle) { if (*handle) CloseHandle(*handle); *handle = NULL; }
#else
static void close_fd(int fd) { close(fd); }
static void close_fd(int* fd) { if (*fd) close(*fd); *fd = 0; }
#endif
static bool poll_process(process_t* proc, int timeout) {
if (!proc->running)
return false;
unsigned int ticks = SDL_GetTicks();
uint32_t ticks = SDL_GetTicks();
if (timeout == WAIT_DEADLINE)
timeout = proc->deadline;
do {
#ifdef _WIN32
DWORD exit_code = -1;
@ -90,13 +92,8 @@ static bool poll_process(process_t* proc, int timeout) {
if (timeout)
SDL_Delay(5);
} while (timeout == WAIT_INFINITE || SDL_GetTicks() - ticks < timeout);
if (!proc->running) {
close_fd(proc->child_pipes[STDIN_FD ][1]);
close_fd(proc->child_pipes[STDOUT_FD][0]);
close_fd(proc->child_pipes[STDERR_FD][0]);
return false;
}
return true;
return proc->running;
}
static bool signal_process(process_t* proc, signal_e sig) {
@ -109,9 +106,9 @@ static bool signal_process(process_t* proc, signal_e sig) {
}
#else
switch (sig) {
case SIGNAL_TERM: terminate = kill(proc->pid, SIGTERM) == 1; break;
case SIGNAL_KILL: terminate = kill(proc->pid, SIGKILL) == 1; break;
case SIGNAL_INTERRUPT: kill(proc->pid, SIGINT); break;
case SIGNAL_TERM: terminate = kill(-proc->pid, SIGTERM) == 1; break;
case SIGNAL_KILL: terminate = kill(-proc->pid, SIGKILL) == 1; break;
case SIGNAL_INTERRUPT: kill(-proc->pid, SIGINT); break;
}
#endif
if (terminate)
@ -121,19 +118,19 @@ static bool signal_process(process_t* proc, signal_e sig) {
static int process_start(lua_State* L) {
size_t env_len = 0, key_len, val_len;
const char *cmd[256], *env[256] = { NULL }, *cwd = NULL;
const char *cmd[256], *env_names[256] = { NULL }, *env_values[256] = { NULL }, *cwd = NULL;
bool detach = false;
int deadline = 10, new_fds[3] = { STDIN_FD, STDOUT_FD, STDERR_FD };
luaL_checktype(L, 1, LUA_TTABLE);
#if LUA_VERSION_NUM > 501
lua_len(L, 1);
#else
lua_pushnumber(L, (int)lua_objlen(L, 1));
lua_pushinteger(L, (int)lua_objlen(L, 1));
#endif
size_t cmd_len = luaL_checknumber(L, -1); lua_pop(L, 1);
size_t arg_len = lua_gettop(L);
for (size_t i = 1; i <= cmd_len; ++i) {
lua_pushnumber(L, i);
lua_pushinteger(L, i);
lua_rawget(L, 1);
cmd[i-1] = luaL_checkstring(L, -1);
}
@ -145,9 +142,12 @@ static int process_start(lua_State* L) {
while (lua_next(L, -2) != 0) {
const char* key = luaL_checklstring(L, -2, &key_len);
const char* val = luaL_checklstring(L, -1, &val_len);
env[env_len] = malloc(key_len+val_len+2);
snprintf((char*)env[env_len++], key_len+val_len+2, "%s=%s", key, val);
env_names[env_len] = malloc(key_len+1);
strcpy((char*)env_names[env_len], key);
env_values[env_len] = malloc(val_len+1);
strcpy((char*)env_values[env_len], val);
lua_pop(L, 1);
++env_len;
}
} else
lua_pop(L, 1);
@ -162,7 +162,6 @@ static int process_start(lua_State* L) {
return luaL_error(L, "redirect to handles, FILE* and paths are not supported");
}
}
env[env_len] = NULL;
process_t* self = lua_newuserdata(L, sizeof(process_t));
memset(self, 0, sizeof(process_t));
@ -217,26 +216,28 @@ static int process_start(lua_State* L) {
siStartInfo.hStdInput = self->child_pipes[STDIN_FD][0];
siStartInfo.hStdOutput = self->child_pipes[STDOUT_FD][1];
siStartInfo.hStdError = self->child_pipes[STDERR_FD][1];
char commandLine[32767] = { 0 }, environmentBlock[32767];
int offset = 0;
char commandLine[32767] = { 0 }, environmentBlock[32767], wideEnvironmentBlock[32767*2];
strcpy(commandLine, cmd[0]);
int offset = 0;
for (size_t i = 1; i < cmd_len; ++i) {
size_t len = strlen(cmd[i]);
if (offset + len + 1 >= sizeof(commandLine))
offset += len + 1;
if (offset >= sizeof(commandLine))
break;
strcat(commandLine, " ");
strcat(commandLine, cmd[i]);
}
offset = 0;
for (size_t i = 0; i < env_len; ++i) {
size_t len = strlen(env[i]);
if (offset + len >= sizeof(environmentBlock))
if (offset + strlen(env_values[i]) + strlen(env_names[i]) + 1 >= sizeof(environmentBlock))
break;
memcpy(&environmentBlock[offset], env[i], len);
offset += len;
offset += snprintf(&environmentBlock[offset], sizeof(environmentBlock) - offset, "%s=%s", env_names[i], env_values[i]);
environmentBlock[offset++] = 0;
}
environmentBlock[offset++] = 0;
if (!CreateProcess(NULL, commandLine, NULL, NULL, true, (detach ? DETACHED_PROCESS : CREATE_NO_WINDOW) | CREATE_UNICODE_ENVIRONMENT, env_len > 0 ? environmentBlock : NULL, cwd, &siStartInfo, &self->process_information))
if (env_len > 0)
MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, environmentBlock, offset, (LPWSTR)wideEnvironmentBlock, sizeof(wideEnvironmentBlock));
if (!CreateProcess(NULL, commandLine, NULL, NULL, true, (detach ? DETACHED_PROCESS : CREATE_NO_WINDOW) | CREATE_UNICODE_ENVIRONMENT, env_len > 0 ? wideEnvironmentBlock : NULL, cwd, &siStartInfo, &self->process_information))
return luaL_error(L, "Error creating a process: %d.", GetLastError());
self->pid = (long)self->process_information.dwProcessId;
if (detach)
@ -255,6 +256,7 @@ static int process_start(lua_State* L) {
}
return luaL_error(L, "Error running fork: %s.", strerror(errno));
} else if (!self->pid) {
setpgrp();
for (int stream = 0; stream < 3; ++stream) {
if (new_fds[stream] == REDIRECT_DISCARD) { // Close the stream if we don't want it.
close(self->child_pipes[stream][stream == STDIN_FD ? 0 : 1]);
@ -263,17 +265,21 @@ static int process_start(lua_State* L) {
dup2(self->child_pipes[new_fds[stream]][new_fds[stream] == STDIN_FD ? 0 : 1], stream);
close(self->child_pipes[stream][stream == STDIN_FD ? 1 : 0]);
}
if ((!detach || setsid() != -1) && (!cwd || chdir(cwd) != -1))
int set;
for (set = 0; set < env_len && setenv(env_names[set], env_values[set], 1) == 0; ++set);
if (set == env_len && (!detach || setsid() != -1) && (!cwd || chdir(cwd) != -1))
execvp((const char*)cmd[0], (char* const*)cmd);
const char* msg = strerror(errno);
int result = write(STDERR_FD, msg, strlen(msg)+1);
exit(result == strlen(msg)+1 ? -1 : -2);
_exit(result == strlen(msg)+1 ? -1 : -2);
}
#endif
for (size_t i = 0; i < env_len; ++i)
free((char*)env[i]);
for (size_t i = 0; i < env_len; ++i) {
free((char*)env_names[i]);
free((char*)env_values[i]);
}
for (int stream = 0; stream < 3; ++stream)
close_fd(self->child_pipes[stream][stream == STDIN_FD ? 0 : 1]);
close_fd(&self->child_pipes[stream][stream == STDIN_FD ? 0 : 1]);
self->running = true;
return 1;
}
@ -306,7 +312,7 @@ static int g_read(lua_State* L, int stream, unsigned long read_size) {
#else
luaL_Buffer b;
luaL_buffinit(L, &b);
uint8_t* buffer = (uint8_t*)luaL_prepbuffer(&b);
uint8_t* buffer = (uint8_t*)luaL_prepbuffsize(&b, READ_BUF_SIZE);
length = read(self->child_pipes[stream][0], buffer, read_size > READ_BUF_SIZE ? READ_BUF_SIZE : read_size);
if (length == 0 && !poll_process(self, WAIT_NONE))
return 0;
@ -330,8 +336,9 @@ static int f_write(lua_State* L) {
#if _WIN32
DWORD dwWritten;
if (!WriteFile(self->child_pipes[STDIN_FD][1], data, data_size, &dwWritten, NULL)) {
int lastError = GetLastError();
signal_process(self, SIGNAL_TERM);
return luaL_error(L, "error writing to process: %d", GetLastError());
return luaL_error(L, "error writing to process: %d", lastError);
}
length = dwWritten;
#else
@ -339,18 +346,19 @@ static int f_write(lua_State* L) {
if (length < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
length = 0;
else if (length < 0) {
const char* lastError = strerror(errno);
signal_process(self, SIGNAL_TERM);
return luaL_error(L, "error writing to process: %s", strerror(errno));
return luaL_error(L, "error writing to process: %s", lastError);
}
#endif
lua_pushnumber(L, length);
lua_pushinteger(L, length);
return 1;
}
static int f_close_stream(lua_State* L) {
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
int stream = luaL_checknumber(L, 2);
close_fd(self->child_pipes[stream][stream == STDIN_FD ? 1 : 0]);
close_fd(&self->child_pipes[stream][stream == STDIN_FD ? 1 : 0]);
lua_pushboolean(L, 1);
return 1;
}
@ -375,7 +383,7 @@ static int f_tostring(lua_State* L) {
static int f_pid(lua_State* L) {
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
lua_pushnumber(L, self->pid);
lua_pushinteger(L, self->pid);
return 1;
}
@ -383,7 +391,7 @@ static int f_returncode(lua_State *L) {
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
if (self->running)
return 0;
lua_pushnumber(L, self->returncode);
lua_pushinteger(L, self->returncode);
return 1;
}
@ -404,7 +412,7 @@ static int f_wait(lua_State* L) {
int timeout = luaL_optnumber(L, 2, 0);
if (poll_process(self, timeout))
return 0;
lua_pushnumber(L, self->returncode);
lua_pushinteger(L, self->returncode);
return 1;
}
@ -417,11 +425,19 @@ static int self_signal(lua_State* L, signal_e sig) {
static int f_terminate(lua_State* L) { return self_signal(L, SIGNAL_TERM); }
static int f_kill(lua_State* L) { return self_signal(L, SIGNAL_KILL); }
static int f_interrupt(lua_State* L) { return self_signal(L, SIGNAL_INTERRUPT); }
static int f_gc(lua_State* L) { return self_signal(L, SIGNAL_TERM); }
static int f_gc(lua_State* L) {
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
signal_process(self, SIGNAL_TERM);
close_fd(&self->child_pipes[STDIN_FD ][1]);
close_fd(&self->child_pipes[STDOUT_FD][0]);
close_fd(&self->child_pipes[STDERR_FD][0]);
poll_process(self, 10);
return 0;
}
static int f_running(lua_State* L) {
process_t* self = (process_t*)luaL_checkudata(L, 1, API_TYPE_PROCESS);
lua_pushboolean(L, self->running);
lua_pushboolean(L, poll_process(self, WAIT_NONE));
return 1;
}

View File

@ -60,12 +60,14 @@ static int f_pcre_match(lua_State *L) {
const char* str = luaL_checklstring(L, 2, &len);
if (lua_gettop(L) > 2)
offset = luaL_checknumber(L, 3);
offset -= 1;
len -= offset;
if (lua_gettop(L) > 3)
opts = luaL_checknumber(L, 4);
lua_rawgeti(L, 1, 1);
pcre2_code* re = (pcre2_code*)lua_touserdata(L, -1);
pcre2_match_data* md = pcre2_match_data_create_from_pattern(re, NULL);
int rc = pcre2_match(re, (PCRE2_SPTR)str, len, offset - 1, opts, md, NULL);
int rc = pcre2_match(re, (PCRE2_SPTR)&str[offset], len, 0, opts, md, NULL);
if (rc < 0) {
pcre2_match_data_free(md);
if (rc != PCRE2_ERROR_NOMATCH) {
@ -86,7 +88,7 @@ static int f_pcre_match(lua_State *L) {
return 0;
}
for (int i = 0; i < rc*2; i++)
lua_pushnumber(L, ovector[i]+1);
lua_pushnumber(L, ovector[i]+offset+1);
pcre2_match_data_free(md);
return rc*2;
}
@ -104,17 +106,17 @@ int luaopen_regex(lua_State *L) {
lua_setfield(L, -2, "__name");
lua_pushvalue(L, -1);
lua_setfield(L, LUA_REGISTRYINDEX, "regex");
lua_pushnumber(L, PCRE2_ANCHORED);
lua_pushinteger(L, PCRE2_ANCHORED);
lua_setfield(L, -2, "ANCHORED");
lua_pushnumber(L, PCRE2_ANCHORED) ;
lua_pushinteger(L, PCRE2_ANCHORED) ;
lua_setfield(L, -2, "ENDANCHORED");
lua_pushnumber(L, PCRE2_NOTBOL);
lua_pushinteger(L, PCRE2_NOTBOL);
lua_setfield(L, -2, "NOTBOL");
lua_pushnumber(L, PCRE2_NOTEOL);
lua_pushinteger(L, PCRE2_NOTEOL);
lua_setfield(L, -2, "NOTEOL");
lua_pushnumber(L, PCRE2_NOTEMPTY);
lua_pushinteger(L, PCRE2_NOTEMPTY);
lua_setfield(L, -2, "NOTEMPTY");
lua_pushnumber(L, PCRE2_NOTEMPTY_ATSTART);
lua_pushinteger(L, PCRE2_NOTEMPTY_ATSTART);
lua_setfield(L, -2, "NOTEMPTY_ATSTART");
return 1;
}

View File

@ -106,8 +106,10 @@ static int f_font_set_tab_size(lua_State *L) {
}
static int f_font_gc(lua_State *L) {
if (lua_istable(L, 1)) return 0; // do not run if its FontGroup
RenFont** self = luaL_checkudata(L, 1, API_TYPE_FONT);
ren_font_free(*self);
return 0;
}

View File

@ -1,4 +1,5 @@
#include <SDL.h>
#include <string.h>
#include <stdbool.h>
#include <ctype.h>
#include <dirent.h>
@ -6,7 +7,6 @@
#include <errno.h>
#include <sys/stat.h>
#include "api.h"
#include "../dirmonitor.h"
#include "../rencache.h"
#ifdef _WIN32
#include <direct.h>
@ -109,9 +109,14 @@ static const char *get_key_name(const SDL_Event *e, char *buf) {
/* We need to correctly handle non-standard layouts such as dvorak.
Therefore, if a Latin letter(code<128) is pressed in the current layout,
then we transmit it as it is. But we also need to support shortcuts in
other languages, so for non-Latin characters we pass the scancode that
matches the letter in the QWERTY layout. */
if (e->key.keysym.sym < 128)
other languages, so for non-Latin characters(code>128) we pass the
scancode based name that matches the letter in the QWERTY layout.
In SDL, the codes of all special buttons such as control, shift, arrows
and others, are masked with SDLK_SCANCODE_MASK, which moves them outside
the unicode range (>0x10FFFF). Users can remap these buttons, so we need
to return the correct name, not scancode based. */
if ((e->key.keysym.sym < 128) || (e->key.keysym.sym & SDLK_SCANCODE_MASK))
strcpy(buf, SDL_GetKeyName(e->key.keysym.sym));
else
strcpy(buf, SDL_GetScancodeName(scancode));
@ -140,8 +145,8 @@ top:
ren_resize_window();
lua_pushstring(L, "resized");
/* The size below will be in points. */
lua_pushnumber(L, e.window.data1);
lua_pushnumber(L, e.window.data2);
lua_pushinteger(L, e.window.data1);
lua_pushinteger(L, e.window.data2);
return 3;
} else if (e.window.event == SDL_WINDOWEVENT_EXPOSED) {
rencache_invalidate();
@ -174,8 +179,8 @@ top:
SDL_GetWindowPosition(window, &wx, &wy);
lua_pushstring(L, "filedropped");
lua_pushstring(L, e.drop.file);
lua_pushnumber(L, mx - wx);
lua_pushnumber(L, my - wy);
lua_pushinteger(L, mx - wx);
lua_pushinteger(L, my - wy);
SDL_free(e.drop.file);
return 4;
@ -215,17 +220,17 @@ top:
if (e.button.button == 1) { SDL_CaptureMouse(1); }
lua_pushstring(L, "mousepressed");
lua_pushstring(L, button_name(e.button.button));
lua_pushnumber(L, e.button.x);
lua_pushnumber(L, e.button.y);
lua_pushnumber(L, e.button.clicks);
lua_pushinteger(L, e.button.x);
lua_pushinteger(L, e.button.y);
lua_pushinteger(L, e.button.clicks);
return 5;
case SDL_MOUSEBUTTONUP:
if (e.button.button == 1) { SDL_CaptureMouse(0); }
lua_pushstring(L, "mousereleased");
lua_pushstring(L, button_name(e.button.button));
lua_pushnumber(L, e.button.x);
lua_pushnumber(L, e.button.y);
lua_pushinteger(L, e.button.x);
lua_pushinteger(L, e.button.y);
return 4;
case SDL_MOUSEMOTION:
@ -238,37 +243,17 @@ top:
e.motion.yrel += event_plus.motion.yrel;
}
lua_pushstring(L, "mousemoved");
lua_pushnumber(L, e.motion.x);
lua_pushnumber(L, e.motion.y);
lua_pushnumber(L, e.motion.xrel);
lua_pushnumber(L, e.motion.yrel);
lua_pushinteger(L, e.motion.x);
lua_pushinteger(L, e.motion.y);
lua_pushinteger(L, e.motion.xrel);
lua_pushinteger(L, e.motion.yrel);
return 5;
case SDL_MOUSEWHEEL:
lua_pushstring(L, "mousewheel");
lua_pushnumber(L, e.wheel.y);
lua_pushinteger(L, e.wheel.y);
return 2;
case SDL_USEREVENT:
lua_pushstring(L, "dirchange");
lua_pushnumber(L, e.user.code >> 16);
switch (e.user.code & 0xffff) {
case DMON_ACTION_DELETE:
lua_pushstring(L, "delete");
break;
case DMON_ACTION_CREATE:
lua_pushstring(L, "create");
break;
case DMON_ACTION_MODIFY:
lua_pushstring(L, "modify");
break;
default:
return luaL_error(L, "unknown dmon event action: %d", e.user.code & 0xffff);
}
lua_pushstring(L, e.user.data1);
free(e.user.data1);
return 4;
default:
goto top;
}
@ -366,10 +351,10 @@ static int f_get_window_size(lua_State *L) {
int x, y, w, h;
SDL_GetWindowSize(window, &w, &h);
SDL_GetWindowPosition(window, &x, &y);
lua_pushnumber(L, w);
lua_pushnumber(L, h);
lua_pushnumber(L, x);
lua_pushnumber(L, y);
lua_pushinteger(L, w);
lua_pushinteger(L, h);
lua_pushinteger(L, x);
lua_pushinteger(L, y);
return 4;
}
@ -539,10 +524,10 @@ static int f_get_file_info(lua_State *L) {
}
lua_newtable(L);
lua_pushnumber(L, s.st_mtime);
lua_pushinteger(L, s.st_mtime);
lua_setfield(L, -2, "modified");
lua_pushnumber(L, s.st_size);
lua_pushinteger(L, s.st_size);
lua_setfield(L, -2, "size");
if (S_ISREG(s.st_mode)) {
@ -554,6 +539,14 @@ static int f_get_file_info(lua_State *L) {
}
lua_setfield(L, -2, "type");
#if __linux__
if (S_ISDIR(s.st_mode)) {
if (lstat(path, &s) == 0) {
lua_pushboolean(L, S_ISLNK(s.st_mode));
lua_setfield(L, -2, "symlink");
}
}
#endif
return 1;
}
@ -578,7 +571,10 @@ static struct f_type_names fs_names[] = {
{ 0x0, NULL },
};
#endif
static int f_get_fs_type(lua_State *L) {
#if __linux__
const char *path = luaL_checkstring(L, 1);
struct statfs buf;
int status = statfs(path, &buf);
@ -591,10 +587,10 @@ static int f_get_fs_type(lua_State *L) {
return 1;
}
}
#endif
lua_pushstring(L, "unknown");
return 1;
}
#endif
static int f_mkdir(lua_State *L) {
@ -632,6 +628,16 @@ static int f_set_clipboard(lua_State *L) {
}
static int f_get_process_id(lua_State *L) {
#ifdef _WIN32
lua_pushinteger(L, GetCurrentProcessId());
#else
lua_pushinteger(L, getpid());
#endif
return 1;
}
static int f_get_time(lua_State *L) {
double n = SDL_GetPerformanceCounter() / (double) SDL_GetPerformanceFrequency();
lua_pushnumber(L, n);
@ -688,7 +694,7 @@ static int f_fuzzy_match(lua_State *L) {
strTarget += increment;
}
if (ptnTarget >= ptn && *ptnTarget) { return 0; }
lua_pushnumber(L, score - (int)strLen * 10);
lua_pushinteger(L, score - (int)strLen * 10);
return 1;
}
@ -713,13 +719,13 @@ static void* api_require(const char* symbol) {
P(error), P(gc), P(getallocf), P(getfield),
P(gethook), P(gethookcount), P(gethookmask), P(getinfo), P(getlocal),
P(getmetatable), P(getstack), P(gettable), P(gettop), P(getupvalue),
P(insert), P(isnumber), P(isstring), P(isuserdata),
P(load), P(newstate), P(newthread), P(newuserdata), P(next),
P(isnumber), P(isstring), P(isuserdata),
P(load), P(newstate), P(newthread), P(next),
P(pushboolean), P(pushcclosure), P(pushfstring), P(pushinteger),
P(pushlightuserdata), P(pushlstring), P(pushnil), P(pushnumber),
P(pushstring), P(pushthread), P(pushvalue),
P(pushvfstring), P(rawequal), P(rawget), P(rawgeti),
P(rawset), P(rawseti), P(remove), P(replace), P(resume),
P(rawset), P(rawseti), P(resume),
P(setallocf), P(setfield), P(sethook), P(setlocal),
P(setmetatable), P(settable), P(settop), P(setupvalue),
P(status), P(tocfunction), P(tointegerx), P(tolstring), P(toboolean),
@ -732,12 +738,12 @@ static void* api_require(const char* symbol) {
U(newstate), U(setfuncs), U(buffinit), U(addlstring), U(addstring),
U(addvalue), U(pushresult),
#if LUA_VERSION_NUM >= 502
P(absindex), P(arith), P(callk), P(compare), P(getctx), P(getglobal), P(getuservalue),
P(len), P(pcallk), P(pushunsigned), P(rawgetp), P(rawlen), P(rawsetp), P(setglobal),
P(iscfunction), P(setuservalue), P(tounsignedx), P(yieldk),
U(checkversion_), U(tolstring), U(checkunsigned), U(len), U(getsubtable), U(prepbuffsize),
P(absindex), P(arith), P(callk), P(compare), P(getglobal),
P(len), P(pcallk), P(rawgetp), P(rawlen), P(rawsetp), P(setglobal),
P(iscfunction), P(yieldk),
U(checkversion_), U(tolstring), U(len), U(getsubtable), U(prepbuffsize),
U(pushresultsize), U(buffinitsize), U(checklstring), U(checkoption), U(gsub), U(loadbufferx),
U(loadfilex), U(optinteger), U(optlstring), U(optunsigned), U(requiref), U(traceback)
U(loadfilex), U(optinteger), U(optlstring), U(requiref), U(traceback)
#else
P(objlen)
#endif
@ -786,34 +792,6 @@ static int f_load_native_plugin(lua_State *L) {
return result;
}
static int f_watch_dir(lua_State *L) {
const char *path = luaL_checkstring(L, 1);
const int recursive = lua_toboolean(L, 2);
uint32_t dmon_flags = (recursive ? DMON_WATCHFLAGS_RECURSIVE : 0);
dmon_watch_id watch_id = dmon_watch(path, dirmonitor_watch_callback, dmon_flags, NULL);
if (watch_id.id == 0) { luaL_error(L, "directory monitoring watch failed"); }
lua_pushnumber(L, watch_id.id);
return 1;
}
#if __linux__
static int f_watch_dir_add(lua_State *L) {
dmon_watch_id watch_id;
watch_id.id = luaL_checkinteger(L, 1);
const char *subdir = luaL_checkstring(L, 2);
lua_pushboolean(L, dmon_watch_add(watch_id, subdir));
return 1;
}
static int f_watch_dir_rm(lua_State *L) {
dmon_watch_id watch_id;
watch_id.id = luaL_checkinteger(L, 1);
const char *subdir = luaL_checkstring(L, 2);
lua_pushboolean(L, dmon_watch_rm(watch_id, subdir));
return 1;
}
#endif
#ifdef _WIN32
#define PATHSEP '\\'
#else
@ -893,19 +871,15 @@ static const luaL_Reg lib[] = {
{ "get_file_info", f_get_file_info },
{ "get_clipboard", f_get_clipboard },
{ "set_clipboard", f_set_clipboard },
{ "get_process_id", f_get_process_id },
{ "get_time", f_get_time },
{ "sleep", f_sleep },
{ "exec", f_exec },
{ "fuzzy_match", f_fuzzy_match },
{ "set_window_opacity", f_set_window_opacity },
{ "load_native_plugin", f_load_native_plugin },
{ "watch_dir", f_watch_dir },
{ "path_compare", f_path_compare },
#if __linux__
{ "watch_dir_add", f_watch_dir_add },
{ "watch_dir_rm", f_watch_dir_rm },
{ "get_fs_type", f_get_fs_type },
#endif
{ NULL, NULL }
};

View File

@ -1,59 +0,0 @@
#include <stdio.h>
#include <string.h>
#include <SDL.h>
#define DMON_IMPL
#include "dmon.h"
#include "dmon_extra.h"
#include "dirmonitor.h"
static void send_sdl_event(dmon_watch_id watch_id, dmon_action action, const char *filepath) {
SDL_Event ev;
const int size = strlen(filepath) + 1;
/* The string allocated below should be deallocated as soon as the event is
treated in the SDL main loop. */
char *new_filepath = malloc(size);
if (!new_filepath) return;
memcpy(new_filepath, filepath, size);
#ifdef _WIN32
for (int i = 0; i < size; i++) {
if (new_filepath[i] == '/') {
new_filepath[i] = '\\';
}
}
#endif
SDL_zero(ev);
ev.type = SDL_USEREVENT;
ev.user.code = ((watch_id.id & 0xffff) << 16) | (action & 0xffff);
ev.user.data1 = new_filepath;
SDL_PushEvent(&ev);
}
void dirmonitor_init() {
dmon_init();
/* In theory we should register our user event but since we
have just one type of user event this is not really needed. */
/* sdl_dmon_event_type = SDL_RegisterEvents(1); */
}
void dirmonitor_deinit() {
dmon_deinit();
}
void dirmonitor_watch_callback(dmon_watch_id watch_id, dmon_action action, const char *rootdir,
const char *filepath, const char *oldfilepath, void *user)
{
(void) rootdir;
(void) user;
switch (action) {
case DMON_ACTION_MOVE:
send_sdl_event(watch_id, DMON_ACTION_DELETE, oldfilepath);
send_sdl_event(watch_id, DMON_ACTION_CREATE, filepath);
break;
default:
send_sdl_event(watch_id, action, filepath);
}
}

View File

@ -1,15 +0,0 @@
#ifndef DIRMONITOR_H
#define DIRMONITOR_H
#include <stdint.h>
#include "dmon.h"
#include "dmon_extra.h"
void dirmonitor_init();
void dirmonitor_deinit();
void dirmonitor_watch_callback(dmon_watch_id watch_id, dmon_action action, const char *rootdir,
const char *filepath, const char *oldfilepath, void *user);
#endif

View File

@ -7,15 +7,13 @@
#ifdef _WIN32
#include <windows.h>
#elif __linux__
#elif __linux__ || __FreeBSD__
#include <unistd.h>
#include <signal.h>
#elif __APPLE__
#include <mach-o/dyld.h>
#endif
#include "dirmonitor.h"
SDL_Window *window;
@ -108,8 +106,6 @@ int main(int argc, char **argv) {
SDL_DisplayMode dm;
SDL_GetCurrentDisplayMode(0, &dm);
dirmonitor_init();
window = SDL_CreateWindow(
"", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, dm.w * 0.8, dm.h * 0.8,
SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN);
@ -192,7 +188,6 @@ init_lua:
lua_close(L);
ren_free_window_resources();
dirmonitor_deinit();
return EXIT_SUCCESS;
}

View File

@ -4,13 +4,47 @@ lite_sources = [
'api/regex.c',
'api/system.c',
'api/process.c',
'dirmonitor.c',
'renderer.c',
'renwindow.c',
'rencache.c',
'main.c',
]
# dirmonitor backend
if get_option('dirmonitor_backend') == ''
if cc.has_function('inotify_init', prefix : '#include<sys/inotify.h>')
dirmonitor_backend = 'inotify'
elif cc.has_function('kqueue', prefix : '#include<sys/event.h>')
dirmonitor_backend = 'kqueue'
elif dependency('libkqueue', required : false).found()
dirmonitor_backend = 'kqueue'
elif host_machine.system() == 'windows'
dirmonitor_backend = 'win32'
else
dirmonitor_backend = 'dummy'
warning('no suitable backend found, defaulting to dummy backend')
endif
else
dirmonitor_backend = get_option('dirmonitor_backend')
endif
message('dirmonitor_backend: @0@'.format(dirmonitor_backend))
if dirmonitor_backend == 'kqueue'
libkqueue_dep = dependency('libkqueue', required : false)
if libkqueue_dep.found()
lite_deps += libkqueue_dep
endif
endif
lite_sources += [
'api/dirmonitor.c',
'api/dirmonitor/' + dirmonitor_backend + '.c',
]
lite_cargs += '-DDIRMONITOR_BACKEND=' + dirmonitor_backend
lite_cargs += '-DDIRMONITOR_' + dirmonitor_backend.to_upper()
lite_rc = []
if host_machine.system() == 'windows'
windows = import('windows')

View File

@ -95,7 +95,7 @@ static Command* push_command(int type, int size) {
return NULL;
}
command_buf_idx = n;
memset(cmd, 0, COMMAND_BARE_SIZE);
memset(cmd, 0, size);
cmd->type = type;
cmd->size = size;
return cmd;

View File

@ -1,5 +1,6 @@
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <assert.h>
#include <math.h>
#include <ft2build.h>
@ -42,7 +43,7 @@ typedef struct RenFont {
FT_Face face;
GlyphSet* sets[SUBPIXEL_BITMAPS_CACHED][MAX_LOADABLE_GLYPHSETS];
float size, space_advance, tab_advance;
short max_height;
short max_height, baseline, height;
ERenFontAntialiasing antialiasing;
ERenFontHinting hinting;
unsigned char style;
@ -113,8 +114,10 @@ static void font_load_glyphset(RenFont* font, int idx) {
font->sets[j][idx] = set;
for (int i = 0; i < MAX_GLYPHSET; ++i) {
int glyph_index = FT_Get_Char_Index(font->face, i + idx * MAX_GLYPHSET);
if (!glyph_index || FT_Load_Glyph(font->face, glyph_index, load_option | FT_LOAD_BITMAP_METRICS_ONLY) || font_set_style(&font->face->glyph->outline, j * (64 / SUBPIXEL_BITMAPS_CACHED), font->style) || FT_Render_Glyph(font->face->glyph, render_option))
if (!glyph_index || FT_Load_Glyph(font->face, glyph_index, load_option | FT_LOAD_BITMAP_METRICS_ONLY)
|| font_set_style(&font->face->glyph->outline, j * (64 / SUBPIXEL_BITMAPS_CACHED), font->style) || FT_Render_Glyph(font->face->glyph, render_option)) {
continue;
}
FT_GlyphSlot slot = font->face->glyph;
int glyph_width = slot->bitmap.width / byte_width;
if (font->antialiasing == FONT_ANTIALIASING_NONE)
@ -122,6 +125,13 @@ static void font_load_glyphset(RenFont* font, int idx) {
set->metrics[i] = (GlyphMetric){ pen_x, pen_x + glyph_width, 0, slot->bitmap.rows, true, slot->bitmap_left, slot->bitmap_top, (slot->advance.x + slot->lsb_delta - slot->rsb_delta) / 64.0f};
pen_x += glyph_width;
font->max_height = slot->bitmap.rows > font->max_height ? slot->bitmap.rows : font->max_height;
// In order to fix issues with monospacing; we need the unhinted xadvance; as FreeType doesn't correctly report the hinted advance for spaces on monospace fonts (like RobotoMono). See #843.
if (!glyph_index || FT_Load_Glyph(font->face, glyph_index, (load_option | FT_LOAD_BITMAP_METRICS_ONLY | FT_LOAD_NO_HINTING) & ~FT_LOAD_FORCE_AUTOHINT)
|| font_set_style(&font->face->glyph->outline, j * (64 / SUBPIXEL_BITMAPS_CACHED), font->style) || FT_Render_Glyph(font->face->glyph, render_option)) {
continue;
}
slot = font->face->glyph;
set->metrics[i].xadvance = slot->advance.x / 64.0f;
}
if (pen_x == 0)
continue;
@ -184,10 +194,12 @@ RenFont* ren_font_load(const char* path, float size, ERenFontAntialiasing antial
strcpy(font->path, path);
font->face = face;
font->size = size;
font->height = (short)((face->height / (float)face->units_per_EM) * font->size);
font->baseline = (short)((face->bbox.yMax / (float)face->units_per_EM) * font->size);
font->antialiasing = antialiasing;
font->hinting = hinting;
font->style = style;
font->space_advance = (int)font_get_glyphset(font, ' ', 0)->metrics[' '].xadvance;
font->space_advance = font_get_glyphset(font, ' ', 0)->metrics[' '].xadvance;
font->tab_advance = font->space_advance * 2;
return font;
failure:
@ -228,7 +240,7 @@ float ren_font_group_get_size(RenFont **fonts) {
return fonts[0]->size;
}
int ren_font_group_get_height(RenFont **fonts) {
return fonts[0]->size + 3;
return fonts[0]->height;
}
float ren_font_group_get_width(RenFont **fonts, const char *text) {
@ -238,8 +250,8 @@ float ren_font_group_get_width(RenFont **fonts, const char *text) {
while (text < end) {
unsigned int codepoint;
text = utf8_to_codepoint(text, &codepoint);
font_group_get_glyph(&set, &metric, fonts, codepoint, 0);
width += metric->xadvance ? metric->xadvance : fonts[0]->space_advance;
RenFont* font = font_group_get_glyph(&set, &metric, fonts, codepoint, 0);
width += (!font || metric->xadvance) ? metric->xadvance : fonts[0]->space_advance;
}
const int surface_scale = renwin_surface_scale(&window_renderer);
return width / surface_scale;
@ -270,14 +282,14 @@ float ren_draw_text(RenFont **fonts, const char *text, float x, int y, RenColor
if (set->surface && color.a > 0 && end_x >= clip.x && start_x < clip_end_x) {
unsigned char* source_pixels = set->surface->pixels;
for (int line = metric->y0; line < metric->y1; ++line) {
int target_y = line + y - metric->y0 - metric->bitmap_top + font->size * surface_scale;
int target_y = line + y - metric->bitmap_top + font->baseline * surface_scale;
if (target_y < clip.y)
continue;
if (target_y >= clip_end_y)
break;
if (start_x + (glyph_end - glyph_start) >= clip_end_x)
glyph_end = glyph_start + (clip_end_x - start_x);
else if (start_x < clip.x) {
if (start_x < clip.x) {
int offset = clip.x - start_x;
start_x += offset;
glyph_start += offset;
@ -331,18 +343,23 @@ void ren_draw_rect(RenRect rect, RenColor color) {
y2 = y2 > clip.y + clip.height ? clip.y + clip.height : y2;
SDL_Surface *surface = renwin_get_surface(&window_renderer);
RenColor *d = (RenColor*) surface->pixels;
uint32_t *d = surface->pixels;
d += x1 + y1 * surface->w;
int dr = surface->w - (x2 - x1);
unsigned int translated = SDL_MapRGB(surface->format, color.r, color.g, color.b);
if (color.a == 0xff) {
uint32_t translated = SDL_MapRGB(surface->format, color.r, color.g, color.b);
SDL_Rect rect = { x1, y1, x2 - x1, y2 - y1 };
SDL_FillRect(surface, &rect, translated);
} else {
RenColor translated_color = (RenColor){ translated & 0xFF, (translated >> 8) & 0xFF, (translated >> 16) & 0xFF, color.a };
RenColor current_color;
RenColor blended_color;
for (int j = y1; j < y2; j++) {
for (int i = x1; i < x2; i++, d++)
*d = blend_pixel(*d, translated_color);
{
SDL_GetRGB(*d, surface->format, &current_color.r, &current_color.g, &current_color.b);
blended_color = blend_pixel(current_color, color);
*d = SDL_MapRGB(surface->format, blended_color.r, blended_color.g, blended_color.b);
}
d += dr;
}
}

View File

@ -0,0 +1,9 @@
[wrap-file]
directory = freetype-2.11.1
source_url = https://download.savannah.gnu.org/releases/freetype/freetype-2.11.1.tar.gz
source_filename = freetype-2.11.1.tar.gz
source_hash = f8db94d307e9c54961b39a1cc799a67d46681480696ed72ecf78d4473770f09b
[provide]
freetype2 = freetype_dep

View File

@ -1,4 +1,12 @@
[wrap-git]
directory = lua
url = https://github.com/franko/lua
revision = v5.2.4-7
[wrap-file]
directory = lua-5.4.3
source_url = https://www.lua.org/ftp/lua-5.4.3.tar.gz
source_filename = lua-5.4.3.tar.gz
source_hash = f8612276169e3bfcbcfb8f226195bfc6e466fe13042f1076cbde92b7ec96bbfb
patch_filename = lua_5.4.3-2_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/lua_5.4.3-2/get_patch
patch_hash = 3c23ec14a3f000d80fe2e2fdddba63a56e13c758d74195daa4ff0da7bfdb02da
[provide]
lua-5.4 = lua_dep

15
subprojects/pcre2.wrap Normal file
View File

@ -0,0 +1,15 @@
[wrap-file]
directory = pcre2-10.39
source_url = https://github.com/PhilipHazel/pcre2/releases/download/pcre2-10.39/pcre2-10.39.tar.bz2
source_filename = pcre2-10.39.tar.bz2
source_hash = 0f03caf57f81d9ff362ac28cd389c055ec2bf0678d277349a1a4bee00ad6d440
patch_filename = pcre2_10.39-2_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/pcre2_10.39-2/get_patch
patch_hash = c4cfffff83e7bb239c8c330339b08f4367b019f79bf810f10c415e35fb09cf14
[provide]
libpcre2-8 = -libpcre2_8
libpcre2-16 = -libpcre2_16
libpcre2-32 = -libpcre2_32
libpcre2-posix = -libpcre2_posix

12
subprojects/sdl2.wrap Normal file
View File

@ -0,0 +1,12 @@
[wrap-file]
directory = SDL2-2.0.18
source_url = https://www.libsdl.org/release/SDL2-2.0.18.tar.gz
source_filename = SDL2-2.0.18.tar.gz
source_hash = 94d40cd73dbfa10bb6eadfbc28f355992bb2d6ef6761ad9d4074eff95ee5711c
patch_filename = sdl2_2.0.18-2_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/sdl2_2.0.18-2/get_patch
patch_hash = cd77f33395d3d019bb89217b9da41fc640ed8c78cbbbebc5c662155a25e2820e
[provide]
sdl2 = sdl2_dep